@inneranimalmedia/agentsam-sdk 2.4.1 → 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 (233) hide show
  1. package/AGENTSAM.md +55 -0
  2. package/README.md +17 -12
  3. package/bin/agentsam +2 -0
  4. package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
  5. package/docs/AGENTSAM_WORKBENCH.md +30 -0
  6. package/docs/AUTH_IDENTITY_CONTRACT.md +52 -0
  7. package/docs/CAPABILITIES.md +5 -3
  8. package/docs/CLI_SHELL.md +167 -80
  9. package/docs/CMS_STUDIO.md +101 -0
  10. package/docs/CONTEXT.md +170 -0
  11. package/docs/LOCAL_OLLAMA.md +58 -0
  12. package/docs/MERKLE.md +19 -19
  13. package/docs/PORTABLE_CONTEXT.md +4 -3
  14. package/docs/PROJECT_CONFIG.md +72 -0
  15. package/docs/RELEASES.md +35 -2
  16. package/docs/REPOSITORY_INTELLIGENCE.md +1 -1
  17. package/docs/REPOSITORY_KNOWLEDGE.md +114 -0
  18. package/docs/SDK_WORKER.md +86 -0
  19. package/docs/SECURITY.md +59 -22
  20. package/docs/client-cms-editor.md +15 -0
  21. package/docs/local-studio/WORKMODE_DONOR_NOTES.md +485 -0
  22. package/package.json +35 -10
  23. package/packages/connectors/cloudflare/package.json +10 -0
  24. package/packages/connectors/cloudflare/src/index.js +127 -0
  25. package/packages/connectors/cloudflare/src/owner.js +76 -0
  26. package/packages/connectors/cloudflare/src/routes.js +223 -0
  27. package/packages/connectors/cloudflare/src/vault.js +80 -0
  28. package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
  29. package/packages/identity/package.json +10 -1
  30. package/packages/identity/src/contracts/auth-config.js +109 -0
  31. package/packages/identity/src/index.js +1 -0
  32. package/packages/identity/src/oauth/README.md +2 -2
  33. package/packages/identity/src/oauth/credentials.js +11 -4
  34. package/packages/identity/src/oauth/iam-platform.js +3 -3
  35. package/packages/identity/src/providers/iam/index.js +7 -7
  36. package/packages/identity/src/providers/iam/oauth.js +6 -4
  37. package/packages/identity/src/providers/iam/profile.js +5 -5
  38. package/packages/identity/tests/auth-config.test.mjs +61 -0
  39. package/packages/identity/tests/oauth-credentials.test.mjs +13 -2
  40. package/protocol/FILEMETA_V1.md +95 -0
  41. package/protocol/INSPECT_VIEWS_V1.md +40 -0
  42. package/protocol/MERKLE_PERSISTENCE_V1.md +50 -0
  43. package/protocol/MERKLE_V1.md +3 -1
  44. package/protocol/README.md +1 -0
  45. package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
  46. package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
  47. package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
  48. package/protocol/capabilities/manifest.json +79 -3
  49. package/protocol/capabilities/repository-snapshot.schema.json +1 -0
  50. package/protocol/context/context-budget.schema.json +36 -0
  51. package/protocol/context/context-item.schema.json +19 -0
  52. package/protocol/context/resolved-context-pack.schema.json +47 -0
  53. package/protocol/context/result-policy.schema.json +17 -0
  54. package/protocol/knowledge/context-pack.schema.json +33 -0
  55. package/protocol/knowledge/retrieval-query.schema.json +68 -13
  56. package/protocol/models/README.md +373 -0
  57. package/protocol/models/model-inventory-v2.schema.json +212 -0
  58. package/python/README.md +15 -7
  59. package/python/agentsam_sdk/cli.py +0 -21
  60. package/python/agentsam_sdk/tui/README.md +17 -12
  61. package/python/agentsam_sdk/tui/bootstrap.py +2 -2
  62. package/python/agentsam_sdk/tui/demo.py +25 -10
  63. package/python/agentsam_sdk/tui/onboarding.py +208 -0
  64. package/python/tests/test_tui_cli.py +7 -6
  65. package/skills/README.md +21 -0
  66. package/skills/agentsam-app-fundamentals/SKILL.md +165 -0
  67. package/skills/agentsam-app-fundamentals/references/graphs-contracts-ast-merkle.md +89 -0
  68. package/skills/agentsam-app-fundamentals/references/trust-credentials-and-destinations.md +99 -0
  69. package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
  70. package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
  71. package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
  72. package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
  73. package/skills/agentsam-jr-dev/SKILL.md +232 -0
  74. package/skills/agentsam-jr-dev/references/real-application-logic.md +156 -0
  75. package/skills/agentsam-jr-dev/references/web-application-fundamentals.md +240 -0
  76. package/skills/agentsam-progression-guard/SKILL.md +197 -0
  77. package/skills/agentsam-progression-guard/references/checkpoint-chain.md +111 -0
  78. package/skills/agentsam-progression-guard/references/hooks-operational-io.md +96 -0
  79. package/skills/catalog.json +71 -0
  80. package/src/agent/capability-adapter.js +25 -13
  81. package/src/agent/index.js +1 -0
  82. package/src/agent/responses-runner.js +325 -0
  83. package/src/capabilities/index.js +7 -0
  84. package/src/capabilities/repository-snapshot-view.js +238 -0
  85. package/src/capabilities/repository-snapshot.js +28 -14
  86. package/src/cli.js +198 -76
  87. package/src/cloudflare/cpu-profile.js +115 -0
  88. package/src/cloudflare/index.js +14 -0
  89. package/src/cloudflare/wrangler.js +132 -0
  90. package/src/commands/account-auth.js +47 -0
  91. package/src/commands/cloudflare.js +58 -0
  92. package/src/commands/connections.js +93 -0
  93. package/src/commands/context-economics.js +114 -0
  94. package/src/commands/context.js +14 -2
  95. package/src/commands/db.js +4 -7
  96. package/src/commands/deploy.js +54 -32
  97. package/src/commands/eval.js +63 -0
  98. package/src/commands/interactive.js +18 -0
  99. package/src/commands/knowledge.js +27 -4
  100. package/src/commands/merkle-persist.js +118 -0
  101. package/src/commands/merkle.js +32 -17
  102. package/src/commands/models.js +152 -0
  103. package/src/commands/ollama.js +259 -0
  104. package/src/commands/preferences.js +144 -0
  105. package/src/commands/product.js +86 -16
  106. package/src/commands/resume.js +67 -0
  107. package/src/commands/security.js +8 -6
  108. package/src/commands/shell.js +405 -100
  109. package/src/commands/skills.js +66 -0
  110. package/src/commands/start-local.js +1 -1
  111. package/src/commands/tunnel.js +5 -4
  112. package/src/commands/whoami.js +86 -0
  113. package/src/context/budget.js +120 -0
  114. package/src/context/compact.js +28 -0
  115. package/src/context/index.js +7 -0
  116. package/src/context/rehydrate.js +35 -0
  117. package/src/context/resolve.js +116 -0
  118. package/src/context/result-policy.js +66 -0
  119. package/src/errors/diagnostic.js +160 -0
  120. package/src/errors/index.js +9 -0
  121. package/src/eval/context.js +191 -0
  122. package/src/eval/index.js +1 -0
  123. package/src/index.js +70 -0
  124. package/src/indexing/execution-boundary.js +144 -0
  125. package/src/indexing/index.js +8 -0
  126. package/src/indexing/provider.js +41 -0
  127. package/src/knowledge/config.js +2 -2
  128. package/src/knowledge/context-pack.js +12 -1
  129. package/src/knowledge/contracts.js +9 -4
  130. package/src/knowledge/engine.js +7 -3
  131. package/src/knowledge/service/server.js +1 -1
  132. package/src/lib/account-session.js +98 -0
  133. package/src/lib/agent-instructions.js +73 -0
  134. package/src/lib/auth.js +4 -0
  135. package/src/lib/bridge-client.js +7 -5
  136. package/src/lib/cli-preferences.js +78 -0
  137. package/src/lib/core-client.js +8 -8
  138. package/src/lib/deploy/git-guard.js +69 -0
  139. package/src/lib/deploy/health.js +57 -0
  140. package/src/lib/deploy/local-studio.js +283 -0
  141. package/src/lib/deploy/secret-scan.js +65 -0
  142. package/src/lib/deploy-receipt/index.js +5 -2
  143. package/src/lib/detect-context.js +6 -5
  144. package/src/lib/execution-approvals.js +59 -0
  145. package/src/lib/identity-scaffold.js +1 -1
  146. package/src/lib/local-scaffold.js +35 -33
  147. package/src/lib/local-sessions.js +127 -0
  148. package/src/lib/local-status.js +9 -17
  149. package/src/lib/merkle/cloudflare-persistence.js +321 -0
  150. package/src/lib/merkle/filemeta.js +43 -0
  151. package/src/lib/merkle/git-ignore.js +24 -0
  152. package/src/lib/merkle/hash.js +1 -0
  153. package/src/lib/merkle/index.js +22 -0
  154. package/src/lib/merkle/persistence.js +72 -0
  155. package/src/lib/merkle/semantic.js +359 -0
  156. package/src/lib/merkle/snapshot.js +9 -3
  157. package/src/lib/merkle/tree.js +11 -6
  158. package/src/lib/project-config.js +227 -0
  159. package/src/lib/project-rules.js +68 -0
  160. package/src/lib/provider-credentials.js +83 -0
  161. package/src/lib/save-sdk-token.js +1 -1
  162. package/src/lib/scaffold/templates/worker-api/index.js +101 -20
  163. package/src/lib/scaffold/wizards/worker-api.js +27 -11
  164. package/src/lib/slash-commands.js +22 -15
  165. package/src/lib/tools.js +11 -5
  166. package/src/models/catalog.js +135 -0
  167. package/src/models/index.js +7 -0
  168. package/src/providers/index.js +5 -0
  169. package/src/providers/openai-responses.js +275 -0
  170. package/src/security/index.js +1 -0
  171. package/src/security/inventory.js +4 -1
  172. package/src/security/process.js +35 -9
  173. package/src/security/render.js +27 -5
  174. package/src/security/scan.js +24 -9
  175. package/src/security/trust-boundary.js +24 -0
  176. package/src/skills/index.js +64 -0
  177. package/src/telemetry/contracts.js +203 -0
  178. package/src/telemetry/events.js +48 -0
  179. package/src/telemetry/index.js +8 -0
  180. package/src/tools/hydrate.js +35 -0
  181. package/src/tools/index.js +2 -0
  182. package/src/tools/search.js +70 -0
  183. package/src/ui/ansi.js +1 -1
  184. package/src/ui/boot.js +54 -0
  185. package/src/ui/merkle/render.js +1 -0
  186. package/src/ui/runtime-activity.js +192 -0
  187. package/src/ui/theme.js +19 -18
  188. package/test/account-session.test.mjs +36 -0
  189. package/test/app-building-skills.test.mjs +61 -0
  190. package/test/apps-scaffold-contract.test.mjs +56 -0
  191. package/test/capabilities.test.mjs +53 -4
  192. package/test/cli-preferences.test.mjs +46 -0
  193. package/test/cloudflare-connector.test.mjs +96 -0
  194. package/test/cloudflare-runtime.test.mjs +75 -0
  195. package/test/context.test.mjs +144 -0
  196. package/test/deploy-health-scan.test.mjs +67 -0
  197. package/test/error-diagnostics.test.mjs +59 -0
  198. package/test/eval-context.test.mjs +37 -0
  199. package/test/execution-approvals.test.mjs +27 -0
  200. package/test/indexing-provider.test.mjs +29 -0
  201. package/test/jr-dev-skill.test.mjs +26 -0
  202. package/test/knowledge-context-pack.test.mjs +19 -0
  203. package/test/knowledge.test.mjs +1 -1
  204. package/test/local-sessions.test.mjs +42 -0
  205. package/test/local-studio-deploy.test.mjs +83 -0
  206. package/test/merkle-persistence.test.mjs +91 -0
  207. package/test/merkle.test.mjs +77 -3
  208. package/test/model-catalog.test.mjs +43 -0
  209. package/test/models.test.mjs +51 -0
  210. package/test/npm10-lock.test.mjs +29 -0
  211. package/test/ollama.test.mjs +94 -0
  212. package/test/openai-responses.test.mjs +95 -0
  213. package/test/project-config.test.mjs +81 -0
  214. package/test/project-rules.test.mjs +44 -0
  215. package/test/provider-credentials.test.mjs +52 -0
  216. package/test/rehydrate.test.mjs +25 -0
  217. package/test/release-hygiene.test.mjs +34 -0
  218. package/test/repository-snapshot-view.test.mjs +112 -0
  219. package/test/responses-runner.test.mjs +148 -0
  220. package/test/runtime-activity.test.mjs +98 -0
  221. package/test/sdk-worker-contract.test.mjs +68 -0
  222. package/test/security.test.mjs +46 -0
  223. package/test/shell.test.mjs +55 -21
  224. package/test/skills.test.mjs +22 -0
  225. package/test/smoke.mjs +5 -2
  226. package/test/telemetry.test.mjs +79 -0
  227. package/test/theme-portability.test.mjs +14 -0
  228. package/test/tools-search.test.mjs +40 -0
  229. package/test/whoami-resume.test.mjs +56 -0
  230. package/examples/agentsam-tui-ansi.mjs +0 -149
  231. package/src/commands/tui.js +0 -120
  232. package/src/ui/splash-xterm.js +0 -290
  233. package/src/ui/splash.js +0 -426
