@inneranimalmedia/agentsam-sdk 2.5.0 → 2.6.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.
Files changed (211) hide show
  1. package/AGENTSAM.md +55 -0
  2. package/README.md +12 -8
  3. package/bin/agentsam +2 -0
  4. package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
  5. package/docs/CLI_SHELL.md +163 -53
  6. package/docs/PLATFORM_RUNTIME_EVENTS.md +48 -0
  7. package/docs/RELEASES.md +16 -7
  8. package/docs/SOURCE_ARCHITECTURE.md +58 -0
  9. package/docs/TEST_TIERS.md +26 -0
  10. package/migrations/runtime/0001_cli_runtime.sql +298 -0
  11. package/package.json +45 -12
  12. package/packages/agentsam-repository/README.md +15 -0
  13. package/packages/agentsam-repository/package.json +25 -0
  14. package/packages/agentsam-repository/src/contracts.js +113 -0
  15. package/packages/agentsam-repository/src/index.js +3 -0
  16. package/{src/lib → packages/agentsam-repository/src}/merkle/cloudflare-persistence.js +14 -24
  17. package/{src/lib → packages/agentsam-repository/src}/merkle/index.js +1 -0
  18. package/{src/lib → packages/agentsam-repository/src}/merkle/persistence.js +6 -4
  19. package/{src/lib → packages/agentsam-repository/src}/merkle/policy.js +1 -0
  20. package/packages/agentsam-repository/test/contracts.test.mjs +40 -0
  21. package/packages/agentsam-repository/test/git-context.test.mjs +24 -0
  22. package/{test/merkle.test.mjs → packages/agentsam-repository/test/merkle-core.test.mjs} +2 -32
  23. package/{test → packages/agentsam-repository/test}/merkle-persistence.test.mjs +11 -6
  24. package/packages/connectors/cloudflare/package.json +10 -0
  25. package/packages/connectors/cloudflare/src/index.js +127 -0
  26. package/packages/connectors/cloudflare/src/owner.js +76 -0
  27. package/packages/connectors/cloudflare/src/routes.js +223 -0
  28. package/packages/connectors/cloudflare/src/vault.js +80 -0
  29. package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
  30. package/packages/identity/package.json +2 -2
  31. package/packages/identity/src/contracts/auth-config.js +18 -7
  32. package/packages/identity/tests/auth-config.test.mjs +9 -5
  33. package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
  34. package/protocol/COMPANY_REPOSITORY_GRAPH_V1.md +91 -0
  35. package/protocol/MERKLE_PERSISTENCE_V1.md +2 -0
  36. package/protocol/MERKLE_PERSISTENCE_V2.md +40 -0
  37. package/protocol/README.md +1 -0
  38. package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
  39. package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
  40. package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
  41. package/protocol/capabilities/manifest.json +47 -0
  42. package/protocol/context/context-budget.schema.json +10 -15
  43. package/protocol/context/context-item.schema.json +4 -5
  44. package/protocol/context/resolved-context-pack.schema.json +19 -14
  45. package/protocol/models/README.md +373 -0
  46. package/protocol/models/model-inventory-v2.schema.json +212 -0
  47. package/protocol/repository/repository-contract.schema.json +24 -0
  48. package/protocol/repository/repository-dependency.schema.json +24 -0
  49. package/protocol/repository/repository-identity.schema.json +17 -0
  50. package/protocol/rpc/v1/common.proto +16 -0
  51. package/protocol/rpc/v1/errors.proto +35 -0
  52. package/protocol/rpc/v1/knowledge.proto +77 -0
  53. package/services/knowledge/package-lock.json +333 -0
  54. package/services/knowledge/package.json +5 -1
  55. package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
  56. package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
  57. package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
  58. package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
  59. package/skills/catalog.json +18 -0
  60. package/src/agent/capability-adapter.js +25 -13
  61. package/src/agent/index.js +1 -0
  62. package/src/agent/responses-runner.js +353 -0
  63. package/src/capabilities/repository-snapshot.js +3 -3
  64. package/src/cli.js +118 -31
  65. package/src/cloudflare/cpu-profile.js +115 -0
  66. package/src/cloudflare/index.js +14 -0
  67. package/src/cloudflare/wrangler.js +132 -0
  68. package/src/commands/account-auth.js +47 -0
  69. package/src/commands/cloudflare.js +58 -0
  70. package/src/commands/connections.js +93 -0
  71. package/src/commands/context-economics.js +129 -0
  72. package/src/commands/context.js +1 -1
  73. package/src/commands/db.js +20 -3
  74. package/src/commands/deploy.js +39 -3
  75. package/src/commands/env.js +90 -0
  76. package/src/commands/eval.js +63 -0
  77. package/src/commands/interactive.js +2 -5
  78. package/src/commands/knowledge.js +12 -4
  79. package/src/commands/merkle-persist.js +30 -11
  80. package/src/commands/merkle.js +1 -1
  81. package/src/commands/models.js +149 -46
  82. package/src/commands/ollama.js +26 -0
  83. package/src/commands/preferences.js +130 -61
  84. package/src/commands/resume.js +67 -0
  85. package/src/commands/security.js +5 -3
  86. package/src/commands/shell.js +568 -119
  87. package/src/commands/tunnel.js +2 -2
  88. package/src/commands/whoami.js +86 -0
  89. package/src/context/budget.js +68 -6
  90. package/src/context/index.js +3 -1
  91. package/src/context/rehydrate.js +35 -0
  92. package/src/context/resolve.js +44 -12
  93. package/src/errors/contract.js +236 -0
  94. package/src/errors/diagnostic.js +160 -0
  95. package/src/errors/index.js +23 -0
  96. package/src/eval/context.js +191 -0
  97. package/src/eval/index.js +1 -0
  98. package/src/index.js +68 -2
  99. package/src/knowledge/service/auth.js +13 -0
  100. package/src/knowledge/service/grpc-client.js +115 -0
  101. package/src/knowledge/service/grpc-codec.js +237 -0
  102. package/src/knowledge/service/grpc-server.js +83 -0
  103. package/src/knowledge/service/job-engine.js +248 -0
  104. package/src/knowledge/service/server.js +87 -135
  105. package/src/knowledge/source.js +1 -1
  106. package/src/lib/account-session.js +98 -0
  107. package/src/lib/agent-instructions.js +73 -0
  108. package/src/lib/auth.js +4 -0
  109. package/src/lib/cli-preferences.js +55 -24
  110. package/src/lib/deploy/git-guard.js +69 -0
  111. package/src/lib/deploy/health.js +57 -0
  112. package/src/lib/deploy/local-studio.js +283 -0
  113. package/src/lib/deploy/secret-scan.js +65 -0
  114. package/src/lib/deploy-receipt/index.js +2 -2
  115. package/src/lib/detect-context.js +2 -2
  116. package/src/lib/execution-approvals.js +59 -0
  117. package/src/lib/knowledge-docker.js +6 -3
  118. package/src/lib/local-sessions.js +148 -0
  119. package/src/lib/local-status.js +1 -1
  120. package/src/lib/project-config.js +1 -1
  121. package/src/lib/provider-credentials.js +183 -0
  122. package/src/lib/scaffold/templates/worker-api/index.js +101 -20
  123. package/src/lib/scaffold/wizards/worker-api.js +27 -11
  124. package/src/lib/slash-commands.js +23 -16
  125. package/src/local/migrations.js +93 -0
  126. package/src/local/runtime-store.js +141 -0
  127. package/src/local/sqlite.js +2 -0
  128. package/src/local-pty/server.js +113 -51
  129. package/src/models/catalog.js +135 -0
  130. package/src/models/discovery.js +292 -0
  131. package/src/models/index.js +7 -0
  132. package/src/providers/anthropic-messages.js +192 -0
  133. package/src/providers/cloudflare-chat.js +183 -0
  134. package/src/providers/factory.js +69 -0
  135. package/src/providers/gemini-generate-content.js +208 -0
  136. package/src/providers/index.js +10 -0
  137. package/src/providers/ollama-chat.js +148 -0
  138. package/src/providers/openai-responses.js +426 -0
  139. package/src/repository/index.js +14 -2
  140. package/src/rpc/generated/common_grpc_pb.js +1 -0
  141. package/src/rpc/generated/common_pb.js +536 -0
  142. package/src/rpc/generated/errors_grpc_pb.js +1 -0
  143. package/src/rpc/generated/errors_pb.js +482 -0
  144. package/src/rpc/generated/knowledge_grpc_pb.js +135 -0
  145. package/src/rpc/generated/knowledge_pb.js +2168 -0
  146. package/src/rpc/generated/package.json +3 -0
  147. package/src/security/process.js +35 -9
  148. package/src/security/trust-boundary.js +2 -2
  149. package/src/telemetry/contracts.js +203 -0
  150. package/src/telemetry/events.js +51 -0
  151. package/src/telemetry/index.js +8 -0
  152. package/src/tools/hydrate.js +35 -0
  153. package/src/tools/index.js +1 -0
  154. package/src/ui/boot.js +15 -17
  155. package/src/ui/cli/activity.js +76 -0
  156. package/src/ui/cli/compaction.js +15 -0
  157. package/src/ui/cli/footer.js +39 -0
  158. package/src/ui/cli/help.js +192 -0
  159. package/src/ui/cli/plan.js +20 -0
  160. package/src/ui/cli/runtime-events.js +110 -0
  161. package/src/ui/cli/waiting.js +16 -0
  162. package/src/ui/merkle/render.js +1 -1
  163. package/test/account-session.test.mjs +36 -0
  164. package/test/cli/preferences-runtime.test.mjs +11 -0
  165. package/test/cli/runtime-ui.test.mjs +74 -0
  166. package/test/cli-preferences.test.mjs +26 -5
  167. package/test/cloudflare-connector.test.mjs +96 -0
  168. package/test/cloudflare-runtime.test.mjs +75 -0
  169. package/test/context.test.mjs +61 -12
  170. package/test/deploy-health-scan.test.mjs +67 -0
  171. package/test/error-diagnostics.test.mjs +115 -0
  172. package/test/eval-context.test.mjs +37 -0
  173. package/test/execution-approvals.test.mjs +27 -0
  174. package/test/fixtures/knowledge-rpc-worker.mjs +16 -0
  175. package/test/integration/cli-help.test.mjs +37 -0
  176. package/test/integration/knowledge-rpc.test.mjs +112 -0
  177. package/test/integration/merkle-cli.test.mjs +61 -0
  178. package/test/integration/merkle-persistence-identity.test.mjs +48 -0
  179. package/test/integration/provider-env-cli.test.mjs +49 -0
  180. package/test/integration/provider-factory.test.mjs +197 -0
  181. package/test/integration/repository-company-graph.test.mjs +90 -0
  182. package/test/integration/runtime-migrations.test.mjs +82 -0
  183. package/test/knowledge-service.test.mjs +5 -0
  184. package/test/knowledge.test.mjs +16 -0
  185. package/test/live/terminal-transport.live.test.mjs +24 -0
  186. package/test/local-sessions.test.mjs +48 -0
  187. package/test/local-studio-deploy.test.mjs +83 -0
  188. package/test/model-catalog.test.mjs +43 -0
  189. package/test/models.test.mjs +127 -16
  190. package/test/npm10-lock.test.mjs +29 -0
  191. package/test/ollama.test.mjs +21 -0
  192. package/test/openai-responses.test.mjs +95 -0
  193. package/test/portable-context.test.mjs +1 -1
  194. package/test/provider-credentials.test.mjs +96 -0
  195. package/test/rehydrate.test.mjs +25 -0
  196. package/test/release-hygiene.test.mjs +13 -5
  197. package/test/responses-runner.test.mjs +150 -0
  198. package/test/shell.test.mjs +92 -23
  199. package/test/smoke.mjs +4 -1
  200. package/test/telemetry.test.mjs +79 -0
  201. package/test/terminal/local-pty.mock.test.mjs +151 -0
  202. package/test/tools-search.test.mjs +14 -1
  203. package/test/whoami-resume.test.mjs +56 -0
  204. /package/{src/lib → packages/agentsam-repository/src}/git-context.js +0 -0
  205. /package/{src/lib → packages/agentsam-repository/src}/merkle/diff.js +0 -0
  206. /package/{src/lib → packages/agentsam-repository/src}/merkle/filemeta.js +0 -0
  207. /package/{src/lib → packages/agentsam-repository/src}/merkle/git-ignore.js +0 -0
  208. /package/{src/lib → packages/agentsam-repository/src}/merkle/hash.js +0 -0
  209. /package/{src/lib → packages/agentsam-repository/src}/merkle/semantic.js +0 -0
  210. /package/{src/lib → packages/agentsam-repository/src}/merkle/snapshot.js +0 -0
  211. /package/{src/lib → packages/agentsam-repository/src}/merkle/tree.js +0 -0
