@inneranimalmedia/agentsam-sdk 2.6.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/docs/PLATFORM_RUNTIME_EVENTS.md +48 -0
  2. package/docs/RELEASES.md +7 -7
  3. package/docs/SOURCE_ARCHITECTURE.md +58 -0
  4. package/docs/TEST_TIERS.md +26 -0
  5. package/migrations/runtime/0001_cli_runtime.sql +298 -0
  6. package/package.json +28 -7
  7. package/packages/agentsam-repository/README.md +15 -0
  8. package/packages/agentsam-repository/package.json +25 -0
  9. package/packages/agentsam-repository/src/contracts.js +113 -0
  10. package/packages/agentsam-repository/src/index.js +3 -0
  11. package/{src/lib → packages/agentsam-repository/src}/merkle/cloudflare-persistence.js +14 -24
  12. package/{src/lib → packages/agentsam-repository/src}/merkle/index.js +1 -0
  13. package/{src/lib → packages/agentsam-repository/src}/merkle/persistence.js +6 -4
  14. package/{src/lib → packages/agentsam-repository/src}/merkle/policy.js +1 -0
  15. package/packages/agentsam-repository/test/contracts.test.mjs +40 -0
  16. package/packages/agentsam-repository/test/git-context.test.mjs +24 -0
  17. package/{test/merkle.test.mjs → packages/agentsam-repository/test/merkle-core.test.mjs} +2 -32
  18. package/{test → packages/agentsam-repository/test}/merkle-persistence.test.mjs +11 -6
  19. package/packages/identity/package.json +1 -1
  20. package/protocol/COMPANY_REPOSITORY_GRAPH_V1.md +91 -0
  21. package/protocol/MERKLE_PERSISTENCE_V1.md +2 -0
  22. package/protocol/MERKLE_PERSISTENCE_V2.md +40 -0
  23. package/protocol/repository/repository-contract.schema.json +24 -0
  24. package/protocol/repository/repository-dependency.schema.json +24 -0
  25. package/protocol/repository/repository-identity.schema.json +17 -0
  26. package/protocol/rpc/v1/common.proto +16 -0
  27. package/protocol/rpc/v1/errors.proto +35 -0
  28. package/protocol/rpc/v1/knowledge.proto +77 -0
  29. package/services/knowledge/package-lock.json +333 -0
  30. package/services/knowledge/package.json +5 -1
  31. package/src/agent/responses-runner.js +63 -35
  32. package/src/capabilities/repository-snapshot.js +3 -3
  33. package/src/cli.js +23 -6
  34. package/src/commands/context-economics.js +17 -2
  35. package/src/commands/context.js +1 -1
  36. package/src/commands/db.js +20 -3
  37. package/src/commands/env.js +90 -0
  38. package/src/commands/knowledge.js +12 -4
  39. package/src/commands/merkle-persist.js +30 -11
  40. package/src/commands/merkle.js +1 -1
  41. package/src/commands/models.js +123 -65
  42. package/src/commands/ollama.js +26 -0
  43. package/src/commands/preferences.js +53 -26
  44. package/src/commands/shell.js +236 -48
  45. package/src/errors/contract.js +236 -0
  46. package/src/errors/index.js +14 -0
  47. package/src/index.js +13 -1
  48. package/src/knowledge/service/auth.js +13 -0
  49. package/src/knowledge/service/grpc-client.js +115 -0
  50. package/src/knowledge/service/grpc-codec.js +237 -0
  51. package/src/knowledge/service/grpc-server.js +83 -0
  52. package/src/knowledge/service/job-engine.js +248 -0
  53. package/src/knowledge/service/server.js +87 -135
  54. package/src/knowledge/source.js +1 -1
  55. package/src/lib/cli-preferences.js +31 -4
  56. package/src/lib/deploy-receipt/index.js +2 -2
  57. package/src/lib/knowledge-docker.js +6 -3
  58. package/src/lib/local-sessions.js +23 -2
  59. package/src/lib/local-status.js +1 -1
  60. package/src/lib/project-config.js +1 -1
  61. package/src/lib/provider-credentials.js +105 -5
  62. package/src/lib/slash-commands.js +4 -3
  63. package/src/local/migrations.js +93 -0
  64. package/src/local/runtime-store.js +141 -0
  65. package/src/local/sqlite.js +2 -0
  66. package/src/local-pty/server.js +113 -51
  67. package/src/models/discovery.js +292 -0
  68. package/src/providers/anthropic-messages.js +192 -0
  69. package/src/providers/cloudflare-chat.js +183 -0
  70. package/src/providers/factory.js +69 -0
  71. package/src/providers/gemini-generate-content.js +208 -0
  72. package/src/providers/index.js +5 -0
  73. package/src/providers/ollama-chat.js +148 -0
  74. package/src/providers/openai-responses.js +226 -75
  75. package/src/repository/index.js +14 -2
  76. package/src/rpc/generated/common_grpc_pb.js +1 -0
  77. package/src/rpc/generated/common_pb.js +536 -0
  78. package/src/rpc/generated/errors_grpc_pb.js +1 -0
  79. package/src/rpc/generated/errors_pb.js +482 -0
  80. package/src/rpc/generated/knowledge_grpc_pb.js +135 -0
  81. package/src/rpc/generated/knowledge_pb.js +2168 -0
  82. package/src/rpc/generated/package.json +3 -0
  83. package/src/security/trust-boundary.js +2 -2
  84. package/src/telemetry/events.js +4 -1
  85. package/src/ui/cli/activity.js +76 -0
  86. package/src/ui/cli/compaction.js +15 -0
  87. package/src/ui/cli/footer.js +39 -0
  88. package/src/ui/cli/help.js +192 -0
  89. package/src/ui/cli/plan.js +20 -0
  90. package/src/ui/cli/runtime-events.js +110 -0
  91. package/src/ui/cli/waiting.js +16 -0
  92. package/src/ui/merkle/render.js +1 -1
  93. package/test/cli/preferences-runtime.test.mjs +11 -0
  94. package/test/cli/runtime-ui.test.mjs +74 -0
  95. package/test/error-diagnostics.test.mjs +57 -1
  96. package/test/fixtures/knowledge-rpc-worker.mjs +16 -0
  97. package/test/integration/cli-help.test.mjs +37 -0
  98. package/test/integration/knowledge-rpc.test.mjs +112 -0
  99. package/test/integration/merkle-cli.test.mjs +61 -0
  100. package/test/integration/merkle-persistence-identity.test.mjs +48 -0
  101. package/test/integration/provider-env-cli.test.mjs +49 -0
  102. package/test/integration/provider-factory.test.mjs +197 -0
  103. package/test/integration/repository-company-graph.test.mjs +90 -0
  104. package/test/integration/runtime-migrations.test.mjs +82 -0
  105. package/test/knowledge-service.test.mjs +5 -0
  106. package/test/knowledge.test.mjs +16 -0
  107. package/test/live/terminal-transport.live.test.mjs +24 -0
  108. package/test/local-sessions.test.mjs +7 -1
  109. package/test/models.test.mjs +101 -4
  110. package/test/ollama.test.mjs +21 -0
  111. package/test/portable-context.test.mjs +1 -1
  112. package/test/provider-credentials.test.mjs +45 -1
  113. package/test/release-hygiene.test.mjs +13 -5
  114. package/test/responses-runner.test.mjs +3 -1
  115. package/test/shell.test.mjs +50 -8
  116. package/test/terminal/local-pty.mock.test.mjs +151 -0
  117. /package/{src/lib → packages/agentsam-repository/src}/git-context.js +0 -0
  118. /package/{src/lib → packages/agentsam-repository/src}/merkle/diff.js +0 -0
  119. /package/{src/lib → packages/agentsam-repository/src}/merkle/filemeta.js +0 -0
  120. /package/{src/lib → packages/agentsam-repository/src}/merkle/git-ignore.js +0 -0
  121. /package/{src/lib → packages/agentsam-repository/src}/merkle/hash.js +0 -0
  122. /package/{src/lib → packages/agentsam-repository/src}/merkle/semantic.js +0 -0
  123. /package/{src/lib → packages/agentsam-repository/src}/merkle/snapshot.js +0 -0
  124. /package/{src/lib → packages/agentsam-repository/src}/merkle/tree.js +0 -0