@@ -0,0 +1,63 @@
1
+ import { evaluateContextFixture, listContextEvalFixtures } from '../eval/index.js';
2
+
3
+ function parse(argv) {
4
+ const out = { subcommand: argv[0] || '', fixture: '', strategy: 'all', model: 'gpt-6-astra', json: false, list: false };
5
+ for (let i = 1; i < argv.length; i += 1) {
6
+ const arg = argv[i];
7
+ if (arg === '--fixture') out.fixture = argv[++i] || '';
8
+ else if (arg === '--strategy') out.strategy = argv[++i] || 'all';
9
+ else if (arg === '--model') out.model = argv[++i] || 'gpt-6-astra';
10
+ else if (arg === '--json') out.json = true;
11
+ else if (arg === '--list') out.list = true;
12
+ else if (arg === '--help' || arg === '-h') out.help = true;
13
+ else throw new Error(`unknown eval option: ${arg}`);
14
+ }
15
+ return out;
16
+ }
17
+
18
+ function render(report) {
19
+ const rows = [
20
+ '',
21
+ ' AgentSam · Context Eval',
22
+ '',
23
+ ` fixture ${report.fixture}`,
24
+ ` model ${report.model}`,
25
+ ` provider call no · deterministic/offline`,
26
+ '',
27
+ ];
28
+ for (const row of report.strategies) {
29
+ rows.push(` ${row.strategy.toUpperCase()}`);
30
+ rows.push(` result ${row.result}`);
31
+ rows.push(` evidence ${row.required_found} / ${row.required_evidence} required · ${row.sources_selected} / ${row.sources_considered} selected`);
32
+ rows.push(` active context ~${row.active_context_tokens.toLocaleString('en-US')}`);
33
+ rows.push(` window ${row.window_tokens.toLocaleString('en-US')}`);
34
+ rows.push(` price threshold ${row.pricing_threshold_tokens.toLocaleString('en-US')}`);
35
+ rows.push(` remaining ${row.tokens_until_pricing_threshold.toLocaleString('en-US')}`);
36
+ rows.push(` tool schemas ${row.hydrated_tools} · ${row.tool_schema_chars.toLocaleString('en-US')} chars`);
37
+ rows.push(` compacted ${row.compacted_chars.toLocaleString('en-US')} chars`);
38
+ rows.push(` rehydrated ${row.rehydrated_refs.length ? row.rehydrated_refs.join(', ') : 'none'}`);
39
+ rows.push(` est input cost $${row.estimated_input_cost_usd.toFixed(4)}`);
40
+ rows.push('');
41
+ }
42
+ rows.push(` winner ${report.winner}`);
43
+ rows.push(` scoring ${report.scoring.join(' → ')}`);
44
+ rows.push('');
45
+ return rows.join('\n');
46
+ }
47
+
48
+ export async function runEval(argv = [], options = {}) {
49
+ const args = parse(argv);
50
+ const write = options.write || (text => process.stdout.write(text));
51
+ if (args.help || args.subcommand !== 'context') {
52
+ write('agentsam eval context --fixture <name> [--strategy bounded|discovery|compact|all] [--model gpt-6-astra] [--json]\n');
53
+ return null;
54
+ }
55
+ if (args.list) {
56
+ const value = { fixtures: listContextEvalFixtures() };
57
+ write(args.json ? `${JSON.stringify(value)}\n` : `${value.fixtures.join('\n')}\n`);
58
+ return value;
59
+ }
60
+ const report = await evaluateContextFixture({ fixture: args.fixture || 'exact-symbol-callers', strategy: args.strategy, model: args.model });
61
+ write(args.json ? `${JSON.stringify(report)}\n` : render(report));
62
+ return report;
63
+ }
@@ -0,0 +1,18 @@
1
+ import { configureCliPreferences } from './preferences.js';
2
+ import { runShell } from './shell.js';
3
+ import { detectCliProject, readCliPreferences } from '../lib/cli-preferences.js';
4
+ import { runBootScene } from '../ui/boot.js';
5
+
6
+ export async function runInteractive(options = {}) {
7
+ let identity = detectCliProject(options.cwd || process.cwd());
8
+ let preferences = readCliPreferences(identity.root);
9
+
10
+ if (!preferences || preferences.trustedDirectory !== true) {
11
+ const configured = await configureCliPreferences({ cwd: identity.root, firstRun: true });
12
+ identity = configured.identity;
13
+ preferences = configured.preferences;
14
+ }
15
+
16
+ await runBootScene({ identity, preferences, animate: options.animate !== false });
17
+ await runShell([], { cwd: identity.root, intro: 'quiet' });
18
+ }
@@ -6,12 +6,15 @@ import { execFile } from 'node:child_process';
6
6
  import { promisify } from 'node:util';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import { randomUUID } from 'node:crypto';
