@inneranimalmedia/agentsam-sdk 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) 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/RELEASES.md +16 -7
  7. package/package.json +20 -8
  8. package/packages/connectors/cloudflare/package.json +10 -0
  9. package/packages/connectors/cloudflare/src/index.js +127 -0
  10. package/packages/connectors/cloudflare/src/owner.js +76 -0
  11. package/packages/connectors/cloudflare/src/routes.js +223 -0
  12. package/packages/connectors/cloudflare/src/vault.js +80 -0
  13. package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
  14. package/packages/identity/package.json +2 -2
  15. package/packages/identity/src/contracts/auth-config.js +18 -7
  16. package/packages/identity/tests/auth-config.test.mjs +9 -5
  17. package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
  18. package/protocol/README.md +1 -0
  19. package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
  20. package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
  21. package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
  22. package/protocol/capabilities/manifest.json +47 -0
  23. package/protocol/context/context-budget.schema.json +10 -15
  24. package/protocol/context/context-item.schema.json +4 -5
  25. package/protocol/context/resolved-context-pack.schema.json +19 -14
  26. package/protocol/models/README.md +373 -0
  27. package/protocol/models/model-inventory-v2.schema.json +212 -0
  28. package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
  29. package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
  30. package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
  31. package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
  32. package/skills/catalog.json +18 -0
  33. package/src/agent/capability-adapter.js +25 -13
  34. package/src/agent/index.js +1 -0
  35. package/src/agent/responses-runner.js +325 -0
  36. package/src/cli.js +98 -28
  37. package/src/cloudflare/cpu-profile.js +115 -0
  38. package/src/cloudflare/index.js +14 -0
  39. package/src/cloudflare/wrangler.js +132 -0
  40. package/src/commands/account-auth.js +47 -0
  41. package/src/commands/cloudflare.js +58 -0
  42. package/src/commands/connections.js +93 -0
  43. package/src/commands/context-economics.js +114 -0
  44. package/src/commands/deploy.js +39 -3
  45. package/src/commands/eval.js +63 -0
  46. package/src/commands/interactive.js +2 -5
  47. package/src/commands/models.js +85 -40
  48. package/src/commands/preferences.js +101 -59
  49. package/src/commands/resume.js +67 -0
  50. package/src/commands/security.js +5 -3
  51. package/src/commands/shell.js +370 -109
  52. package/src/commands/tunnel.js +2 -2
  53. package/src/commands/whoami.js +86 -0
  54. package/src/context/budget.js +68 -6
  55. package/src/context/index.js +3 -1
  56. package/src/context/rehydrate.js +35 -0
  57. package/src/context/resolve.js +44 -12
  58. package/src/errors/diagnostic.js +160 -0
  59. package/src/errors/index.js +9 -0
  60. package/src/eval/context.js +191 -0
  61. package/src/eval/index.js +1 -0
  62. package/src/index.js +55 -1
  63. package/src/lib/account-session.js +98 -0
  64. package/src/lib/agent-instructions.js +73 -0
  65. package/src/lib/auth.js +4 -0
  66. package/src/lib/cli-preferences.js +28 -24
  67. package/src/lib/deploy/git-guard.js +69 -0
  68. package/src/lib/deploy/health.js +57 -0
  69. package/src/lib/deploy/local-studio.js +283 -0
  70. package/src/lib/deploy/secret-scan.js +65 -0
  71. package/src/lib/detect-context.js +2 -2
  72. package/src/lib/execution-approvals.js +59 -0
  73. package/src/lib/local-sessions.js +127 -0
  74. package/src/lib/provider-credentials.js +83 -0
  75. package/src/lib/scaffold/templates/worker-api/index.js +101 -20
  76. package/src/lib/scaffold/wizards/worker-api.js +27 -11
  77. package/src/lib/slash-commands.js +22 -16
  78. package/src/models/catalog.js +135 -0
  79. package/src/models/index.js +7 -0
  80. package/src/providers/index.js +5 -0
  81. package/src/providers/openai-responses.js +275 -0
  82. package/src/security/process.js +35 -9
  83. package/src/telemetry/contracts.js +203 -0
  84. package/src/telemetry/events.js +48 -0
  85. package/src/telemetry/index.js +8 -0
  86. package/src/tools/hydrate.js +35 -0
  87. package/src/tools/index.js +1 -0
  88. package/src/ui/boot.js +15 -17
  89. package/test/account-session.test.mjs +36 -0
  90. package/test/cli-preferences.test.mjs +26 -5
  91. package/test/cloudflare-connector.test.mjs +96 -0
  92. package/test/cloudflare-runtime.test.mjs +75 -0
  93. package/test/context.test.mjs +61 -12
  94. package/test/deploy-health-scan.test.mjs +67 -0
  95. package/test/error-diagnostics.test.mjs +59 -0
  96. package/test/eval-context.test.mjs +37 -0
  97. package/test/execution-approvals.test.mjs +27 -0
  98. package/test/local-sessions.test.mjs +42 -0
  99. package/test/local-studio-deploy.test.mjs +83 -0
  100. package/test/model-catalog.test.mjs +43 -0
  101. package/test/models.test.mjs +30 -16
  102. package/test/npm10-lock.test.mjs +29 -0
  103. package/test/openai-responses.test.mjs +95 -0
  104. package/test/provider-credentials.test.mjs +52 -0
  105. package/test/rehydrate.test.mjs +25 -0
  106. package/test/release-hygiene.test.mjs +4 -4
  107. package/test/responses-runner.test.mjs +148 -0
  108. package/test/shell.test.mjs +47 -20
  109. package/test/smoke.mjs +4 -1
  110. package/test/telemetry.test.mjs +79 -0
  111. package/test/tools-search.test.mjs +14 -1
  112. package/test/whoami-resume.test.mjs +56 -0
