@inneranimalmedia/agentsam-sdk 2.6.0 → 2.6.2

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 (145) hide show
  1. package/docs/AUTH_IDENTITY_CONTRACT.md +25 -27
  2. package/docs/PLATFORM_RUNTIME_EVENTS.md +48 -0
  3. package/docs/RELEASES.md +7 -7
  4. package/docs/SDK_WORKER.md +4 -3
  5. package/docs/SOURCE_ARCHITECTURE.md +58 -0
  6. package/docs/TEST_TIERS.md +26 -0
  7. package/migrations/runtime/0001_cli_runtime.sql +298 -0
  8. package/package.json +28 -7
  9. package/packages/agentsam-repository/README.md +15 -0
  10. package/packages/agentsam-repository/package.json +25 -0
  11. package/packages/agentsam-repository/src/contracts.js +113 -0
  12. package/packages/agentsam-repository/src/index.js +3 -0
  13. package/{src/lib → packages/agentsam-repository/src}/merkle/cloudflare-persistence.js +14 -24
  14. package/{src/lib → packages/agentsam-repository/src}/merkle/index.js +1 -0
  15. package/{src/lib → packages/agentsam-repository/src}/merkle/persistence.js +6 -4
  16. package/{src/lib → packages/agentsam-repository/src}/merkle/policy.js +1 -0
  17. package/packages/agentsam-repository/test/contracts.test.mjs +40 -0
  18. package/packages/agentsam-repository/test/git-context.test.mjs +24 -0
  19. package/{test/merkle.test.mjs → packages/agentsam-repository/test/merkle-core.test.mjs} +2 -32
  20. package/{test → packages/agentsam-repository/test}/merkle-persistence.test.mjs +11 -6
  21. package/packages/identity/package.json +2 -2
  22. package/packages/identity/src/contracts/auth-config.js +23 -40
  23. package/packages/identity/tests/auth-config.test.mjs +14 -23
  24. package/protocol/COMPANY_REPOSITORY_GRAPH_V1.md +91 -0
  25. package/protocol/MERKLE_PERSISTENCE_V1.md +2 -0
  26. package/protocol/MERKLE_PERSISTENCE_V2.md +40 -0
  27. package/protocol/repository/repository-contract.schema.json +24 -0
  28. package/protocol/repository/repository-dependency.schema.json +24 -0
  29. package/protocol/repository/repository-identity.schema.json +17 -0
  30. package/protocol/rpc/v1/common.proto +16 -0
  31. package/protocol/rpc/v1/errors.proto +35 -0
  32. package/protocol/rpc/v1/knowledge.proto +77 -0
  33. package/services/knowledge/package-lock.json +333 -0
  34. package/services/knowledge/package.json +5 -1
  35. package/src/agent/responses-runner.js +63 -35
  36. package/src/capabilities/repository-snapshot.js +3 -3
  37. package/src/cli.js +36 -29
  38. package/src/commands/account-auth.js +1 -1
  39. package/src/commands/context-economics.js +17 -2
  40. package/src/commands/context.js +1 -1
  41. package/src/commands/db.js +20 -3
  42. package/src/commands/deploy.js +2 -2
  43. package/src/commands/env.js +90 -0
  44. package/src/commands/knowledge.js +12 -4
  45. package/src/commands/merkle-persist.js +30 -11
  46. package/src/commands/merkle.js +1 -1
  47. package/src/commands/models.js +123 -65
  48. package/src/commands/ollama.js +26 -0
  49. package/src/commands/preferences.js +53 -26
  50. package/src/commands/providers.js +308 -0
  51. package/src/commands/shell.js +240 -48
  52. package/src/commands/tunnel.js +8 -8
  53. package/src/commands/whoami.js +42 -21
  54. package/src/errors/contract.js +236 -0
  55. package/src/errors/diagnostic.js +1 -0
  56. package/src/errors/index.js +14 -0
  57. package/src/index.js +13 -1
  58. package/src/knowledge/service/auth.js +13 -0
  59. package/src/knowledge/service/grpc-client.js +115 -0
  60. package/src/knowledge/service/grpc-codec.js +237 -0
  61. package/src/knowledge/service/grpc-server.js +83 -0
  62. package/src/knowledge/service/job-engine.js +248 -0
  63. package/src/knowledge/service/server.js +87 -135
  64. package/src/knowledge/source.js +1 -1
  65. package/src/lib/account-session.js +94 -25
  66. package/src/lib/auth.js +314 -51
  67. package/src/lib/cli-preferences.js +31 -4
  68. package/src/lib/core-client.js +75 -30
  69. package/src/lib/deploy-receipt/index.js +2 -2
  70. package/src/lib/detect-context.js +14 -16
  71. package/src/lib/knowledge-docker.js +6 -3
  72. package/src/lib/local-sessions.js +23 -2
  73. package/src/lib/local-status.js +1 -1
  74. package/src/lib/project-config.js +1 -1
  75. package/src/lib/provider-credentials.js +230 -14
  76. package/src/lib/slash-commands.js +5 -3
  77. package/src/local/migrations.js +93 -0
  78. package/src/local/runtime-store.js +141 -0
  79. package/src/local/sqlite.js +2 -0
  80. package/src/local-pty/server.js +113 -51
  81. package/src/models/discovery.js +327 -0
  82. package/src/providers/anthropic-messages.js +192 -0
  83. package/src/providers/cloudflare-chat.js +183 -0
  84. package/src/providers/factory.js +69 -0
  85. package/src/providers/gemini-generate-content.js +208 -0
  86. package/src/providers/index.js +5 -0
  87. package/src/providers/ollama-chat.js +148 -0
  88. package/src/providers/openai-responses.js +226 -75
  89. package/src/repository/index.js +14 -2
  90. package/src/rpc/generated/common_grpc_pb.js +1 -0
  91. package/src/rpc/generated/common_pb.js +536 -0
  92. package/src/rpc/generated/errors_grpc_pb.js +1 -0
  93. package/src/rpc/generated/errors_pb.js +482 -0
  94. package/src/rpc/generated/knowledge_grpc_pb.js +135 -0
  95. package/src/rpc/generated/knowledge_pb.js +2168 -0
  96. package/src/rpc/generated/package.json +3 -0
  97. package/src/security/trust-boundary.js +2 -2
  98. package/src/telemetry/events.js +4 -1
  99. package/src/ui/cli/activity.js +76 -0
  100. package/src/ui/cli/compaction.js +15 -0
  101. package/src/ui/cli/footer.js +39 -0
  102. package/src/ui/cli/help.js +194 -0
  103. package/src/ui/cli/plan.js +20 -0
  104. package/src/ui/cli/runtime-events.js +110 -0
  105. package/src/ui/cli/waiting.js +16 -0
  106. package/src/ui/merkle/render.js +1 -1
  107. package/test/account-session.test.mjs +48 -11
  108. package/test/apps-scaffold-contract.test.mjs +43 -0
  109. package/test/cli/preferences-runtime.test.mjs +11 -0
  110. package/test/cli/runtime-ui.test.mjs +74 -0
  111. package/test/error-diagnostics.test.mjs +57 -1
  112. package/test/fixtures/knowledge-rpc-worker.mjs +16 -0
  113. package/test/integration/cli-help.test.mjs +37 -0
  114. package/test/integration/knowledge-rpc.test.mjs +112 -0
  115. package/test/integration/merkle-cli.test.mjs +61 -0
  116. package/test/integration/merkle-persistence-identity.test.mjs +48 -0
  117. package/test/integration/provider-env-cli.test.mjs +49 -0
  118. package/test/integration/provider-factory.test.mjs +197 -0
  119. package/test/integration/repository-company-graph.test.mjs +90 -0
  120. package/test/integration/runtime-migrations.test.mjs +82 -0
  121. package/test/knowledge-service.test.mjs +5 -0
  122. package/test/knowledge.test.mjs +16 -0
  123. package/test/live/terminal-transport.live.test.mjs +24 -0
  124. package/test/local-sessions.test.mjs +7 -1
  125. package/test/models.test.mjs +101 -4
  126. package/test/ollama.test.mjs +21 -0
  127. package/test/portable-context.test.mjs +1 -1
  128. package/test/provider-credentials.test.mjs +45 -1
  129. package/test/release-hygiene.test.mjs +13 -5
  130. package/test/responses-runner.test.mjs +3 -1
  131. package/test/sdk-worker-contract.test.mjs +5 -1
  132. package/test/shell.test.mjs +50 -8
  133. package/test/smoke.mjs +2 -1
  134. package/test/terminal/local-pty.mock.test.mjs +151 -0
  135. package/test/whoami-resume.test.mjs +6 -5
  136. package/src/lib/prompt-byok.js +0 -57
  137. package/src/lib/save-sdk-token.js +0 -19
  138. /package/{src/lib → packages/agentsam-repository/src}/git-context.js +0 -0
  139. /package/{src/lib → packages/agentsam-repository/src}/merkle/diff.js +0 -0
  140. /package/{src/lib → packages/agentsam-repository/src}/merkle/filemeta.js +0 -0
  141. /package/{src/lib → packages/agentsam-repository/src}/merkle/git-ignore.js +0 -0
  142. /package/{src/lib → packages/agentsam-repository/src}/merkle/hash.js +0 -0
  143. /package/{src/lib → packages/agentsam-repository/src}/merkle/semantic.js +0 -0
  144. /package/{src/lib → packages/agentsam-repository/src}/merkle/snapshot.js +0 -0
  145. /package/{src/lib → packages/agentsam-repository/src}/merkle/tree.js +0 -0
