Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 50 additions & 7 deletions src/commands/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ interface Agent {
const agentsList = new Command("list")
.description("List agents")
.option("--json", "Output raw JSON")
.addHelpText("after", `
Examples:
$ delega agents list
$ delega agents list --json Output as JSON (for scripting)
`)
.action(async (opts) => {
const data = await apiCall<Agent[]>("GET", "/agents");

Expand Down Expand Up @@ -46,6 +51,12 @@ const agentsCreate = new Command("create")
.argument("<name>", "Agent name")
.option("--display-name <name>", "Friendly display name")
.option("--json", "Output raw JSON")
.addHelpText("after", `
Examples:
$ delega agents create my-agent
$ delega agents create deploy-bot --display-name "Deploy Bot"
$ delega agents create ci-agent --json Get agent details (incl. API key) as JSON
`)
.action(async (name: string, opts) => {
const body: Record<string, unknown> = { name };
if (opts.displayName) body.display_name = opts.displayName;
Expand Down Expand Up @@ -74,20 +85,52 @@ const agentsCreate = new Command("create")
const agentsRotate = new Command("rotate")
.description("Rotate an agent's API key")
.argument("<id>", "Agent ID")
.action(async (id: string) => {
const yes = await confirm(
`Rotate key for agent ${id}? Old key will stop working immediately. (y/N) `,
);
if (!yes) {
console.log("Cancelled.");
.option("-y, --yes", "Skip confirmation prompt")
.option("--json", "Output raw JSON")
.option("--dry-run", "Show what would happen without rotating")
.addHelpText("after", `
Examples:
$ delega agents rotate abc123
$ delega agents rotate abc123 --yes Skip confirmation (for scripts/agents)
$ delega agents rotate abc123 --json Get new API key as JSON
$ delega agents rotate abc123 --dry-run Preview without rotating
`)
.action(async (id: string, opts) => {
if (opts.dryRun) {
let agent: Agent | undefined;
try {
agent = await apiCall<Agent>("GET", `/agents/${id}`);
} catch {
// Graceful degradation: if GET fails, just show the ID
}
if (opts.json) {
console.log(JSON.stringify({ dry_run: true, agent_id: id, agent_name: agent?.name ?? null, action: "rotate-key" }, null, 2));
return;
}
const display = agent?.name ? `${agent.name} (${id})` : id;
console.log(`Would rotate API key for agent ${display}. Old key would stop working immediately.`);
return;
}
if (!opts.yes) {
const ok = await confirm(
`Rotate key for agent ${id}? Old key will stop working immediately. (y/N) `,
);
if (!ok) {
console.log("Cancelled.");
return;
}
}

const result = await apiCall<{ api_key: string }>(
const result = await apiCall<{ id: string; api_key: string }>(
"POST",
`/agents/${id}/rotate-key`,
);

if (opts.json) {
console.log(JSON.stringify({ id: result.id, api_key: result.api_key }, null, 2));
return;
}

console.log();
console.log(` New API Key: ${chalk.cyan.bold(result.api_key)}`);
console.log(chalk.yellow(" Save this key — it will not be shown again."));
Expand Down
5 changes: 5 additions & 0 deletions src/commands/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ async function promptSecret(question: string): Promise<string> {

export const loginCommand = new Command("login")
.description("Authenticate with the Delega API")
.addHelpText("after", `
Examples:
$ delega login Interactive API key prompt
$ DELEGA_API_KEY=dlg_xxx delega whoami Authenticate via env var instead
`)
.action(async () => {
printBanner();

Expand Down
34 changes: 33 additions & 1 deletion src/commands/reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,35 @@ import { confirm } from "../ui.js";
export const resetCommand = new Command("reset")
.description("Remove local credentials and config")
.option("--force", "Skip confirmation prompt")
.option("--dry-run", "Show what would be cleaned without doing it")
.option("--json", "Output raw JSON")
.addHelpText("after", `
Examples:
$ delega reset Interactive confirmation
$ delega reset --force Skip confirmation (for scripts/agents)
$ delega reset --dry-run Preview what would be removed
$ delega reset --dry-run --json Preview as JSON (for scripting)
$ delega reset --force --json Reset and output result as JSON
`)
.action(async (opts) => {
const configDir = node_path.join(node_os.homedir(), ".delega");

if (opts.dryRun) {
const hasConfig = node_fs.existsSync(configDir);
if (opts.json) {
console.log(JSON.stringify({ dry_run: true, would_remove: { api_key: true, config_dir: hasConfig ? configDir : null } }, null, 2));
return;
}
console.log("Dry run — the following would be removed:");
console.log(` - API key from secure storage (if stored)`);
if (hasConfig) {
console.log(` - ${configDir}`);
} else {
console.log(` - ${configDir} (not found)`);
}
return;
}

if (!opts.force) {
const yes = await confirm(
"This will remove your stored API key and config. Continue? (y/N) ",
Expand All @@ -24,14 +52,18 @@ export const resetCommand = new Command("reset")
const keyDeleted = deleteStoredApiKey();

// 2. Delete ~/.delega/ directory
const configDir = node_path.join(node_os.homedir(), ".delega");
let configDeleted = false;
if (node_fs.existsSync(configDir)) {
node_fs.rmSync(configDir, { recursive: true, force: true });
configDeleted = true;
}

// 3. Report what was cleaned
if (opts.json) {
console.log(JSON.stringify({ api_key_removed: keyDeleted, config_dir_removed: configDeleted ? configDir : null }, null, 2));
return;
}

if (keyDeleted) console.log(chalk.green("✓ API key removed from secure storage"));
if (configDeleted) console.log(chalk.green(`✓ Removed ${configDir}`));
if (!keyDeleted && !configDeleted) {
Expand Down
5 changes: 5 additions & 0 deletions src/commands/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ interface Stats {
export const statsCommand = new Command("stats")
.description("Show usage statistics")
.option("--json", "Output raw JSON")
.addHelpText("after", `
Examples:
$ delega stats
$ delega stats --json Output as JSON (for scripting)
`)
.action(async (opts) => {
const data = await apiCall<Stats>("GET", "/stats");

Expand Down
5 changes: 5 additions & 0 deletions src/commands/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ interface Stats {
export const statusCommand = new Command("status")
.description("Check connection and show environment info")
.option("--json", "Output raw JSON")
.addHelpText("after", `
Examples:
$ delega status
$ delega status --json Output as JSON (for scripting)
`)
.action(async (opts) => {
// 1. Check for API key (non-fatal — we can still show health info)
const apiKey = getApiKey();
Expand Down
80 changes: 73 additions & 7 deletions src/commands/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ const tasksList = new Command("list")
.option("--completed", "Include completed tasks")
.option("--limit <n>", "Limit results", parseInt)
.option("--json", "Output raw JSON")
.addHelpText("after", `
Examples:
$ delega tasks list List pending tasks
$ delega tasks list --completed Include completed tasks
$ delega tasks list --limit 5 Show only 5 tasks
$ delega tasks list --json Output as JSON (for scripting)
$ delega tasks list --json | jq '.[0]' Get first task with jq
`)
.action(async (opts) => {
let path = "/tasks";
const params: string[] = [];
Expand Down Expand Up @@ -78,6 +86,14 @@ const tasksCreate = new Command("create")
.option("--labels <labels>", "Comma-separated labels")
.option("--due <date>", "Due date (YYYY-MM-DD)")
.option("--json", "Output raw JSON")
.addHelpText("after", `
Examples:
$ delega tasks create "Fix login bug"
$ delega tasks create "Deploy v2" --priority 1
$ delega tasks create "Write tests" --labels "testing,backend"
$ delega tasks create "Ship feature" --due 2025-12-31
$ delega tasks create "Audit deps" --json Get created task as JSON
`)
.action(async (content: string, opts) => {
const body: Record<string, unknown> = { content };
if (opts.priority) body.priority = opts.priority;
Expand All @@ -103,6 +119,11 @@ const tasksShow = new Command("show")
.description("Show task details")
.argument("<id>", "Task ID")
.option("--json", "Output raw JSON")
.addHelpText("after", `
Examples:
$ delega tasks show abc123
$ delega tasks show abc123 --json Get full task details as JSON
`)
.action(async (id: string, opts) => {
const task = await apiCall<Task>("GET", `/tasks/${id}`);

Expand Down Expand Up @@ -141,21 +162,56 @@ const tasksShow = new Command("show")
const tasksComplete = new Command("complete")
.description("Mark a task as completed")
.argument("<id>", "Task ID")
.action(async (id: string) => {
await apiCall("POST", `/tasks/${id}/complete`);
.option("--json", "Output raw JSON")
.addHelpText("after", `
Examples:
$ delega tasks complete abc123
$ delega tasks complete abc123 --json Get completed task as JSON
`)
.action(async (id: string, opts) => {
const task = await apiCall<Task>("POST", `/tasks/${id}/complete`);
if (opts.json) {
console.log(JSON.stringify(task, null, 2));
return;
}
console.log(`Task ${id} completed.`);
});

const tasksDelete = new Command("delete")
.description("Delete a task")
.argument("<id>", "Task ID")
.action(async (id: string) => {
const yes = await confirm(`Delete task ${id}? (y/N) `);
if (!yes) {
console.log("Cancelled.");
.option("-y, --yes", "Skip confirmation prompt")
.option("--json", "Output raw JSON")
.option("--dry-run", "Show what would be deleted without doing it")
.addHelpText("after", `
Examples:
$ delega tasks delete abc123
$ delega tasks delete abc123 --yes Skip confirmation (for scripts/agents)
$ delega tasks delete abc123 --json Output result as JSON
$ delega tasks delete abc123 --dry-run Preview without deleting
`)
.action(async (id: string, opts) => {
if (opts.dryRun) {
const task = await apiCall<Task>("GET", `/tasks/${id}`);
if (opts.json) {
console.log(JSON.stringify({ dry_run: true, would_delete: task }, null, 2));
return;
}
console.log(`Would delete task ${id}: ${task.content}`);
return;
Comment on lines +194 to 201
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 --dry-run still makes a real network call

tasks delete --dry-run fetches GET /tasks/{id} before exiting, so it still makes a live API request. This is inconsistent with the agents rotate --dry-run path, which makes no API call and simply echoes the action from the CLI argument.

Both approaches are defensible, but the inconsistency is worth noting: an agent using --dry-run to avoid side effects may not expect a network hop. Consider either (a) aligning the two dry-run paths, or (b) documenting this in the help text (e.g. "Preview without deleting — still reads the task from the API").

}
if (!opts.yes) {
const ok = await confirm(`Delete task ${id}? (y/N) `);
if (!ok) {
console.log("Cancelled.");
return;
}
}
await apiCall("DELETE", `/tasks/${id}`);
if (opts.json) {
console.log(JSON.stringify({ id, deleted: true }, null, 2));
return;
}
console.log(`Task ${id} deleted.`);
});

Expand All @@ -164,11 +220,21 @@ const tasksDelegate = new Command("delegate")
.argument("<task_id>", "Task ID")
.argument("<agent_id>", "Agent ID to delegate to")
.requiredOption("--content <content>", "Subtask description (required)")
.option("--json", "Output raw JSON")
.addHelpText("after", `
Examples:
$ delega tasks delegate abc123 agent456 --content "Handle the frontend"
$ delega tasks delegate abc123 agent456 --content "Run tests" --json
`)
.action(async (taskId: string, agentId: string, opts) => {
const body: Record<string, unknown> = { assigned_to_agent_id: agentId };
if (opts.content) body.content = opts.content;

await apiCall("POST", `/tasks/${taskId}/delegate`, body);
const subtask = await apiCall<Task>("POST", `/tasks/${taskId}/delegate`, body);
if (opts.json) {
console.log(JSON.stringify(subtask, null, 2));
return;
}
console.log(`Task delegated to ${agentId}.`);
});

Expand Down
4 changes: 4 additions & 0 deletions src/commands/whoami.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ interface MeResponse {

export const whoamiCommand = new Command("whoami")
.description("Show current authenticated agent")
.addHelpText("after", `
Examples:
$ delega whoami Show current agent identity
`)
.action(async () => {
const me = await apiRequest<MeResponse>("GET", "/agent/me");
if (me.ok) {
Expand Down