package/src/cli.js CHANGED
@@ -15,6 +15,7 @@ import { runOllama } from './commands/ollama.js';
15
15
  import { runModels } from './commands/models.js';
16
16
  import { runTunnel } from './commands/tunnel.js';
17
17
  import { runDeploy } from './commands/deploy.js';
18
+ import { runConnections } from './commands/connections.js';
18
19
  import { runIdentityPreview } from './commands/identity-preview.js';
19
20
  import { runIdentityInit } from './commands/identity-init.js';
20
21
  import { runContext } from './commands/context.js';
@@ -30,14 +31,26 @@ import { runSecurity } from './commands/security.js';
30
31
  import { runRecon } from './commands/recon.js';
31
32
  import { runCad } from './commands/cad.js';
32
33
  import { runSkills } from './commands/skills.js';
34
+ import { runEval } from './commands/eval.js';
35
+ import { runCloudflare } from './commands/cloudflare.js';
36
+ import { runWhoami } from './commands/whoami.js';
37
+ import { runResume } from './commands/resume.js';
38
+ import { runLogin, runLogout } from './commands/account-auth.js';
33
39
  import { applyPresetSelection, runAdd, runCapabilities, runDev, runInspect } from './commands/product.js';
34
40
  import { listPresets, resolvePreset } from './presets/index.js';
35
41
  import fs from 'node:fs';
36
42
  import { repositoryRoot } from './knowledge/config.js';
37
- import { resolveSdkKey } from '../packages/identity/src/contracts/auth-config.js';
43
+ import { resolveAccountSdkKey } from './lib/account-session.js';
44
+ import { renderDiagnosticError } from './errors/index.js';
38
45
 
39
46
  const VERSION = pkg.version;
40
47
 
