@agenthippo-ai/agenthippo 0.2.3-dataplane → 1.110.11

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.
Files changed (139) hide show
  1. package/out/agentpack/index.js +3 -1
  2. package/out/agentpack/paths.js +25 -2
  3. package/out/analytics/dashboard/localFleetAdapter.js +82 -2
  4. package/out/analytics/dashboard/provider.js +13 -2
  5. package/out/analytics/dashboard/webview/panel.js +3 -2
  6. package/out/analytics/identity.js +0 -0
  7. package/out/analytics/service.js +29 -25
  8. package/out/analytics/sessionEvents.js +100 -24
  9. package/out/auth/authBaseUrl.js +59 -0
  10. package/out/auth/bootstrapApply.js +9 -10
  11. package/out/auth/brokerClient.js +48 -14
  12. package/out/auth/provider.js +8 -21
  13. package/out/auth/sessionStore.js +3 -40
  14. package/out/auth/signInFlow.js +2 -1
  15. package/out/auth-broker/config.js +50 -5
  16. package/out/auth-broker/server.js +126 -26
  17. package/out/auth-broker/store.js +25 -24
  18. package/out/authority/warrant.js +206 -0
  19. package/out/branding.js +27 -1
  20. package/out/cli/commands/conformance.js +249 -0
  21. package/out/cli/commands/data-plane.js +151 -0
  22. package/out/cli/commands/deploy.js +1 -0
  23. package/out/cli/commands/gateway.js +4 -0
  24. package/out/cli/commands/pack.js +182 -3
  25. package/out/cli/commands/sandbox.js +351 -0
  26. package/out/cli/commands/serve.js +1145 -130
  27. package/out/cli/commands/store.js +12 -4
  28. package/out/cli/credential-context.js +17 -0
  29. package/out/cli/data-plane/gateway.js +28 -5
  30. package/out/cli/data-plane/index.js +18 -2
  31. package/out/cli/data-plane/registry.js +9 -0
  32. package/out/cli/data-plane/remote.js +106 -0
  33. package/out/cli/data-plane/sidecar.js +225 -0
  34. package/out/cli/deploy/layers/identity.js +88 -4
  35. package/out/cli/deploy-init.js +89 -24
  36. package/out/cli/deps.js +106 -2
  37. package/out/cli/idp-init.js +35 -0
  38. package/out/cli/index.js +171 -17
  39. package/out/cli/pack-install-ref.js +15 -0
  40. package/out/cli/pack-loader.js +8 -1
  41. package/out/cli/pack-runtime.js +61 -0
  42. package/out/cli/runtime.js +35 -1
  43. package/out/cli/serve-hardening.js +260 -14
  44. package/out/cli/utils.js +12 -1
  45. package/out/engines/base.js +32 -3
  46. package/out/engines/claude.js +33 -15
  47. package/out/engines/codex.js +111 -27
  48. package/out/engines/custom.js +15 -1
  49. package/out/evidence/sessionShip.js +643 -0
  50. package/out/extension.js +11 -2
  51. package/out/gateway/bridge.js +40 -2
  52. package/out/gateway/client.js +34 -1
  53. package/out/gateway/installSource.js +68 -0
  54. package/out/gateway/lifecycle.js +185 -51
  55. package/out/gateway/outboundPaths.js +4 -1
  56. package/out/gateway/secretsPreflight.js +90 -0
  57. package/out/gateway/service.js +7 -2
  58. package/out/headless/index.js +25 -2
  59. package/out/live-e2e/mcp-demo/server.mjs +32 -0
  60. package/out/mcp.js +30 -0
  61. package/out/pack/caps.js +271 -0
  62. package/out/pack/governor.js +326 -0
  63. package/out/pack/lease-schema.js +273 -0
  64. package/out/pack/lease.js +682 -0
  65. package/out/pack/reconcile.js +217 -0
  66. package/out/pack/signature.js +839 -0
  67. package/out/remote/handleRemoteChat.js +44 -3
  68. package/out/remote/remoteTrust.js +277 -0
  69. package/out/remote/serveClient.js +52 -5
  70. package/out/runner/host.js +141 -0
  71. package/out/runner/protocol.js +128 -0
  72. package/out/runner/remoteEngine.js +166 -0
  73. package/out/runtime/interfaces/edit-tracker.js +2 -0
  74. package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
  75. package/out/sandbox/containerDriver.js +572 -0
  76. package/out/sandbox/processDriver.js +318 -0
  77. package/out/sandbox/sandboxRunner.js +303 -0
  78. package/out/sandbox/support.js +301 -0
  79. package/out/sandbox/types.js +55 -0
  80. package/out/scripts/agentide-litellm.sh +67 -5
  81. package/out/scripts/agentide_litellm_hooks_v1.py +266 -21
  82. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +98 -132
  83. package/out/scripts/analytics/setup-analytics.ps1 +45 -14
  84. package/out/scripts/analytics/setup-analytics.sh +106 -15
  85. package/out/scripts/analytics/start-analytics.sh +86 -14
  86. package/out/scripts/gateway/setup-gateway.sh +72 -32
  87. package/out/shared/models.js +11 -110
  88. package/out/spotlight/install.js +7 -1
  89. package/out/store/index.js +41 -10
  90. package/out/sync.js +3 -1
  91. package/out/test/analytics.test.js +162 -23
  92. package/out/test/auth-baseurl.test.js +132 -0
  93. package/out/test/auth-bootstrapApply.test.js +27 -6
  94. package/out/test/auth-broker-hardening.test.js +227 -0
  95. package/out/test/auth-broker.test.js +59 -9
  96. package/out/test/branding.test.js +13 -0
  97. package/out/test/caps-governor.test.js +460 -0
  98. package/out/test/codex-new-file-survival.test.js +119 -0
  99. package/out/test/configuration-scopes.test.js +131 -0
  100. package/out/test/data-plane-engine-wiring.test.js +32 -0
  101. package/out/test/data-plane.test.js +380 -0
  102. package/out/test/deploy-init.test.js +47 -1
  103. package/out/test/gateway-client.test.js +28 -0
  104. package/out/test/gateway-install-source.test.js +111 -0
  105. package/out/test/gateway-lifecycle.test.js +1 -1
  106. package/out/test/gateway-outbound.test.js +7 -0
  107. package/out/test/gateway-secrets-preflight.test.js +127 -0
  108. package/out/test/gateway-session-routing.test.js +18 -0
  109. package/out/test/governor-hardening.test.js +222 -0
  110. package/out/test/headless-sync.test.js +22 -0
  111. package/out/test/lease-signature.test.js +357 -0
  112. package/out/test/lease-trust-root.test.js +273 -0
  113. package/out/test/lease-validation.test.js +224 -0
  114. package/out/test/lease.test.js +114 -0
  115. package/out/test/localFleetAdapter.test.js +91 -0
  116. package/out/test/mcp.test.js +27 -1
  117. package/out/test/pack-admission.test.js +216 -0
  118. package/out/test/pack-digest-parity.test.js +140 -0
  119. package/out/test/pack-reconcile.test.js +183 -0
  120. package/out/test/pack-signature-artifact.test.js +103 -0
  121. package/out/test/pack-signature.test.js +176 -0
  122. package/out/test/pack-trust-root.test.js +202 -0
  123. package/out/test/remote-trust.test.js +242 -0
  124. package/out/test/runner-protocol.test.js +249 -0
  125. package/out/test/sandbox-driver.test.js +553 -0
  126. package/out/test/sandbox-hardening.test.js +406 -0
  127. package/out/test/sandbox-runner.test.js +343 -0
  128. package/out/test/sandbox-scope.test.js +449 -0
  129. package/out/test/serve-cron-acceptance.test.js +370 -0
  130. package/out/test/serve-gateway-turn.test.js +122 -0
  131. package/out/test/serve-hardening.test.js +227 -15
  132. package/out/test/serveClient.test.js +6 -5
  133. package/out/test/sessionShip.test.js +618 -0
  134. package/out/test/spotlightPublish.test.js +28 -0
  135. package/out/test/store-install-safe.test.js +69 -0
  136. package/out/test/tls-bypass.test.js +90 -0
  137. package/out/test/warrant.test.js +138 -0
  138. package/out/utils.js +46 -9
  139. package/package.json +1 -1
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) AgentHippo.ai. All rights reserved.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createDataPlaneAuditSink = createDataPlaneAuditSink;
7
+ exports.dataPlaneSidecarCommand = dataPlaneSidecarCommand;
8
+ /**
9
+ * `agenthippo data-plane` — run the connector as a SIDECAR (secure-obo-design §7.6).
10
+ *
11
+ * This process is the connector's own trust domain: it holds the backend BASE credential
12
+ * (AWS keys, Postgres pool login, …) and runs in a SEPARATE container from the engine. The
13
+ * agent container's `serve` registers turns with this sidecar's authenticated control API and
14
+ * never sees the base credential.
15
+ */
16
+ const fs_1 = require("fs");
17
+ const pack_loader_1 = require("../pack-loader");
18
+ const data_plane_1 = require("../data-plane");
19
+ const serve_hardening_1 = require("../serve-hardening");
20
+ const sessionEvents_1 = require("../../analytics/sessionEvents");
21
+ const identity_1 = require("../../analytics/identity");
22
+ const base_1 = require("../../engines/base");
23
+ const headless_1 = require("../../headless");
24
+ const utils_1 = require("../utils");
25
+ /**
26
+ * Build the sidecar's audit sink.
27
+ *
28
+ * Until this existed the sidecar passed no `audit` to `startDataPlaneSidecar`, and since every
29
+ * emit site in the data plane is `opts.audit?.(…)` the entire trust-boundary record —
30
+ * `tool_authorize`, `tool_deny`, `sidecar_register_reject`, `sidecar_edge_register` — was
31
+ * discarded in sidecar and edge mode: the two shapes the blueprints actually deploy. The
32
+ * "which user touched which rows" claim had no writer exactly where it was being made.
33
+ *
34
+ * `ok: false` means the directory could not be prepared. The caller decides what that costs,
35
+ * using the existing AGENTHIPPO_EVIDENCE_REQUIRED contract rather than a new knob.
36
+ */
37
+ async function createDataPlaneAuditSink(stateDirOpt,
38
+ /**
39
+ * The pack THIS sidecar loaded — stamped onto every row so evidence answers "via which agent",
40
+ * not only "for which user" (PLAN/agent-identity.md §1 failure 3).
41
+ *
42
+ * There is no digest here on purpose: the sidecar does not verify a signature (`pack-loader.ts`
43
+ * has no signature code), so claiming one would dress an unverified read as an attestation.
44
+ * What serve ASSERTS about the pack arrives separately, per turn, via the register payload —
45
+ * and the two being different is the §4.4 detection.
46
+ */
47
+ agent) {
48
+ const { dir: stateDir, ephemeral } = (0, serve_hardening_1.resolveStateDir)(stateDirOpt);
49
+ const auditDir = (0, serve_hardening_1.resolveAnalyticsSessionsDir)(stateDir, ephemeral);
50
+ let ok = true;
51
+ let error;
52
+ // Preflight, rather than discovering it on the first denied tool call.
53
+ try {
54
+ await fs_1.promises.mkdir(auditDir, { recursive: true });
55
+ await fs_1.promises.access(auditDir);
56
+ }
57
+ catch (err) {
58
+ ok = false;
59
+ error = err instanceof Error ? err.message : String(err);
60
+ }
61
+ const writer = new sessionEvents_1.SessionEventWriter(auditDir);
62
+ const audit = (sessionKey, event, fields) => {
63
+ // Pseudonymise identity before it lands, matching the tool-event lane
64
+ // (ObservableStreamEmitter → hashIdentity). These records ship wholesale to the evidence
65
+ // server, so a raw address here leaves the machine; hashIdentity honours the operator's
66
+ // AGENTHIPPO_EVIDENCE_RAW_IDS / _ID_SALT choices so this lane makes the same trade as
67
+ // every other producer rather than a private one.
68
+ const rawUser = typeof fields.userId === 'string' ? fields.userId : undefined;
69
+ const anonUser = (0, identity_1.hashIdentity)(rawUser);
70
+ const safeFields = anonUser ? { ...fields, userId: anonUser } : fields;
71
+ // In EDGE mode the sidecar registers with `sessionKey: userId` (sidecar.ts:97), so the
72
+ // session key IS the identity and would otherwise become the evidence FILENAME —
73
+ // `alice@corp.com.jsonl`. Hash it the same way when, and only when, it is exactly the
74
+ // identity; a genuine session key is left alone so it still joins across lanes.
75
+ const safeSession = anonUser && sessionKey === rawUser ? anonUser : sessionKey;
76
+ void writer.append(safeSession, (0, base_1.generateRequestId)(), event, agent ? { ...safeFields, agent: { ...agent, digest: null } } : safeFields);
77
+ };
78
+ return { audit, auditDir, ephemeral, ok, error };
79
+ }
80
+ async function dataPlaneSidecarCommand(options) {
81
+ const registerToken = process.env.AGENTHIPPO_DATA_PLANE_TOKEN?.trim();
82
+ if (!registerToken) {
83
+ console.error('Error: AGENTHIPPO_DATA_PLANE_TOKEN is required (shared secret the trusted EDGE — or, legacy, the agent\'s serve — presents to register turns).');
84
+ process.exit(1);
85
+ }
86
+ let pack;
87
+ try {
88
+ pack = await (0, pack_loader_1.loadAgentPack)(options.agent, options.workspace);
89
+ }
90
+ catch (err) {
91
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
92
+ process.exit(1);
93
+ return;
94
+ }
95
+ const cfg = (0, data_plane_1.resolveDataPlaneConfig)(pack.manifest.spec, pack.packPath);
96
+ if (!cfg) {
97
+ console.error('Error: pack declares no spec.dataPlane — nothing for the sidecar to run.');
98
+ process.exit(1);
99
+ return;
100
+ }
101
+ const logger = options.verbose ? new headless_1.ConsoleLogger('[Sidecar]') : new headless_1.NoOpLogger();
102
+ const { audit: auditEvent, auditDir, ephemeral, ok: auditOk, error: auditError } = await createDataPlaneAuditSink(options.stateDir, {
103
+ pack: pack.manifest.metadata.name,
104
+ version: pack.manifest.metadata.version ?? '0.0.0',
105
+ });
106
+ if (!auditOk) {
107
+ const detail = `${auditDir}: ${auditError}`;
108
+ // The existing AGENTHIPPO_EVIDENCE_REQUIRED contract decides what an unwritable sink
109
+ // costs — no new knob, and it already means "a hole in the record must fail the turn".
110
+ if ((0, sessionEvents_1.evidenceRequired)()) {
111
+ console.error(`${utils_1.COLORS.red}Error: data-plane audit directory is not writable${utils_1.COLORS.reset} — ${detail}`);
112
+ console.error(`AGENTHIPPO_EVIDENCE_REQUIRED is set, so refusing to run an unauditable data plane. Unset it to downgrade this to a warning.`);
113
+ process.exit(1);
114
+ return;
115
+ }
116
+ console.warn(`${utils_1.COLORS.yellow}Warning:${utils_1.COLORS.reset} data-plane audit directory is not writable — ${detail}`);
117
+ console.warn(' Tool authorizations and denials will NOT be recorded. Set --state-dir or AGENTHIPPO_HOME to a writable path.');
118
+ }
119
+ let sidecar;
120
+ try {
121
+ sidecar = await (0, data_plane_1.startDataPlaneSidecar)({
122
+ config: cfg,
123
+ registerToken,
124
+ gatewayPort: options.gatewayPort,
125
+ controlPort: options.controlPort,
126
+ bindHost: options.host ?? '0.0.0.0',
127
+ logger,
128
+ audit: auditEvent,
129
+ agent: {
130
+ pack: pack.manifest.metadata.name,
131
+ version: pack.manifest.metadata.version ?? '0.0.0',
132
+ },
133
+ });
134
+ }
135
+ catch (err) {
136
+ console.error(`${utils_1.COLORS.red}Error: failed to start the data-plane sidecar:${utils_1.COLORS.reset} ${err instanceof Error ? err.message : err}`);
137
+ process.exit(1);
138
+ return;
139
+ }
140
+ console.log(`OK: Data-plane sidecar (provider=${cfg.provider ?? 'passthrough'}) — connector: ${cfg.connectorPath}`);
141
+ console.log(` gateway (engine dials, nonce-auth): ${sidecar.gatewayUrl}`);
142
+ console.log(` control (edge/serve registers, bearer): :${sidecar.controlPort}`);
143
+ console.log(` audit (tool_authorize / tool_deny / register events): ${auditDir}${ephemeral ? ' (EPHEMERAL — lost on restart; set AGENTHIPPO_HOME or --state-dir)' : ''}`);
144
+ console.log(' Base credentials live here only — never in the agent container.');
145
+ console.log(' Edge mode (P1): give the register token to the IdP edge only; serve runs with AGENTHIPPO_DATA_PLANE_EDGE=1 and no token.');
146
+ const shutdown = () => { void sidecar.close().finally(() => process.exit(0)); };
147
+ process.on('SIGINT', shutdown);
148
+ process.on('SIGTERM', shutdown);
149
+ // Keep the process alive.
150
+ await new Promise(() => { });
151
+ }
@@ -215,6 +215,7 @@ async function deployCommand(action, packArg, options) {
215
215
  cookieDomain: options.cookieDomain,
216
216
  colocate: options.colocate,
217
217
  modelRouting,
218
+ edgeTurnConnector: options.edgeTurnConnector?.trim() || undefined,
218
219
  };