package/src/cli.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import pkg from '../package.json' with { type: 'json' };
4
- import readline from 'readline';
5
4
  import { cancel, intro, isCancel, outro, select, text } from '@clack/prompts';
6
5
  import path from 'node:path';
7
6
  import { buildLocalScaffoldMeta } from './lib/local-scaffold.js';
@@ -9,10 +8,11 @@ import { writeScaffoldFiles } from './lib/write-files.js';
9
8
  import { initializeGitRepository } from './lib/init-git.js';
10
9
  import { initializeLocalSqlite } from './local/sqlite.js';
11
10
  import { printContextSummary } from './lib/detect-context.js';
12
- import { promptOptionalByokKeys } from './lib/prompt-byok.js';
13
11
  import { runStartLocal } from './commands/start-local.js';
14
12
  import { runOllama } from './commands/ollama.js';
15
13
  import { runModels } from './commands/models.js';
14
+ import { runProviders } from './commands/providers.js';
15
+ import { runEnv } from './commands/env.js';
16
16
  import { runTunnel } from './commands/tunnel.js';
17
17
  import { runDeploy } from './commands/deploy.js';
18
18
  import { runConnections } from './commands/connections.js';
@@ -40,8 +40,9 @@ import { applyPresetSelection, runAdd, runCapabilities, runDev, runInspect } fro
40
40
  import { listPresets, resolvePreset } from './presets/index.js';