48
+ function reportCliError(error) {
49
+ if (error?.reported) return;
50
+ const rendered = renderDiagnosticError(error).split('\n').map((line) => ` ${line}`).join('\n');
51
+ console.error(`\n${rendered}\n`);
52
+ }
53
+
41
54
  function createPrompt() {
42
55
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
43
56
  return {
@@ -76,7 +89,13 @@ function printHelp() {
76
89
  agentsam security Dependency scan, log triage, and verified repair (--help)
77
90
  agentsam status [--json] Live local Git + DB + API + PTY status
78
91
  agentsam db init|status Manage the project-local SQLite database
79
- agentsam models Show configured providers and local model inventory
92
+ agentsam models Verify configured providers and selectable hosted/local models
93
+ agentsam login Authenticate IAM and persist a secure machine-local session
94
+ agentsam logout Remove the local IAM session; provider keys stay untouched
95
+ agentsam whoami [--json] Authenticated IAM identity + safe credential status
96
+ agentsam resume [session] Resume a saved Agent Sam session; omit id for picker
97
+ agentsam eval context Offline context-strategy/economics fixtures (--help)
98
+ agentsam cloudflare Native Wrangler reads + Worker CPU profile analysis (--help)
80
99
  agentsam start-local Local PTY on ws://127.0.0.1:3099 (no tunnel, no Cloudflare)
81
100
  agentsam ollama Opt-in local Ollama setup/status/model management
82
101
  agentsam shell Interactive Agent Sam slash-command shell
@@ -143,11 +162,13 @@ function parseInitArgs(argv) {
143
162
  }
144
163
 
145
164
  function parseDeployArgs(argv) {
146
- const opts = { target: '', accountId: '' };
165
+ const opts = { target: '', accountId: '', dryRun: false, plan: false };
147
166
  for (let i = 0; i < argv.length; i += 1) {
148
167
  const arg = argv[i];
149
168
  if (arg === '--target') opts.target = argv[++i] || '';
150
169
  else if (arg === '--account-id') opts.accountId = argv[++i] || '';
170
+ else if (arg === '--dry-run') opts.dryRun = true;
171
+ else if (arg === '--plan') opts.plan = true;
151
172
  }
152
173
  return opts;
153
174
  }
@@ -206,7 +227,7 @@ async function runLocalInit(config) {
206
227
  console.log(` ${step}`);
207
228
  }
208
229
 
209
- const sdkKey = resolveSdkKey(process.env);
230
+ const sdkKey = resolveAccountSdkKey({ env: process.env }).value;
210
231
  if (prompt && sdkKey) {
211
232
  console.log('\n Optional — BYOK keys for IAM dashboard Agent Sam (skip with Enter):\n');
212
233
  await promptOptionalByokKeys(sdkKey, prompt);
@@ -262,12 +283,12 @@ async function initInteractive(partial = {}) {
262
283
  if (runTarget !== 'local') {
263
284
  const { detectContext, missingForInit } = await import('./lib/detect-context.js');
264
285
  const ctx = await detectContext();
265
- if (missingForInit(ctx, resolveSdkKey(process.env), { runTarget }).length) {
286
+ if (missingForInit(ctx, resolveAccountSdkKey({ env: process.env }).value, { runTarget }).length) {
266
287
  printContextSummary(ctx);
267
288
  }
268
289
  }
269
290
 
270
- const prompt = resolveSdkKey(process.env) ? createPrompt() : null;
291
+ const prompt = resolveAccountSdkKey({ env: process.env }).value ? createPrompt() : null;
271
292
  try {
272
293
  await runLocalInit({ projectName, lane: laneKey, runTarget, prompt });
273
294
  } finally {
@@ -297,7 +318,7 @@ if (command === '--version' || command === '-v') {
297
318
  try { await runInteractive(); }
298
319
  catch (e) {
299
320
  if (e?.code !== 'AGENTSAM_SETUP_CANCELLED') {
300
- console.error(`\n ✗ ${e?.message || e}\n`);
321
+ reportCliError(e);
301
322
  process.exitCode = 1;
302
323
  }
303
324
  }
@@ -315,52 +336,94 @@ if (command === '--version' || command === '-v') {
315
336
  applyPresetSelection(created.dir, preset);
316
337
  console.log(` ✓ Preset ${preset.id}\n ✓ Features ${preset.features.join(', ') || 'none'}\n ✓ Capabilities ${preset.capabilities.length}\n`);
317
338
  }
318
- } catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
339
+ } catch (e) { reportCliError(e); process.exitCode = 1; }
319
340
  } else if (command === 'add') {
320
341
  try { await runAdd(rest); }
321
- catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
342
+ catch (e) { reportCliError(e); process.exitCode = 1; }
322
343
  } else if (command === 'dev') {
323
344
  try { await runDev(rest); }
324
- catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
345
+ catch (e) { reportCliError(e); process.exitCode = 1; }
325
346
  } else if (command === 'inspect') {
326
347
  try { await runInspect(rest); }
327
- catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
348
+ catch (e) { reportCliError(e); process.exitCode = 1; }
328
349
  } else if (command === 'capabilities') {
329
350
  try { await runCapabilities(rest); }
330
- catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
351
+ catch (e) { reportCliError(e); process.exitCode = 1; }
331
352
  } else if (command === 'context') {
332
353
  try {
333
354
  await runContext(rest);
334
355
  } catch (e) {
335
- console.error(`\n ✗ ${e?.message || e}\n`);
356
+ reportCliError(e);
336
357
  process.exit(1);
337
358
  }
338
359
  } else if (command === 'status') {
339
360
  try {
340
361
  await runStatus(rest);
341
362
  } catch (e) {
342
- console.error(`\n ✗ ${e?.message || e}\n`);
363
+ reportCliError(e);
343
364
  process.exit(1);
344
365
  }
345
366
  } else if (command === 'db') {
346
367
  try {
347
368
  await runDb(rest);
348
369
  } catch (e) {
349
- console.error(`\n ✗ ${e?.message || e}\n`);
370
+ reportCliError(e);
350
371
  process.exit(1);
351
372
  }
352
373
  } else if (command === 'models') {
353
374
  try {
354
375
  await runModels(rest);
355
376
  } catch (e) {
356
- console.error(`\n ✗ ${e?.message || e}\n`);
377
+ reportCliError(e);
378
+ process.exit(1);
379
+ }
380
+ } else if (command === 'eval') {
381
+ try {
382
+ await runEval(rest);
383
+ } catch (e) {
384
+ reportCliError(e);
357
385
  process.exit(1);
358
386
  }
387
+ } else if (command === 'cloudflare' || command === 'cf') {
388
+ try {
389
+ await runCloudflare(rest);
390
+ } catch (e) {
391
+ if (!e?.reported) reportCliError(e);
392
+ process.exitCode = 1;
393
+ }
394
+ } else if (command === 'login') {
395
+ try {
396
+ await runLogin(rest);
397
+ } catch (e) {
398
+ reportCliError(e);
399
+ process.exitCode = 1;
400
+ }
401
+ } else if (command === 'logout') {
402
+ try {
403
+ runLogout(rest);
404
+ } catch (e) {
405
+ reportCliError(e);
406
+ process.exitCode = 1;
407
+ }
408
+ } else if (command === 'whoami') {
409
+ try {
410
+ await runWhoami(rest);
411
+ } catch (e) {
412
+ reportCliError(e);
413
+ process.exitCode = 1;
414
+ }
415
+ } else if (command === 'resume') {
416
+ try {
417
+ await runResume(rest);
418
+ } catch (e) {
419
+ reportCliError(e);
420
+ process.exitCode = 1;
421
+ }
359
422
  } else if (command === 'shell') {
360
423
  try {
361
424
  await runShell(rest);
362
425
  } catch (e) {
363
- console.error(`\n ✗ ${e?.message || e}\n`);
426
+ reportCliError(e);
364
427
  process.exit(1);
365
428
  }
366
429
  } else if (command === 'start-local') {
@@ -369,35 +432,42 @@ if (command === '--version' || command === '-v') {
369
432
  try {
370
433
  await runOllama(rest);
371
434
  } catch (e) {
372
- console.error(`\n ✗ ${e?.message || e}\n`);
435
+ reportCliError(e);
373
436
  process.exitCode = 1;
374
437
  }
375
438
  } else if (command === 'tunnel') {
376
439
  try {
377
440
  await runTunnel(rest);
378
441
  } catch (e) {
379
- console.error(`\n ✗ ${e?.message || e}\n`);
442
+ reportCliError(e);
443
+ process.exit(1);
444
+ }
445
+ } else if (command === 'connections' || command === 'connection') {
446
+ try {
447
+ await runConnections(rest);
448
+ } catch (e) {
449
+ reportCliError(e);
380
450
  process.exit(1);
381
451
  }
382
452
  } else if (command === 'deploy') {
383
453
  try {
384
454
  await runDeploy(parseDeployArgs(rest));
385
455
  } catch (e) {
386
- console.error(`\n ✗ ${e?.message || e}\n`);
456
+ reportCliError(e);
387
457
  process.exit(1);
388
458
  }
389
459
  } else if (command === 'dockerize') {
390
460
  try {
391
461
  await runDockerize(rest);
392
462
  } catch (e) {
393
- console.error(`\n ✗ ${e?.message || e}\n`);
463
+ reportCliError(e);
394
464
  process.exit(1);
395
465
  }
396
466
  } else if (command === 'cad') {
397
467
  try {
398
468
  await runCad(rest);
399
469
  } catch (e) {
400
- console.error(`\n ✗ ${e?.message || e}\n`);
470
+ reportCliError(e);
401
471
  process.exitCode = 1;
402
472
  }
403
473
  } else if (command === 'security' || command === 'sca') {
@@ -406,7 +476,7 @@ if (command === '--version' || command === '-v') {
406
476
  try {
407
477
  runSkills(rest);
408
478
  } catch (e) {
409
- console.error(`\n ✗ ${e?.message || e}\n`);
479
+ reportCliError(e);
410
480
  process.exitCode = 1;
411
481
  }
412
482
  } else if (command === 'merkle') {
@@ -419,14 +489,14 @@ if (command === '--version' || command === '-v') {
419
489
  try {
420
490
  await runMini(rest);
421
491
  } catch (e) {
422
- console.error(`\n ${e?.message || e}\n`);
492
+ reportCliError(e);
423
493
  process.exitCode = 1;
424
494
  }
425
495
  } else if (['index', 'search', 'repo'].includes(command)) {
426
496
  try {
427
497
  const commands = await import('./commands/knowledge.js');
428
498
  await ({ index: commands.runKnowledge, search: commands.runSearch, repo: commands.runRepository })[command](rest);
429
- } catch (e) { console.error(e.message); process.exitCode = 1; }
499
+ } catch (e) { reportCliError(e); process.exitCode = 1; }
430
500
  } else if (command === 'init') {
431
501
  try {
432
502
  const existing = !rest.includes('--name') && (rest.includes('.') || rest.includes('--existing') || rest.includes('--cwd') || fs.existsSync(path.join(repositoryRoot(), '.git')));
@@ -434,21 +504,21 @@ if (command === '--version' || command === '-v') {
434
504
  else if (rest.includes('--help') || rest.includes('-h')) printHelp();
435
505
  else if (rest.some((a) => a.startsWith('--'))) await initFromArgs(rest);
436
506
  else await initInteractive({});
437
- } catch (e) { console.error(e.message); process.exitCode = 1; }
507
+ } catch (e) { reportCliError(e); process.exitCode = 1; }
438
508
  } else if (command === 'identity') {
439
509
  const sub = rest[0];
440
510
  if (sub === 'preview') {
441
511
  try {
442
512
  await runIdentityPreview(rest.slice(1));
443
513
  } catch (e) {
444
- console.error(`\n ✗ ${e?.message || e}\n`);
514
+ reportCliError(e);
445
515
  process.exit(1);
446
516
  }
447
517
  } else if (sub === 'init') {
448
518
  try {
449
519
  await runIdentityInit(rest);
450
520
  } catch (e) {
451
- console.error(`\n ✗ ${e?.message || e}\n`);
521
+ reportCliError(e);
452
522
  process.exit(1);
453
523
  }
454
524
  } 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
+ }
@@ -0,0 +1,58 @@
1
+ import path from 'node:path';
2
+ import { listWranglerNativeCommands, runWranglerNative, summarizeCloudflareCpuProfileFile, WRANGLER_OPERATION_FAMILIES } from '../cloudflare/index.js';
3
+ import { renderDiagnosticError } from '../errors/index.js';
4
+
5
+ function parse(argv = []) {
6
+ const subcommand = argv[0] || 'status';
7
+ const takesAction = subcommand === 'run' || subcommand === 'cpu';
8
+ const out = { subcommand, action: takesAction ? (argv[1] || '') : '', cwd: process.cwd(), json: false, name: '', account: '', config: '', env: '', profile: '', path: '', page: null, file: '' };
9
+ for (let i = takesAction ? 2 : 1; i < argv.length; i += 1) {
10
+ const arg = argv[i];
11
+ if (arg === '--json') out.json = true;
12
+ else if (arg === '--cwd') out.cwd = argv[++i] || out.cwd;
13
+ else if (arg === '--name') out.name = argv[++i] || '';
14
+ else if (arg === '--account') out.account = argv[++i] || '';
15
+ else if (arg === '--config') out.config = argv[++i] || '';
16
+ else if (arg === '--env') out.env = argv[++i] || '';
17
+ else if (arg === '--profile') out.profile = argv[++i] || '';
18
+ else if (arg === '--path') out.path = argv[++i] || '';
19
+ else if (arg === '--page') out.page = Number(argv[++i] || 1);
20
+ else if (arg === '--file') out.file = argv[++i] || '';
21
+ else if (arg === '--help' || arg === '-h') out.help = true;
22
+ else if (out.subcommand === 'cpu' && out.action === 'analyze' && !out.file) out.file = arg;
23
+ else throw new Error(`unknown cloudflare option: ${arg}`);
24
+ }
25
+ return out;
26
+ }
27
+
28
+ const help = `Agent Sam · Cloudflare\n\n agentsam cloudflare status [--cwd PATH] [--json]\n agentsam cloudflare commands [--json]\n agentsam cloudflare run <whoami|deployments.list|versions.list|types.check|queues.list> [options] [--json]\n agentsam cloudflare cpu analyze <profile.cpuprofile> [--cwd PATH] [--json]\n\nSafe native runner is read-only. Deploy, rollback, secret reads, D1 mutation, R2 writes, and long-running tail/dev sessions remain explicit operator actions.\n`;
29
+
30
+ export async function runCloudflare(argv = [], options = {}) {
31
+ const args = parse(argv);
32
+ if (options.cwd && !argv.includes('--cwd')) args.cwd = path.resolve(options.cwd);
33
+ const write = options.write || ((value) => process.stdout.write(value));
34
+ if (args.help) { write(help); return null; }
35
+ try {
36
+ let result;
37
+ if (args.subcommand === 'status') {
38
+ result = await runWranglerNative('whoami', args, options);
39
+ } else if (args.subcommand === 'commands') {
40
+ result = { schema_version: 1, native: listWranglerNativeCommands(), families: WRANGLER_OPERATION_FAMILIES };
41
+ } else if (args.subcommand === 'run') {
42
+ if (!args.action) throw new Error('cloudflare native command id required');
43
+ result = await runWranglerNative(args.action, args, options);
44
+ } else if (args.subcommand === 'cpu' && args.action === 'analyze') {
45
+ result = summarizeCloudflareCpuProfileFile({ cwd: path.resolve(args.cwd), file: args.file });
46
+ } else {
47
+ write(help);
48
+ return null;
49
+ }
50
+ write(args.json ? `${JSON.stringify(result)}\n` : `${JSON.stringify(result, null, 2)}\n`);
51
+ return result;
52
+ } catch (error) {
53
+ if (args.json) write(`${JSON.stringify({ ok: false, error: error?.diagnostic || { code: error?.code || 'cloudflare_operation_failed', message: error?.message || String(error) } })}\n`);
54
+ else write(`${renderDiagnosticError(error)}\n`);
55
+ error.reported = true;
56
+ throw error;
57
+ }
58
+ }