@usezombie/zombiectl 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 +76 -0
- package/bin/zombiectl.js +11 -0
- package/bun.lock +29 -0
- package/package.json +28 -0
- package/scripts/run-tests.mjs +38 -0
- package/src/cli.js +275 -0
- package/src/commands/admin.js +39 -0
- package/src/commands/agent.js +98 -0
- package/src/commands/agent_harness.js +43 -0
- package/src/commands/agent_improvement_report.js +42 -0
- package/src/commands/agent_profile.js +39 -0
- package/src/commands/agent_proposals.js +158 -0
- package/src/commands/agent_scores.js +44 -0
- package/src/commands/core-ops.js +108 -0
- package/src/commands/core.js +537 -0
- package/src/commands/harness.js +35 -0
- package/src/commands/harness_activate.js +53 -0
- package/src/commands/harness_active.js +32 -0
- package/src/commands/harness_compile.js +40 -0
- package/src/commands/harness_source.js +72 -0
- package/src/commands/run_preview.js +212 -0
- package/src/commands/run_preview_walk.js +1 -0
- package/src/commands/runs.js +35 -0
- package/src/commands/spec_init.js +287 -0
- package/src/commands/workspace_billing.js +26 -0
- package/src/constants/error-codes.js +1 -0
- package/src/lib/agent-loop.js +106 -0
- package/src/lib/analytics.js +114 -0
- package/src/lib/api-paths.js +2 -0
- package/src/lib/browser.js +96 -0
- package/src/lib/http.js +149 -0
- package/src/lib/sse-parser.js +50 -0
- package/src/lib/state.js +67 -0
- package/src/lib/tool-executors.js +110 -0
- package/src/lib/walk-dir.js +41 -0
- package/src/program/args.js +95 -0
- package/src/program/auth-guard.js +12 -0
- package/src/program/auth-token.js +44 -0
- package/src/program/banner.js +46 -0
- package/src/program/command-registry.js +17 -0
- package/src/program/http-client.js +38 -0
- package/src/program/io.js +83 -0
- package/src/program/routes.js +20 -0
- package/src/program/suggest.js +76 -0
- package/src/program/validate.js +24 -0
- package/src/ui-progress.js +59 -0
- package/src/ui-theme.js +62 -0
- package/test/admin_config.unit.test.js +25 -0
- package/test/agent-loop.unit.test.js +497 -0
- package/test/agent_harness.unit.test.js +52 -0
- package/test/agent_improvement_report.unit.test.js +74 -0
- package/test/agent_profile.unit.test.js +156 -0
- package/test/agent_proposals.unit.test.js +167 -0
- package/test/agent_scores.unit.test.js +220 -0
- package/test/analytics.unit.test.js +41 -0
- package/test/args.unit.test.js +69 -0
- package/test/auth-guard.test.js +33 -0
- package/test/auth-token.unit.test.js +112 -0
- package/test/banner.unit.test.js +442 -0
- package/test/browser.unit.test.js +16 -0
- package/test/cli-analytics.unit.test.js +296 -0
- package/test/did-you-mean.integration.test.js +76 -0
- package/test/doctor-json.test.js +81 -0
- package/test/error-codes.unit.test.js +7 -0
- package/test/harness-command.unit.test.js +180 -0
- package/test/harness-compile.test.js +81 -0
- package/test/harness-lifecycle.integration.test.js +339 -0
- package/test/harness-source-put.test.js +72 -0
- package/test/harness_activate.unit.test.js +48 -0
- package/test/harness_active.unit.test.js +53 -0
- package/test/harness_compile.unit.test.js +54 -0
- package/test/harness_source.unit.test.js +59 -0
- package/test/help.test.js +276 -0
- package/test/helpers-fs.js +32 -0
- package/test/helpers.js +31 -0
- package/test/io.unit.test.js +57 -0
- package/test/login.unit.test.js +115 -0
- package/test/logout.unit.test.js +65 -0
- package/test/parse.test.js +16 -0
- package/test/run-preview.edge.test.js +422 -0
- package/test/run-preview.integration.test.js +135 -0
- package/test/run-preview.security.test.js +246 -0
- package/test/run-preview.unit.test.js +131 -0
- package/test/run.unit.test.js +149 -0
- package/test/runs-cancel.unit.test.js +288 -0
- package/test/runs-list.unit.test.js +105 -0
- package/test/skill-secret.unit.test.js +94 -0
- package/test/spec-init.edge.test.js +232 -0
- package/test/spec-init.integration.test.js +128 -0
- package/test/spec-init.security.test.js +285 -0
- package/test/spec-init.unit.test.js +160 -0
- package/test/specs-sync.unit.test.js +164 -0
- package/test/sse-parser.unit.test.js +54 -0
- package/test/state.unit.test.js +34 -0
- package/test/streamfetch.unit.test.js +211 -0
- package/test/suggest.test.js +75 -0
- package/test/tool-executors.unit.test.js +165 -0
- package/test/validate.test.js +81 -0
- package/test/workspace-add.test.js +106 -0
- package/test/workspace.unit.test.js +230 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# zombiectl
|
|
2
|
+
|
|
3
|
+
JavaScript CLI for UseZombie operator workflows.
|
|
4
|
+
|
|
5
|
+
Local state lives in `~/.config/zombiectl/` by default:
|
|
6
|
+
- `credentials.json` — auth/session state
|
|
7
|
+
- `workspaces.json` — current workspace selection and local cache
|
|
8
|
+
- `ZOMBIE_STATE_DIR` overrides the base directory for tests or custom automation
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @usezombie/zombiectl
|
|
14
|
+
zombiectl --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Common flows (installed binary):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
zombiectl login
|
|
23
|
+
zombiectl workspace add https://github.com/org/repo
|
|
24
|
+
zombiectl specs sync
|
|
25
|
+
zombiectl run
|
|
26
|
+
zombiectl run status <run_id>
|
|
27
|
+
zombiectl doctor --json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Operator trajectory flow:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
zombiectl agent profile <agent-id>
|
|
34
|
+
zombiectl agent improvement-report <agent-id>
|
|
35
|
+
zombiectl agent proposals <agent-id>
|
|
36
|
+
zombiectl agent proposals <agent-id> veto <proposal-id> --reason "operator pause"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`workspace add` opens the UseZombie GitHub App install page and binds workspace via callback automatically.
|
|
40
|
+
Global flags:
|
|
41
|
+
- `--api <url>` API base URL (default `http://localhost:3000`)
|
|
42
|
+
- `--json` machine-readable output
|
|
43
|
+
- `--no-open` do not auto-open browser on login
|
|
44
|
+
- `--no-input` disable prompts (reserved for non-interactive flows)
|
|
45
|
+
- `--help`
|
|
46
|
+
- `--version`
|
|
47
|
+
|
|
48
|
+
Analytics env vars (optional):
|
|
49
|
+
- `ZOMBIE_POSTHOG_KEY` override the bundled PostHog project API key (`phc_...`) for local/dev testing
|
|
50
|
+
- `ZOMBIE_POSTHOG_ENABLED` set `false`/`0` to disable telemetry even when the bundled key exists
|
|
51
|
+
- `ZOMBIE_POSTHOG_HOST` override PostHog host (default `https://us.i.posthog.com`)
|
|
52
|
+
|
|
53
|
+
Standard operator path:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# DEV
|
|
57
|
+
export ZOMBIE_POSTHOG_KEY="$(op read 'op://ZMB_CD_DEV/posthog-dev/credential')"
|
|
58
|
+
|
|
59
|
+
# PROD
|
|
60
|
+
export ZOMBIE_POSTHOG_KEY="$(op read 'op://ZMB_CD_PROD/posthog-prod/credential')"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This follows the same milestone playbook and `scripts/check-credentials.sh` contract as the other deploy/runtime keys.
|
|
64
|
+
|
|
65
|
+
Analytics key policy:
|
|
66
|
+
- `zombiectl` ships with a bundled default PostHog project key so end users do not need analytics setup after `npm install -g`.
|
|
67
|
+
- The bundled key is not an auth secret; it is a write-scoped ingestion key.
|
|
68
|
+
- Do not persist analytics keys in CLI auth/session state such as `credentials.json`.
|
|
69
|
+
- If exposed, the expected risk is analytics pollution or noisy metrics, not control-plane access.
|
|
70
|
+
|
|
71
|
+
## Verify
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bun test
|
|
75
|
+
bun run build
|
|
76
|
+
```
|
package/bin/zombiectl.js
ADDED
package/bun.lock
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "zombiectl",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"posthog-node": "^5.10.0",
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
"packages": {
|
|
13
|
+
"@posthog/core": ["@posthog/core@1.23.3", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-nehG2nig9qiU4lEUIyfXQLaBnylm5wdDiIBsp2tBFJX5BcUHNAXSwpkHjKLQ9TDfik0HW1HwZ2mY/3hJgJNToQ=="],
|
|
14
|
+
|
|
15
|
+
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
|
16
|
+
|
|
17
|
+
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
|
18
|
+
|
|
19
|
+
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
|
20
|
+
|
|
21
|
+
"posthog-node": ["posthog-node@5.28.1", "", { "dependencies": { "@posthog/core": "1.23.3" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-dfUaeNwKc/YZI/vbP5IJSMuMprPLbtzWM/ZQFkuyWj0fhU3PW0VmxNO1gkqy48SsUauamczEPBKTQRYZVLcacg=="],
|
|
22
|
+
|
|
23
|
+
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
|
24
|
+
|
|
25
|
+
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
|
26
|
+
|
|
27
|
+
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
|
28
|
+
}
|
|
29
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usezombie/zombiectl",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "UseZombie CLI",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/usezombie/usezombie"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"zombiectl": "./bin/zombiectl.js"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=24.0.0",
|
|
18
|
+
"bun": ">=1.3.0"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "node ./scripts/run-tests.mjs",
|
|
22
|
+
"build": "node --check ./src/cli.js && node --check ./bin/zombiectl.js"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"posthog-node": "^5.10.0"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT"
|
|
28
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
const testDir = path.resolve("test");
|
|
6
|
+
const files = fs.readdirSync(testDir)
|
|
7
|
+
.filter((file) => file.endsWith(".js"))
|
|
8
|
+
.map((file) => path.join("test", file))
|
|
9
|
+
.sort();
|
|
10
|
+
|
|
11
|
+
const nodeTests = [];
|
|
12
|
+
const bunTests = [];
|
|
13
|
+
|
|
14
|
+
for (const file of files) {
|
|
15
|
+
const source = fs.readFileSync(path.resolve(file), "utf8");
|
|
16
|
+
if (source.includes('from "node:test"') || source.includes("from 'node:test'")) {
|
|
17
|
+
nodeTests.push(file);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (source.includes('from "bun:test"') || source.includes("from 'bun:test'")) {
|
|
21
|
+
bunTests.push(file);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function run(command, args) {
|
|
26
|
+
if (args.length === 0) return;
|
|
27
|
+
const result = spawnSync(command, args, {
|
|
28
|
+
stdio: "inherit",
|
|
29
|
+
cwd: process.cwd(),
|
|
30
|
+
env: process.env,
|
|
31
|
+
});
|
|
32
|
+
if (result.status !== 0) {
|
|
33
|
+
process.exit(result.status ?? 1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
run("node", ["--test", ...nodeTests]);
|
|
38
|
+
run("bun", ["test", ...bunTests]);
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { openUrl } from "./lib/browser.js";
|
|
2
|
+
import {
|
|
3
|
+
cliAnalytics,
|
|
4
|
+
drainCliAnalyticsEvents,
|
|
5
|
+
getCliAnalyticsContext,
|
|
6
|
+
} from "./lib/analytics.js";
|
|
7
|
+
import { findRoute } from "./program/routes.js";
|
|
8
|
+
import { registerProgramCommands } from "./program/command-registry.js";
|
|
9
|
+
import { commandHarness as commandHarnessModule } from "./commands/harness.js";
|
|
10
|
+
import { commandAgent as commandAgentModule } from "./commands/agent.js";
|
|
11
|
+
import { commandAdmin as commandAdminModule } from "./commands/admin.js";
|
|
12
|
+
import { commandRuns as commandRunsModule } from "./commands/runs.js";
|
|
13
|
+
import { ui, printKeyValue, printSection, printTable } from "./ui-theme.js";
|
|
14
|
+
import { createSpinner } from "./ui-progress.js";
|
|
15
|
+
import {
|
|
16
|
+
clearCredentials,
|
|
17
|
+
loadCredentials,
|
|
18
|
+
loadWorkspaces,
|
|
19
|
+
newIdempotencyKey,
|
|
20
|
+
saveCredentials,
|
|
21
|
+
saveWorkspaces,
|
|
22
|
+
} from "./lib/state.js";
|
|
23
|
+
import { ApiError, apiHeaders, printApiError, request } from "./program/http-client.js";
|
|
24
|
+
import { parseFlags, parseGlobalArgs, normalizeApiUrl, DEFAULT_API_URL } from "./program/args.js";
|
|
25
|
+
import { extractDistinctIdFromToken, extractRoleFromToken } from "./program/auth-token.js";
|
|
26
|
+
import { printHelp, printJson, writeLine } from "./program/io.js";
|
|
27
|
+
import { printBanner, printPreReleaseWarning } from "./program/banner.js";
|
|
28
|
+
import { suggestCommand } from "./program/suggest.js";
|
|
29
|
+
import { requireAuth, AUTH_FAIL_MESSAGE } from "./program/auth-guard.js";
|
|
30
|
+
import { createCoreHandlers } from "./commands/core.js";
|
|
31
|
+
|
|
32
|
+
export const VERSION = "0.3.0";
|
|
33
|
+
|
|
34
|
+
export { parseGlobalArgs };
|
|
35
|
+
|
|
36
|
+
const AUTH_EXEMPT_ROUTES = new Set(["login", "doctor", "spec.init"]);
|
|
37
|
+
|
|
38
|
+
export async function runCli(argv, io = {}) {
|
|
39
|
+
const stdout = io.stdout || process.stdout;
|
|
40
|
+
const stderr = io.stderr || process.stderr;
|
|
41
|
+
const env = io.env || process.env;
|
|
42
|
+
const fetchImpl = io.fetchImpl || globalThis.fetch;
|
|
43
|
+
|
|
44
|
+
const { global, rest } = parseGlobalArgs(argv, env);
|
|
45
|
+
const noColor = Boolean(env.NO_COLOR === "1" || env.NO_COLOR === "true");
|
|
46
|
+
|
|
47
|
+
printPreReleaseWarning(stderr, { noColor, jsonMode: global.json, ttyOnly: !stderr.isTTY });
|
|
48
|
+
|
|
49
|
+
if (global.version) {
|
|
50
|
+
if (global.json) {
|
|
51
|
+
printJson(stdout, { version: VERSION });
|
|
52
|
+
} else {
|
|
53
|
+
printBanner(stdout, VERSION, { noColor, jsonMode: false });
|
|
54
|
+
}
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const creds = await loadCredentials().catch(() => ({}));
|
|
59
|
+
const workspaces = await loadWorkspaces().catch(() => ({ items: [], current_workspace_id: null }));
|
|
60
|
+
const resolvedToken = creds.token || env.ZOMBIE_TOKEN || null;
|
|
61
|
+
const resolvedApiKey = env.API_KEY || env.ZOMBIE_API_KEY || null;
|
|
62
|
+
const resolvedAuthRole = extractRoleFromToken(resolvedToken) || (resolvedApiKey ? "admin" : null);
|
|
63
|
+
|
|
64
|
+
if (global.help || rest.length === 0) {
|
|
65
|
+
printHelp(stdout, ui, {
|
|
66
|
+
version: VERSION,
|
|
67
|
+
env,
|
|
68
|
+
jsonMode: global.json,
|
|
69
|
+
authRole: resolvedAuthRole,
|
|
70
|
+
});
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const ctx = {
|
|
75
|
+
apiUrl: normalizeApiUrl(global.apiUrl || creds.api_url || DEFAULT_API_URL),
|
|
76
|
+
token: resolvedToken,
|
|
77
|
+
apiKey: resolvedApiKey,
|
|
78
|
+
authRole: resolvedAuthRole,
|
|
79
|
+
jsonMode: global.json,
|
|
80
|
+
noOpen: global.noOpen,
|
|
81
|
+
noInput: global.noInput,
|
|
82
|
+
stdout,
|
|
83
|
+
stderr,
|
|
84
|
+
env,
|
|
85
|
+
fetchImpl,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const command = rest[0];
|
|
89
|
+
const args = rest.slice(1);
|
|
90
|
+
const route = findRoute(command, args);
|
|
91
|
+
|
|
92
|
+
// Auth guard: skip for login, doctor, help, version
|
|
93
|
+
if (route && !AUTH_EXEMPT_ROUTES.has(route.key)) {
|
|
94
|
+
const auth = requireAuth(ctx);
|
|
95
|
+
if (!auth.ok) {
|
|
96
|
+
if (ctx.jsonMode) {
|
|
97
|
+
printJson(stderr, { error: { code: "AUTH_REQUIRED", message: AUTH_FAIL_MESSAGE } });
|
|
98
|
+
} else {
|
|
99
|
+
writeLine(stderr, ui.err(AUTH_FAIL_MESSAGE));
|
|
100
|
+
}
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const core = createCoreHandlers(ctx, workspaces, {
|
|
106
|
+
clearCredentials,
|
|
107
|
+
createSpinner,
|
|
108
|
+
newIdempotencyKey,
|
|
109
|
+
openUrl,
|
|
110
|
+
parseFlags,
|
|
111
|
+
printJson,
|
|
112
|
+
printKeyValue,
|
|
113
|
+
printSection,
|
|
114
|
+
printTable,
|
|
115
|
+
request,
|
|
116
|
+
saveCredentials,
|
|
117
|
+
saveWorkspaces,
|
|
118
|
+
ui,
|
|
119
|
+
writeLine,
|
|
120
|
+
apiHeaders,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const analyticsClient = await cliAnalytics.createCliAnalytics(env);
|
|
124
|
+
const distinctId = extractDistinctIdFromToken(ctx.token);
|
|
125
|
+
|
|
126
|
+
const handlers = registerProgramCommands({
|
|
127
|
+
login: (routeArgs) => core.commandLogin(routeArgs),
|
|
128
|
+
logout: () => core.commandLogout(),
|
|
129
|
+
workspace: (routeArgs) => core.commandWorkspace(routeArgs),
|
|
130
|
+
specInit: (routeArgs) => core.commandSpecInit(routeArgs.slice(1)),
|
|
131
|
+
specsSync: (routeArgs) => core.commandSpecsSync(routeArgs.slice(1)),
|
|
132
|
+
run: (routeArgs) => core.commandRun(routeArgs),
|
|
133
|
+
runsList: (routeArgs) => core.commandRunsList(routeArgs.slice(1)),
|
|
134
|
+
doctor: () => core.commandDoctor(),
|
|
135
|
+
harness: (routeArgs) => commandHarnessModule(ctx, routeArgs, workspaces, {
|
|
136
|
+
parseFlags,
|
|
137
|
+
request,
|
|
138
|
+
apiHeaders,
|
|
139
|
+
ui,
|
|
140
|
+
printJson,
|
|
141
|
+
printKeyValue,
|
|
142
|
+
printSection,
|
|
143
|
+
writeLine,
|
|
144
|
+
}),
|
|
145
|
+
skillSecret: (routeArgs) => core.commandSkillSecret(routeArgs),
|
|
146
|
+
agent: (routeArgs) => commandAgentModule(ctx, routeArgs, workspaces, {
|
|
147
|
+
parseFlags,
|
|
148
|
+
request,
|
|
149
|
+
apiHeaders,
|
|
150
|
+
ui,
|
|
151
|
+
printJson,
|
|
152
|
+
printKeyValue,
|
|
153
|
+
printSection,
|
|
154
|
+
printTable,
|
|
155
|
+
writeLine,
|
|
156
|
+
}),
|
|
157
|
+
admin: (routeArgs) => commandAdminModule(ctx, routeArgs, workspaces, {
|
|
158
|
+
parseFlags,
|
|
159
|
+
request,
|
|
160
|
+
apiHeaders,
|
|
161
|
+
ui,
|
|
162
|
+
printJson,
|
|
163
|
+
printSection,
|
|
164
|
+
writeLine,
|
|
165
|
+
}),
|
|
166
|
+
runsCancel: (routeArgs) => commandRunsModule(ctx, routeArgs, {
|
|
167
|
+
parseFlags,
|
|
168
|
+
request,
|
|
169
|
+
apiHeaders,
|
|
170
|
+
ui,
|
|
171
|
+
printJson,
|
|
172
|
+
writeLine,
|
|
173
|
+
}),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
if (route && handlers[route.key]) {
|
|
178
|
+
cliAnalytics.trackCliEvent(analyticsClient, distinctId, "cli_command_started", {
|
|
179
|
+
command: route.key,
|
|
180
|
+
json_mode: String(ctx.jsonMode),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const exitCode = await handlers[route.key](args);
|
|
184
|
+
const analyticsContext = getCliAnalyticsContext(ctx);
|
|
185
|
+
let eventDistinctId = distinctId;
|
|
186
|
+
if (exitCode === 0 && route.key === "login") {
|
|
187
|
+
const latestCreds = await loadCredentials();
|
|
188
|
+
eventDistinctId = extractDistinctIdFromToken(latestCreds.token) || distinctId;
|
|
189
|
+
}
|
|
190
|
+
cliAnalytics.trackCliEvent(analyticsClient, distinctId, "cli_command_finished", {
|
|
191
|
+
command: route.key,
|
|
192
|
+
exit_code: String(exitCode),
|
|
193
|
+
...analyticsContext,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (exitCode === 0 && route.key === "login") {
|
|
197
|
+
cliAnalytics.trackCliEvent(analyticsClient, eventDistinctId, "user_authenticated", {
|
|
198
|
+
command: route.key,
|
|
199
|
+
...analyticsContext,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (exitCode === 0 && route.key === "workspace" && args[0] === "add") {
|
|
203
|
+
cliAnalytics.trackCliEvent(analyticsClient, distinctId, "workspace_created", {
|
|
204
|
+
command: route.key,
|
|
205
|
+
...analyticsContext,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (exitCode === 0 && route.key === "run" && args[0] !== "status") {
|
|
209
|
+
cliAnalytics.trackCliEvent(analyticsClient, distinctId, "run_triggered", {
|
|
210
|
+
command: route.key,
|
|
211
|
+
...analyticsContext,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
for (const queuedEvent of drainCliAnalyticsEvents(ctx)) {
|
|
215
|
+
cliAnalytics.trackCliEvent(analyticsClient, eventDistinctId, queuedEvent.event, {
|
|
216
|
+
command: route.key,
|
|
217
|
+
...analyticsContext,
|
|
218
|
+
...queuedEvent.properties,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (exitCode !== 0) {
|
|
222
|
+
cliAnalytics.trackCliEvent(analyticsClient, distinctId, "cli_error", {
|
|
223
|
+
command: route.key,
|
|
224
|
+
exit_code: String(exitCode),
|
|
225
|
+
...analyticsContext,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return exitCode;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// "Did you mean?" suggestion for unknown commands
|
|
232
|
+
const fullInput = [command, ...args].join(" ");
|
|
233
|
+
const suggestions = suggestCommand(fullInput);
|
|
234
|
+
if (suggestions.length > 0) {
|
|
235
|
+
writeLine(stderr, ui.err(`unknown command: ${command}`));
|
|
236
|
+
writeLine(stderr);
|
|
237
|
+
writeLine(stderr, "The most similar commands are");
|
|
238
|
+
for (const s of suggestions) {
|
|
239
|
+
writeLine(stderr, ` ${s}`);
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
writeLine(stderr, ui.err(`unknown command: ${command}`));
|
|
243
|
+
writeLine(stderr, `Run 'zombiectl --help' for usage.`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
cliAnalytics.trackCliEvent(analyticsClient, distinctId, "cli_error", {
|
|
247
|
+
command,
|
|
248
|
+
error_code: "UNKNOWN_COMMAND",
|
|
249
|
+
exit_code: "2",
|
|
250
|
+
...getCliAnalyticsContext(ctx),
|
|
251
|
+
});
|
|
252
|
+
return 2;
|
|
253
|
+
} catch (err) {
|
|
254
|
+
const errorCode = err instanceof ApiError ? err.code || "API_ERROR" : "UNEXPECTED";
|
|
255
|
+
cliAnalytics.trackCliEvent(analyticsClient, distinctId, "cli_error", {
|
|
256
|
+
command: route?.key || command || "unknown",
|
|
257
|
+
error_code: errorCode,
|
|
258
|
+
exit_code: "1",
|
|
259
|
+
...getCliAnalyticsContext(ctx),
|
|
260
|
+
});
|
|
261
|
+
try {
|
|
262
|
+
printApiError(stderr, err, global.json, printJson, writeLine);
|
|
263
|
+
return 1;
|
|
264
|
+
} catch {
|
|
265
|
+
if (global.json) {
|
|
266
|
+
printJson(stderr, { error: { code: "UNEXPECTED", message: String(err) } });
|
|
267
|
+
} else {
|
|
268
|
+
writeLine(stderr, `error: ${String(err)}`);
|
|
269
|
+
}
|
|
270
|
+
return 1;
|
|
271
|
+
}
|
|
272
|
+
} finally {
|
|
273
|
+
await cliAnalytics.shutdownCliAnalytics(analyticsClient);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export async function commandAdmin(ctx, args, workspaces, deps) {
|
|
2
|
+
const { parseFlags, ui, writeLine } = deps;
|
|
3
|
+
const group = args[0];
|
|
4
|
+
const action = args[1];
|
|
5
|
+
const key = args[2];
|
|
6
|
+
const parsed = parseFlags(args.slice(3));
|
|
7
|
+
|
|
8
|
+
if (group === "config" && action === "set" && key === "scoring_context_max_tokens") {
|
|
9
|
+
const workspaceId = parsed.options["workspace-id"];
|
|
10
|
+
const rawValue = parsed.positionals[0] || parsed.options.value;
|
|
11
|
+
if (!workspaceId) {
|
|
12
|
+
writeLine(ctx.stderr, ui.err("admin config set scoring_context_max_tokens requires --workspace-id"));
|
|
13
|
+
return 2;
|
|
14
|
+
}
|
|
15
|
+
if (!rawValue) {
|
|
16
|
+
writeLine(ctx.stderr, ui.err("admin config set scoring_context_max_tokens requires <value>"));
|
|
17
|
+
return 2;
|
|
18
|
+
}
|
|
19
|
+
const parsedValue = Number.parseInt(rawValue, 10);
|
|
20
|
+
if (!Number.isInteger(parsedValue) || parsedValue < 512 || parsedValue > 8192) {
|
|
21
|
+
writeLine(ctx.stderr, ui.err("scoring_context_max_tokens must be an integer between 512 and 8192"));
|
|
22
|
+
return 2;
|
|
23
|
+
}
|
|
24
|
+
const res = await deps.request(ctx, `/v1/workspaces/${encodeURIComponent(workspaceId)}/scoring/config`, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: deps.apiHeaders(ctx),
|
|
27
|
+
body: JSON.stringify({ scoring_context_max_tokens: parsedValue }),
|
|
28
|
+
});
|
|
29
|
+
if (ctx.jsonMode) {
|
|
30
|
+
deps.printJson(ctx.stdout, res);
|
|
31
|
+
} else {
|
|
32
|
+
writeLine(ctx.stdout, ui.ok(`workspace scoring_context_max_tokens=${res.scoring_context_max_tokens}`));
|
|
33
|
+
}
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
writeLine(ctx.stderr, ui.err("usage: admin config set scoring_context_max_tokens <value> --workspace-id ID"));
|
|
38
|
+
return 2;
|
|
39
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { setCliAnalyticsContext } from "../lib/analytics.js";
|
|
2
|
+
import { commandAgentScores } from "./agent_scores.js";
|
|
3
|
+
import { commandAgentProfile } from "./agent_profile.js";
|
|
4
|
+
import { commandAgentImprovementReport } from "./agent_improvement_report.js";
|
|
5
|
+
import { commandAgentProposals } from "./agent_proposals.js";
|
|
6
|
+
import { commandAgentHarness } from "./agent_harness.js";
|
|
7
|
+
import { validateRequiredId } from "../program/validate.js";
|
|
8
|
+
|
|
9
|
+
export async function commandAgent(ctx, args, workspaces, deps) {
|
|
10
|
+
const { parseFlags, ui, writeLine } = deps;
|
|
11
|
+
|
|
12
|
+
const action = args[0];
|
|
13
|
+
const parsed = parseFlags(args.slice(1));
|
|
14
|
+
const agentId = parsed.positionals[0] || parsed.options["agent-id"];
|
|
15
|
+
if (agentId) setCliAnalyticsContext(ctx, { agent_id: agentId });
|
|
16
|
+
|
|
17
|
+
if (action === "scores") {
|
|
18
|
+
if (!agentId) {
|
|
19
|
+
writeLine(ctx.stderr, ui.err("agent scores requires <agent-id>"));
|
|
20
|
+
return 2;
|
|
21
|
+
}
|
|
22
|
+
const check = validateRequiredId(agentId, "agent-id");
|
|
23
|
+
if (!check.ok) {
|
|
24
|
+
writeLine(ctx.stderr, ui.err(check.message));
|
|
25
|
+
return 2;
|
|
26
|
+
}
|
|
27
|
+
return commandAgentScores(ctx, parsed, agentId, deps);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (action === "profile") {
|
|
31
|
+
if (!agentId) {
|
|
32
|
+
writeLine(ctx.stderr, ui.err("agent profile requires <agent-id>"));
|
|
33
|
+
return 2;
|
|
34
|
+
}
|
|
35
|
+
const check = validateRequiredId(agentId, "agent-id");
|
|
36
|
+
if (!check.ok) {
|
|
37
|
+
writeLine(ctx.stderr, ui.err(check.message));
|
|
38
|
+
return 2;
|
|
39
|
+
}
|
|
40
|
+
return commandAgentProfile(ctx, parsed, agentId, deps);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (action === "improvement-report") {
|
|
44
|
+
if (!agentId) {
|
|
45
|
+
writeLine(ctx.stderr, ui.err("agent improvement-report requires <agent-id>"));
|
|
46
|
+
return 2;
|
|
47
|
+
}
|
|
48
|
+
const check = validateRequiredId(agentId, "agent-id");
|
|
49
|
+
if (!check.ok) {
|
|
50
|
+
writeLine(ctx.stderr, ui.err(check.message));
|
|
51
|
+
return 2;
|
|
52
|
+
}
|
|
53
|
+
return commandAgentImprovementReport(ctx, parsed, agentId, deps);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (action === "proposals") {
|
|
57
|
+
if (!agentId) {
|
|
58
|
+
writeLine(ctx.stderr, ui.err("agent proposals requires <agent-id>"));
|
|
59
|
+
return 2;
|
|
60
|
+
}
|
|
61
|
+
const check = validateRequiredId(agentId, "agent-id");
|
|
62
|
+
if (!check.ok) {
|
|
63
|
+
writeLine(ctx.stderr, ui.err(check.message));
|
|
64
|
+
return 2;
|
|
65
|
+
}
|
|
66
|
+
return commandAgentProposals(ctx, parsed, agentId, deps);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (action === "harness") {
|
|
70
|
+
if (!agentId) {
|
|
71
|
+
writeLine(ctx.stderr, ui.err("agent harness revert requires <agent-id>"));
|
|
72
|
+
return 2;
|
|
73
|
+
}
|
|
74
|
+
const check = validateRequiredId(agentId, "agent-id");
|
|
75
|
+
if (!check.ok) {
|
|
76
|
+
writeLine(ctx.stderr, ui.err(check.message));
|
|
77
|
+
return 2;
|
|
78
|
+
}
|
|
79
|
+
const changeId = parsed.options["to-change"];
|
|
80
|
+
if (!changeId) {
|
|
81
|
+
writeLine(ctx.stderr, ui.err("agent harness revert requires --to-change <change-id>"));
|
|
82
|
+
return 2;
|
|
83
|
+
}
|
|
84
|
+
const changeCheck = validateRequiredId(changeId, "change-id");
|
|
85
|
+
if (!changeCheck.ok) {
|
|
86
|
+
writeLine(ctx.stderr, ui.err(changeCheck.message));
|
|
87
|
+
return 2;
|
|
88
|
+
}
|
|
89
|
+
return commandAgentHarness(ctx, parsed, agentId, deps);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
writeLine(ctx.stderr, ui.err("usage: agent scores <agent-id> [--limit N] [--starting-after ID] [--json]"));
|
|
93
|
+
writeLine(ctx.stderr, ui.err(" agent profile <agent-id> [--json]"));
|
|
94
|
+
writeLine(ctx.stderr, ui.err(" agent improvement-report <agent-id> [--json]"));
|
|
95
|
+
writeLine(ctx.stderr, ui.err(" agent proposals <agent-id> [approve <proposal-id> | reject <proposal-id> [--reason TEXT] | veto <proposal-id> [--reason TEXT] | --json]"));
|
|
96
|
+
writeLine(ctx.stderr, ui.err(" agent harness revert <agent-id> --to-change <change-id>"));
|
|
97
|
+
return 2;
|
|
98
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AGENTS_PATH } from "../lib/api-paths.js";
|
|
2
|
+
import { queueCliAnalyticsEvent, setCliAnalyticsContext } from "../lib/analytics.js";
|
|
3
|
+
|
|
4
|
+
export async function commandAgentHarness(ctx, parsed, agentId, deps) {
|
|
5
|
+
const { request, apiHeaders, printJson, ui, writeLine } = deps;
|
|
6
|
+
|
|
7
|
+
const subaction = parsed.positionals[0] || null;
|
|
8
|
+
if (subaction !== "revert") {
|
|
9
|
+
writeLine(ctx.stderr, ui.err("usage: agent harness revert <agent-id> --to-change <change-id>"));
|
|
10
|
+
return 2;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const changeId = parsed.options["to-change"] || null;
|
|
14
|
+
if (!changeId) {
|
|
15
|
+
writeLine(ctx.stderr, ui.err("agent harness revert requires --to-change <change-id>"));
|
|
16
|
+
return 2;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const res = await request(
|
|
20
|
+
ctx,
|
|
21
|
+
`${AGENTS_PATH}${encodeURIComponent(agentId)}/harness/changes/${encodeURIComponent(changeId)}:revert`,
|
|
22
|
+
{
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: apiHeaders(ctx),
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
setCliAnalyticsContext(ctx, {
|
|
28
|
+
agent_id: agentId,
|
|
29
|
+
change_id: res.change_id,
|
|
30
|
+
reverted_from: res.reverted_from,
|
|
31
|
+
});
|
|
32
|
+
queueCliAnalyticsEvent(ctx, "agent_harness_reverted", {
|
|
33
|
+
agent_id: agentId,
|
|
34
|
+
change_id: res.change_id,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (ctx.jsonMode) {
|
|
38
|
+
printJson(ctx.stdout, res);
|
|
39
|
+
} else {
|
|
40
|
+
writeLine(ctx.stdout, ui.ok(`reverted ${res.reverted_from} -> ${res.change_id}`));
|
|
41
|
+
}
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AGENTS_PATH } from "../lib/api-paths.js";
|
|
2
|
+
import { queueCliAnalyticsEvent, setCliAnalyticsContext } from "../lib/analytics.js";
|
|
3
|
+
|
|
4
|
+
export async function commandAgentImprovementReport(ctx, parsed, agentId, deps) {
|
|
5
|
+
const { request, apiHeaders, printJson, printKeyValue, printSection = () => {} } = deps;
|
|
6
|
+
|
|
7
|
+
const res = await request(ctx, `${AGENTS_PATH}${encodeURIComponent(agentId)}/improvement-report`, {
|
|
8
|
+
method: "GET",
|
|
9
|
+
headers: apiHeaders(ctx),
|
|
10
|
+
});
|
|
11
|
+
setCliAnalyticsContext(ctx, {
|
|
12
|
+
agent_id: res.agent_id,
|
|
13
|
+
trust_level: res.trust_level,
|
|
14
|
+
proposals_generated: res.proposals_generated,
|
|
15
|
+
proposals_applied: res.proposals_applied,
|
|
16
|
+
avg_score_delta_per_applied_change: res.avg_score_delta_per_applied_change,
|
|
17
|
+
});
|
|
18
|
+
queueCliAnalyticsEvent(ctx, "agent_improvement_report_viewed", {
|
|
19
|
+
agent_id: res.agent_id,
|
|
20
|
+
proposals_generated: res.proposals_generated,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (ctx.jsonMode) {
|
|
24
|
+
printJson(ctx.stdout, res);
|
|
25
|
+
} else {
|
|
26
|
+
printSection(ctx.stdout, `Agent improvement report · ${res.agent_id}`);
|
|
27
|
+
printKeyValue(ctx.stdout, {
|
|
28
|
+
agent_id: res.agent_id,
|
|
29
|
+
trust_level: res.trust_level,
|
|
30
|
+
improvement_stalled_warning: res.improvement_stalled_warning,
|
|
31
|
+
proposals_generated: res.proposals_generated,
|
|
32
|
+
proposals_approved: res.proposals_approved,
|
|
33
|
+
proposals_vetoed: res.proposals_vetoed,
|
|
34
|
+
proposals_rejected: res.proposals_rejected,
|
|
35
|
+
proposals_applied: res.proposals_applied,
|
|
36
|
+
avg_score_delta_per_applied_change: res.avg_score_delta_per_applied_change,
|
|
37
|
+
current_tier: res.current_tier,
|
|
38
|
+
baseline_tier: res.baseline_tier,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return 0;
|
|
42
|
+
}
|