41
41
  import fs from 'node:fs';
42
42
  import { repositoryRoot } from './knowledge/config.js';
43
- import { resolveAccountSdkKey } from './lib/account-session.js';
43
+ import { resolveAccountAuth } from './lib/account-session.js';
44
44
  import { renderDiagnosticError } from './errors/index.js';
45
+ import { renderHelpOverview, runHelp } from './ui/cli/help.js';
45
46
 
46
47
  const VERSION = pkg.version;
47
48
 
@@ -51,15 +52,12 @@ function reportCliError(error) {
51
52
  console.error(`\n${rendered}\n`);
52
53
  }
53
54
 
54
- function createPrompt() {
55
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
56
- return {
57
- ask: (q) => new Promise((resolve) => rl.question(q, resolve)),
58
- close: () => rl.close(),
59
- };
60
- }
61
55
 
62
56
  function printHelp() {
57
+ console.log(renderHelpOverview(VERSION));
58
+ }
59
+
60
+ function printLegacyHelp() {
63
61
  console.log(`
64
62
  Agent Sam SDK — CLI v${VERSION}
65
63
 
@@ -90,9 +88,11 @@ function printHelp() {
90
88
  agentsam status [--json] Live local Git + DB + API + PTY status
91
89
  agentsam db init|status Manage the project-local SQLite database
92
90
  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
91
+ agentsam providers Configure, verify, and remove machine provider credentials
92
+ agentsam env init <name> Create a secure provider profile + reusable shell loader
93
+ agentsam login Sign in to Inner Animal Media and persist a secure machine-local session
94
+ agentsam logout Sign out locally; provider credentials stay untouched
95
+ agentsam whoami [--json] Authenticated account identity + safe credential status
96
96
  agentsam resume [session] Resume a saved Agent Sam session; omit id for picker
97
97
  agentsam eval context Offline context-strategy/economics fixtures (--help)
98
98
  agentsam cloudflare Native Wrangler reads + Worker CPU profile analysis (--help)
@@ -102,8 +102,9 @@ function printHelp() {
102
102
  agentsam tunnel Explicitly expose local PTY when remote access is wanted
103
103
  agentsam deploy Graduate to Cloudflare / GCP when ready
104
104
  agentsam dockerize Build/run app, knowledge, or CAD containers (--help)
105
- agentsam identity preview Local auth portal preview
105
+ agentsam identity preview Preview the reusable local auth portal (not production login)
106
106
  agentsam identity init Add reusable identity package surfaces
107
+ agentsam help
107
108
  agentsam --version
108
109
  agentsam --help
109
110
 
@@ -127,14 +128,14 @@ function printHelp() {
127
128
  --pretty Pretty-print JSON; machine JSON is compact by default
128
129
  --remote <name> Preferred Git remote (default origin; falls back to first remote)
129
130
 
130
- Init is completable with Node only — no IAM login, no OAuth, no Cloudflare.
131
- Prove locally first; deploy prompts for accounts only when you choose to ship.
131
+ Run agentsam from any project to enter the account-aware interactive experience.
132
+ Account, model-provider, terminal, and deploy permissions are requested only when the related capability needs them.
132
133
 
133
134
  Tunnel options:
134
135
  --quick Quick tunnel (default) — trycloudflare.com URL
135
136
  --named Named CF tunnel (needs --tunnel-name --hostname --zone-id)
136
137
  --port <n> Local PTY port (default 3099)
137
- --token <sdk_…> Use existing AGENTSAM_SDK_KEY (skip browser auth)
138
+ --token <aak_…> Use AGENTSAM_API_KEY-compatible account credential
138
139
 
139
140
  Init options:
140
141
  --name <name> Project directory name
@@ -227,11 +228,6 @@ async function runLocalInit(config) {
227
228
  console.log(` ${step}`);
228
229
  }
229
230
 
230
- const sdkKey = resolveAccountSdkKey({ env: process.env }).value;
231
- if (prompt && sdkKey) {
232
- console.log('\n Optional — BYOK keys for IAM dashboard Agent Sam (skip with Enter):\n');
233
- await promptOptionalByokKeys(sdkKey, prompt);
234
- }
235
231
 
236
232
  console.log(`
237
233
  Local means local: no Worker, tunnel, IAM login, or cloud database is required.
@@ -283,17 +279,12 @@ async function initInteractive(partial = {}) {
283
279
  if (runTarget !== 'local') {
284
280
  const { detectContext, missingForInit } = await import('./lib/detect-context.js');
285
281
  const ctx = await detectContext();
286
- if (missingForInit(ctx, resolveAccountSdkKey({ env: process.env }).value, { runTarget }).length) {
282
+ if (missingForInit(ctx, resolveAccountAuth({ env: process.env }).value, { runTarget }).length) {
287
283
  printContextSummary(ctx);
288
284
  }
289
285
  }
290
286
 
291
- const prompt = resolveAccountSdkKey({ env: process.env }).value ? createPrompt() : null;
292
- try {
293
- await runLocalInit({ projectName, lane: laneKey, runTarget, prompt });
294
- } finally {
295
- prompt?.close();
296
- }
287
+ await runLocalInit({ projectName, lane: laneKey, runTarget, prompt: null });
297
288
  outro(`Created ${projectName}`);
298
289
  }
299
290
 
@@ -311,6 +302,8 @@ const rest = process.argv.slice(3);
311
302
 
312
303
  if (command === '--version' || command === '-v') {
313
304
  console.log(VERSION);
305
+ } else if (command === 'help') {
306
+ await runHelp(rest, { version: VERSION });
314
307
  } else if (command === '--help' || command === '-h') {
315
308
  printHelp();
316
309
  } else if (!command) {
@@ -377,6 +370,20 @@ if (command === '--version' || command === '-v') {
377
370
  reportCliError(e);
378
371
  process.exit(1);
379
372
  }
373
+ } else if (command === 'providers') {
374
+ try {
375
+ await runProviders(rest);
376
+ } catch (e) {
377
+ reportCliError(e);
378
+ process.exit(1);
379
+ }
380
+ } else if (command === 'env') {
381
+ try {
382
+ await runEnv(rest);
383
+ } catch (e) {
384
+ reportCliError(e);
385
+ process.exit(1);
386
+ }
380
387
  } else if (command === 'eval') {
381
388
  try {
382
389
  await runEval(rest);
@@ -11,7 +11,7 @@ export async function runLogin(argv = [], options = {}) {
11
11
  const write = options.write || ((text) => process.stdout.write(text));
12
12
  const authenticate = options.authenticateImpl || authenticateViaBrowser;
13
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');
14
+ if (!String(session?.access_token || '').trim()) throw new Error('Agent Sam login did not return a browser session credential');
15
15
  // authenticateViaBrowser persists by default. Keep injected transports/test flows equivalent.
16
16
  if (!readAccountSession({ home: options.home })) saveAccountSession(session, { home: options.home });
17
17
  const status = await collectWhoami({ home: options.home, env: options.env || process.env, contextLoader: options.contextLoader });
@@ -12,7 +12,9 @@ function percent(value) {
12
12
 
13
13
  export function buildContextEconomicsReport(cwd, options = {}) {
14
14
  const preferences = options.preferences || readCliPreferences(cwd) || {};
15
- const model = getModelRecord(preferences.modelPreference);
15
+ const model = preferences.modelSnapshot?.model_key === preferences.modelPreference
16
+ ? preferences.modelSnapshot
17
+ : getModelRecord(preferences.modelPreference);
16
18
  if (!model) {
17
19
  return Object.freeze({
18
20
  model: preferences.modelPreference || 'auto',
@@ -24,9 +26,22 @@ export function buildContextEconomicsReport(cwd, options = {}) {
24
26
  });
25
27
  }
26
28
 
29
+ const windowTokens = Number(model.context_window);
30
+ if (!(Number.isFinite(windowTokens) && windowTokens > 0)) {
31
+ return Object.freeze({
32
+ model: model.provider_model_id,
33
+ model_key: model.model_key,
34
+ resolved: false,
35
+ reason: 'This provider verified the model but did not expose a trustworthy context-window limit. Agent Sam will show ctx unknown rather than guess.',
36
+ reasoning_effort: preferences.reasoningEffort || 'auto',
37
+ service_tier: preferences.serviceTier || 'default',
38
+ active_input_tokens: Number.isFinite(options.activeInputTokens) ? Math.floor(options.activeInputTokens) : null,
39
+ });
40
+ }
41
+
27
42
  const policy = model.context_policy || {};
28
43
  const budget = createContextBudget({
29
- windowTokens: model.context_window,
44
+ windowTokens,
30
45
  targetInputTokens: policy.target_input_tokens,
31
46
  compactAtTokens: policy.compact_at_tokens,
32
47
  interveneAtTokens: policy.intervene_at_tokens,
@@ -1,4 +1,4 @@
1
- import { tryResolveGitContext } from '../lib/git-context.js';
1
+ import { tryResolveGitContext } from '../../packages/agentsam-repository/src/git-context.js';
2
2
  import { resolveAgentSamBaseUrl, resolveBridgeKey } from '../lib/bridge-client.js';
3
3
  import { loadProjectRules } from '../lib/project-rules.js';
4
4
 
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { initializeLocalSqlite, inspectLocalSqlite } from '../local/sqlite.js';
3
+ import { createLocalSqliteDatabase, initializeLocalSqlite, inspectLocalSqlite } from '../local/sqlite.js';
4
+ import { applyRuntimeMigrations } from '../local/migrations.js';
4
5
  import { getLocalDatabasePath, getLocalSchemaPath, readProjectConfig } from '../lib/project-config.js';
5
6
 
6
7
  function findProjectRoot(startDir) {
@@ -23,7 +24,7 @@ function resolveDb(root, config) {
23
24
 
24
25
  export async function runDb(argv = [], opts = {}) {
25
26
  const sub = argv[0] || 'status';
26
- if (!['init', 'status'].includes(sub)) {
27
+ if (!['init', 'migrate', 'status'].includes(sub)) {
27
28
  throw new Error(`unknown db command: ${sub}`);
28
29
  }
29
30
 
@@ -36,10 +37,26 @@ export async function runDb(argv = [], opts = {}) {
36
37
  console.log(`\n Agent Sam local DB\n`);
37
38
  console.log(` ✓ SQLite ${result.dbPath}`);
38
39
  console.log(` ✓ Tables ${result.tables.length}`);
39
- console.log(` ✓ Schema ${paths.schemaPath}\n`);
40
+ console.log(` ✓ Schema ${paths.schemaPath}`);
41
+ console.log(' ✓ Migrations current\n');
40
42
  return result;
41
43
  }
42
44
 
45
+ if (sub === 'migrate') {
46
+ if (!fs.existsSync(paths.dbPath)) throw new Error('Local DB is not initialized — run `agentsam db init` first.');
47
+ const db = await createLocalSqliteDatabase(paths.dbPath);
48
+ try {
49
+ const result = await applyRuntimeMigrations(db);
50
+ console.log(`\n Agent Sam local DB migrations\n`);
51
+ console.log(` applied ${result.applied}`);
52
+ console.log(` known ${result.total}`);
53
+ console.log(` path ${paths.dbPath}\n`);
54
+ return result;
55
+ } finally {
56
+ db.close();
57
+ }
58
+ }
59
+
43
60
  const result = await inspectLocalSqlite(paths.dbPath);
44
61
  console.log(`\n Agent Sam local DB\n`);
45
62
  if (!result.exists) {
@@ -6,7 +6,7 @@ import path from 'node:path';
6
6
  import readline from 'node:readline';
7
7
  import { authenticateViaBrowser } from '../lib/auth.js';
8
8
  import { getJson, streamScaffold } from '../lib/core-client.js';
9
- import { resolveAccountSdkKey } from '../lib/account-session.js';
9
+ import { resolveAccountAuth } from '../lib/account-session.js';
10
10
  import { getDefaultProfile, getDeployTarget, getLocalSchemaPath, getProjectName, getProjectPreset, readProjectConfig, setDeployTarget, writeProjectConfig } from '../lib/project-config.js';
11
11
  import { isLocalStudioCheckout, runLocalStudioDeploy } from '../lib/deploy/local-studio.js';
12
12
 
@@ -68,7 +68,7 @@ function ask(question) {
68
68
  async function runCloudflareDeploy(cwd, config, accountId) {
69
69
  console.log('\n Cloudflare deploy — browser sign-in + resource provisioning…\n');
70
70
 
71
- let token = resolveAccountSdkKey({ env: process.env }).value;
71
+ let token = resolveAccountAuth({ env: process.env }).value;
72
72
  if (!token) {
73
73
  const session = await authenticateViaBrowser();
74
74
  token = session.access_token;
@@ -0,0 +1,90 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { describeProviderCredential, ensureProviderEnvProfile, providerCredentialSpec } from '../lib/provider-credentials.js';
3
+
4
+ const PROVIDERS = Object.freeze(['openai', 'anthropic', 'gemini', 'grok', 'cloudflare']);
5
+
6
+ function writeLine(write, value = '') { write(`${value}\n`); }
7
+
8
+ function detectCloudflareAccounts(options = {}) {
9
+ const env = options.env || process.env;
10
+ const explicit = String(options.accountId || env.ACCOUNT_ID || env.CLOUDFLARE_ACCOUNT_ID || '').trim();
11
+ if (explicit) return { accounts: [{ id: explicit, name: null }], source: 'environment' };
12
+ const spawn = options.spawnSyncImpl || spawnSync;
13
+ const result = spawn('npx', ['--no-install', 'wrangler', 'whoami', '--json'], { cwd: options.cwd || process.cwd(), env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
14
+ if (result?.status !== 0) return { accounts: [], source: 'wrangler', error: String(result?.stderr || '').trim() || `wrangler exited ${result?.status ?? 'unknown'}` };
15
+ try {
16
+ const parsed = JSON.parse(String(result.stdout || '{}'));
17
+ const accounts = (Array.isArray(parsed?.accounts) ? parsed.accounts : []).map((row) => ({
18
+ id: String(row?.id || row?.account_id || '').trim(),
19
+ name: String(row?.name || row?.account_name || '').trim() || null,
20
+ })).filter((row) => row.id);
21
+ return { accounts, source: 'wrangler', error: null };
22
+ } catch (error) {
23
+ return { accounts: [], source: 'wrangler', error: `invalid wrangler JSON: ${error?.message || error}` };
24
+ }
25
+ }
26
+
27
+ function cloudflareGuidance(write) {
28
+ writeLine(write, '');
29
+ writeLine(write, ' Cloudflare token guidance');
30
+ writeLine(write, ' models only Workers AI Read');
31
+ writeLine(write, ' run Workers AI Workers AI Read + Edit');
32
+ writeLine(write, ' deploy Workers Workers Editor for existing Workers; Admin only when create/delete is required');
33
+ writeLine(write, ' routes/domains add Workers Routes Write only when AgentSam must change them');
34
+ writeLine(write, ' D1 / R2 / KV add direct product permissions only when AgentSam must read/write those resources directly');
35
+ }
36
+
37
+ export async function runEnv(argv = [], options = {}) {
38
+ const write = options.write || ((text) => process.stdout.write(text));
39
+ const [command = 'status', providerArg] = argv;
40
+ let accountId = '';
41
+ for (let i = 2; i < argv.length; i += 1) {
42
+ if (argv[i] === '--account-id') accountId = String(argv[++i] || '').trim();
43
+ else throw new Error(`unexpected env argument: ${argv[i]}`);
44
+ }
45
+ if (command === '--help' || command === '-h' || command === 'help') {
46
+ writeLine(write, 'agentsam env init <openai|anthropic|gemini|grok|cloudflare> [--account-id <id>]');
47
+ writeLine(write, 'agentsam env status [provider]');
48
+ return;
49
+ }
50
+ if (command === 'init') {
51
+ const provider = String(providerArg || '').trim().toLowerCase();
52
+ if (!PROVIDERS.includes(provider)) throw new Error(`env init requires one of: ${PROVIDERS.join(', ')}`);
53
+ let cloudflareAccounts = null;
54
+ if (provider === 'cloudflare') {
55
+ if (accountId && !/^[a-f0-9]{32}$/i.test(accountId)) throw new Error('Cloudflare --account-id must be a 32-character hexadecimal account ID');
56
+ cloudflareAccounts = detectCloudflareAccounts({ ...options, accountId });
57
+ if (!accountId && cloudflareAccounts.accounts.length === 1) accountId = cloudflareAccounts.accounts[0].id;
58
+ }
59
+ const result = ensureProviderEnvProfile(provider, { ...options, accountId });
60
+ writeLine(write, '');
61
+ writeLine(write, ` AgentSam · ${provider} environment`);
62
+ writeLine(write, ` profile ${result.file}${result.created ? ' · created' : ' · existing'}`);
63
+ writeLine(write, ` loader ${result.loader}`);
64
+ writeLine(write, '');
65
+ writeLine(write, ' Add the credential to the profile, then load it into this shell:');
66
+ writeLine(write, ` ${result.source_command}`);
67
+ if (provider === 'cloudflare') {
68
+ if (accountId) writeLine(write, ` account detected/configured (${cloudflareAccounts?.source || 'explicit'})`);
69
+ else if ((cloudflareAccounts?.accounts || []).length > 1) writeLine(write, ` account ${cloudflareAccounts.accounts.length} Wrangler accounts found · rerun with --account-id <id>`);
70
+ else writeLine(write, ' account not detected · set ACCOUNT_ID in the profile or rerun with --account-id <id>');
71
+ cloudflareGuidance(write);
72
+ }
73
+ writeLine(write, '');
74
+ return result;
75
+ }
76
+ if (command === 'status') {
77
+ const providers = providerArg ? [String(providerArg).trim().toLowerCase()] : PROVIDERS;
78
+ for (const provider of providers) if (!providerCredentialSpec(provider)) throw new Error(`unsupported_provider:${provider}`);
79
+ writeLine(write, '');
80
+ writeLine(write, ' AgentSam · provider environments');
81
+ for (const provider of providers) {
82
+ const row = describeProviderCredential(provider, options);
83
+ const extra = row.account_id ? ` · account ${row.account_id}` : '';
84
+ writeLine(write, ` ${provider.padEnd(12)} ${row.configured ? 'configured' : row.error ? `blocked (${row.error})` : 'not configured'}${extra}`);
85
+ }
86
+ writeLine(write, '');
87
+ return;
88
+ }
89
+ throw new Error(`unknown env command: ${command}`);
90
+ }
@@ -7,13 +7,13 @@ import { promisify } from 'node:util';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import { randomUUID } from 'node:crypto';
9
9
  import pkg from '../../package.json' with { type: 'json' };
10
- import { CONFIG_PATH, repositoryRoot, initRepository, readConfig, scopeKey, cacheNamespace } from '../knowledge/config.js';
10
+ import { CONFIG_PATH, repositoryRoot, initRepository, readConfig, defaultConfig, scopeKey, cacheNamespace } from '../knowledge/config.js';
11
11
  import { openSqliteStore } from '../knowledge/stores/sqlite.js';
12
12
  import { openPostgresStore } from '../knowledge/stores/postgres.js';
13
13
  import { planIndex, runIndex, retrieve } from '../knowledge/engine.js';
14
14
  import { createGeminiEmbedder } from '../knowledge/providers/gemini.js';
15
15
  import { compareObservations } from '../knowledge/evolution.js';
16
- import { ensureProjectManifest, getProjectName, getRepositoryId } from '../lib/project-config.js';
16
+ import { ensureProjectManifest, getProjectName, getRepositoryId, portableRepositoryIdFromGit, tryReadProjectConfig } from '../lib/project-config.js';
17
17
  import { ensureProjectRules } from '../lib/project-rules.js';
18
18
 
19
19
  const execute = promisify(execFile);
@@ -25,6 +25,14 @@ async function openStore(root, config, readOnly = false) {
25
25
  return config.storage.driver === 'sqlite' ? openSqliteStore(localPath(root), { readOnly }) : openPostgresStore(process.env[config.storage.connection_env]);
26
26
  }
27
27
  function provider() { return createGeminiEmbedder({ apiKey: process.env.GEMINI_API_KEY }); }
28
+ function resolveKnowledgeConfig(root) {
29
+ const filename = path.join(root, CONFIG_PATH);
30
+ if (fs.existsSync(filename)) return readConfig(root);
31
+ const project = tryReadProjectConfig(root);
32
+ const repositoryId = getRepositoryId(project) || portableRepositoryIdFromGit(root);
33
+ if (!repositoryId) throw new Error(`${CONFIG_PATH} is absent and repository identity could not be derived; run agentsam init . --yes to configure this repository.`);
34
+ return defaultConfig({ repositoryId });
35
+ }
28
36
 
29
37
  export async function runRepositoryInit(argv) {
30
38
  const { values: opts, positionals } = flags(argv, { existing: { type: 'boolean' }, yes: { type: 'boolean', short: 'y' }, include: { type: 'string' }, exclude: { type: 'string' }, scope: { type: 'string' }, target: { type: 'string' }, dimensions: { type: 'string' } });
@@ -73,7 +81,7 @@ export async function runKnowledge(argv) {
73
81
  const command = positionals[0] || 'plan';
74
82
  if (opts.help) { console.log('agentsam index plan|run|status|history|show|setup-store [--cwd PATH] [--embed] [--max-inputs 100] [--max-characters 200000] [--generation ID] [--json]\nplan is read-only; run defaults to AST/text only; --embed sends selected chunks to the configured provider.'); return; }
75
83
  if (positionals.length > 1 || !['plan', 'run', 'status', 'history', 'show', 'setup-store'].includes(command)) throw new Error('Unknown index command; use agentsam index --help.');
76
- const root = repositoryRoot(opts.cwd), config = readConfig(root);
84
+ const root = repositoryRoot(opts.cwd), config = resolveKnowledgeConfig(root);
77
85
  const store = await openStore(root, config, !['run', 'setup-store'].includes(command));
78
86
  try {
79
87
  if (command === 'setup-store') {
@@ -97,7 +105,7 @@ export async function runKnowledge(argv) {
97
105
  export async function runSearch(argv) {
98
106
  const { values: opts, positionals } = flags(argv, { semantic: { type: 'boolean' }, 'top-k': { type: 'string' }, 'token-budget': { type: 'string' }, generation: { type: 'string' } });
99
107
  if (opts.help) { console.log('agentsam search "query" [--cwd PATH] [--semantic] [--top-k 8] [--token-budget 6000] [--generation ID]'); return; }
100
- const root = repositoryRoot(opts.cwd), config = readConfig(root), store = await openStore(root, config, true);
108
+ const root = repositoryRoot(opts.cwd), config = resolveKnowledgeConfig(root), store = await openStore(root, config, true);
101
109
  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 })); }
102
110
  finally { await store?.close(); }
103
111
  }
@@ -1,10 +1,12 @@
1
1
  import path from 'node:path';
2
- import { readSnapshot } from '../lib/merkle/snapshot.js';
2
+ import { readSnapshot } from '../../packages/agentsam-repository/src/merkle/snapshot.js';
3
+ import { readAccountSession } from '../lib/account-session.js';
4
+ import { getRepositoryId, portableRepositoryIdFromGit, tryReadProjectConfig } from '../lib/project-config.js';
3
5
  import {
4
6
  buildMerklePersistencePlan,
5
7
  persistMerkleSnapshotCloudflare,
6
8
  resolveWranglerMerklePersistence,
7
- } from '../lib/merkle/cloudflare-persistence.js';
9
+ } from '../../packages/agentsam-repository/src/merkle/cloudflare-persistence.js';
8
10
 
9
11
  function value(args, index, flag) {
10
12
  const next = args[index + 1];
@@ -17,8 +19,6 @@ export function printMerklePersistHelp() {
17
19
  agentsam merkle persist <snapshot.json> — publish a saved Merkle snapshot through host bindings
18
20
 
19
21
  --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
22
  --capture-kind <kind> deploy|manual|agent|index (default manual)
23
23
  --connection-id <id> Execution provenance for non-deploy captures
24
24
  --runtime-lease-id <id> Alternative execution provenance for non-deploy captures
@@ -35,9 +35,11 @@ export function printMerklePersistHelp() {
35
35
  --dry-run Resolve bindings and emit the exact storage/index plan without writes
36
36
  --json Machine-readable output
37
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.
38
+ CLI ownership comes from the authenticated AgentSam session, while repository identity is
39
+ derived from Git/provider identity (with the committed project manifest as local fallback).
40
+ Programmatic hosts pass account_id + repository_id directly to the persistence plan. Physical
41
+ bucket/database names come from Wrangler bindings, so customer installs keep WEBSITE_ASSETS/DB
42
+ while selecting their own storage resources.
41
43
  `);
42
44
  }
43
45
 
@@ -55,7 +57,7 @@ function parse(args) {
55
57
  if (arg === '--dry-run') { opts.dryRun = true; continue; }
56
58
  if (arg === '--r2-only') { opts.r2Only = true; continue; }
57
59
  const map = {
58
- '--wrangler-config': 'wranglerConfig', '--owner-user-id': 'ownerUserId', '--repo-id': 'repoId',
60
+ '--wrangler-config': 'wranglerConfig',
59
61
  '--capture-kind': 'captureKind', '--connection-id': 'connectionId', '--runtime-lease-id': 'runtimeLeaseId',
60
62
  '--deployment-id': 'deploymentId', '--worker-version': 'workerVersionId', '--reference-label': 'referenceLabel',
61
63
  '--source': 'source', '--prefix': 'storagePrefix', '--r2-binding': 'r2Binding', '--d1-binding': 'd1Binding',
@@ -65,19 +67,36 @@ function parse(args) {
65
67
  throw new Error(`Unknown merkle persist option: ${arg}`);
66
68
  }
67
69
  if (!opts.snapshotPath) throw new Error('snapshot_file_required');
68
- opts.ownerUserId ||= process.env.AGENTSAM_OWNER_USER_ID || '';
69
70
  opts.wranglerConfig ||= process.env.AGENTSAM_WRANGLER_CONFIG || '';
70
71
  opts.connectionId ||= process.env.AGENTSAM_CONNECTION_ID || '';
71
72
  opts.runtimeLeaseId ||= process.env.AGENTSAM_RUNTIME_LEASE_ID || '';
72
73
  return opts;
73
74
  }
74
75
 
76
+ export function resolveMerklePersistenceIdentity(root, options = {}) {
77
+ const session = options.session ?? readAccountSession(options.sessionOptions || {});
78
+ const accountId = String(session?.account_id || '').trim();
79
+ if (!accountId) throw new Error('agentsam_login_required_for_merkle_persistence');
80
+
81
+ const projectConfig = options.projectConfig ?? tryReadProjectConfig(root);
82
+ const gitRepositoryId = portableRepositoryIdFromGit(root);
83
+ const repositoryId = gitRepositoryId || getRepositoryId(projectConfig);
84
+ if (!repositoryId) throw new Error('repository_identity_unresolved');
85
+
86
+ return {
87
+ accountId,
88
+ repositoryId,
89
+ repositoryIdentitySource: gitRepositoryId ? 'git' : 'project_manifest',
90
+ };
91
+ }
92
+
75
93
  export async function runMerklePersist(args = []) {
76
94
  const opts = parse(args);
77
95
  if (opts.help) { printMerklePersistHelp(); return null; }
78
96
  const snapshotPath = path.resolve(opts.snapshotPath);
79
97
  const snapshot = await readSnapshot(snapshotPath);
80
98
  const root = path.resolve(opts.root || snapshot.rootPath || process.cwd());
99
+ const identity = resolveMerklePersistenceIdentity(root);
81
100
  const wrangler = resolveWranglerMerklePersistence({
82
101
  configPath: opts.wranglerConfig,
83
102
  environment: opts.environment || null,
@@ -88,8 +107,8 @@ export async function runMerklePersist(args = []) {
88
107
  const plan = buildMerklePersistencePlan({
89
108
  snapshot,
90
109
  root,
91
- ownerUserId: opts.ownerUserId,
92
- repoId: opts.repoId,
110
+ accountId: identity.accountId,
111
+ repositoryId: identity.repositoryId,
93
112
  source: opts.source,
94
113
  captureKind: opts.captureKind,
95
114
  connectionId: opts.connectionId,
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { buildMerkleTree, saveSnapshot, readSnapshot, diffTrees, normalizePolicy } from '../lib/merkle/index.js';
3
+ import { buildMerkleTree, saveSnapshot, readSnapshot, diffTrees, normalizePolicy } from '../../packages/agentsam-repository/src/merkle/index.js';
4
4
  import { renderSummary, safeText } from '../ui/merkle/render.js';
5
5
  import { runMerkleExplorer } from '../ui/merkle/explorer.js';
6
6
  import { runMerklePersist } from './merkle-persist.js';