capyai 0.2.1 → 0.3.1
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 +1 -1
- package/bin/capy.js +12 -53
- package/bin/capy.ts +36 -56
- package/package.json +8 -6
- package/src/api.ts +31 -8
- package/src/commands/_shared.ts +32 -0
- package/src/commands/agents.ts +48 -0
- package/src/commands/diff-pr.ts +46 -0
- package/src/commands/monitoring.ts +95 -0
- package/src/commands/quality.ts +313 -0
- package/src/commands/setup.ts +294 -0
- package/src/commands/tasks.ts +103 -0
- package/src/commands/threads.ts +95 -0
- package/src/{format.ts → output.ts} +8 -9
- package/src/types.ts +1 -0
- package/dist/capy.js +0 -1619
- package/src/cli.ts +0 -722
- /package/src/{quality.ts → quality-engine.ts} +0 -0
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
|
-
|
|
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 {
|
|
4
|
-
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
5
6
|
|
|
6
|
-
const
|
|
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
|
-
|
|
53
|
-
|
|
11
|
+
execFileSync("bun", ["run", tsEntry, ...process.argv.slice(2)], {
|
|
12
|
+
stdio: "inherit",
|
|
13
|
+
env: process.env,
|
|
14
|
+
});
|
|
54
15
|
} catch (e) {
|
|
55
|
-
if (e.
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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.
|
|
3
|
+
"version": "0.3.1",
|
|
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
|
-
"
|
|
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
|
}
|
package/src/api.ts
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import * as config from "./config.js";
|
|
2
2
|
import type { Task, Thread, ThreadMessage, DiffData, Model, ListResponse, PullRequestRef } from "./types.js";
|
|
3
3
|
|
|
4
|
-
async function
|
|
5
|
-
const
|
|
6
|
-
if (!cfg.apiKey) {
|
|
7
|
-
console.error("capy: API key not configured. Run: capy init");
|
|
8
|
-
process.exit(1);
|
|
9
|
-
}
|
|
10
|
-
const url = `${cfg.server}${path}`;
|
|
4
|
+
async function rawRequest(apiKey: string, server: string, method: string, path: string, body?: unknown): Promise<any> {
|
|
5
|
+
const url = `${server}${path}`;
|
|
11
6
|
const headers: Record<string, string> = {
|
|
12
|
-
"Authorization": `Bearer ${
|
|
7
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
13
8
|
"Accept": "application/json",
|
|
14
9
|
};
|
|
15
10
|
const init: RequestInit = { method, headers };
|
|
@@ -40,6 +35,34 @@ async function request(method: string, path: string, body?: unknown): Promise<an
|
|
|
40
35
|
}
|
|
41
36
|
}
|
|
42
37
|
|
|
38
|
+
async function request(method: string, path: string, body?: unknown): Promise<any> {
|
|
39
|
+
const cfg = config.load();
|
|
40
|
+
if (!cfg.apiKey) {
|
|
41
|
+
console.error("capy: API key not configured. Run: capy init");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
return rawRequest(cfg.apiKey, cfg.server, method, path, body);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Init helpers (accept key directly, before config is saved) ---
|
|
48
|
+
export interface Project {
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
description?: string | null;
|
|
52
|
+
taskCode: string;
|
|
53
|
+
repos: { repoFullName: string; branch: string }[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function listProjects(apiKey: string, server = "https://capy.ai/api/v1"): Promise<Project[]> {
|
|
57
|
+
const data = await rawRequest(apiKey, server, "GET", "/projects");
|
|
58
|
+
return data.items || [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function listModelsWithKey(apiKey: string, server = "https://capy.ai/api/v1"): Promise<Model[]> {
|
|
62
|
+
const data = await rawRequest(apiKey, server, "GET", "/models");
|
|
63
|
+
return data.models || [];
|
|
64
|
+
}
|
|
65
|
+
|
|
43
66
|
// --- Threads ---
|
|
44
67
|
export async function createThread(prompt: string, model?: string, repos?: unknown[]): Promise<Thread> {
|
|
45
68
|
const cfg = config.load();
|
|
@@ -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
|
+
});
|