219
220
  for (const target of targetsRequested) {
220
221
  const routingSuffix = modelRouting === 'gateway' ? '-gateway' : '';
@@ -115,6 +115,10 @@ async function gatewaySendCommand(options) {
115
115
  await sendViaEgressBroker(egressUrl, nonce, options);
116
116
  return;
117
117
  }
118
+ if (egressUrl) {
119
+ console.error('Error: egress broker is active but AGENTHIPPO_TURN_NONCE is missing — refusing direct gateway send');
120
+ process.exit(1);
121
+ }
118
122
  const storePath = resolveOutboundPath(options.outbound);
119
123
  await ensureStoreFileExists(storePath);
120
124
  const agentId = options.agent.trim();
@@ -44,6 +44,10 @@ const fs = __importStar(require("fs"));
44
44
  const path = __importStar(require("path"));
45
45
  const pack_loader_1 = require("../pack-loader");
46
46
  const branding_1 = require("../../branding");
47
+ const warrant_1 = require("../../authority/warrant");
48
+ const signature_1 = require("../../pack/signature");
49
+ const store_1 = require("./store");
50
+ const pack_install_ref_1 = require("../pack-install-ref");
47
51
  const BANNER_LINE = '-'.repeat(62);
48
52
  async function packCommand(options) {
49
53
  const workspace = options.workspace;
@@ -65,9 +69,20 @@ async function packCommand(options) {
65
69
  }
66
70
  await showPackInfo(options.name, workspace);
67
71
  break;
72
+ case 'sign':
73
+ await signPackCommand(options);
74
+ break;
75
+ case 'verify':
76
+ await verifyPackCommand(options);
77
+ break;
78
+ case 'digest':
79
+ await digestCommand(options);
80
+ break;
81
+ case 'keygen':
82
+ keygenCommand(options);
83
+ break;
68
84
  case 'install':
69
- console.log('Pack installation is not yet implemented.');
70
- console.log(` For now, manually copy your pack to ~/${branding_1.AGENTIDE_DIR_NAME}/agents/<name>/`);
85
+ await installPackCommand(options);
71
86
  break;
72
87
  case 'export':
73
88
  console.log('Pack export is not yet implemented.');
@@ -79,11 +94,44 @@ async function packCommand(options) {
79
94
  console.log(' list List installed packs');
80
95
  console.log(' validate Validate a pack');
81
96
  console.log(' info Show pack details');
82
- console.log(' install Install a pack (coming soon)');
97
+ console.log(' sign Sign a pack so a fleet can verify it before running');
98
+ console.log(' verify Verify a pack against trusted public keys');
99
+ console.log(' digest Print the pack content digest (what a signature covers)');
100
+ console.log(' keygen Generate an ES256 signing keypair');
101
+ console.log(' install Install a pack from the store to a folder');
83
102
  console.log(' export Export a pack (coming soon)');
84
103
  process.exit(1);
85
104
  }
86
105
  }
106
+ async function installPackCommand(options) {
107
+ if (!options.name?.trim()) {
108
+ console.error('Error: pack slug required.');
109
+ console.log('Usage: agenthippo pack install <slug>[@version] --to <dir>');
110
+ console.log('');
111
+ console.log('Examples:');
112
+ console.log(' agenthippo pack install joker1 --to ./packs/joker1');
113
+ console.log(' agenthippo pack install code-reviewer@1.0.0 --to ./packs/code-reviewer');
114
+ process.exit(1);
115
+ }
116
+ const dest = options.to?.trim();
117
+ if (!dest) {
118
+ console.error('Error: --to <dir> is required.');
119
+ console.log('Usage: agenthippo pack install <slug>[@version] --to <dir>');
120
+ console.log('');
121
+ console.log('Same download as `agenthippo store install pack/<slug>`, extracted flat for serve/deploy.');
122
+ process.exit(1);
123
+ }
124
+ const { slug, version } = (0, pack_install_ref_1.parsePackInstallRef)(options.name);
125
+ await (0, store_1.installFromStore)({
126
+ action: 'install',
127
+ query: `pack/${slug}${version ? `@${version}` : ''}`,
128
+ workspace: options.workspace,
129
+ version,
130
+ force: options.force,
131
+ installDir: path.resolve(dest),
132
+ verbose: options.verbose,
133
+ });
134
+ }
87
135
  async function listPacks(workspace) {
88
136
  const packs = (0, pack_loader_1.listAvailablePacks)(workspace);
89
137
  if (packs.length === 0) {
@@ -218,3 +266,134 @@ async function showPackInfo(nameOrPath, workspace) {
218
266
  process.exit(1);
219
267
  }
220
268
  }
269
+ // -----------------------------------------------------------------------------
270
+ // Signing
271
+ //
272
+ // Publishing has to be a one-liner before `--require-signed-pack` can be turned
273
+ // on in anger: a requirement nobody can satisfy just gets switched off again.
274
+ // -----------------------------------------------------------------------------
275
+ /** Resolve a pack path and its declared identity — a signature binds both, not just bytes. */
276
+ async function loadForSigning(nameOrPath, workspace) {
277
+ if (!nameOrPath) {
278
+ console.error('Error: pack name or path required');
279
+ process.exit(1);
280
+ }
281
+ const packPath = (0, pack_loader_1.resolvePackPath)(nameOrPath, workspace);
282
+ if (!packPath) {
283
+ console.error(`Error: pack not found: ${nameOrPath}`);
284
+ process.exit(1);
285
+ }
286
+ const loaded = await (0, pack_loader_1.loadAgentPack)(packPath, workspace);
287
+ return {
288
+ packPath: loaded.packPath,
289
+ name: loaded.manifest.metadata.name,
290
+ version: loaded.manifest.metadata.version ?? '0.0.0',
291
+ };
292
+ }
293
+ function keygenCommand(options) {
294
+ const kid = options.kid || `sign-${new Date().toISOString().slice(0, 10)}`;
295
+ const key = (0, warrant_1.generateWarrantKeypair)(kid);
296
+ const out = options.output;
297
+ if (out) {
298
+ fs.writeFileSync(out, key.privateKeyPem, { mode: 0o600 });
299
+ console.log(`Private key written to ${out} (mode 0600) — keep it off shared disks.`);
300
+ }
301
+ else {
302
+ console.log(key.privateKeyPem);
303
+ }
304
+ console.log('');
305
+ console.log(`kid: ${kid}`);
306
+ console.log('Public half — publish this to the control plane so instances can verify:');
307
+ console.log(key.publicKeyPem.trim());
308
+ console.log('');
309
+ console.log(` curl -X POST "$CONTROL/api/keys" -H 'content-type: application/json' \\`);
310
+ console.log(` -d '{"kid":"${kid}","publicPem":"…"}'`);
311
+ }
312
+ async function signPackCommand(options) {
313
+ const { packPath, name, version } = await loadForSigning(options.name, options.workspace);
314
+ if (!options.key) {
315
+ console.error('Error: --key <private-key.pem> required. Generate one with: agenthippo pack keygen -o key.pem');
316
+ process.exit(1);
317
+ }
318
+ let privateKeyPem;
319
+ try {
320
+ privateKeyPem = fs.readFileSync(options.key, 'utf8');
321
+ }
322
+ catch (err) {
323
+ console.error(`Error: cannot read key ${options.key}: ${err instanceof Error ? err.message : err}`);
324
+ process.exit(1);
325
+ return;
326
+ }
327
+ const kid = options.kid;
328
+ if (!kid) {
329
+ console.error('Error: --kid <id> required — it is how a verifier finds the public half in JWKS.');
330
+ process.exit(1);
331
+ }
332
+ const { digest } = (0, signature_1.signPack)({ packDir: packPath, pack: name, packVersion: version, privateKeyPem, kid: kid });
333
+ console.log(`Signed ${name}@${version}`);
334
+ console.log(` digest ${digest}`);
335
+ console.log(` key ${kid}`);
336
+ console.log(` written ${path.join(packPath, signature_1.PACK_SIGNATURE_FILE)}`);
337
+ console.log('');
338
+ console.log('Commit pack.sig with the pack: it travels WITH the artifact, and a fleet that');
339
+ console.log('requires signatures refuses anything whose contents no longer match it.');
340
+ }
341
+ async function verifyPackCommand(options) {
342
+ const { packPath, name, version } = await loadForSigning(options.name, options.workspace);
343
+ const token = (0, signature_1.readPackSignature)(packPath);
344
+ if (!token) {
345
+ console.error(`UNSIGNED: no ${signature_1.PACK_SIGNATURE_FILE} at ${packPath}`);
346
+ console.error((0, signature_1.describePackRefusal)({ kind: 'unsigned', detail: `no ${signature_1.PACK_SIGNATURE_FILE}`, pack: name, packVersion: version, packDir: packPath }));
347
+ process.exit(1);
348
+ }
349
+ let keys = {};
350
+ if (options.keys) {
351
+ try {
352
+ keys = JSON.parse(fs.readFileSync(options.keys, 'utf8'));
353
+ }
354
+ catch (err) {
355
+ console.error(`Error: cannot read keys ${options.keys}: ${err instanceof Error ? err.message : err}`);
356
+ process.exit(1);
357
+ }
358
+ }
359
+ else {
360
+ const trust = await (0, signature_1.loadPackTrustRoot)({
361
+ env: options.jwks ? { ...process.env, [signature_1.PACK_JWKS_URL_ENV]: options.jwks } : process.env,
362
+ leaseUrl: process.env.AGENTHIPPO_LEASE_URL,
363
+ });
364
+ for (const w of trust.warnings) {
365
+ console.error(`Warning: ${w}`);
366
+ }
367
+ if (trust.status === 'unavailable') {
368
+ // Distinct from "no keys configured", and with a different fix. Collapsing the two into
369
+ // an empty keyring is finding A4 — a 503 read exactly like an unmanaged install.
370
+ console.error(`REFUSED: a trust root is configured but could not be loaded — ${trust.reason}`);
371
+ console.error((0, signature_1.describePackRefusal)({
372
+ kind: 'trust-root-unavailable', detail: trust.reason ?? 'unavailable', pack: name, packVersion: version,
373
+ packDir: packPath, trustRootHint: trust.url ? `Trust root: ${trust.url}` : undefined,
374
+ }));
375
+ process.exit(1);
376
+ }
377
+ keys = trust.keys;
378
+ }
379
+ if (Object.keys(keys).length === 0) {
380
+ // "No keys" is a refusal, never a pass: verifying against an empty keyring would report
381
+ // success for any signature at all.
382
+ console.error('REFUSED: no trusted public keys. Pass --keys <file>, --jwks <url>, or set AGENTHIPPO_PACK_SIGNING_KEYS.');
383
+ process.exit(1);
384
+ }
385
+ const res = (0, signature_1.verifyPack)(token, keys, { packDir: packPath, pack: name, packVersion: version });
386
+ if (!res.ok) {
387
+ console.error(`INVALID: ${res.reason}`);
388
+ console.error((0, signature_1.describePackRefusal)({ kind: res.kind, detail: res.reason, pack: name, packVersion: version, packDir: packPath }));
389
+ process.exit(1);
390
+ }
391
+ console.log(`OK: ${name}@${version} verified`);
392
+ console.log(` digest ${res.claims.digest}`);
393
+ console.log(` NOTE: .claude/, .codex/ and .agent-hippo/ ARE covered by this digest — they carry hooks`);
394
+ console.log(' and other executable content, and were previously outside the signature.');
395
+ }
396
+ async function digestCommand(options) {
397
+ const { packPath, name, version } = await loadForSigning(options.name, options.workspace);
398
+ console.log(`${(0, signature_1.computePackDigest)(packPath)} ${name}@${version}`);
399
+ }