capyai 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -72,7 +72,7 @@ Env vars: `CAPY_API_KEY`, `CAPY_PROJECT_ID`, `CAPY_SERVER`, `CAPY_ENV_FILE`, `GR
72
72
 
73
73
  ## Requirements
74
74
 
75
- Node.js 18+ or Bun. GitHub CLI (`gh`) for quality gate checks.
75
+ [Bun](https://bun.sh) runtime. GitHub CLI (`gh`) for quality gate checks.
76
76
 
77
77
  ## License
78
78
 
package/bin/capy.js CHANGED
@@ -1,61 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createRequire } from "node:module";
4
- const require = createRequire(import.meta.url);
3
+ import { execFileSync } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+ import { dirname, join } from "node:path";
5
6
 
6
- const cmd = process.argv[2];
7
-
8
- if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
9
- const { version } = require("../package.json");
10
- console.log(`capy — agent orchestrator with quality gates
11
-
12
- Usage: capy <command> [args] [flags]
13
-
14
- Agents:
15
- captain <prompt> Start Captain thread
16
- build <prompt> Start Build agent
17
- threads [list|get|msg|stop|messages]
18
-
19
- Tasks:
20
- status Dashboard
21
- list [status] List tasks
22
- get <id> Task details
23
- start/stop/msg <id> Control tasks
24
- diff <id> View diff
25
- pr <id> [title] Create PR
26
-
27
- Quality:
28
- review <id> Gate check
29
- re-review <id> Trigger Greptile re-review
30
- approve <id> [--force] Approve if gates pass
31
- retry <id> [--fix="..."] Retry with context
32
-
33
- Monitoring:
34
- watch/unwatch <id> Auto-poll + notify
35
- watches List watches
36
-
37
- Config:
38
- init Interactive setup
39
- config [key] [value] Get/set config
40
- models List models
41
- tools All commands + env vars
42
-
43
- Flags:
44
- --json --model=<id> --opus --sonnet --fast
45
-
46
- v${version}
47
- `);
48
- process.exit(0);
49
- }
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const tsEntry = join(__dirname, "capy.ts");
50
9
 
51
10
  try {
52
- const { run } = await import("../dist/capy.js");
53
- await run(cmd, process.argv.slice(3));
11
+ execFileSync("bun", ["run", tsEntry, ...process.argv.slice(2)], {
12
+ stdio: "inherit",
13
+ env: process.env,
14
+ });
54
15
  } catch (e) {
55
- if (e.code === "ERR_MODULE_NOT_FOUND" || e.code === "MODULE_NOT_FOUND") {
56
- console.error("capy: not built. Run: bun run build");
57
- process.exit(1);
58
- }
59
- console.error(e.message);
16
+ if (e.status != null) process.exit(e.status);
17
+ console.error("capy requires Bun. Install: curl -fsSL https://bun.sh/install | bash");
18
+ console.error("Then: bun i -g capyai && capy --help");
60
19
  process.exit(1);
61
20
  }
package/bin/capy.ts CHANGED
@@ -1,62 +1,42 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
+ import { defineCommand, runMain } from "citty";
3
4
  import { createRequire } from "node:module";
5
+
4
6
  const require = createRequire(import.meta.url);
5
7
  const { version } = require("../package.json");
6
8
 
7
- const cmd = process.argv[2];
8
-
9
- if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
10
- console.log(`capy — agent orchestrator with quality gates
11
-
12
- Usage: capy <command> [args] [flags]
13
-
14
- Agents:
15
- captain <prompt> Start Captain thread
16
- build <prompt> Start Build agent
17
- threads [list|get|msg|stop|messages]
18
-
19
- Tasks:
20
- status Dashboard
21
- list [status] List tasks
22
- get <id> Task details
23
- start/stop/msg <id> Control tasks
24
- diff <id> View diff
25
- pr <id> [title] Create PR
26
-
27
- Quality:
28
- review <id> Gate check
29
- re-review <id> Trigger Greptile re-review
30
- approve <id> [--force] Approve if gates pass
31
- retry <id> [--fix="..."] Retry with context
32
-
33
- Monitoring:
34
- watch/unwatch <id> Auto-poll + notify
35
- watches List watches
36
-
37
- Config:
38
- init Interactive setup
39
- config [key] [value] Get/set config
40
- models List models
41
- tools All commands + env vars
42
-
43
- Flags:
44
- --json --model=<id> --opus --sonnet --fast
45
-
46
- v${version}
47
- `);
48
- process.exit(0);
49
- }
50
-
51
- try {
52
- const { run } = await import("../src/cli.js");
53
- await run(cmd, process.argv.slice(3));
54
- } catch (e: unknown) {
55
- const err = e as Error & { code?: string };
56
- if (err.code === "MODULE_NOT_FOUND" || err.code === "ERR_MODULE_NOT_FOUND") {
57
- console.error("capy: broken install. Reinstall: npm i -g capy-cli");
58
- process.exit(1);
59
- }
60
- console.error(err.message);
61
- process.exit(1);
62
- }
9
+ const main = defineCommand({
10
+ meta: {
11
+ name: "capy",
12
+ version,
13
+ description: "Agent orchestrator with quality gates",
14
+ },
15
+ subCommands: {
16
+ captain: () => import("../src/commands/agents.js").then(m => m.captain),
17
+ build: () => import("../src/commands/agents.js").then(m => m.build),
18
+ threads: () => import("../src/commands/threads.js").then(m => m.default),
19
+ list: () => import("../src/commands/tasks.js").then(m => m.list),
20
+ get: () => import("../src/commands/tasks.js").then(m => m.get),
21
+ start: () => import("../src/commands/tasks.js").then(m => m.start),
22
+ stop: () => import("../src/commands/tasks.js").then(m => m.stop),
23
+ msg: () => import("../src/commands/tasks.js").then(m => m.msg),
24
+ diff: () => import("../src/commands/diff-pr.js").then(m => m.diff),
25
+ pr: () => import("../src/commands/diff-pr.js").then(m => m.pr),
26
+ models: () => import("../src/commands/setup.js").then(m => m.models),
27
+ tools: () => import("../src/commands/setup.js").then(m => m.tools),
28
+ status: () => import("../src/commands/setup.js").then(m => m.status),
29
+ review: () => import("../src/commands/quality.js").then(m => m.review),
30
+ "re-review": () => import("../src/commands/quality.js").then(m => m.reReview),
31
+ approve: () => import("../src/commands/quality.js").then(m => m.approve),
32
+ retry: () => import("../src/commands/quality.js").then(m => m.retry),
33
+ watch: () => import("../src/commands/monitoring.js").then(m => m.watch),
34
+ unwatch: () => import("../src/commands/monitoring.js").then(m => m.unwatch),
35
+ watches: () => import("../src/commands/monitoring.js").then(m => m.watches),
36
+ _poll: () => import("../src/commands/monitoring.js").then(m => m._poll),
37
+ init: () => import("../src/commands/setup.js").then(m => m.init),
38
+ config: () => import("../src/commands/setup.js").then(m => m.config),
39
+ },
40
+ });
41
+
42
+ runMain(main);
package/package.json CHANGED
@@ -1,19 +1,17 @@
1
1
  {
2
2
  "name": "capyai",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Unofficial Capy.ai CLI for agent orchestration with quality gates",
6
6
  "bin": {
7
7
  "capy": "./bin/capy.js"
8
8
  },
9
9
  "scripts": {
10
- "build": "bun build bin/capy.ts --outdir dist --target node",
11
- "prepublishOnly": "bun run build"
10
+ "typecheck": "tsc --noEmit"
12
11
  },
13
12
  "files": [
14
13
  "bin/",
15
- "src/",
16
- "dist/"
14
+ "src/"
17
15
  ],
18
16
  "engines": {
19
17
  "node": ">=18"
@@ -32,5 +30,9 @@
32
30
  "coding-agent",
33
31
  "orchestration",
34
32
  "cli"
35
- ]
33
+ ],
34
+ "dependencies": {
35
+ "@clack/prompts": "^1.2.0",
36
+ "citty": "^0.2.2"
37
+ }
36
38
  }
@@ -0,0 +1,32 @@
1
+ import type { ArgsDef } from "citty";
2
+
3
+ export const modelArgs = {
4
+ model: { type: "string", description: "Model ID override" },
5
+ opus: { type: "boolean", description: "Use claude-opus-4-6" },
6
+ sonnet: { type: "boolean", description: "Use claude-sonnet-4-6" },
7
+ mini: { type: "boolean", description: "Use gpt-5.4-mini" },
8
+ fast: { type: "boolean", description: "Use gpt-5.4-fast" },
9
+ kimi: { type: "boolean", description: "Use kimi-k2.5" },
10
+ glm: { type: "boolean", description: "Use glm-5" },
11
+ gemini: { type: "boolean", description: "Use gemini-3.1-pro" },
12
+ grok: { type: "boolean", description: "Use grok-4.1-fast" },
13
+ qwen: { type: "boolean", description: "Use qwen-3-coder" },
14
+ } as const satisfies ArgsDef;
15
+
16
+ export const jsonArg = {
17
+ json: { type: "boolean", description: "Machine-readable JSON output", default: false },
18
+ } as const satisfies ArgsDef;
19
+
20
+ export function resolveModel(args: Record<string, unknown>): string | null {
21
+ if (args.model) return String(args.model);
22
+ if (args.opus) return "claude-opus-4-6";
23
+ if (args.sonnet) return "claude-sonnet-4-6";
24
+ if (args.mini) return "gpt-5.4-mini";
25
+ if (args.fast) return "gpt-5.4-fast";
26
+ if (args.kimi) return "kimi-k2.5";
27
+ if (args.glm) return "glm-5";
28
+ if (args.gemini) return "gemini-3.1-pro";
29
+ if (args.grok) return "grok-4.1-fast";
30
+ if (args.qwen) return "qwen-3-coder";
31
+ return null;
32
+ }
@@ -0,0 +1,48 @@
1
+ import { defineCommand } from "citty";
2
+ import { modelArgs, jsonArg, resolveModel } from "./_shared.js";
3
+
4
+ export const captain = defineCommand({
5
+ meta: { name: "captain", description: "Start Captain thread", alias: "plan" },
6
+ args: {
7
+ prompt: { type: "positional", description: "Task prompt", required: true },
8
+ ...modelArgs,
9
+ ...jsonArg,
10
+ },
11
+ async run({ args }) {
12
+ const api = await import("../api.js");
13
+ const config = await import("../config.js");
14
+ const { out, IS_JSON } = await import("../output.js");
15
+ const { log } = await import("@clack/prompts");
16
+
17
+ const cfg = config.load();
18
+ const model = resolveModel(args) || cfg.defaultModel;
19
+ const data = await api.createThread(args.prompt, model);
20
+
21
+ if (IS_JSON) { out(data); return; }
22
+ log.success(`Captain started: https://capy.ai/project/${cfg.projectId}/captain/${data.id}`);
23
+ log.info(`Thread: ${data.id} Model: ${model}`);
24
+ },
25
+ });
26
+
27
+ export const build = defineCommand({
28
+ meta: { name: "build", description: "Start Build agent (isolated)", alias: "run" },
29
+ args: {
30
+ prompt: { type: "positional", description: "Task prompt", required: true },
31
+ ...modelArgs,
32
+ ...jsonArg,
33
+ },
34
+ async run({ args }) {
35
+ const api = await import("../api.js");
36
+ const config = await import("../config.js");
37
+ const { out, IS_JSON } = await import("../output.js");
38
+ const { log } = await import("@clack/prompts");
39
+
40
+ const cfg = config.load();
41
+ const model = resolveModel(args) || cfg.defaultModel;
42
+ const data = await api.createTask(args.prompt, model);
43
+
44
+ if (IS_JSON) { out(data); return; }
45
+ log.success(`Build started: https://capy.ai/project/${cfg.projectId}/tasks/${data.id}`);
46
+ log.info(`ID: ${data.identifier} Model: ${model}`);
47
+ },
48
+ });
@@ -0,0 +1,46 @@
1
+ import { defineCommand } from "citty";
2
+ import { jsonArg } from "./_shared.js";
3
+
4
+ export const diff = defineCommand({
5
+ meta: { name: "diff", description: "View task diff" },
6
+ args: {
7
+ id: { type: "positional", description: "Task ID", required: true },
8
+ mode: { type: "string", description: "Diff mode", default: "run" },
9
+ ...jsonArg,
10
+ },
11
+ async run({ args }) {
12
+ const api = await import("../api.js");
13
+ const fmt = await import("../output.js");
14
+
15
+ const data = await api.getDiff(args.id, args.mode);
16
+ if (args.json) { fmt.out(data); return; }
17
+ console.log(`Diff (${data.source || "unknown"}): +${data.stats?.additions || 0} -${data.stats?.deletions || 0} in ${data.stats?.files || 0} files\n`);
18
+ if (data.files) {
19
+ data.files.forEach(f => {
20
+ console.log(`--- ${f.path} (${f.state}) +${f.additions} -${f.deletions}`);
21
+ if (f.patch) console.log(f.patch);
22
+ console.log();
23
+ });
24
+ }
25
+ },
26
+ });
27
+
28
+ export const pr = defineCommand({
29
+ meta: { name: "pr", description: "Create a PR" },
30
+ args: {
31
+ id: { type: "positional", description: "Task ID", required: true },
32
+ title: { type: "positional", description: "PR title" },
33
+ ...jsonArg,
34
+ },
35
+ async run({ args }) {
36
+ const api = await import("../api.js");
37
+ const fmt = await import("../output.js");
38
+ const { log } = await import("@clack/prompts");
39
+
40
+ const body = args.title ? { title: args.title } : {};
41
+ const data = await api.createPR(args.id, body);
42
+ if (args.json) { fmt.out(data); return; }
43
+ log.success(`PR: ${data.url}`);
44
+ log.info(`#${data.number} ${data.title} (${data.headRef} \u2192 ${data.baseRef})`);
45
+ },
46
+ });
@@ -0,0 +1,95 @@
1
+ import { defineCommand } from "citty";
2
+ import { jsonArg } from "./_shared.js";
3
+
4
+ export const watch = defineCommand({
5
+ meta: { name: "watch", description: "Poll + notify on completion" },
6
+ args: {
7
+ id: { type: "positional", description: "Task or thread ID", required: true },
8
+ interval: { type: "string", description: "Poll interval in minutes (1-30)", default: "3" },
9
+ ...jsonArg,
10
+ },
11
+ async run({ args }) {
12
+ const w = await import("../watch.js");
13
+ const config = await import("../config.js");
14
+ const fmt = await import("../output.js");
15
+
16
+ const interval = Math.max(1, Math.min(parseInt(args.interval) || config.load().watchInterval, 30));
17
+ const type = (args.id.length > 20 || (args.id.length > 10 && !args.id.match(/^[A-Z]+-\d+$/))) ? "thread" : "task";
18
+ const added = w.add(args.id, type, interval);
19
+
20
+ if (args.json) { fmt.out({ id: args.id, type, interval, added }); return; }
21
+ if (added) {
22
+ console.log(`Watching ${args.id} (${type}) every ${interval}min. Will notify when done.`);
23
+ } else {
24
+ console.log(`Already watching ${args.id}.`);
25
+ }
26
+ },
27
+ });
28
+
29
+ export const unwatch = defineCommand({
30
+ meta: { name: "unwatch", description: "Stop watching" },
31
+ args: {
32
+ id: { type: "positional", description: "Task or thread ID", required: true },
33
+ ...jsonArg,
34
+ },
35
+ async run({ args }) {
36
+ const w = await import("../watch.js");
37
+ const fmt = await import("../output.js");
38
+
39
+ w.remove(args.id);
40
+ if (args.json) { fmt.out({ id: args.id, status: "removed" }); return; }
41
+ console.log(`Stopped watching ${args.id}.`);
42
+ },
43
+ });
44
+
45
+ export const watches = defineCommand({
46
+ meta: { name: "watches", description: "List active watches" },
47
+ args: { ...jsonArg },
48
+ async run({ args }) {
49
+ const w = await import("../watch.js");
50
+ const fmt = await import("../output.js");
51
+
52
+ const entries = w.list();
53
+ if (args.json) { fmt.out(entries); return; }
54
+ if (!entries.length) { console.log("No active watches."); return; }
55
+ entries.forEach(e => console.log(`${fmt.pad(e.id.slice(0, 20), 22)} type=${e.type} every ${e.intervalMin}min since ${e.created}`));
56
+ },
57
+ });
58
+
59
+ export const _poll = defineCommand({
60
+ meta: { name: "_poll", description: "Internal cron poll", hidden: true },
61
+ args: {
62
+ id: { type: "positional", description: "ID", required: true },
63
+ type: { type: "positional", description: "task or thread" },
64
+ },
65
+ async run({ args }) {
66
+ const api = await import("../api.js");
67
+ const w = await import("../watch.js");
68
+
69
+ const type = args.type || "task";
70
+
71
+ if (type === "thread") {
72
+ const data = await api.getThread(args.id);
73
+ if (data.status === "idle" || data.status === "archived") {
74
+ const taskLines = (data.tasks || []).map(t => ` ${t.identifier}: ${t.title} [${t.status}]`).join("\n");
75
+ const prLines = (data.pullRequests || []).map(p => ` PR#${p.number}: ${p.url} [${p.state}]`).join("\n");
76
+ let msg = `[Capy] Captain thread finished.\nTitle: ${data.title || "(untitled)"}\nStatus: ${data.status}`;
77
+ if (taskLines) msg += `\n\nTasks:\n${taskLines}`;
78
+ if (prLines) msg += `\n\nPRs:\n${prLines}`;
79
+ msg += `\n\nRun: capy review <task-id> for each task, then capy approve <task-id> if quality passes.`;
80
+ w.notify(msg);
81
+ w.remove(args.id);
82
+ }
83
+ return;
84
+ }
85
+
86
+ const data = await api.getTask(args.id);
87
+ if (data.status === "needs_review" || data.status === "archived") {
88
+ let msg = `[Capy] Task ${data.identifier} ready.\nTitle: ${data.title}\nStatus: ${data.status}`;
89
+ if (data.pullRequest) msg += `\nPR: ${data.pullRequest.url || "#" + data.pullRequest.number}`;
90
+ msg += `\n\nRun: capy review ${data.identifier}, then capy approve ${data.identifier} if quality passes.`;
91
+ w.notify(msg);
92
+ w.remove(args.id);
93
+ }
94
+ },
95
+ });