@@ -23,9 +23,11 @@ function userMessage(text) {
23
23
  }
24
24
 
25
25
  function modelBudget(record) {
26
+ const windowTokens = Number(record?.context_window);
27
+ if (!Number.isFinite(windowTokens) || windowTokens <= 0) return null;
26
28
  const policy = record.context_policy || {};
27
29
  return createContextBudget({
28
- windowTokens: record.context_window,
30
+ windowTokens,
29
31
  targetInputTokens: policy.target_input_tokens,
30
32
  compactAtTokens: policy.compact_at_tokens,
31
33
  interveneAtTokens: policy.intervene_at_tokens,
@@ -102,6 +104,7 @@ function boundedToolOutput(value, callId, maxChars) {
102
104
  }
103
105
 
104
106
  function toolResultCharBudget(activeTokens, budget) {
107
+ if (!budget) return 48_000;
105
108
  if (!Number.isFinite(activeTokens) || activeTokens < 0) return budget.maxToolResultChars;
106
109
  const reserveTokens = 4_000;
107
110
  const headroomTokens = Math.max(256, budget.maxNormalInputTokens - Math.ceil(activeTokens) - reserveTokens);
@@ -110,12 +113,13 @@ function toolResultCharBudget(activeTokens, budget) {
110
113
 
111
114
  function projectedInputTokens({ instructions, input, toolSurface, priorActiveTokens, budget }) {
112
115
  const inputChars = typeof input === 'string' ? input.length : JSON.stringify(input ?? '').length;
113
- const newTokens = estimateContextTokens(String(instructions || '').length + inputChars + toolSurface.receipt.hydrated_schema_chars, budget.charsPerToken);
116
+ const charsPerToken = budget?.charsPerToken || 4;
117
+ const newTokens = estimateContextTokens(String(instructions || '').length + inputChars + toolSurface.receipt.hydrated_schema_chars, charsPerToken);
114
118
  return Math.max(newTokens, Number.isFinite(priorActiveTokens) ? Math.ceil(priorActiveTokens) + newTokens : newTokens);
115
119
  }
116
120
 
117
121
  function assertEconomicPreflight(projectedTokens, budget, allowOverride) {
118
- if (allowOverride) return;
122
+ if (!budget || allowOverride) return;
119
123
  if (budget.pricingThresholdTokens != null && projectedTokens > budget.pricingThresholdTokens) {
120
124
  throw new Error(`context_preflight_pricing_threshold:${projectedTokens}>${budget.pricingThresholdTokens}`);
121
125
  }
@@ -130,46 +134,56 @@ export async function runResponsesAgent(options = {}) {
130
134
  const cwd = path.resolve(options.cwd || process.cwd());
131
135
  const objective = clean(options.prompt);
132
136
  if (!objective) throw new TypeError('prompt is required');
133
- const record = getModelRecord(options.model);
137
+ const record = options.modelRecord || getModelRecord(options.model);
134
138
  if (!record) throw new RangeError(`unknown model: ${options.model}`);
135
- const reasoningEffort = clean(options.reasoningEffort || 'low');
139
+ const reasoningEffort = clean(options.reasoningEffort || 'auto');
136
140
  const serviceTier = clean(options.serviceTier || 'default');
137
141
  const budget = modelBudget(record);
138
- const instructionSet = options.instructions == null ? compileAgentInstructions(cwd, { maxChars: budget.maxSystemChars }) : null;
142
+ const instructionSet = options.instructions == null ? compileAgentInstructions(cwd, { maxChars: budget?.maxSystemChars || 48_000 }) : null;
139
143
  const instructions = options.instructions == null ? instructionSet.content : String(options.instructions);
140
144
  const toolSurface = buildAgentToolSurface(options.capabilityAdapter, objective, options);
141
145
  const emit = options.emit;
142
146
  const runId = options.runId;
143
147
  event(emit, 'tool.search', toolSurface.receipt, runId);
144
148
 
145
- let previousResponseId = clean(options.previousResponseId) || null;
149
+ let previousResponseId = clean(options.previousResponseId || options.previousProviderState?.previous_response_id) || null;
150
+ let providerState = options.previousProviderState && typeof options.previousProviderState === 'object'
151
+ ? structuredClone(options.previousProviderState)
152
+ : previousResponseId ? { previous_response_id: previousResponseId } : null;
146
153
  let priorActiveTokens = options.previousUsageSnapshot?.current_context?.input_tokens;
147
154
  let input = objective;
148
155
  let compacted = null;
149
156
  let projected = projectedInputTokens({ instructions, input, toolSurface, priorActiveTokens, budget });
150
157
 
151
- if (projected >= budget.compactAtTokens && previousResponseId && options.autoCompact !== false && typeof provider.compact === 'function') {
158
+ if (budget && projected >= budget.compactAtTokens && providerState && options.autoCompact !== false && typeof provider.compact === 'function') {
152
159
  compacted = await provider.compact({
153
160
  model: record.provider_model_id,
161
+ modelRecord: record,
154
162
  previousResponseId,
163
+ providerState,
155
164
  instructions,
156
165
  promptCacheKey: options.promptCacheKey,
166
+ tokensBefore: Number.isFinite(priorActiveTokens) ? priorActiveTokens : projected,
157
167
  emit,
158
168
  runId,
159
169
  });
160
- input = [...(compacted.output || []), userMessage(objective)];
161
- previousResponseId = null;
170
+ if (Array.isArray(compacted.output) && compacted.output.length) input = [...compacted.output, userMessage(objective)];
171
+ providerState = compacted.provider_state || null;
172
+ previousResponseId = clean(providerState?.previous_response_id) || null;
162
173
  priorActiveTokens = null;
163
174
  projected = projectedInputTokens({ instructions, input, toolSurface, priorActiveTokens, budget });
164
175
  }
165
176
 
166
177
  assertEconomicPreflight(projected, budget, options.allowEconomicOverride === true);
167
- const pressure = assessContextUsage(projected, budget);
178
+ const pressure = budget ? assessContextUsage(projected, budget) : null;
179
+ const declaredMaxOutput = Number(record.max_output_tokens);
168
180
  const maxOutputTokens = Number.isInteger(options.maxOutputTokens) && options.maxOutputTokens > 0
169
- ? Math.min(options.maxOutputTokens, record.max_output_tokens)
170
- : Math.min(32_768, record.max_output_tokens);
171
- const projectedCost = calculateModelCost(record, { input_tokens: projected, output_tokens: maxOutputTokens }, { serviceTier });
172
- if (Number.isFinite(options.maxCallCostUsd) && projectedCost.total_usd > options.maxCallCostUsd) {
181
+ ? (Number.isFinite(declaredMaxOutput) && declaredMaxOutput > 0 ? Math.min(options.maxOutputTokens, declaredMaxOutput) : options.maxOutputTokens)
182
+ : (Number.isFinite(declaredMaxOutput) && declaredMaxOutput > 0 ? Math.min(32_768, declaredMaxOutput) : 16_384);
183
+ const projectedCost = record.pricing
184
+ ? calculateModelCost(record, { input_tokens: projected, output_tokens: maxOutputTokens }, { serviceTier })
185
+ : null;
186
+ if (Number.isFinite(options.maxCallCostUsd) && projectedCost && projectedCost.total_usd > options.maxCallCostUsd) {
173
187
  throw new Error(`projected_call_cost_exceeds_budget:${projectedCost.total_usd.toFixed(6)}>${Number(options.maxCallCostUsd).toFixed(6)}`);
174
188
  }
175
189
  const preflight = Object.freeze({
@@ -178,9 +192,9 @@ export async function runResponsesAgent(options = {}) {
178
192
  service_tier: serviceTier,
179
193
  estimated_input_tokens: projected,
180
194
  max_output_tokens: maxOutputTokens,
181
- projected_max_call_cost_usd: projectedCost.total_usd,
182
- pricing_threshold_tokens: budget.pricingThresholdTokens,
183
- tokens_until_pricing_threshold: pressure.tokensUntilPricingThreshold,
195
+ projected_max_call_cost_usd: projectedCost?.total_usd ?? null,
196
+ pricing_threshold_tokens: budget?.pricingThresholdTokens ?? null,
197
+ tokens_until_pricing_threshold: pressure?.tokensUntilPricingThreshold ?? null,
184
198
  compacted_before_turn: Boolean(compacted),
185
199
  estimate_kind: 'local',
186
200
  });
@@ -191,32 +205,40 @@ export async function runResponsesAgent(options = {}) {
191
205
  event(emit, 'context.snapshot', {
192
206
  estimate_kind: 'local',
193
207
  estimated_input_tokens: projected,
194
- window_tokens: budget.windowTokens,
195
- utilization_ratio: pressure.utilizationRatio,
196
- pricing_threshold_tokens: budget.pricingThresholdTokens,
197
- tokens_until_pricing_threshold: pressure.tokensUntilPricingThreshold,
198
- pressure: pressure.stage,
199
- projected_max_call_cost_usd: projectedCost.total_usd,
208
+ window_tokens: budget?.windowTokens ?? null,
209
+ utilization_ratio: pressure?.utilizationRatio ?? null,
210
+ pricing_threshold_tokens: budget?.pricingThresholdTokens ?? null,
211
+ tokens_until_pricing_threshold: pressure?.tokensUntilPricingThreshold ?? null,
212
+ pressure: pressure?.stage ?? 'unknown',
213
+ projected_max_call_cost_usd: projectedCost?.total_usd ?? null,
200
214
  tool_surface: toolSurface.receipt,
201
215
  }, runId);
202
216
 
203
217
  let cumulativeUsage = options.cumulativeUsage || null;
204
218
  let totalCostUsd = 0;
219
+ const costBreakdownUsd = { input: 0, cached_input: 0, cache_write: 0, output: 0 };
220
+ const accumulateCost = (cost) => {
221
+ totalCostUsd += Number(cost?.total_usd || 0);
222
+ for (const key of Object.keys(costBreakdownUsd)) costBreakdownUsd[key] += Number(cost?.components_usd?.[key] || 0);
223
+ };
205
224
  let response = await provider.create({
206
225
  model: record.provider_model_id,
226
+ modelRecord: record,
207
227
  input,
208
228
  instructions,
209
229
  reasoningEffort,
210
230
  serviceTier,
211
231
  tools: toolSurface.tools,
212
232
  previousResponseId: previousResponseId || undefined,
233
+ providerState,
213
234
  maxOutputTokens,
214
235
  promptCacheKey: options.promptCacheKey,
215
236
  cumulativeUsage,
216
237
  emit,
217
238
  runId,
218
239
  });
219
- totalCostUsd += response.cost?.total_usd || 0;
240
+ accumulateCost(response.cost);
241
+ providerState = response.provider_state || (response.response_id ? { previous_response_id: response.response_id } : providerState);
220
242
  cumulativeUsage = response.usage_snapshot?.cumulative || cumulativeUsage;
221
243
 
222
244
  const toolReceipts = [];
@@ -275,6 +297,8 @@ export async function runResponsesAgent(options = {}) {
275
297
  response = await provider.continueWithToolOutputs({
276
298
  model: record.provider_model_id,
277
299
  previousResponseId: response.response_id,
300
+ providerState: response.provider_state || providerState,
301
+ modelRecord: record,
278
302
  toolOutputs: outputs,
279
303
  instructions,
280
304
  reasoningEffort,
@@ -286,21 +310,22 @@ export async function runResponsesAgent(options = {}) {
286
310
  emit,
287
311
  runId,
288
312
  });
289
- totalCostUsd += response.cost?.total_usd || 0;
313
+ accumulateCost(response.cost);
314
+ providerState = response.provider_state || (response.response_id ? { previous_response_id: response.response_id } : providerState);
290
315
  cumulativeUsage = response.usage_snapshot?.cumulative || cumulativeUsage;
291
316
  }
292
317
 
293
318
  const active = response.usage_snapshot?.current_context?.input_tokens ?? 0;
294
- const finalPressure = assessContextUsage(active, budget);
319
+ const finalPressure = budget ? assessContextUsage(active, budget) : null;
295
320
  event(emit, 'context.snapshot', {
296
321
  estimate_kind: 'provider',
297
322
  active_input_tokens: active,
298
- window_tokens: budget.windowTokens,
299
- utilization_ratio: finalPressure.utilizationRatio,
300
- pricing_threshold_tokens: budget.pricingThresholdTokens,
301
- tokens_until_pricing_threshold: finalPressure.tokensUntilPricingThreshold,
302
- pressure: finalPressure.stage,
303
- compact_before_next_turn: finalPressure.shouldCompact,
323
+ window_tokens: budget?.windowTokens ?? null,
324
+ utilization_ratio: finalPressure?.utilizationRatio ?? null,
325
+ pricing_threshold_tokens: budget?.pricingThresholdTokens ?? null,
326
+ tokens_until_pricing_threshold: finalPressure?.tokensUntilPricingThreshold ?? null,
327
+ pressure: finalPressure?.stage ?? 'unknown',
328
+ compact_before_next_turn: finalPressure?.shouldCompact ?? false,
304
329
  }, runId);
305
330
 
306
331
  return Object.freeze({
@@ -313,13 +338,16 @@ export async function runResponsesAgent(options = {}) {
313
338
  usage_snapshot: response.usage_snapshot,
314
339
  cumulative_usage: cumulativeUsage,
315
340
  total_cost_usd: totalCostUsd,
341
+ cost_breakdown_usd: Object.freeze({ ...costBreakdownUsd }),
316
342
  tool_surface: toolSurface.receipt,
317
343
  tool_receipts: Object.freeze(toolReceipts),
318
344
  compacted_before_turn: Boolean(compacted),
345
+ provider_state: providerState,
319
346
  continuation: Object.freeze({
320
- previous_response_id: response.response_id,
347
+ previous_response_id: response.response_id || providerState?.previous_response_id || null,
348
+ provider_state: providerState,
321
349
  usage_snapshot: response.usage_snapshot,
322
- compact_before_next_turn: finalPressure.shouldCompact,
350
+ compact_before_next_turn: finalPressure?.shouldCompact ?? false,
323
351
  }),
324
352
  });
325
353
  }
@@ -4,10 +4,10 @@ import { execFile } from 'node:child_process';
4
4
  import { promisify } from 'node:util';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { createHash } from 'node:crypto';
7
- import { resolveGitContext } from '../lib/git-context.js';
7
+ import { resolveGitContext } from '../../packages/agentsam-repository/src/git-context.js';
8
8
  import { getRepositoryId, tryReadProjectConfig } from '../lib/project-config.js';
9
- import { buildMerkleTree } from '../lib/merkle/index.js';
10
- import { gitIgnoredPaths } from '../lib/merkle/git-ignore.js';
9
+ import { buildMerkleTree } from '../../packages/agentsam-repository/src/merkle/index.js';
10
+ import { gitIgnoredPaths } from '../../packages/agentsam-repository/src/merkle/git-ignore.js';
11
11
  import { analyzeExecutionBoundaries } from '../indexing/execution-boundary.js';
12
12
  import { showLatestDeployReceipt } from '../lib/deploy-receipt/index.js';
13
13
  import { CONFIG_PATH, canonical, readConfig, scopeKey } from '../knowledge/config.js';
package/src/cli.js CHANGED
@@ -13,6 +13,7 @@ import { promptOptionalByokKeys } from './lib/prompt-byok.js';
13
13
  import { runStartLocal } from './commands/start-local.js';
14
14
  import { runOllama } from './commands/ollama.js';
15
15
  import { runModels } from './commands/models.js';
16
+ import { runEnv } from './commands/env.js';
16
17
  import { runTunnel } from './commands/tunnel.js';
17
18
  import { runDeploy } from './commands/deploy.js';
18
19
  import { runConnections } from './commands/connections.js';
@@ -42,6 +43,7 @@ import fs from 'node:fs';
42
43
  import { repositoryRoot } from './knowledge/config.js';
43
44
  import { resolveAccountSdkKey } from './lib/account-session.js';
44
45
  import { renderDiagnosticError } from './errors/index.js';
46
+ import { renderHelpOverview, runHelp } from './ui/cli/help.js';
45
47
 
46
48
  const VERSION = pkg.version;
47
49
 
@@ -60,6 +62,10 @@ function createPrompt() {
60
62
  }
61
63
 
62
64
  function printHelp() {
65
+ console.log(renderHelpOverview(VERSION));
66
+ }
67
+
68
+ function printLegacyHelp() {
63
69
  console.log(`
64
70
  Agent Sam SDK — CLI v${VERSION}
65
71
 
@@ -90,9 +96,10 @@ function printHelp() {
90
96
  agentsam status [--json] Live local Git + DB + API + PTY status
91
97
  agentsam db init|status Manage the project-local SQLite database
92
98
  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
99
+ agentsam env init <name> Create a secure provider profile + reusable shell loader
100
+ agentsam login Sign in to Inner Animal Media and persist a secure machine-local session
101
+ agentsam logout Sign out locally; provider credentials stay untouched
102
+ agentsam whoami [--json] Authenticated account identity + safe credential status
96
103
  agentsam resume [session] Resume a saved Agent Sam session; omit id for picker
97
104
  agentsam eval context Offline context-strategy/economics fixtures (--help)
98
105
  agentsam cloudflare Native Wrangler reads + Worker CPU profile analysis (--help)
@@ -102,8 +109,9 @@ function printHelp() {
102
109
  agentsam tunnel Explicitly expose local PTY when remote access is wanted
103
110
  agentsam deploy Graduate to Cloudflare / GCP when ready
104
111
  agentsam dockerize Build/run app, knowledge, or CAD containers (--help)
105
- agentsam identity preview Local auth portal preview
112
+ agentsam identity preview Preview the reusable local auth portal (not production login)
106
113
  agentsam identity init Add reusable identity package surfaces
114
+ agentsam help
107
115
  agentsam --version
108
116
  agentsam --help
109
117
 
@@ -127,8 +135,8 @@ function printHelp() {
127
135
  --pretty Pretty-print JSON; machine JSON is compact by default
128
136
  --remote <name> Preferred Git remote (default origin; falls back to first remote)
129
137
 
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.
138
+ Run agentsam from any project to enter the account-aware interactive experience.
139
+ Account, model-provider, terminal, and deploy permissions are requested only when the related capability needs them.
132
140
 
133
141
  Tunnel options:
134
142
  --quick Quick tunnel (default) — trycloudflare.com URL
@@ -311,6 +319,8 @@ const rest = process.argv.slice(3);
311
319
 
312
320
  if (command === '--version' || command === '-v') {
313
321
  console.log(VERSION);
322
+ } else if (command === 'help') {
323
+ await runHelp(rest, { version: VERSION });
314
324
  } else if (command === '--help' || command === '-h') {
315
325
  printHelp();
316
326
  } else if (!command) {
@@ -377,6 +387,13 @@ if (command === '--version' || command === '-v') {
377
387
  reportCliError(e);
378
388
  process.exit(1);
379
389
  }
390
+ } else if (command === 'env') {
391
+ try {
392
+ await runEnv(rest);
393
+ } catch (e) {
394
+ reportCliError(e);
395
+ process.exit(1);
396
+ }
380
397
  } else if (command === 'eval') {
381
398
  try {
382
399
  await runEval(rest);
@@ -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) {
@@ -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,