Skip to content

Commit 598bef6

Browse files
RusDynclaude
andcommitted
Support non-interactive key add/rotate/deactivate
- `key add`: add --project <slug> and --department <slug> flags; skip interactive project/department prompts in headless mode - `key rotate`: add -y/--yes flag to skip confirmation - `key deactivate`: add -y/--yes flag to skip confirmation - Update CLI version string to 0.3.1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 91b8d87 commit 598bef6

File tree

2 files changed

+56
-19
lines changed

2 files changed

+56
-19
lines changed

cli/src/bin/writbase.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const program = new Command();
1717
program
1818
.name('writbase')
1919
.description('WritBase CLI — agent-first task management')
20-
.version('0.2.4');
20+
.version('0.3.1');
2121

2222
program
2323
.command('init')
@@ -42,8 +42,10 @@ const key = program
4242
key
4343
.command('add')
4444
.description('Create an agent key, grant permissions, and optionally write .mcp.json')
45-
.option('--name <name>', 'Key name (non-interactive)')
46-
.option('--role <role>', 'Role: worker or manager (non-interactive)')
45+
.option('--name <name>', 'Key name')
46+
.option('--role <role>', 'Role: worker or manager')
47+
.option('--project <slug>', 'Default project (by slug)')
48+
.option('--department <slug>', 'Default department (by slug, requires --project)')
4749
.option('--mcp', 'Write .mcp.json to current directory')
4850
.option('--no-mcp', 'Skip writing .mcp.json')
4951
.action(keyAddCommand);
@@ -56,11 +58,13 @@ key
5658
key
5759
.command('rotate <name-or-id>')
5860
.description('Rotate an agent key (generates new secret)')
61+
.option('-y, --yes', 'Skip confirmation prompt')
5962
.action(keyRotateCommand);
6063

6164
key
6265
.command('deactivate <name-or-id>')
6366
.description('Deactivate an agent key')
67+
.option('-y, --yes', 'Skip confirmation prompt')
6468
.action(keyDeactivateCommand);
6569

6670
key

cli/src/commands/key.ts

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ async function resolveKeyByNameOrId(
5858
interface KeyAddOptions {
5959
name?: string;
6060
role?: string;
61+
project?: string;
62+
department?: string;
6163
mcp?: boolean;
6264
}
6365

6466
export async function keyAddCommand(opts: KeyAddOptions) {
6567
const config = loadConfig();
6668
const supabase = createAdminClient(config.supabaseUrl, config.supabaseServiceRoleKey);
67-
const headless = !!(opts.name || opts.role || opts.mcp === true || opts.mcp === false);
69+
const headless = !!(opts.name || opts.role || opts.project || opts.mcp === true || opts.mcp === false);
6870

6971
const nameInput = opts.name ?? await input({ message: 'Key name:' });
7072
if (!nameInput.trim()) {
@@ -101,7 +103,36 @@ export async function keyAddCommand(opts: KeyAddOptions) {
101103
.eq('is_archived', false)
102104
.order('name');
103105

104-
if (projects && projects.length > 0) {
106+
if (opts.project) {
107+
// Headless: resolve project by slug
108+
const match = projects?.find(
109+
(p: { slug: string }) => p.slug.toLowerCase() === opts.project!.toLowerCase(),
110+
);
111+
if (!match) {
112+
const slugs = projects?.map((p: { slug: string }) => p.slug).join(', ') ?? '(none)';
113+
error(`Project "${opts.project}" not found. Available: ${slugs}`);
114+
process.exit(1);
115+
}
116+
projectId = match.id;
117+
118+
if (opts.department) {
119+
const { data: departments } = await supabase
120+
.from('departments')
121+
.select('id, name, slug')
122+
.eq('project_id', projectId)
123+
.order('name');
124+
const deptMatch = departments?.find(
125+
(d: { slug: string }) => d.slug.toLowerCase() === opts.department!.toLowerCase(),
126+
);
127+
if (!deptMatch) {
128+
const slugs = departments?.map((d: { slug: string }) => d.slug).join(', ') ?? '(none)';
129+
error(`Department "${opts.department}" not found. Available: ${slugs}`);
130+
process.exit(1);
131+
}
132+
departmentId = deptMatch.id;
133+
}
134+
} else if (!headless && projects && projects.length > 0) {
135+
// Interactive: prompt for project and department
105136
const projectChoice = await select({
106137
message: 'Default project (optional):',
107138
choices: [
@@ -310,18 +341,19 @@ export async function keyListCommand() {
310341
}
311342
}
312343

313-
export async function keyRotateCommand(nameOrId: string) {
344+
export async function keyRotateCommand(nameOrId: string, opts: { yes?: boolean }) {
314345
const config = loadConfig();
315346
const supabase = createAdminClient(config.supabaseUrl, config.supabaseServiceRoleKey);
316347

317348
const key = await resolveKeyByNameOrId(supabase, config.workspaceId, nameOrId);
318349

319-
const confirmed = await confirm({
320-
message: `Rotate key "${key.name}" (${key.key_prefix}...)? The old key will stop working immediately.`,
321-
default: false,
322-
});
323-
324-
if (!confirmed) return;
350+
if (!opts.yes) {
351+
const confirmed = await confirm({
352+
message: `Rotate key "${key.name}" (${key.key_prefix}...)? The old key will stop working immediately.`,
353+
default: false,
354+
});
355+
if (!confirmed) return;
356+
}
325357

326358
const spinner = createSpinner('Rotating key...').start();
327359

@@ -663,7 +695,7 @@ export async function keyPermitCommand(nameOrId: string, opts: KeyPermitOptions)
663695
}
664696
}
665697

666-
export async function keyDeactivateCommand(nameOrId: string) {
698+
export async function keyDeactivateCommand(nameOrId: string, opts: { yes?: boolean }) {
667699
const config = loadConfig();
668700
const supabase = createAdminClient(config.supabaseUrl, config.supabaseServiceRoleKey);
669701

@@ -674,12 +706,13 @@ export async function keyDeactivateCommand(nameOrId: string) {
674706
return;
675707
}
676708

677-
const confirmed = await confirm({
678-
message: `Deactivate key "${key.name}" (${key.key_prefix}...)? This key will stop working immediately.`,
679-
default: false,
680-
});
681-
682-
if (!confirmed) return;
709+
if (!opts.yes) {
710+
const confirmed = await confirm({
711+
message: `Deactivate key "${key.name}" (${key.key_prefix}...)? This key will stop working immediately.`,
712+
default: false,
713+
});
714+
if (!confirmed) return;
715+
}
683716

684717
const spinner = createSpinner('Deactivating key...').start();
685718

0 commit comments

Comments
 (0)