crewhaus 0.1.3 → 0.1.4
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/package.json +64 -64
- package/src/index.test.ts +127 -7
- package/src/index.ts +110 -64
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ CrewHaus compiles one spec into the shape each situation calls for — a CLI age
|
|
|
11
11
|
The fastest path is the self-contained binary — one file, no Bun/Node required:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
brew
|
|
14
|
+
brew tap crewhaus/tap && brew install crewhaus # macOS / Linux (Homebrew)
|
|
15
15
|
scoop install crewhaus # Windows (Scoop; see repo for the bucket)
|
|
16
16
|
winget install CrewHaus.CLI # Windows (winget)
|
|
17
17
|
# Debian / Ubuntu (apt): signed repo at https://crewhaus.github.io/apt
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crewhaus",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CrewHaus — the meta-harness compiler for AI agents. Compile one crewhaus.yaml spec into a CLI agent, channel bot, RAG pipeline, multi-agent crew, eval harness, voice or browser agent, and more.",
|
|
6
6
|
"keywords": [
|
|
@@ -28,69 +28,69 @@
|
|
|
28
28
|
"test": "bun test src"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@crewhaus/adapter-anthropic": "0.1.
|
|
32
|
-
"@crewhaus/agent-context-isolation": "0.1.
|
|
33
|
-
"@crewhaus/audit-log": "0.1.
|
|
34
|
-
"@crewhaus/eval-optimizer-orchestrator": "0.1.
|
|
35
|
-
"@crewhaus/prompt-optimizer": "0.1.
|
|
36
|
-
"@crewhaus/prompt-optimizer-claude": "0.1.
|
|
37
|
-
"@crewhaus/justification-judge-claude": "0.1.
|
|
38
|
-
"@crewhaus/spec-patch": "0.1.
|
|
39
|
-
"@crewhaus/canary-controller": "0.1.
|
|
40
|
-
"@crewhaus/compiler": "0.1.
|
|
41
|
-
"@crewhaus/computer-use-driver": "0.1.
|
|
42
|
-
"@crewhaus/context-bundle": "0.1.
|
|
43
|
-
"@crewhaus/deployment-controller": "0.1.
|
|
44
|
-
"@crewhaus/egress-classifier": "0.1.
|
|
45
|
-
"@crewhaus/egress-matcher-semantic": "0.1.
|
|
46
|
-
"@crewhaus/embedder": "0.1.
|
|
47
|
-
"@crewhaus/errors": "0.1.
|
|
48
|
-
"@crewhaus/eval-dataset": "0.1.
|
|
49
|
-
"@crewhaus/eval-grader": "0.1.
|
|
50
|
-
"@crewhaus/eval-report": "0.1.
|
|
51
|
-
"@crewhaus/eval-runner": "0.1.
|
|
52
|
-
"@crewhaus/hooks-engine": "0.1.
|
|
53
|
-
"@crewhaus/infra-utils": "0.1.
|
|
54
|
-
"@crewhaus/ir": "0.1.
|
|
55
|
-
"@crewhaus/logging": "0.1.
|
|
56
|
-
"@crewhaus/mcp-host": "0.1.
|
|
57
|
-
"@crewhaus/model-router": "0.1.
|
|
58
|
-
"@crewhaus/migration-engine": "0.1.
|
|
59
|
-
"@crewhaus/migration-runner": "0.1.
|
|
60
|
-
"@crewhaus/permission-engine": "0.1.
|
|
61
|
-
"@crewhaus/run-context": "0.1.
|
|
62
|
-
"@crewhaus/runtime-core": "0.1.
|
|
63
|
-
"@crewhaus/secrets-manager": "0.1.
|
|
64
|
-
"@crewhaus/session-store": "0.1.
|
|
65
|
-
"@crewhaus/spec-registry": "0.1.
|
|
66
|
-
"@crewhaus/skills-registry": "0.1.
|
|
67
|
-
"@crewhaus/slash-commands": "0.1.
|
|
68
|
-
"@crewhaus/spec": "0.1.
|
|
69
|
-
"@crewhaus/sub-agent-spawner": "0.1.
|
|
70
|
-
"@crewhaus/trace-event-bus": "0.1.
|
|
71
|
-
"@crewhaus/tool-bash": "0.1.
|
|
72
|
-
"@crewhaus/tool-builder": "0.1.
|
|
73
|
-
"@crewhaus/tool-catalog": "0.1.
|
|
74
|
-
"@crewhaus/tool-codegraph": "0.1.
|
|
75
|
-
"@crewhaus/tool-fetch": "0.1.
|
|
76
|
-
"@crewhaus/tool-fs": "0.1.
|
|
77
|
-
"@crewhaus/tool-image": "0.1.
|
|
78
|
-
"@crewhaus/tool-image-generation": "0.1.
|
|
79
|
-
"@crewhaus/tool-document-ingest": "0.1.
|
|
80
|
-
"@crewhaus/tool-mcp": "0.1.
|
|
81
|
-
"@crewhaus/tool-mouse-keyboard": "0.1.
|
|
82
|
-
"@crewhaus/tool-navigate": "0.1.
|
|
83
|
-
"@crewhaus/tool-screen-capture": "0.1.
|
|
84
|
-
"@crewhaus/tool-task": "0.1.
|
|
85
|
-
"@crewhaus/tool-todo": "0.1.
|
|
86
|
-
"@crewhaus/tool-vision-grounding": "0.1.
|
|
87
|
-
"@crewhaus/tool-web": "0.1.
|
|
88
|
-
"@crewhaus/docker-images": "0.1.
|
|
89
|
-
"@crewhaus/crewhaus-cloud": "0.1.
|
|
90
|
-
"@crewhaus/federation-discovery": "0.1.
|
|
91
|
-
"@crewhaus/sandbox": "0.1.
|
|
92
|
-
"@crewhaus/sandbox-image-registry": "0.1.
|
|
93
|
-
"@crewhaus/compliance-controls": "0.1.
|
|
31
|
+
"@crewhaus/adapter-anthropic": "0.1.4",
|
|
32
|
+
"@crewhaus/agent-context-isolation": "0.1.4",
|
|
33
|
+
"@crewhaus/audit-log": "0.1.4",
|
|
34
|
+
"@crewhaus/eval-optimizer-orchestrator": "0.1.4",
|
|
35
|
+
"@crewhaus/prompt-optimizer": "0.1.4",
|
|
36
|
+
"@crewhaus/prompt-optimizer-claude": "0.1.4",
|
|
37
|
+
"@crewhaus/justification-judge-claude": "0.1.4",
|
|
38
|
+
"@crewhaus/spec-patch": "0.1.4",
|
|
39
|
+
"@crewhaus/canary-controller": "0.1.4",
|
|
40
|
+
"@crewhaus/compiler": "0.1.4",
|
|
41
|
+
"@crewhaus/computer-use-driver": "0.1.4",
|
|
42
|
+
"@crewhaus/context-bundle": "0.1.4",
|
|
43
|
+
"@crewhaus/deployment-controller": "0.1.4",
|
|
44
|
+
"@crewhaus/egress-classifier": "0.1.4",
|
|
45
|
+
"@crewhaus/egress-matcher-semantic": "0.1.4",
|
|
46
|
+
"@crewhaus/embedder": "0.1.4",
|
|
47
|
+
"@crewhaus/errors": "0.1.4",
|
|
48
|
+
"@crewhaus/eval-dataset": "0.1.4",
|
|
49
|
+
"@crewhaus/eval-grader": "0.1.4",
|
|
50
|
+
"@crewhaus/eval-report": "0.1.4",
|
|
51
|
+
"@crewhaus/eval-runner": "0.1.4",
|
|
52
|
+
"@crewhaus/hooks-engine": "0.1.4",
|
|
53
|
+
"@crewhaus/infra-utils": "0.1.4",
|
|
54
|
+
"@crewhaus/ir": "0.1.4",
|
|
55
|
+
"@crewhaus/logging": "0.1.4",
|
|
56
|
+
"@crewhaus/mcp-host": "0.1.4",
|
|
57
|
+
"@crewhaus/model-router": "0.1.4",
|
|
58
|
+
"@crewhaus/migration-engine": "0.1.4",
|
|
59
|
+
"@crewhaus/migration-runner": "0.1.4",
|
|
60
|
+
"@crewhaus/permission-engine": "0.1.4",
|
|
61
|
+
"@crewhaus/run-context": "0.1.4",
|
|
62
|
+
"@crewhaus/runtime-core": "0.1.4",
|
|
63
|
+
"@crewhaus/secrets-manager": "0.1.4",
|
|
64
|
+
"@crewhaus/session-store": "0.1.4",
|
|
65
|
+
"@crewhaus/spec-registry": "0.1.4",
|
|
66
|
+
"@crewhaus/skills-registry": "0.1.4",
|
|
67
|
+
"@crewhaus/slash-commands": "0.1.4",
|
|
68
|
+
"@crewhaus/spec": "0.1.4",
|
|
69
|
+
"@crewhaus/sub-agent-spawner": "0.1.4",
|
|
70
|
+
"@crewhaus/trace-event-bus": "0.1.4",
|
|
71
|
+
"@crewhaus/tool-bash": "0.1.4",
|
|
72
|
+
"@crewhaus/tool-builder": "0.1.4",
|
|
73
|
+
"@crewhaus/tool-catalog": "0.1.4",
|
|
74
|
+
"@crewhaus/tool-codegraph": "0.1.4",
|
|
75
|
+
"@crewhaus/tool-fetch": "0.1.4",
|
|
76
|
+
"@crewhaus/tool-fs": "0.1.4",
|
|
77
|
+
"@crewhaus/tool-image": "0.1.4",
|
|
78
|
+
"@crewhaus/tool-image-generation": "0.1.4",
|
|
79
|
+
"@crewhaus/tool-document-ingest": "0.1.4",
|
|
80
|
+
"@crewhaus/tool-mcp": "0.1.4",
|
|
81
|
+
"@crewhaus/tool-mouse-keyboard": "0.1.4",
|
|
82
|
+
"@crewhaus/tool-navigate": "0.1.4",
|
|
83
|
+
"@crewhaus/tool-screen-capture": "0.1.4",
|
|
84
|
+
"@crewhaus/tool-task": "0.1.4",
|
|
85
|
+
"@crewhaus/tool-todo": "0.1.4",
|
|
86
|
+
"@crewhaus/tool-vision-grounding": "0.1.4",
|
|
87
|
+
"@crewhaus/tool-web": "0.1.4",
|
|
88
|
+
"@crewhaus/docker-images": "0.1.4",
|
|
89
|
+
"@crewhaus/crewhaus-cloud": "0.1.4",
|
|
90
|
+
"@crewhaus/federation-discovery": "0.1.4",
|
|
91
|
+
"@crewhaus/sandbox": "0.1.4",
|
|
92
|
+
"@crewhaus/sandbox-image-registry": "0.1.4",
|
|
93
|
+
"@crewhaus/compliance-controls": "0.1.4"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
96
|
"zod": "^3.23.8"
|
package/src/index.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { parseSpec } from "@crewhaus/spec";
|
|
@@ -213,6 +213,35 @@ describe("crewhaus compile", () => {
|
|
|
213
213
|
expect(existsSync(join(outDir, "agent.ts"))).toBe(false);
|
|
214
214
|
});
|
|
215
215
|
|
|
216
|
+
// A malformed credential env-ref (`$lowercase` on a Slack token) is a hard
|
|
217
|
+
// compile error from lowerCredential(). It must render as a clean die()
|
|
218
|
+
// one-liner — exit 1, `crewhaus: ` prefix, no uncaught stack trace — on BOTH
|
|
219
|
+
// the --emit-ir and bundle paths (the lower() catch routes CompilerError, not
|
|
220
|
+
// just SpecParseError, through die()).
|
|
221
|
+
const BAD_CRED_SPEC =
|
|
222
|
+
"name: status-bot\ntarget: channel\nagent:\n model: claude-sonnet-4-6\n instructions: announce status\nchannels:\n slack:\n botToken: $slack_bot_token\n signingSecret: $SLACK_SIGNING_SECRET\nrouting:\n sessionKey: thread\n";
|
|
223
|
+
|
|
224
|
+
for (const mode of ["--emit-ir", "bundle"] as const) {
|
|
225
|
+
test(`compile (${mode}) on a malformed credential $ref fails cleanly (die, not crash)`, async () => {
|
|
226
|
+
const specPath = join(tmp, "crewhaus.yaml");
|
|
227
|
+
writeFileSync(specPath, BAD_CRED_SPEC);
|
|
228
|
+
const outDir = join(tmp, "out");
|
|
229
|
+
const argv =
|
|
230
|
+
mode === "--emit-ir"
|
|
231
|
+
? ["compile", specPath, "--emit-ir"]
|
|
232
|
+
: ["compile", specPath, "-o", outDir];
|
|
233
|
+
const result = await runCli(argv);
|
|
234
|
+
expect(result.exitCode).toBe(1);
|
|
235
|
+
expect(result.stderr).toContain(
|
|
236
|
+
'crewhaus: channels.slack.botToken value "$slack_bot_token" looks like an environment reference',
|
|
237
|
+
);
|
|
238
|
+
// Not an uncaught crash: no error-class name, no "at file:line:col" frame.
|
|
239
|
+
expect(result.stderr).not.toContain("CompilerError");
|
|
240
|
+
expect(result.stderr).not.toMatch(/\bat .+:\d+:\d+/);
|
|
241
|
+
if (mode === "bundle") expect(existsSync(join(outDir, "agent.ts"))).toBe(false);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
216
245
|
// (c) CLEAN spec still compiles by default (no flag, no opt-out).
|
|
217
246
|
test("compile (no flag) on a clean (toolless) spec still emits and exits 0", async () => {
|
|
218
247
|
const result = await runCli(["compile", HELLO_SPEC, "-o", tmp]);
|
|
@@ -663,13 +692,21 @@ describe("crewhaus optimize --budget-usd", () => {
|
|
|
663
692
|
});
|
|
664
693
|
|
|
665
694
|
describe("crewhaus help and unknown subcommand", () => {
|
|
666
|
-
|
|
667
|
-
|
|
695
|
+
for (const flag of ["--help", "-h"]) {
|
|
696
|
+
test(`${flag} prints usage to stdout and exits 0 (works in \`set -e\` health checks)`, async () => {
|
|
697
|
+
const result = await runCli([flag]);
|
|
698
|
+
expect(result.exitCode).toBe(0);
|
|
699
|
+
expect(result.stdout).toContain("compile");
|
|
700
|
+
expect(result.stdout).toContain("run");
|
|
701
|
+
expect(result.stdout).toContain("init");
|
|
702
|
+
expect(result.stdout).toContain("doctor");
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
test("no subcommand prints usage to stderr and exits 1 (usage error)", async () => {
|
|
707
|
+
const result = await runCli([]);
|
|
668
708
|
expect(result.exitCode).toBe(1);
|
|
669
|
-
expect(result.stderr).toContain("
|
|
670
|
-
expect(result.stderr).toContain("run");
|
|
671
|
-
expect(result.stderr).toContain("init");
|
|
672
|
-
expect(result.stderr).toContain("doctor");
|
|
709
|
+
expect(result.stderr).toContain("usage: crewhaus");
|
|
673
710
|
});
|
|
674
711
|
|
|
675
712
|
test("unknown subcommand exits 1 with a clear message", async () => {
|
|
@@ -679,6 +716,89 @@ describe("crewhaus help and unknown subcommand", () => {
|
|
|
679
716
|
});
|
|
680
717
|
});
|
|
681
718
|
|
|
719
|
+
describe("crewhaus secrets backend selection", () => {
|
|
720
|
+
test("--backend vault errors clearly instead of silently using env-var", async () => {
|
|
721
|
+
const result = await runCli(["secrets", "doctor", "--backend", "vault"]);
|
|
722
|
+
expect(result.exitCode).toBe(1);
|
|
723
|
+
expect(result.stderr).toContain("vault backend is not wired into the CLI");
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
test("an unknown backend is rejected", async () => {
|
|
727
|
+
const result = await runCli(["secrets", "doctor", "--backend", "bogus"]);
|
|
728
|
+
expect(result.exitCode).toBe(1);
|
|
729
|
+
expect(result.stderr).toContain('unknown secrets backend "bogus"');
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
test("--backend file rotates into a not-yet-existing root dir (auto-mkdir)", async () => {
|
|
733
|
+
const root = join(tmp, "nested", "secrets");
|
|
734
|
+
const result = await runCli([
|
|
735
|
+
"secrets",
|
|
736
|
+
"rotate",
|
|
737
|
+
"demo",
|
|
738
|
+
"--backend",
|
|
739
|
+
"file",
|
|
740
|
+
"--root-dir",
|
|
741
|
+
root,
|
|
742
|
+
]);
|
|
743
|
+
expect(result.exitCode).toBe(0);
|
|
744
|
+
expect(result.stdout).toContain("rotated demo");
|
|
745
|
+
expect(existsSync(join(root, "demo"))).toBe(true);
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
describe("crewhaus cost-summary", () => {
|
|
750
|
+
const SESSION_ID = "sess_00000000000000ab";
|
|
751
|
+
|
|
752
|
+
function seedSession(lines: ReadonlyArray<unknown>): void {
|
|
753
|
+
const dir = join(tmp, ".crewhaus", "sessions");
|
|
754
|
+
mkdirSync(dir, { recursive: true });
|
|
755
|
+
writeFileSync(
|
|
756
|
+
join(dir, `${SESSION_ID}.jsonl`),
|
|
757
|
+
`${lines.map((l) => JSON.stringify(l)).join("\n")}\n`,
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
test("sums cost_accrual events written in the real event-log envelope shape", async () => {
|
|
762
|
+
seedSession([
|
|
763
|
+
{ ts: 1, version: 1, kind: "user_message", payload: { content: "hi" } },
|
|
764
|
+
{
|
|
765
|
+
ts: 2,
|
|
766
|
+
version: 1,
|
|
767
|
+
kind: "cost_accrual",
|
|
768
|
+
payload: { provider: "anthropic", modelId: "claude-sonnet-4-6", costUsdMicros: 450 },
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
ts: 3,
|
|
772
|
+
version: 1,
|
|
773
|
+
kind: "cost_accrual",
|
|
774
|
+
payload: { provider: "anthropic", modelId: "claude-sonnet-4-6", costUsdMicros: 300 },
|
|
775
|
+
},
|
|
776
|
+
]);
|
|
777
|
+
const result = await runCli(["cost-summary", "--session", SESSION_ID, "--format", "json"], {
|
|
778
|
+
cwd: tmp,
|
|
779
|
+
});
|
|
780
|
+
expect(result.exitCode).toBe(0);
|
|
781
|
+
const out = JSON.parse(result.stdout) as {
|
|
782
|
+
count: number;
|
|
783
|
+
totalUsdMicros: number;
|
|
784
|
+
byProvider: Record<string, number>;
|
|
785
|
+
};
|
|
786
|
+
expect(out.count).toBe(2);
|
|
787
|
+
expect(out.totalUsdMicros).toBe(750);
|
|
788
|
+
expect(out.byProvider["anthropic"]).toBe(750);
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
test("still reads flat (non-enveloped) cost_accrual lines for back-compat", async () => {
|
|
792
|
+
seedSession([{ kind: "cost_accrual", provider: "openai", costUsdMicros: 1000 }]);
|
|
793
|
+
const result = await runCli(["cost-summary", "--session", SESSION_ID, "--format", "json"], {
|
|
794
|
+
cwd: tmp,
|
|
795
|
+
});
|
|
796
|
+
expect(result.exitCode).toBe(0);
|
|
797
|
+
const out = JSON.parse(result.stdout) as { totalUsdMicros: number };
|
|
798
|
+
expect(out.totalUsdMicros).toBe(1000);
|
|
799
|
+
});
|
|
800
|
+
});
|
|
801
|
+
|
|
682
802
|
describe("crewhaus version", () => {
|
|
683
803
|
const pkgVersion = (
|
|
684
804
|
JSON.parse(readFileSync(join(import.meta.dir, "..", "package.json"), "utf-8")) as {
|
package/src/index.ts
CHANGED
|
@@ -296,62 +296,76 @@ const FEDERATION_SCHEMA: ParseArgsSchema = {
|
|
|
296
296
|
],
|
|
297
297
|
};
|
|
298
298
|
|
|
299
|
+
function usageText(): string {
|
|
300
|
+
return [
|
|
301
|
+
"usage: crewhaus <subcommand> [args]",
|
|
302
|
+
"",
|
|
303
|
+
"subcommands:",
|
|
304
|
+
" compile <spec.yaml> -o <out-dir> compile a spec to a runnable bundle",
|
|
305
|
+
" (fails if an outward tool is left non-external — FR-002)",
|
|
306
|
+
" [--allow-unmarked-sinks] opt out of the external-sink scope gate",
|
|
307
|
+
" compile <spec.yaml> --emit-ir print the lowered IR as JSON (debug)",
|
|
308
|
+
" run <spec.yaml> [--model <model>] compile in-memory and execute the agent",
|
|
309
|
+
" [--resume <id>] resume a specific session (cli targets only)",
|
|
310
|
+
" [--continue] resume the most-recent session (cli targets only)",
|
|
311
|
+
" [--prompt <text>] initial user prompt (browser targets; defaults to stdin)",
|
|
312
|
+
" [--justification-judge rule-based|claude] Pillar 3 intent-gate judge (FR-004)",
|
|
313
|
+
" [--egress-matcher substring|semantic] Pillar 3 sink-side matcher (FR-006)",
|
|
314
|
+
" [--egress-embedder <model>] embedder for --egress-matcher semantic",
|
|
315
|
+
" eval <spec.yaml> --dataset <data> run the agent against a dataset and grade",
|
|
316
|
+
" --graders <graders.yaml> (deterministic graders + LLM-as-judge)",
|
|
317
|
+
" [--judge-model <model>] [--concurrency N] [--seed N] -o <out-dir>",
|
|
318
|
+
" eval-report diff <prev> <new> compare two eval runs and emit a diff report",
|
|
319
|
+
" [-o <out-dir>]",
|
|
320
|
+
" optimize <spec.yaml> --dataset <data> --graders <graders.yaml>",
|
|
321
|
+
" [--mutator rule-based|claude] [--iterations N] [--seed N]",
|
|
322
|
+
" [--budget-usd N] stop a model-driven run before it exceeds $N (FR-003)",
|
|
323
|
+
" [--write-back] [-o <out-dir>] active eval-driven optimization (Pillar 2)",
|
|
324
|
+
" init [name] scaffold a new crewhaus.yaml",
|
|
325
|
+
" doctor check environment health",
|
|
326
|
+
" context --bundle [-o <file>] emit a single-markdown orientation manifest",
|
|
327
|
+
" [--factory-root <p>] [--docs-root <p>] [--demos-root <p>]",
|
|
328
|
+
" cost-summary --session <id> summarize cost_accrual events for a session",
|
|
329
|
+
" secrets doctor list known secrets via the configured backend",
|
|
330
|
+
" secrets rotate <name> [--value V] rotate a named secret (file backend)",
|
|
331
|
+
" spec put|list|get|pin|alias ... versioned spec storage (Section 28 spec-registry)",
|
|
332
|
+
" deploy promote|rollback ... re-pin a spec for an environment (Section 28)",
|
|
333
|
+
" migrate-all --from N --to N batch-migrate every spec in the registry",
|
|
334
|
+
" build-image <target> --tag <tag> build the docker image for a target shape (Section 32)",
|
|
335
|
+
" [--platform <p>] [--push]",
|
|
336
|
+
" cloud deploy --provider <p> deploy a managed CrewHaus cluster (Section 32)",
|
|
337
|
+
" --region <r> [--tier <t>] [--image-tag <tag>]",
|
|
338
|
+
" cloud teardown --provider <p> tear down a managed cluster",
|
|
339
|
+
" --region <r>",
|
|
340
|
+
" federation discover <deployment> resolve a federated peer's endpoint + cert fingerprint (Section 34)",
|
|
341
|
+
" [--srv-domain <d>] [--format json|yaml]",
|
|
342
|
+
" sandbox doctor [--probe] list registered sandbox images + healthcheck status (Section 36)",
|
|
343
|
+
" [--format json|table]",
|
|
344
|
+
" compliance evidence collect SOC 2 / ISO 27001 / HIPAA evidence (Section 39)",
|
|
345
|
+
" --framework <id> [--control <id>] --period <p> [--audit-dir <d>]",
|
|
346
|
+
" [--out-dir <d>] [--signing-key-env <ENV>]",
|
|
347
|
+
" version print the CLI version (also: --version, -v)",
|
|
348
|
+
"",
|
|
349
|
+
].join("\n");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** No subcommand given (or a parse error) — usage to stderr, exit 1. */
|
|
299
353
|
function usage(): never {
|
|
300
|
-
process.stderr.write(
|
|
301
|
-
[
|
|
302
|
-
"usage: crewhaus <subcommand> [args]",
|
|
303
|
-
"",
|
|
304
|
-
"subcommands:",
|
|
305
|
-
" compile <spec.yaml> -o <out-dir> compile a spec to a runnable bundle",
|
|
306
|
-
" (fails if an outward tool is left non-external — FR-002)",
|
|
307
|
-
" [--allow-unmarked-sinks] opt out of the external-sink scope gate",
|
|
308
|
-
" compile <spec.yaml> --emit-ir print the lowered IR as JSON (debug)",
|
|
309
|
-
" run <spec.yaml> [--model <model>] compile in-memory and execute the agent",
|
|
310
|
-
" [--resume <id>] resume a specific session (cli targets only)",
|
|
311
|
-
" [--continue] resume the most-recent session (cli targets only)",
|
|
312
|
-
" [--prompt <text>] initial user prompt (browser targets; defaults to stdin)",
|
|
313
|
-
" [--justification-judge rule-based|claude] Pillar 3 intent-gate judge (FR-004)",
|
|
314
|
-
" [--egress-matcher substring|semantic] Pillar 3 sink-side matcher (FR-006)",
|
|
315
|
-
" [--egress-embedder <model>] embedder for --egress-matcher semantic",
|
|
316
|
-
" eval <spec.yaml> --dataset <data> run the agent against a dataset and grade",
|
|
317
|
-
" --graders <graders.yaml> (deterministic graders + LLM-as-judge)",
|
|
318
|
-
" [--judge-model <model>] [--concurrency N] [--seed N] -o <out-dir>",
|
|
319
|
-
" eval-report diff <prev> <new> compare two eval runs and emit a diff report",
|
|
320
|
-
" [-o <out-dir>]",
|
|
321
|
-
" optimize <spec.yaml> --dataset <data> --graders <graders.yaml>",
|
|
322
|
-
" [--mutator rule-based|claude] [--iterations N] [--seed N]",
|
|
323
|
-
" [--budget-usd N] stop a model-driven run before it exceeds $N (FR-003)",
|
|
324
|
-
" [--write-back] [-o <out-dir>] active eval-driven optimization (Pillar 2)",
|
|
325
|
-
" init [name] scaffold a new crewhaus.yaml",
|
|
326
|
-
" doctor check environment health",
|
|
327
|
-
" context --bundle [-o <file>] emit a single-markdown orientation manifest",
|
|
328
|
-
" [--factory-root <p>] [--docs-root <p>] [--demos-root <p>]",
|
|
329
|
-
" cost-summary --session <id> summarize cost_accrual events for a session",
|
|
330
|
-
" secrets doctor list known secrets via the configured backend",
|
|
331
|
-
" secrets rotate <name> [--value V] rotate a named secret (file or vault backend)",
|
|
332
|
-
" spec put|list|get|pin|alias ... versioned spec storage (Section 28 spec-registry)",
|
|
333
|
-
" deploy promote|rollback ... re-pin a spec for an environment (Section 28)",
|
|
334
|
-
" migrate-all --from N --to N batch-migrate every spec in the registry",
|
|
335
|
-
" build-image <target> --tag <tag> build the docker image for a target shape (Section 32)",
|
|
336
|
-
" [--platform <p>] [--push]",
|
|
337
|
-
" cloud deploy --provider <p> deploy a managed CrewHaus cluster (Section 32)",
|
|
338
|
-
" --region <r> [--tier <t>] [--image-tag <tag>]",
|
|
339
|
-
" cloud teardown --provider <p> tear down a managed cluster",
|
|
340
|
-
" --region <r>",
|
|
341
|
-
" federation discover <deployment> resolve a federated peer's endpoint + cert fingerprint (Section 34)",
|
|
342
|
-
" [--srv-domain <d>] [--format json|yaml]",
|
|
343
|
-
" sandbox doctor [--probe] list registered sandbox images + healthcheck status (Section 36)",
|
|
344
|
-
" [--format json|table]",
|
|
345
|
-
" compliance evidence collect SOC 2 / ISO 27001 / HIPAA evidence (Section 39)",
|
|
346
|
-
" --framework <id> [--control <id>] --period <p> [--audit-dir <d>]",
|
|
347
|
-
" [--out-dir <d>] [--signing-key-env <ENV>]",
|
|
348
|
-
" version print the CLI version (also: --version, -v)",
|
|
349
|
-
"",
|
|
350
|
-
].join("\n"),
|
|
351
|
-
);
|
|
354
|
+
process.stderr.write(usageText());
|
|
352
355
|
process.exit(1);
|
|
353
356
|
}
|
|
354
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Explicit `-h`/`--help` at the top level — usage to stdout, exit 0. Help was
|
|
360
|
+
* requested, so it is not an error; this matches every subcommand's own
|
|
361
|
+
* `--help` (stdout, exit 0) and lets `crewhaus --help` work in `set -e` health
|
|
362
|
+
* checks instead of looking like a broken CLI.
|
|
363
|
+
*/
|
|
364
|
+
function help(): never {
|
|
365
|
+
process.stdout.write(usageText());
|
|
366
|
+
process.exit(0);
|
|
367
|
+
}
|
|
368
|
+
|
|
355
369
|
function die(message: string): never {
|
|
356
370
|
process.stderr.write(`crewhaus: ${message}\n`);
|
|
357
371
|
process.exit(1);
|
|
@@ -464,7 +478,10 @@ async function runCompile(args: ParsedArgs): Promise<void> {
|
|
|
464
478
|
try {
|
|
465
479
|
ir = lower(parseSpec(yamlText));
|
|
466
480
|
} catch (err) {
|
|
467
|
-
|
|
481
|
+
// parseSpec throws SpecParseError; lower() can throw CompilerError (e.g. a
|
|
482
|
+
// malformed credential env-ref). Both extend CrewhausError — route the
|
|
483
|
+
// family through die() for a clean one-liner instead of a raw stack trace.
|
|
484
|
+
if (err instanceof CrewhausError) die(err.message);
|
|
468
485
|
throw err;
|
|
469
486
|
}
|
|
470
487
|
const json = `${JSON.stringify(ir, null, 2)}\n`;
|
|
@@ -524,7 +541,10 @@ async function runStrictScopeGate(yamlText: string): Promise<void> {
|
|
|
524
541
|
try {
|
|
525
542
|
ir = lower(parseSpec(yamlText));
|
|
526
543
|
} catch (err) {
|
|
527
|
-
|
|
544
|
+
// SpecParseError (parse) and CompilerError (lower, e.g. a malformed
|
|
545
|
+
// credential env-ref) both extend CrewhausError — render as a clean die()
|
|
546
|
+
// one-liner rather than an uncaught stack trace.
|
|
547
|
+
if (err instanceof CrewhausError) die(err.message);
|
|
528
548
|
throw err;
|
|
529
549
|
}
|
|
530
550
|
const toolNames = collectToolNames(ir);
|
|
@@ -1828,13 +1848,21 @@ async function runCostSummary(args: ParsedArgs): Promise<void> {
|
|
|
1828
1848
|
"kind" in parsed &&
|
|
1829
1849
|
(parsed as { kind?: string }).kind === "cost_accrual"
|
|
1830
1850
|
) {
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1851
|
+
// event-log writes a `{ ts, version, kind, payload }` envelope, so the
|
|
1852
|
+
// cost fields live under `payload`. Fall back to top-level fields so a
|
|
1853
|
+
// hand-written/flat cost_accrual line still aggregates.
|
|
1854
|
+
const e = parsed as {
|
|
1855
|
+
provider?: string;
|
|
1856
|
+
costUsdMicros?: number;
|
|
1857
|
+
payload?: { provider?: string; costUsdMicros?: number };
|
|
1834
1858
|
};
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1859
|
+
const provider = e.payload?.provider ?? e.provider;
|
|
1860
|
+
const micros = e.payload?.costUsdMicros ?? e.costUsdMicros;
|
|
1861
|
+
if (typeof provider === "string" && typeof micros === "number") {
|
|
1862
|
+
totalMicros += micros;
|
|
1863
|
+
byProvider[provider] = (byProvider[provider] ?? 0) + micros;
|
|
1864
|
+
count++;
|
|
1865
|
+
}
|
|
1838
1866
|
}
|
|
1839
1867
|
}
|
|
1840
1868
|
const totalDollars = totalMicros / 1_000_000;
|
|
@@ -1855,13 +1883,13 @@ async function runCostSummary(args: ParsedArgs): Promise<void> {
|
|
|
1855
1883
|
/**
|
|
1856
1884
|
* Section 27 — `crewhaus secrets <action> <name> [opts]`. Two actions:
|
|
1857
1885
|
* doctor list configured secrets and report missing
|
|
1858
|
-
* rotate <name> rotate the named secret (file
|
|
1886
|
+
* rotate <name> rotate the named secret (file backend)
|
|
1859
1887
|
*/
|
|
1860
1888
|
async function runSecrets(args: ParsedArgs, action: string): Promise<void> {
|
|
1861
1889
|
if (args.flags["help"]) {
|
|
1862
1890
|
process.stdout.write(
|
|
1863
1891
|
"usage:\n" +
|
|
1864
|
-
" crewhaus secrets doctor [--backend env-var|file
|
|
1892
|
+
" crewhaus secrets doctor [--backend env-var|file] [--root-dir <dir>]\n" +
|
|
1865
1893
|
" crewhaus secrets rotate <name> [--value <new-value>] [--backend ...]\n",
|
|
1866
1894
|
);
|
|
1867
1895
|
return;
|
|
@@ -1874,7 +1902,23 @@ async function runSecrets(args: ParsedArgs, action: string): Promise<void> {
|
|
|
1874
1902
|
const { createSecrets, createEnvVarBackend, createFileBackend } = await import(
|
|
1875
1903
|
"@crewhaus/secrets-manager"
|
|
1876
1904
|
);
|
|
1877
|
-
|
|
1905
|
+
// The `vault` backend exists in @crewhaus/secrets-manager but is not wired
|
|
1906
|
+
// into the CLI (it needs an address/auth story the CLI does not yet expose).
|
|
1907
|
+
// Fail loudly rather than silently degrading a security-sensitive flag to the
|
|
1908
|
+
// env-var backend — a silent fallback would, e.g., make `secrets doctor`
|
|
1909
|
+
// cheerfully list process env var names as if they were Vault secrets.
|
|
1910
|
+
let backend: ReturnType<typeof createFileBackend>;
|
|
1911
|
+
if (backendId === "env-var") {
|
|
1912
|
+
backend = createEnvVarBackend();
|
|
1913
|
+
} else if (backendId === "file") {
|
|
1914
|
+
backend = createFileBackend({ rootDir });
|
|
1915
|
+
} else if (backendId === "vault") {
|
|
1916
|
+
die(
|
|
1917
|
+
"vault backend is not wired into the CLI in this build — construct it programmatically via createVaultBackend(), or use --backend env-var|file",
|
|
1918
|
+
);
|
|
1919
|
+
} else {
|
|
1920
|
+
die(`unknown secrets backend "${backendId}" (expected: env-var | file)`);
|
|
1921
|
+
}
|
|
1878
1922
|
const secrets = createSecrets({ backend });
|
|
1879
1923
|
|
|
1880
1924
|
if (action === "doctor") {
|
|
@@ -2503,9 +2547,11 @@ switch (subcommand) {
|
|
|
2503
2547
|
case "--version":
|
|
2504
2548
|
printVersion();
|
|
2505
2549
|
break;
|
|
2506
|
-
case "":
|
|
2507
2550
|
case "-h":
|
|
2508
2551
|
case "--help":
|
|
2552
|
+
help();
|
|
2553
|
+
break;
|
|
2554
|
+
case "":
|
|
2509
2555
|
usage();
|
|
2510
2556
|
break;
|
|
2511
2557
|
default:
|