package/src/cli.js CHANGED
@@ -13,8 +13,10 @@ import { promptOptionalByokKeys } from './lib/prompt-byok.js';
13
13
  import { runStartLocal } from './commands/start-local.js';
14
14
  import { runOllama } from './commands/ollama.js';
15
15
  import { runModels } from './commands/models.js';
16
+ import { runEnv } from './commands/env.js';
16
17
  import { runTunnel } from './commands/tunnel.js';
17
18
  import { runDeploy } from './commands/deploy.js';
19
+ import { runConnections } from './commands/connections.js';
18
20
  import { runIdentityPreview } from './commands/identity-preview.js';
19
21
  import { runIdentityInit } from './commands/identity-init.js';
20
22
  import { runContext } from './commands/context.js';
@@ -30,14 +32,27 @@ import { runSecurity } from './commands/security.js';
30
32
  import { runRecon } from './commands/recon.js';
31
33
  import { runCad } from './commands/cad.js';
32
34
  import { runSkills } from './commands/skills.js';
35
+ import { runEval } from './commands/eval.js';
36
+ import { runCloudflare } from './commands/cloudflare.js';
37
+ import { runWhoami } from './commands/whoami.js';
38
+ import { runResume } from './commands/resume.js';
39
+ import { runLogin, runLogout } from './commands/account-auth.js';
33
40
  import { applyPresetSelection, runAdd, runCapabilities, runDev, runInspect } from './commands/product.js';