9
+ import pkg from '../../package.json' with { type: 'json' };
9
10
  import { CONFIG_PATH, repositoryRoot, initRepository, readConfig, scopeKey, cacheNamespace } from '../knowledge/config.js';
10
11
  import { openSqliteStore } from '../knowledge/stores/sqlite.js';
11
12
  import { openPostgresStore } from '../knowledge/stores/postgres.js';
12
13
  import { planIndex, runIndex, retrieve } from '../knowledge/engine.js';
13
14
  import { createGeminiEmbedder } from '../knowledge/providers/gemini.js';
14
15
  import { compareObservations } from '../knowledge/evolution.js';
16
+ import { ensureProjectManifest, getProjectName, getRepositoryId } from '../lib/project-config.js';
17
+ import { ensureProjectRules } from '../lib/project-rules.js';
15
18
 
16
19
  const execute = promisify(execFile);
17
20
  const split = text => text.split(',').map(s => s.trim()).filter(Boolean);
@@ -28,7 +31,20 @@ export async function runRepositoryInit(argv) {
28
31
  if (opts.help) { console.log('agentsam init [.] [--cwd PATH] [--yes] [--include src,docs] [--exclude src/generated] [--scope NAME] [--target local|production] [--dimensions 768]'); return; }
29
32
  if (positionals.length > 1 || (positionals[0] && positionals[0] !== '.')) throw new Error('Use init . --cwd PATH to adopt an existing repository, or init --name NAME to scaffold.');
30
33
  const root = repositoryRoot(opts.cwd);
31
- if (fs.existsSync(path.join(root, CONFIG_PATH))) throw new Error(`${CONFIG_PATH} already exists; edit it to change scope/profile. Existing configuration was preserved.`);
34
+ const knowledgePath = path.join(root, CONFIG_PATH);
35
+ const existingKnowledge = fs.existsSync(knowledgePath) ? readConfig(root) : null;
36
+ const projectManifest = ensureProjectManifest(root, {
37
+ repositoryId: existingKnowledge?.repository_id || undefined,
38
+ sdkVersion: pkg.version,
39
+ });
40
+ ensureProjectRules(root, getProjectName(projectManifest));
41
+ const projectRepositoryId = getRepositoryId(projectManifest);
42
+ if (existingKnowledge) {
43
+ if (projectRepositoryId && existingKnowledge.repository_id !== projectRepositoryId) {
44
+ throw new Error(`repository_identity_mismatch: project=${projectRepositoryId} knowledge=${existingKnowledge.repository_id}`);
45
+ }
46
+ throw new Error(`${CONFIG_PATH} already exists; edit it to change scope/profile. Existing configuration was preserved.`);
47
+ }
32
48
  let include = opts.include, exclude = opts.exclude, target = opts.target, dimensions = opts.dimensions;
33
49
  if (!opts.yes) {
34
50
  if (!process.stdin.isTTY) throw new Error('Existing-repository setup needs a terminal or --yes with explicit options.');
@@ -41,7 +57,14 @@ export async function runRepositoryInit(argv) {
41
57
  dimensions ??= await prompt.question('4) Gemini Embedding 2 dimensions [768]: ') || '768';
42
58
  } finally { prompt.close(); }
43
59
  }
44
- const config = initRepository(root, { include: split(include || '.'), exclude: split(exclude || ''), scope: opts.scope || 'default', target: target || 'local', dimensions: Number(dimensions || 768) });
60
+ const config = initRepository(root, {
61
+ include: split(include || '.'),
62
+ exclude: split(exclude || ''),
63
+ scope: opts.scope || 'default',
64
+ target: target || 'local',
65
+ dimensions: Number(dimensions || 768),
66
+ repositoryId: projectRepositoryId,
67
+ });
45
68
  show({ root, config: CONFIG_PATH, storage: config.storage.driver, next: config.storage.driver === 'postgres' ? ['agentsam index setup-store', 'agentsam index plan', 'agentsam index run'] : ['agentsam index plan', 'agentsam index run', 'agentsam search "your symbol"'], note: 'No indexing, credentials, network calls, or source-file changes during setup.' });
46
69
  }