34
41
  import { listPresets, resolvePreset } from './presets/index.js';
35
42
  import fs from 'node:fs';
36
43
  import { repositoryRoot } from './knowledge/config.js';
37
- import { resolveSdkKey } from '../packages/identity/src/contracts/auth-config.js';
44
+ import { resolveAccountSdkKey } from './lib/account-session.js';
45
+ import { renderDiagnosticError } from './errors/index.js';
46
+ import { renderHelpOverview, runHelp } from './ui/cli/help.js';
38
47
 
39
48
  const VERSION = pkg.version;
40
49
 
50
+ function reportCliError(error) {
51
+ if (error?.reported) return;
52
+ const rendered = renderDiagnosticError(error).split('\n').map((line) => ` ${line}`).join('\n');
53
+ console.error(`\n${rendered}\n`);
54
+ }
55
+
41
56
  function createPrompt() {
42
57
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
43
58
  return {
@@ -47,6 +62,10 @@ function createPrompt() {
47
62
  }
48
63
 
49
64
  function printHelp() {
65
+ console.log(renderHelpOverview(VERSION));
66
+ }
67
+
68
+ function printLegacyHelp() {
50
69
  console.log(`
51
70
  Agent Sam SDK — CLI v${VERSION}
52
71
 
@@ -76,15 +95,23 @@ function printHelp() {
76
95
  agentsam security Dependency scan, log triage, and verified repair (--help)
77
96
  agentsam status [--json] Live local Git + DB + API + PTY status
78
97
  agentsam db init|status Manage the project-local SQLite database
79
- agentsam models Show configured providers and local model inventory
98
+ agentsam models Verify configured providers and selectable hosted/local models
99
+ agentsam env init <name> Create a secure provider profile + reusable shell loader
100
+ agentsam login Sign in to Inner Animal Media and persist a secure machine-local session
101
+ agentsam logout Sign out locally; provider credentials stay untouched
102
+ agentsam whoami [--json] Authenticated account identity + safe credential status
103
+ agentsam resume [session] Resume a saved Agent Sam session; omit id for picker
104
+ agentsam eval context Offline context-strategy/economics fixtures (--help)
105
+ agentsam cloudflare Native Wrangler reads + Worker CPU profile analysis (--help)
80
106
  agentsam start-local Local PTY on ws://127.0.0.1:3099 (no tunnel, no Cloudflare)
81
107
  agentsam ollama Opt-in local Ollama setup/status/model management
82
108
  agentsam shell Interactive Agent Sam slash-command shell
83
109
  agentsam tunnel Explicitly expose local PTY when remote access is wanted
84
110
  agentsam deploy Graduate to Cloudflare / GCP when ready
85
111
  agentsam dockerize Build/run app, knowledge, or CAD containers (--help)
86
- agentsam identity preview Local auth portal preview
112
+ agentsam identity preview Preview the reusable local auth portal (not production login)
87
113
  agentsam identity init Add reusable identity package surfaces
114
+ agentsam help
88
115
  agentsam --version
89
116
  agentsam --help
90
117
 
@@ -108,8 +135,8 @@ function printHelp() {
108
135
  --pretty Pretty-print JSON; machine JSON is compact by default
109
136
  --remote <name> Preferred Git remote (default origin; falls back to first remote)
110
137
 
111
- Init is completable with Node only — no IAM login, no OAuth, no Cloudflare.
112
- Prove locally first; deploy prompts for accounts only when you choose to ship.
138
+ Run agentsam from any project to enter the account-aware interactive experience.
139
+ Account, model-provider, terminal, and deploy permissions are requested only when the related capability needs them.
113
140
 
114
141
  Tunnel options:
115
142
  --quick Quick tunnel (default) — trycloudflare.com URL
@@ -143,11 +170,13 @@ function parseInitArgs(argv) {
143
170
  }
144
171
 
145
172
  function parseDeployArgs(argv) {
146
- const opts = { target: '', accountId: '' };
173
+ const opts = { target: '', accountId: '', dryRun: false, plan: false };
147
174
  for (let i = 0; i < argv.length; i += 1) {
148
175
  const arg = argv[i];
149
176
  if (arg === '--target') opts.target = argv[++i] || '';
150
177
  else if (arg === '--account-id') opts.accountId = argv[++i] || '';
178
+ else if (arg === '--dry-run') opts.dryRun = true;
179
+ else if (arg === '--plan') opts.plan = true;
151
180
  }
152
181
  return opts;
153
182
  }
@@ -206,7 +235,7 @@ async function runLocalInit(config) {
206
235
  console.log(` ${step}`);
207
236
  }
208
237
 
209
- const sdkKey = resolveSdkKey(process.env);
238
+ const sdkKey = resolveAccountSdkKey({ env: process.env }).value;
210
239
  if (prompt && sdkKey) {
211
240
  console.log('\n Optional — BYOK keys for IAM dashboard Agent Sam (skip with Enter):\n');
212
241
  await promptOptionalByokKeys(sdkKey, prompt);
@@ -262,12 +291,12 @@ async function initInteractive(partial = {}) {
262
291
  if (runTarget !== 'local') {
263
292
  const { detectContext, missingForInit } = await import('./lib/detect-context.js');
264
293
  const ctx = await detectContext();
265
- if (missingForInit(ctx, resolveSdkKey(process.env), { runTarget }).length) {
294
+ if (missingForInit(ctx, resolveAccountSdkKey({ env: process.env }).value, { runTarget }).length) {
266
295
  printContextSummary(ctx);
267
296
  }
268
297
  }
269
298
 
270
- const prompt = resolveSdkKey(process.env) ? createPrompt() : null;
299
+ const prompt = resolveAccountSdkKey({ env: process.env }).value ? createPrompt() : null;
271
300
  try {
272
301
  await runLocalInit({ projectName, lane: laneKey, runTarget, prompt });
273
302
  } finally {
@@ -290,6 +319,8 @@ const rest = process.argv.slice(3);
290
319
 
291
320
  if (command === '--version' || command === '-v') {
292
321
  console.log(VERSION);
322
+ } else if (command === 'help') {
323
+ await runHelp(rest, { version: VERSION });
293
324
  } else if (command === '--help' || command === '-h') {
294
325
  printHelp();
295
326
  } else if (!command) {
@@ -297,7 +328,7 @@ if (command === '--version' || command === '-v') {
297
328
  try { await runInteractive(); }
298
329
  catch (e) {
299
330
  if (e?.code !== 'AGENTSAM_SETUP_CANCELLED') {
300
- console.error(`\n ✗ ${e?.message || e}\n`);
331
+ reportCliError(e);
301
332
  process.exitCode = 1;
302
333
  }
303
334
  }
@@ -315,52 +346,101 @@ if (command === '--version' || command === '-v') {
315
346
  applyPresetSelection(created.dir, preset);
316
347
  console.log(` ✓ Preset ${preset.id}\n ✓ Features ${preset.features.join(', ') || 'none'}\n ✓ Capabilities ${preset.capabilities.length}\n`);
317
348
  }
318
- } catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
349
+ } catch (e) { reportCliError(e); process.exitCode = 1; }
319
350
  } else if (command === 'add') {
320
351
  try { await runAdd(rest); }
321
- catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
352
+ catch (e) { reportCliError(e); process.exitCode = 1; }
322
353
  } else if (command === 'dev') {
323
354
  try { await runDev(rest); }
324
- catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
355
+ catch (e) { reportCliError(e); process.exitCode = 1; }
325
356
  } else if (command === 'inspect') {
326
357
  try { await runInspect(rest); }
327
- catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
358
+ catch (e) { reportCliError(e); process.exitCode = 1; }
328
359
  } else if (command === 'capabilities') {
329
360
  try { await runCapabilities(rest); }
330
- catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
361
+ catch (e) { reportCliError(e); process.exitCode = 1; }
331
362
  } else if (command === 'context') {
332
363
  try {
333
364
  await runContext(rest);
334
365
  } catch (e) {
335
- console.error(`\n ✗ ${e?.message || e}\n`);
366
+ reportCliError(e);
336
367
  process.exit(1);
337
368
  }
338
369
  } else if (command === 'status') {
339
370
  try {
340
371
  await runStatus(rest);
341
372
  } catch (e) {
342
- console.error(`\n ✗ ${e?.message || e}\n`);
373
+ reportCliError(e);
343
374
  process.exit(1);
344
375
  }
345
376
  } else if (command === 'db') {
346
377
  try {
347
378
  await runDb(rest);
348
379
  } catch (e) {
349
- console.error(`\n ✗ ${e?.message || e}\n`);
380
+ reportCliError(e);
350
381
  process.exit(1);
351
382
  }
352
383
  } else if (command === 'models') {
353
384
  try {
354
385
  await runModels(rest);
355
386
  } catch (e) {
356
- console.error(`\n ✗ ${e?.message || e}\n`);
387
+ reportCliError(e);
388
+ process.exit(1);
389
+ }
390
+ } else if (command === 'env') {
391
+ try {
392
+ await runEnv(rest);
393
+ } catch (e) {
394
+ reportCliError(e);
395
+ process.exit(1);
396
+ }
397
+ } else if (command === 'eval') {
398
+ try {
399
+ await runEval(rest);
400
+ } catch (e) {
401
+ reportCliError(e);
357
402
  process.exit(1);
358
403
  }
404
+ } else if (command === 'cloudflare' || command === 'cf') {
405
+ try {
406
+ await runCloudflare(rest);
407
+ } catch (e) {
408
+ if (!e?.reported) reportCliError(e);
409
+ process.exitCode = 1;
410
+ }
411
+ } else if (command === 'login') {
412
+ try {
413
+ await runLogin(rest);
414
+ } catch (e) {
415
+ reportCliError(e);
416
+ process.exitCode = 1;
417
+ }
418
+ } else if (command === 'logout') {
419
+ try {
420
+ runLogout(rest);
421
+ } catch (e) {
422
+ reportCliError(e);
423
+ process.exitCode = 1;
424
+ }
425
+ } else if (command === 'whoami') {
426
+ try {
427
+ await runWhoami(rest);
428
+ } catch (e) {
429
+ reportCliError(e);
430
+ process.exitCode = 1;
431
+ }
432
+ } else if (command === 'resume') {
433
+ try {
434
+ await runResume(rest);
435
+ } catch (e) {
436
+ reportCliError(e);
437
+ process.exitCode = 1;
438
+ }
359
439
  } else if (command === 'shell') {
360
440
  try {
361
441
  await runShell(rest);
362
442
  } catch (e) {
363
- console.error(`\n ✗ ${e?.message || e}\n`);
443
+ reportCliError(e);
364
444
  process.exit(1);
365
445
  }
366
446
  } else if (command === 'start-local') {
@@ -369,35 +449,42 @@ if (command === '--version' || command === '-v') {
369
449
  try {
370
450
  await runOllama(rest);
371
451
  } catch (e) {
372
- console.error(`\n ✗ ${e?.message || e}\n`);
452
+ reportCliError(e);
373
453
  process.exitCode = 1;
374
454
  }
375
455
  } else if (command === 'tunnel') {
376
456
  try {
377
457
  await runTunnel(rest);
378
458
  } catch (e) {
379
- console.error(`\n ✗ ${e?.message || e}\n`);
459
+ reportCliError(e);
460
+ process.exit(1);
461
+ }
462
+ } else if (command === 'connections' || command === 'connection') {
463
+ try {
464
+ await runConnections(rest);
465
+ } catch (e) {
466
+ reportCliError(e);
380
467
  process.exit(1);
381
468
  }
382
469
  } else if (command === 'deploy') {
383
470
  try {
384
471
  await runDeploy(parseDeployArgs(rest));
385
472
  } catch (e) {
386
- console.error(`\n ✗ ${e?.message || e}\n`);
473
+ reportCliError(e);
387
474
  process.exit(1);
388
475
  }
389
476
  } else if (command === 'dockerize') {
390
477
  try {
391
478
  await runDockerize(rest);
392
479
  } catch (e) {
393
- console.error(`\n ✗ ${e?.message || e}\n`);
480
+ reportCliError(e);
394
481
  process.exit(1);
395
482
  }
396
483
  } else if (command === 'cad') {
397
484
  try {
398
485
  await runCad(rest);
399
486
  } catch (e) {
400
- console.error(`\n ✗ ${e?.message || e}\n`);
487
+ reportCliError(e);
401
488
  process.exitCode = 1;
402
489
  }
403
490
  } else if (command === 'security' || command === 'sca') {
@@ -406,7 +493,7 @@ if (command === '--version' || command === '-v') {
406
493
  try {
407
494
  runSkills(rest);
408
495
  } catch (e) {
409
- console.error(`\n ✗ ${e?.message || e}\n`);
496
+ reportCliError(e);
410
497
  process.exitCode = 1;
411
498
  }
412
499
  } else if (command === 'merkle') {
@@ -419,14 +506,14 @@ if (command === '--version' || command === '-v') {
419
506
  try {
420
507
  await runMini(rest);
421
508
  } catch (e) {
422
- console.error(`\n ${e?.message || e}\n`);
509
+ reportCliError(e);
423
510
  process.exitCode = 1;
424
511
  }
425
512
  } else if (['index', 'search', 'repo'].includes(command)) {
426
513
  try {
427
514
  const commands = await import('./commands/knowledge.js');
428
515
  await ({ index: commands.runKnowledge, search: commands.runSearch, repo: commands.runRepository })[command](rest);
429
- } catch (e) { console.error(e.message); process.exitCode = 1; }
516
+ } catch (e) { reportCliError(e); process.exitCode = 1; }
430
517
  } else if (command === 'init') {
431
518
  try {
432
519
  const existing = !rest.includes('--name') && (rest.includes('.') || rest.includes('--existing') || rest.includes('--cwd') || fs.existsSync(path.join(repositoryRoot(), '.git')));
@@ -434,21 +521,21 @@ if (command === '--version' || command === '-v') {
434
521
  else if (rest.includes('--help') || rest.includes('-h')) printHelp();
435
522
  else if (rest.some((a) => a.startsWith('--'))) await initFromArgs(rest);
436
523
  else await initInteractive({});
437
- } catch (e) { console.error(e.message); process.exitCode = 1; }
524
+ } catch (e) { reportCliError(e); process.exitCode = 1; }
438
525
  } else if (command === 'identity') {
439
526
  const sub = rest[0];
440
527
  if (sub === 'preview') {
441
528
  try {
442
529
  await runIdentityPreview(rest.slice(1));
443
530
  } catch (e) {
444
- console.error(`\n ✗ ${e?.message || e}\n`);
531
+ reportCliError(e);
445
532
  process.exit(1);
446
533
  }
447
534
  } else if (sub === 'init') {
448
535
  try {
449
536
  await runIdentityInit(rest);
450
537
  } catch (e) {
451
- console.error(`\n ✗ ${e?.message || e}\n`);
538
+ reportCliError(e);
452
539
  process.exit(1);
453
540
  }
454
541
  } else {
@@ -0,0 +1,115 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ function clean(value) { return value == null ? '' : String(value).trim(); }
5
+ function number(value) { const n = Number(value ?? 0); return Number.isFinite(n) && n >= 0 ? n : 0; }
6
+
7
+ function parseProfile(value) {
8
+ if (typeof value === 'string') return JSON.parse(value);
9
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new TypeError('Cloudflare CPU profile must be a Chrome .cpuprofile object');
10
+ return value;
11
+ }
12
+
13
+ function frameOf(node = {}) {
14
+ const frame = node.callFrame || {};
15
+ return {
16
+ node_id: node.id,
17
+ function: clean(frame.functionName) || '(anonymous)',
18
+ url: clean(frame.url) || null,
19
+ line: Number.isInteger(frame.lineNumber) && frame.lineNumber >= 0 ? frame.lineNumber + 1 : null,
20
+ column: Number.isInteger(frame.columnNumber) && frame.columnNumber >= 0 ? frame.columnNumber + 1 : null,
21
+ };
22
+ }
23
+
24
+ export function summarizeCloudflareCpuProfile(value, options = {}) {
25
+ const profile = parseProfile(value);
26
+ if (!Array.isArray(profile.nodes) || !Array.isArray(profile.samples)) throw new TypeError('CPU profile requires nodes[] and samples[]');
27
+ const deltas = Array.isArray(profile.timeDeltas) ? profile.timeDeltas : [];
28
+ const byId = new Map(profile.nodes.map((node) => [node.id, node]));
29
+ const totals = new Map();
30
+ let totalUs = 0;
31
+ for (let index = 0; index < profile.samples.length; index += 1) {
32
+ const id = profile.samples[index];
33
+ const deltaUs = number(deltas[index]);
34
+ totalUs += deltaUs;
35
+ totals.set(id, (totals.get(id) || 0) + deltaUs);
36
+ }
37
+ if (!totalUs && Number.isFinite(profile.endTime) && Number.isFinite(profile.startTime)) totalUs = Math.max(0, Number(profile.endTime) - Number(profile.startTime));
38
+ const maxFrames = Number.isInteger(options.maxFrames) && options.maxFrames > 0 ? Math.min(options.maxFrames, 100) : 25;
39
+ const frames = [...totals.entries()].map(([id, selfUs]) => {
40
+ const frame = frameOf(byId.get(id));
41
+ return Object.freeze({
42
+ ...frame,
43
+ self_us: selfUs,
44
+ self_ms: selfUs / 1000,
45
+ percent: totalUs > 0 ? (selfUs / totalUs) * 100 : 0,
46
+ garbage_collection: /(?:garbage collector|\bgc\b)/i.test(frame.function),
47
+ });
48
+ }).sort((a, b) => b.self_us - a.self_us).slice(0, maxFrames);
49
+ return Object.freeze({
50
+ schema_version: 1,
51
+ profile_kind: 'chrome-cpu-profile',
52
+ samples: profile.samples.length,
53
+ total_profile_us: totalUs,
54
+ total_profile_ms: totalUs / 1000,
55
+ top_frames: Object.freeze(frames),
56
+ garbage_collection_ms: frames.filter((row) => row.garbage_collection).reduce((sum, row) => sum + row.self_ms, 0),
57
+ timer_semantics: 'Deployed Workers timers do not advance during CPU-only execution; use local workerd/DevTools CPU profiles plus production CPU metrics.',
58
+ });
59
+ }
60
+
61
+ function within(root, file) { return file === root || file.startsWith(`${root}${path.sep}`); }
62
+
63
+ export function summarizeCloudflareCpuProfileFile(input = {}) {
64
+ const cwd = path.resolve(input.cwd || process.cwd());
65
+ const file = path.resolve(cwd, clean(input.file));
66
+ if (!clean(input.file)) throw new TypeError('cpu profile file is required');
67
+ if (!within(cwd, file)) throw new Error('cpu_profile_outside_cwd');
68
+ const stat = fs.statSync(file);
69
+ if (!stat.isFile()) throw new Error('cpu_profile_not_file');
70
+ if (stat.size > 64 * 1024 * 1024) throw new Error('cpu_profile_too_large');
71
+ return Object.freeze({ file: path.relative(cwd, file) || path.basename(file), ...summarizeCloudflareCpuProfile(fs.readFileSync(file, 'utf8'), input) });
72
+ }
73
+
74
+ function sourceItems(cwd, sources = [], maxChars = 24_000) {
75
+ const rows = [];
76
+ let chars = 0;
77
+ for (const source of sources.slice(0, 12)) {
78
+ const file = path.resolve(cwd, String(source));
79
+ if (!within(cwd, file) || !fs.existsSync(file) || !fs.statSync(file).isFile()) continue;
80
+ const text = fs.readFileSync(file, 'utf8');
81
+ const excerpt = text.slice(0, Math.min(8_000, Math.max(0, maxChars - chars)));
82
+ if (!excerpt) break;
83
+ rows.push(Object.freeze({ ref: `file:${path.relative(cwd, file)}`, chars: excerpt.length, content: excerpt }));
84
+ chars += excerpt.length;
85
+ if (chars >= maxChars) break;
86
+ }
87
+ return Object.freeze(rows);
88
+ }
89
+
90
+ export function buildCloudflareCpuAuditPacket(input = {}) {
91
+ const cwd = path.resolve(input.cwd || process.cwd());
92
+ const profile = input.profile ? summarizeCloudflareCpuProfile(input.profile, input) : summarizeCloudflareCpuProfileFile({ ...input, cwd });
93
+ return Object.freeze({
94
+ schema_version: 1,
95
+ primitive: 'cloudflare.cpu.audit',
96
+ rules: Object.freeze({ read_only: true, may_edit: false, may_deploy: false, production_timer_cpu_measurement_valid: false }),
97
+ profile,
98
+ source_evidence: sourceItems(cwd, input.sources || [], Number(input.maxSourceChars || 24_000)),
99
+ questions: Object.freeze([
100
+ 'Which frames dominate self CPU time?',
101
+ 'Is garbage collection material?',
102
+ 'Which source changes are most likely to reduce CPU without changing behavior?',
103
+ 'What local production-like request should reproduce the hotspot?',
104
+ 'What production metric/log evidence should confirm improvement?',
105
+ ]),
106
+ });
107
+ }
108
+
109
+ export async function runCloudflareCpuAudit(input = {}) {
110
+ if (typeof input.reasoner !== 'function') throw new TypeError('cloudflare.cpu.audit requires an injected reasoner(packet) function');
111
+ const packet = buildCloudflareCpuAuditPacket(input);
112
+ const result = await input.reasoner(structuredClone(packet));
113
+ if (!result || typeof result !== 'object' || Array.isArray(result)) throw new TypeError('cloudflare.cpu.audit reasoner must return an object');
114
+ return Object.freeze({ schema_version: 1, primitive: 'cloudflare.cpu.audit', profile: packet.profile, analysis: result });
115
+ }
@@ -0,0 +1,14 @@
1
+ export {
2
+ WRANGLER_NATIVE_COMMANDS,
3
+ WRANGLER_OPERATION_FAMILIES,
4
+ buildWranglerInvocation,
5
+ listWranglerNativeCommands,
6
+ parseWranglerErrorEvidence,
7
+ runWranglerNative,
8
+ } from './wrangler.js';
9
+ export {
10
+ summarizeCloudflareCpuProfile,
11
+ summarizeCloudflareCpuProfileFile,
12
+ buildCloudflareCpuAuditPacket,
13
+ runCloudflareCpuAudit,
14
+ } from './cpu-profile.js';
@@ -0,0 +1,132 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { runProcess } from '../security/process.js';
4
+ import { AgentSamDiagnosticError, redactDiagnosticValue } from '../errors/index.js';
5
+
6
+ function clean(value) { return value == null ? '' : String(value).trim(); }
7
+ function positiveInteger(value, fallback) { const n = Number(value); return Number.isInteger(n) && n > 0 ? n : fallback; }
8
+
9
+ export const WRANGLER_NATIVE_COMMANDS = Object.freeze([
10
+ Object.freeze({ id: 'whoami', argv: ['whoami', '--json'], risk: 'read', output: 'json', description: 'Read authenticated Cloudflare user/account membership without exposing the auth token.' }),
11
+ Object.freeze({ id: 'deployments.list', argv: ['deployments', 'list', '--json'], risk: 'read', output: 'json', description: 'List recent Worker deployments.' }),
12
+ Object.freeze({ id: 'versions.list', argv: ['versions', 'list', '--json'], risk: 'read', output: 'json', description: 'List recent Worker versions.' }),
13
+ Object.freeze({ id: 'types.check', argv: ['types', '--check'], risk: 'read', output: 'text', description: 'Check generated Worker binding/runtime types without rewriting them.' }),
14
+ Object.freeze({ id: 'queues.list', argv: ['queues', 'list'], risk: 'read', output: 'text', description: 'List Workers Queues visible to the active Cloudflare identity.' }),
15
+ ]);
16
+
17
+ export const WRANGLER_OPERATION_FAMILIES = Object.freeze([
18
+ Object.freeze({ family: 'identity', examples: ['whoami', 'auth list', 'auth activate'], default_risk: 'read/config' }),
19
+ Object.freeze({ family: 'development', examples: ['dev', 'types --check'], default_risk: 'local-runtime' }),
20
+ Object.freeze({ family: 'observability', examples: ['tail --format json', 'deployments list', 'versions list'], default_risk: 'read/stream' }),
21
+ Object.freeze({ family: 'delivery', examples: ['deploy', 'versions deploy', 'rollback'], default_risk: 'remote-write' }),
22
+ Object.freeze({ family: 'data', examples: ['d1', 'r2', 'kv', 'queues', 'hyperdrive', 'vectorize'], default_risk: 'read-or-remote-write' }),
23
+ Object.freeze({ family: 'compute', examples: ['containers', 'browser', 'ai', 'workflows'], default_risk: 'read-or-remote-write' }),
24
+ ]);
25
+
26
+ export function listWranglerNativeCommands() { return WRANGLER_NATIVE_COMMANDS.map((row) => ({ ...row, argv: [...row.argv] })); }
27
+
28
+ function descriptor(id) {
29
+ const row = WRANGLER_NATIVE_COMMANDS.find((item) => item.id === clean(id));
30
+ if (!row) throw new RangeError(`unsupported_wrangler_native_command:${id}`);
31
+ return row;
32
+ }
33
+
34
+ function resolveConfig(cwd, explicit = '') {
35
+ if (clean(explicit)) {
36
+ const file = path.resolve(cwd, explicit);
37
+ if (!file.startsWith(`${cwd}${path.sep}`) && file !== cwd) throw new Error('cloudflare_config_outside_cwd');
38
+ if (!fs.existsSync(file)) throw new Error(`cloudflare_config_not_found:${explicit}`);
39
+ return file;
40
+ }
41
+ for (const name of ['wrangler.jsonc', 'wrangler.json', 'wrangler.toml']) {
42
+ const file = path.join(cwd, name);
43
+ if (fs.existsSync(file)) return file;
44
+ }
45
+ return '';
46
+ }
47
+
48
+ export function buildWranglerInvocation(id, input = {}) {
49
+ const row = descriptor(id);
50
+ const cwd = path.resolve(input.cwd || process.cwd());
51
+ const args = [...row.argv];
52
+ if (id === 'whoami' && clean(input.account)) args.push('--account', clean(input.account));
53
+ if ((id === 'deployments.list' || id === 'versions.list') && clean(input.name)) args.push('--name', clean(input.name));
54
+ if (id === 'types.check' && clean(input.path)) args.splice(1, 0, clean(input.path));
55
+ if (id === 'queues.list' && input.page != null) args.push('--page', String(positiveInteger(input.page, 1)));
56
+ const config = resolveConfig(cwd, input.config);
57
+ if (config) args.push('--config', config);
58
+ if (clean(input.env)) args.push('--env', clean(input.env));
59
+ if (clean(input.profile)) args.push('--profile', clean(input.profile));
60
+ return Object.freeze({ command_id: row.id, command: 'wrangler', args: Object.freeze(args), cwd, risk: row.risk, output: row.output });
61
+ }
62
+
63
+ function parseJsonOutput(text) {
64
+ const source = clean(text);
65
+ if (!source) return null;
66
+ try { return JSON.parse(source); } catch { return null; }
67
+ }
68
+
69
+ function stripAnsi(value) { return String(value || '').replace(/\x1b\[[0-?]*[ -\/]*[@-~]/g, ''); }
70
+ function nestedError(payload) {
71
+ if (!payload || typeof payload !== 'object') return null;
72
+ const rows = [payload.error, ...(Array.isArray(payload.errors) ? payload.errors : [])].filter((row) => row && typeof row === 'object');
73
+ return rows[0] || payload;
74
+ }
75
+
76
+ export function parseWranglerErrorEvidence(stderr = '', stdout = '') {
77
+ const errorText = stripAnsi(stderr);
78
+ const outputText = stripAnsi(stdout);
79
+ let payload = parseJsonOutput(outputText) || parseJsonOutput(errorText);
80
+ const error = nestedError(payload);
81
+ const combined = `${errorText}\n${outputText}`;
82
+ const regexCode = combined.match(/\[code:\s*([A-Za-z0-9_.:-]+)\]/i)?.[1] || combined.match(/\bcode[:=\s]+([A-Za-z0-9_.:-]+)/i)?.[1] || '';
83
+ const code = clean(error?.code || regexCode) || null;
84
+ const requestId = clean(error?.request_id || error?.requestId || combined.match(/\brequest[_ -]?id[:=\s]+([A-Za-z0-9_-]+)/i)?.[1]) || null;
85
+ const rayId = clean(error?.ray_id || error?.rayId || combined.match(/\b(?:cf[- ]?ray|ray id)[:=\s]+([A-Za-z0-9-]+)/i)?.[1]) || null;
86
+ const message = clean(error?.message || error?.error || errorText.split('\n').find((line) => clean(line)) || outputText.split('\n').find((line) => clean(line))) || 'Wrangler operation failed';
87
+ return Object.freeze({ code, request_id: requestId, ray_id: rayId, message, details: payload ? redactDiagnosticValue(payload) : null });
88
+ }
89
+
90
+ export async function runWranglerNative(id, input = {}, options = {}) {
91
+ const plan = buildWranglerInvocation(id, input);
92
+ const runner = options.run || runProcess;
93
+ const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 30_000;
94
+ const result = await runner(options.bin || 'npx', ['--yes', 'wrangler', ...plan.args], {
95
+ cwd: plan.cwd,
96
+ timeoutMs,
97
+ signal: options.signal,
98
+ maxBytes: options.maxBytes || 2 * 1024 * 1024,
99
+ });
100
+ if (result.code !== 0) {
101
+ const evidence = parseWranglerErrorEvidence(result.stderr, result.stdout);
102
+ throw new AgentSamDiagnosticError({
103
+ source: 'cloudflare',
104
+ kind: 'wrangler_error',
105
+ code: evidence.code || 'wrangler_exit_nonzero',
106
+ message: evidence.message || `Wrangler ${plan.command_id} exited ${result.code}`,
107
+ retriable: false,
108
+ retry_strategy: 'inspect_error',
109
+ operation: plan.command_id,
110
+ exit_code: result.code,
111
+ request_id: evidence.request_id,
112
+ ray_id: evidence.ray_id,
113
+ cwd: plan.cwd,
114
+ stderr: redactDiagnosticValue(String(result.stderr || '').slice(0, 12_000)) || null,
115
+ stdout: redactDiagnosticValue(String(result.stdout || '').slice(0, 4_000)) || null,
116
+ details: evidence.details,
117
+ });
118
+ }
119
+ const parsed = plan.output === 'json' ? parseJsonOutput(result.stdout) : null;
120
+ return Object.freeze({
121
+ ok: true,
122
+ schema_version: 1,
123
+ command_id: plan.command_id,
124
+ risk: plan.risk,
125
+ cwd: plan.cwd,
126
+ exit_code: result.code,
127
+ format: parsed == null ? 'text' : 'json',
128
+ data: parsed == null ? undefined : redactDiagnosticValue(parsed),
129
+ stdout: parsed == null ? String(result.stdout || '').slice(0, 24_000) : undefined,
130
+ stderr: String(result.stderr || '').slice(0, 8_000) || undefined,
131
+ });
132
+ }
@@ -0,0 +1,47 @@
1
+ import { authenticateViaBrowser } from '../lib/auth.js';
2
+ import { clearAccountSession, readAccountSession, saveAccountSession } from '../lib/account-session.js';
3
+ import { collectWhoami, renderWhoami } from './whoami.js';
4
+
5
+ function writeLine(write, value = '') { write(`${value}\n`); }
6
+
7
+ export async function runLogin(argv = [], options = {}) {
8
+ const allowed = new Set(['--json']);
9
+ const unknown = argv.filter((arg) => !allowed.has(arg));
10
+ if (unknown.length) throw new Error(`unknown login option: ${unknown[0]}`);
11
+ const write = options.write || ((text) => process.stdout.write(text));
12
+ const authenticate = options.authenticateImpl || authenticateViaBrowser;
13
+ const session = await authenticate();
14
+ if (!String(session?.access_token || '').trim().startsWith('sdk_')) throw new Error('Agent Sam login did not return a valid SDK session');
15
+ // authenticateViaBrowser persists by default. Keep injected transports/test flows equivalent.
16
+ if (!readAccountSession({ home: options.home })) saveAccountSession(session, { home: options.home });
17
+ const status = await collectWhoami({ home: options.home, env: options.env || process.env, contextLoader: options.contextLoader });
18
+ if (argv.includes('--json')) writeLine(write, JSON.stringify(status, null, 2));
19
+ else {
20
+ writeLine(write, '');
21
+ writeLine(write, ' Agent Sam login complete.');
22
+ write(renderWhoami(status));
23
+ }
24
+ return status;
25
+ }
26
+
27
+ export function runLogout(argv = [], options = {}) {
28
+ const allowed = new Set(['--json']);
29
+ const unknown = argv.filter((arg) => !allowed.has(arg));
30
+ if (unknown.length) throw new Error(`unknown logout option: ${unknown[0]}`);
31
+ const write = options.write || ((text) => process.stdout.write(text));
32
+ const removed = clearAccountSession({ home: options.home });
33
+ const result = {
34
+ schema_version: 1,
35
+ local_session_removed: removed,
36
+ provider_credentials_unchanged: true,
37
+ note: 'Local IAM session removed. Provider credentials were not deleted or revoked.',
38
+ };
39
+ if (argv.includes('--json')) writeLine(write, JSON.stringify(result, null, 2));
40
+ else {
41
+ writeLine(write, '');
42
+ writeLine(write, removed ? ' Signed out of the local Agent Sam IAM session.' : ' No local Agent Sam IAM session was stored.');
43
+ writeLine(write, ' Provider credentials were not deleted or revoked.');
44
+ writeLine(write, '');
45
+ }
46
+ return result;
47
+ }