47
70
 
@@ -73,9 +96,9 @@ export async function runKnowledge(argv) {
73
96
 
74
97
  export async function runSearch(argv) {
75
98
  const { values: opts, positionals } = flags(argv, { semantic: { type: 'boolean' }, 'top-k': { type: 'string' }, 'token-budget': { type: 'string' }, generation: { type: 'string' } });
76
- if (opts.help) { console.log('agentsam search "query" [--cwd PATH] [--semantic] [--top-k 8] [--token-budget 8000] [--generation ID]'); return; }
99
+ if (opts.help) { console.log('agentsam search "query" [--cwd PATH] [--semantic] [--top-k 8] [--token-budget 6000] [--generation ID]'); return; }
77
100
  const root = repositoryRoot(opts.cwd), config = readConfig(root), store = await openStore(root, config, true);
78
- try { show(await retrieve({ store, config, text: positionals.join(' '), semantic: opts.semantic, embedder: opts.semantic ? provider() : null, topK: Number(opts['top-k'] || 8), tokenBudget: Number(opts['token-budget'] || 8000), generationId: opts.generation })); }
101
+ try { show(await retrieve({ store, config, text: positionals.join(' '), semantic: opts.semantic, embedder: opts.semantic ? provider() : null, topK: Number(opts['top-k'] || 8), tokenBudget: Number(opts['token-budget'] || 6000), generationId: opts.generation })); }
79
102
  finally { await store?.close(); }
80
103
  }
81
104
 
@@ -0,0 +1,118 @@
1
+ import path from 'node:path';
2
+ import { readSnapshot } from '../lib/merkle/snapshot.js';
3
+ import {
4
+ buildMerklePersistencePlan,
5
+ persistMerkleSnapshotCloudflare,
6
+ resolveWranglerMerklePersistence,
7
+ } from '../lib/merkle/cloudflare-persistence.js';
8
+
9
+ function value(args, index, flag) {
10
+ const next = args[index + 1];
11
+ if (!next || next.startsWith('--')) throw new Error(`Missing value for ${flag}`);
12
+ return next;
13
+ }
14
+
15
+ export function printMerklePersistHelp() {
16
+ console.log(`
17
+ agentsam merkle persist <snapshot.json> — publish a saved Merkle snapshot through host bindings
18
+
19
+ --wrangler-config <file> Worker config containing WEBSITE_ASSETS and optionally DB
20
+ --owner-user-id <id> Snapshot owner authority (or AGENTSAM_OWNER_USER_ID)
21
+ --repo-id <id> Canonical repo id; inferred for GitHub/GitLab/Bitbucket when possible
22
+ --capture-kind <kind> deploy|manual|agent|index (default manual)
23
+ --connection-id <id> Execution provenance for non-deploy captures
24
+ --runtime-lease-id <id> Alternative execution provenance for non-deploy captures
25
+ --deployment-id <id> Optional deployment linkage
26
+ --worker-version <id> Optional provider worker version linkage
27
+ --reference-label <label> Optional human/audit label
28
+ --source <source> github|gitlab|bitbucket|local|upload (default local)
29
+ --prefix <prefix> Object prefix (default agentsam_fs_merkle_snapshots)
30
+ --r2-binding <name> Logical R2 role (default WEBSITE_ASSETS)
31
+ --d1-binding <name> Logical D1 role (default DB)
32
+ --environment <name> Wrangler environment
33
+ --wrangler-bin <path> Wrangler executable (default WRANGLER_BIN or wrangler)
34
+ --r2-only Upload object without indexing in D1
35
+ --dry-run Resolve bindings and emit the exact storage/index plan without writes
36
+ --json Machine-readable output
37
+
38
+ The SDK never infers owner identity. Hosts should pass authenticated owner_user_id.
39
+ Physical bucket/database names come from Wrangler bindings, so customer installs keep
40
+ WEBSITE_ASSETS/DB while selecting their own storage resources.
41
+ `);
42
+ }
43
+
44
+ function parse(args) {
45
+ if (!args.length || args.includes('--help') || args.includes('-h')) return { help: true };
46
+ const opts = {
47
+ snapshotPath: '', json: false, dryRun: false, r2Only: false,
48
+ captureKind: 'manual', source: 'local', r2Binding: 'WEBSITE_ASSETS', d1Binding: 'DB',
49
+ storagePrefix: 'agentsam_fs_merkle_snapshots',
50
+ };
51
+ for (let i = 0; i < args.length; i += 1) {
52
+ const arg = args[i];
53
+ if (!arg.startsWith('-') && !opts.snapshotPath) { opts.snapshotPath = arg; continue; }
54
+ if (arg === '--json') { opts.json = true; continue; }
55
+ if (arg === '--dry-run') { opts.dryRun = true; continue; }
56
+ if (arg === '--r2-only') { opts.r2Only = true; continue; }
57
+ const map = {
58
+ '--wrangler-config': 'wranglerConfig', '--owner-user-id': 'ownerUserId', '--repo-id': 'repoId',
59
+ '--capture-kind': 'captureKind', '--connection-id': 'connectionId', '--runtime-lease-id': 'runtimeLeaseId',
60
+ '--deployment-id': 'deploymentId', '--worker-version': 'workerVersionId', '--reference-label': 'referenceLabel',
61
+ '--source': 'source', '--prefix': 'storagePrefix', '--r2-binding': 'r2Binding', '--d1-binding': 'd1Binding',
62
+ '--environment': 'environment', '--wrangler-bin': 'wranglerBin', '--root': 'root',
63
+ };
64
+ if (map[arg]) { opts[map[arg]] = value(args, i, arg); i += 1; continue; }
65
+ throw new Error(`Unknown merkle persist option: ${arg}`);
66
+ }
67
+ if (!opts.snapshotPath) throw new Error('snapshot_file_required');
68
+ opts.ownerUserId ||= process.env.AGENTSAM_OWNER_USER_ID || '';
69
+ opts.wranglerConfig ||= process.env.AGENTSAM_WRANGLER_CONFIG || '';
70
+ opts.connectionId ||= process.env.AGENTSAM_CONNECTION_ID || '';
71
+ opts.runtimeLeaseId ||= process.env.AGENTSAM_RUNTIME_LEASE_ID || '';
72
+ return opts;
73
+ }
74
+
75
+ export async function runMerklePersist(args = []) {
76
+ const opts = parse(args);
77
+ if (opts.help) { printMerklePersistHelp(); return null; }
78
+ const snapshotPath = path.resolve(opts.snapshotPath);
79
+ const snapshot = await readSnapshot(snapshotPath);
80
+ const root = path.resolve(opts.root || snapshot.rootPath || process.cwd());
81
+ const wrangler = resolveWranglerMerklePersistence({
82
+ configPath: opts.wranglerConfig,
83
+ environment: opts.environment || null,
84
+ r2Binding: opts.r2Binding,
85
+ d1Binding: opts.d1Binding,
86
+ r2Only: opts.r2Only,
87
+ });
88
+ const plan = buildMerklePersistencePlan({
89
+ snapshot,
90
+ root,
91
+ ownerUserId: opts.ownerUserId,
92
+ repoId: opts.repoId,
93
+ source: opts.source,
94
+ captureKind: opts.captureKind,
95
+ connectionId: opts.connectionId,
96
+ runtimeLeaseId: opts.runtimeLeaseId,
97
+ deploymentId: opts.deploymentId,
98
+ workerVersionId: opts.workerVersionId,
99
+ referenceLabel: opts.referenceLabel,
100
+ storagePrefix: opts.storagePrefix,
101
+ wrangler,
102
+ });
103
+ const result = persistMerkleSnapshotCloudflare({
104
+ snapshotPath,
105
+ snapshot,
106
+ plan,
107
+ wranglerBin: opts.wranglerBin,
108
+ r2Only: opts.r2Only,
109
+ dryRun: opts.dryRun,
110
+ });
111
+ if (opts.json) process.stdout.write(`${JSON.stringify(result)}\n`);
112
+ else {
113
+ console.log(`Persisted ${result.snapshot_id}${result.dry_run ? ' (dry run)' : ''}`);
114
+ console.log(` R2 ${result.binding} -> ${result.storage_bucket}/${result.storage_key}`);
115
+ if (result.table) console.log(` D1 ${result.database_binding} -> ${result.database_name}.${result.table}`);
116
+ }
117
+ return result;
118
+ }
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { buildMerkleTree, saveSnapshot, readSnapshot, diffTrees, normalizePolicy } from '../lib/merkle/index.js';
4
4
  import { renderSummary, safeText } from '../ui/merkle/render.js';
5
5
  import { runMerkleExplorer } from '../ui/merkle/explorer.js';
6
+ import { runMerklePersist } from './merkle-persist.js';
6
7
 
7
8
  export function printMerkleHelp() {
8
9
  console.log(`
@@ -13,24 +14,26 @@ export function printMerkleHelp() {
13
14
  verify <snapshot> Check current files against a saved manifest
14
15
  diff <a> <b> Compare two directories or snapshot files
15
16
  inspect [path] Show every path and hash (also accepts a snapshot)
16
- tui [path] Open the interactive tree explorer
17
+ persist <snapshot> Publish a saved snapshot through WEBSITE_ASSETS/DB bindings
18
+ explore [path] Open the interactive tree explorer
17
19
 
18
20
  --out <file> Snapshot destination
19
21
  --force Replace an existing snapshot file
22
+ --semantic Add agentsam-filemeta classification + AST metadata
20
23
  --root <path> Verify a snapshot against a different directory
21
24
  --include <default-rule> Include an ignored category, e.g. --include dist
22
25
  --exclude <path-or-name> Ignore an additional name or relative subtree
23
- --tui Explore root/inspect/verify/diff interactively
24
- --json Machine-readable output; never opens a TUI
26
+ --interactive Explore root/inspect/verify/diff interactively
27
+ --json Machine-readable output; never opens the interactive explorer
25
28
 
26
29
  Examples:
27
30
  agentsam merkle root .
28
- agentsam merkle snapshot . --out .agentsam/merkle.json
29
- agentsam merkle verify .agentsam/merkle.json --tui
30
- agentsam merkle diff ./mac-copy ./vm-copy --tui
31
- agentsam tui merkle .
31
+ agentsam merkle snapshot . --semantic --out .agentsam/merkle.json
32
+ agentsam merkle verify .agentsam/merkle.json --interactive
33
+ agentsam merkle diff ./mac-copy ./vm-copy --interactive
34
+ agentsam merkle explore .
32
35
 
33
- TUI: arrows or j/k to move; Enter/right to expand; left to collapse;
36
+ Explorer: arrows or j/k to move; Enter/right to expand; left to collapse;
34
37
  c to filter changes; r to rescan; q/Esc/Ctrl+C to close.
35
38
  Exit codes: 0 success/match, 1 differences, 2 error, 130 interrupted scan.
36
39
  Includes/excludes are literal rules, not globs or .gitignore syntax.
@@ -39,14 +42,16 @@ export function printMerkleHelp() {
39
42
 
40
43
  function parse(argv) {
41
44
  const [command, ...args] = argv;
42
- if (!['root', 'snapshot', 'verify', 'diff', 'inspect', 'tui'].includes(command)) throw new Error(`Unknown Merkle command: ${command}`);
43
- const opts = { command, paths: [], include: [], exclude: [], tui: command === 'tui', json: false, force: false };
45
+ if (!['root', 'snapshot', 'verify', 'diff', 'inspect', 'explore', 'tui'].includes(command)) throw new Error(`Unknown Merkle command: ${command}`);
46
+ const publicCommand = command === 'tui' ? 'explore' : command;
47
+ const opts = { command: publicCommand, paths: [], include: [], exclude: [], tui: publicCommand === 'explore', json: false, force: false, semantic: false };
44
48
  let positionalOnly = false;
45
49
  for (let i = 0; i < args.length; i++) {
46
50
  const arg = args[i];
47
51
  if (arg === '--' && !positionalOnly) { positionalOnly = true; continue; }
48
52
  if (positionalOnly || !arg.startsWith('-')) opts.paths.push(arg);
49
- else if (['--tui', '--json', '--force'].includes(arg)) opts[arg.slice(2)] = true;
53
+ else if (arg === '--interactive' || arg === '--tui') opts.tui = true;
54
+ else if (['--json', '--force', '--semantic'].includes(arg)) opts[arg.slice(2)] = true;
50
55
  else if (['--out', '--root', '--include', '--exclude'].includes(arg)) {
51
56
  const value = args[++i];
52
57
  if (!value || value.startsWith('--')) throw new Error(`Missing value for ${arg}`);
@@ -59,7 +64,8 @@ function parse(argv) {
59
64
  if (!count && !opts.paths.length) opts.paths.push('.');
60
65
  if ((opts.out || opts.force) && command !== 'snapshot') throw new Error('--out and --force apply only to snapshot.');
61
66
  if (opts.root && command !== 'verify') throw new Error('--root applies only to verify.');
62
- if (command === 'snapshot' && opts.tui) throw new Error('Use inspect --tui after saving a snapshot.');
67
+ if (command === 'snapshot' && opts.tui) throw new Error('Use inspect --interactive after saving a snapshot.');
68
+ if (opts.semantic && !['root', 'snapshot', 'inspect'].includes(command)) throw new Error('--semantic applies only to root, snapshot, or inspect.');
63
69
  if (command === 'verify' && (opts.include.length || opts.exclude.length)) throw new Error('Verification uses the snapshot\'s saved ignore rules.');
64
70
  opts.policy = normalizePolicy(opts);
65
71
  return opts;
@@ -78,6 +84,14 @@ async function comparison(opts, progress) {
78
84
  }
79
85
 
80
86
  export async function runMerkle(argv = []) {
87
+ if (argv[0] === 'persist') {
88
+ try { return await runMerklePersist(argv.slice(1)); }
89
+ catch (error) {
90
+ process.exitCode = 2;
91
+ process.stderr.write(argv.includes('--json') ? JSON.stringify({ error: error.message }) + '\n' : `Merkle persist: ${safeText(error.message)}\n`);
92
+ return null;
93
+ }
94
+ }
81
95
  if (!argv.length || argv.includes('--help') || argv.includes('-h')) { printMerkleHelp(); return; }
82
96
  let opts;
83
97
  try {
@@ -91,12 +105,12 @@ export async function runMerkle(argv = []) {
91
105
  }
92
106
  if (opts.command === 'diff') return comparison(opts, progress);
93
107
  if (opts.command === 'snapshot') {
94
- const { snapshot: tree, output } = await saveSnapshot(target, { ...progress, policy: opts.policy, out: opts.out, force: opts.force });
108
+ const { snapshot: tree, output } = await saveSnapshot(target, { ...progress, policy: opts.policy, out: opts.out, force: opts.force, semantic: opts.semantic });
95
109
  return { title: 'Merkle snapshot', tree, output };
96
110
  }
97
- const isSnapshot = ['inspect', 'tui'].includes(opts.command) && !(await fs.stat(target)).isDirectory();
111
+ const isSnapshot = ['inspect', 'explore'].includes(opts.command) && !(await fs.stat(target)).isDirectory();
98
112
  if (isSnapshot && (opts.include.length || opts.exclude.length)) throw new Error('Snapshot inspection uses saved ignore rules.');
99
- const tree = isSnapshot ? await readSnapshot(target) : await buildMerkleTree(target, { ...progress, policy: opts.policy });
113
+ const tree = isSnapshot ? await readSnapshot(target) : await buildMerkleTree(target, { ...progress, policy: opts.policy, semantic: opts.semantic });
100
114
  return { title: isSnapshot ? 'Saved Merkle tree' : 'Merkle root', tree };
101
115
  };
102
116
  let result;
@@ -108,9 +122,10 @@ export async function runMerkle(argv = []) {
108
122
  try { result = await load({ signal: controller.signal }); }
109
123
  finally { process.removeListener('SIGINT', stop); process.removeListener('SIGTERM', stop); }
110
124
  if (opts.json) {
111
- const { rootPath, rootHash, stats, policy } = result.tree;
125
+ const { rootPath, rootHash, stats, policy, policyHash, semantic } = result.tree;
126
+ const semanticSummary = semantic ? { metadataRoot: semantic.rootHash, classifier: semantic.classifier, semanticStats: semantic.stats } : {};
112
127
  const value = result.diff ? { ...result.diff, rootPath, policy } : opts.command === 'snapshot' ? { ...result.tree, output: result.output }
113
- : opts.command === 'root' ? { rootPath, rootHash, stats, policy } : result.tree;
128
+ : opts.command === 'root' ? { rootPath, rootHash, stats, policy, policyHash, ...semanticSummary } : result.tree;
114
129
  process.stdout.write(JSON.stringify(value, null, 2) + '\n');
115
130
  } else process.stdout.write(renderSummary(result, { inspect: opts.command === 'inspect', color: process.stdout.isTTY && !Object.hasOwn(process.env, 'NO_COLOR') }));
116
131
  }
@@ -0,0 +1,152 @@
1
+ import pc from 'picocolors';
2
+ import { probeOllama, resolveOllamaConfig } from './ollama.js';
3
+ import { listModelCatalog } from '../models/index.js';
4
+ import { resolveProviderCredential } from '../lib/provider-credentials.js';
5
+
6
+ const API_PROVIDERS = Object.freeze([
7
+ { id: 'openai', label: 'OpenAI', credential: 'OPENAI_API_KEY' },
8
+ { id: 'gemini', label: 'Gemini', credential: 'GEMINI_API_KEY' },
9
+ { id: 'grok', label: 'Grok', credential: 'XAI_API_KEY' },
10
+ { id: 'anthropic', label: 'Anthropic', credential: 'ANTHROPIC_API_KEY' },
11
+ ]);
12
+
13
+ function clean(value) { return value == null ? '' : String(value).trim(); }
14
+ function configured(value) { return Boolean(clean(value)); }
15
+
16
+ async function discoverOpenAIModels(apiKey, fetchImpl) {
17
+ if (!apiKey) return { attempted: false, ok: false, models: [], error: null };
18
+ try {
19
+ const response = await fetchImpl('https://api.openai.com/v1/models', {
20
+ headers: { authorization: `Bearer ${apiKey}` },
21
+ signal: typeof AbortSignal?.timeout === 'function' ? AbortSignal.timeout(8_000) : undefined,
22
+ });
23
+ if (!response.ok) return { attempted: true, ok: false, models: [], error: `HTTP ${response.status}` };
24
+ const body = await response.json();
25
+ const models = Array.isArray(body?.data) ? body.data.map((row) => clean(row?.id)).filter(Boolean) : [];
26
+ return { attempted: true, ok: true, models, error: null };
27
+ } catch (error) {
28
+ return { attempted: true, ok: false, models: [], error: error?.message || String(error) };
29
+ }
30
+ }
31
+
32
+ export async function collectModelsStatus(options = {}) {
33
+ const env = options.env || process.env;
34
+ const ollamaFetchImpl = options.fetchImpl || fetch;
35
+ const providerFetchImpl = options.providerFetchImpl || fetch;
36
+ const ollamaConfig = resolveOllamaConfig({}, env);
37
+ const ollama = await probeOllama(ollamaConfig, ollamaFetchImpl);
38
+ const credentials = new Map(API_PROVIDERS.map((provider) => [provider.id, resolveProviderCredential(provider.id, { env, home: options.home })]));
39
+ const providers = API_PROVIDERS.map((provider) => {
40
+ const credential = credentials.get(provider.id);
41
+ return {
42
+ ...provider,
43
+ configured: credential?.configured === true,
44
+ source: credential?.source || null,
45
+ credentialError: credential?.error || null,
46
+ };
47
+ });
48
+
49
+ const openaiCredential = credentials.get('openai');
50
+ const shouldDiscover = options.discoverRemote !== false && Boolean(openaiCredential?.configured);
51
+ const openai = shouldDiscover
52
+ ? await discoverOpenAIModels(clean(openaiCredential?.value), providerFetchImpl)
53
+ : { attempted: false, ok: false, models: [], error: null };
54
+ const availableIds = new Set(openai.models);
55
+ const catalogModels = listModelCatalog().map((record) => ({
56
+ model_key: record.model_key,
57
+ provider: record.provider,
58
+ provider_model_id: record.provider_model_id,
59
+ label: record.label,
60
+ availability: record.provider === 'openai' && openai.ok
61
+ ? (availableIds.has(record.provider_model_id) ? 'available' : 'unavailable')
62
+ : 'unverified',
63
+ source: record.source,
64
+ }));
65
+
66
+ return {
67
+ schemaVersion: 'agentsam-model-inventory-v2',
68
+ providers,
69
+ discovery: {
70
+ openai: {
71
+ attempted: openai.attempted,
72
+ ok: openai.ok,
73
+ error: openai.error,
74
+ returnedModelCount: openai.models.length,
75
+ },
76
+ },
77
+ catalogModels,
78
+ availableModels: catalogModels.filter((row) => row.availability === 'available'),
79
+ local: {
80
+ provider: 'ollama',
81
+ configured: ollama.online,
82
+ online: ollama.online,
83
+ endpoint: ollama.endpoint,
84
+ chatModel: ollamaConfig.model,
85
+ embedModel: ollamaConfig.embedModel,
86
+ models: ollama.models || [],
87
+ error: ollama.error || null,
88
+ },
89
+ };
90
+ }
91
+
92
+ function statusMark(ok) { return ok ? pc.green('●') : pc.dim('○'); }
93
+ function writeLine(write, value = '') { write(`${value}\n`); }
94
+
95
+ export function renderModelsStatus(status) {
96
+ const lines = [];
97
+ lines.push('');
98
+ lines.push(` ${pc.bold('Agent Sam · models')}`);
99
+ lines.push(` ${pc.dim('Credential presence is local evidence; exact hosted-model availability is provider-verified when discovery succeeds.')}`);
100
+ lines.push('');
101
+
102
+ for (const provider of status.providers) {
103
+ const state = provider.configured ? pc.green('configured') : pc.dim(provider.credentialError ? 'blocked' : 'not configured');
104
+ const detail = provider.configured ? `credential available · ${provider.source || 'runtime'}` : provider.credentialError ? `${provider.credential} · ${provider.credentialError}` : provider.credential;
105
+ lines.push(` ${statusMark(provider.configured)} ${pc.cyan(provider.label.padEnd(10))} ${state.padEnd(20)} ${pc.dim(detail)}`);
106
+ }
107
+
108
+ const exact = status.availableModels || [];
109
+ if (exact.length) {
110
+ lines.push('');
111
+ lines.push(` ${pc.dim('provider-verified selectable models')}`);
112
+ for (const model of exact) lines.push(` ${pc.green('•')} ${model.provider_model_id}`);
113
+ } else if (status.discovery?.openai?.attempted) {
114
+ lines.push('');
115
+ lines.push(` ${pc.dim(`OpenAI discovery ${status.discovery.openai.ok ? 'completed; no catalog models matched' : `failed: ${status.discovery.openai.error || 'unknown error'}`}`)}`);
116
+ }
117
+
118
+ const local = status.local;
119
+ const localState = local.online ? pc.green('online') : pc.dim('offline');
120
+ lines.push(` ${statusMark(local.online)} ${pc.cyan('Ollama'.padEnd(10))} ${localState.padEnd(20)} ${pc.dim('local only')}`);
121
+ if (local.online) {
122
+ const names = local.models.map((row) => row.name).filter(Boolean);
123
+ lines.push('');
124
+ lines.push(` ${pc.dim('local models')}`);
125
+ if (names.length) for (const name of names) lines.push(` ${pc.green('•')} ${name}`);
126
+ else lines.push(` ${pc.dim('no models reported')}`);
127
+ lines.push('');
128
+ lines.push(` ${pc.dim('chat default')} ${local.chatModel}`);
129
+ lines.push(` ${pc.dim('embed default')} ${local.embedModel}`);
130
+ }
131
+ lines.push('');
132
+ lines.push(` ${pc.dim('Use /model inside Agent Sam to choose an exact verified model, reasoning effort, and processing tier.')}`);
133
+ lines.push('');
134
+ return lines.join('\n');
135
+ }
136
+
137
+ export async function runModels(argv = [], options = {}) {
138
+ if (argv.some((arg) => arg === '--help' || arg === '-h')) {
139
+ const text = 'agentsam models [--json] [--no-discover]\n\nShow configured providers, provider-verified known models when available, and local Ollama inventory.\n';
140
+ (options.write || process.stdout.write.bind(process.stdout))(text);
141
+ return;
142
+ }
143
+ const allowed = new Set(['--json', '--no-discover']);
144
+ const unknown = argv.filter((arg) => !allowed.has(arg));
145
+ if (unknown.length) throw new Error(`unknown models option: ${unknown[0]}`);
146
+
147
+ const status = await collectModelsStatus({ ...options, discoverRemote: !argv.includes('--no-discover') });
148
+ const write = options.write || ((text) => process.stdout.write(text));
149
+ if (argv.includes('--json')) writeLine(write, JSON.stringify(status, null, 2));
150
+ else write(renderModelsStatus(status));
151
+ return status;
152
+ }