@inneranimalmedia/agentsam-sdk 2.4.1 → 2.5.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 (158) hide show
  1. package/README.md +7 -6
  2. package/docs/AGENTSAM_WORKBENCH.md +30 -0
  3. package/docs/AUTH_IDENTITY_CONTRACT.md +52 -0
  4. package/docs/CAPABILITIES.md +5 -3
  5. package/docs/CLI_SHELL.md +57 -80
  6. package/docs/CMS_STUDIO.md +101 -0
  7. package/docs/CONTEXT.md +170 -0
  8. package/docs/LOCAL_OLLAMA.md +58 -0
  9. package/docs/MERKLE.md +19 -19
  10. package/docs/PORTABLE_CONTEXT.md +4 -3
  11. package/docs/PROJECT_CONFIG.md +72 -0
  12. package/docs/RELEASES.md +26 -2
  13. package/docs/REPOSITORY_INTELLIGENCE.md +1 -1
  14. package/docs/REPOSITORY_KNOWLEDGE.md +114 -0
  15. package/docs/SDK_WORKER.md +86 -0
  16. package/docs/SECURITY.md +59 -22
  17. package/docs/client-cms-editor.md +15 -0
  18. package/docs/local-studio/WORKMODE_DONOR_NOTES.md +485 -0
  19. package/package.json +17 -4
  20. package/packages/identity/package.json +11 -2
  21. package/packages/identity/src/contracts/auth-config.js +98 -0
  22. package/packages/identity/src/index.js +1 -0
  23. package/packages/identity/src/oauth/README.md +2 -2
  24. package/packages/identity/src/oauth/credentials.js +11 -4
  25. package/packages/identity/src/oauth/iam-platform.js +3 -3
  26. package/packages/identity/src/providers/iam/index.js +7 -7
  27. package/packages/identity/src/providers/iam/oauth.js +6 -4
  28. package/packages/identity/src/providers/iam/profile.js +5 -5
  29. package/packages/identity/tests/auth-config.test.mjs +57 -0
  30. package/packages/identity/tests/oauth-credentials.test.mjs +13 -2
  31. package/protocol/FILEMETA_V1.md +95 -0
  32. package/protocol/INSPECT_VIEWS_V1.md +40 -0
  33. package/protocol/MERKLE_PERSISTENCE_V1.md +50 -0
  34. package/protocol/MERKLE_V1.md +3 -1
  35. package/protocol/capabilities/manifest.json +32 -3
  36. package/protocol/capabilities/repository-snapshot.schema.json +1 -0
  37. package/protocol/context/context-budget.schema.json +41 -0
  38. package/protocol/context/context-item.schema.json +20 -0
  39. package/protocol/context/resolved-context-pack.schema.json +42 -0
  40. package/protocol/context/result-policy.schema.json +17 -0
  41. package/protocol/knowledge/context-pack.schema.json +33 -0
  42. package/protocol/knowledge/retrieval-query.schema.json +68 -13
  43. package/python/README.md +15 -7
  44. package/python/agentsam_sdk/cli.py +0 -21
  45. package/python/agentsam_sdk/tui/README.md +17 -12
  46. package/python/agentsam_sdk/tui/bootstrap.py +2 -2
  47. package/python/agentsam_sdk/tui/demo.py +25 -10
  48. package/python/agentsam_sdk/tui/onboarding.py +208 -0
  49. package/python/tests/test_tui_cli.py +7 -6
  50. package/skills/README.md +21 -0
  51. package/skills/agentsam-app-fundamentals/SKILL.md +165 -0
  52. package/skills/agentsam-app-fundamentals/references/graphs-contracts-ast-merkle.md +89 -0
  53. package/skills/agentsam-app-fundamentals/references/trust-credentials-and-destinations.md +99 -0
  54. package/skills/agentsam-jr-dev/SKILL.md +232 -0
  55. package/skills/agentsam-jr-dev/references/real-application-logic.md +156 -0
  56. package/skills/agentsam-jr-dev/references/web-application-fundamentals.md +240 -0
  57. package/skills/agentsam-progression-guard/SKILL.md +197 -0
  58. package/skills/agentsam-progression-guard/references/checkpoint-chain.md +111 -0
  59. package/skills/agentsam-progression-guard/references/hooks-operational-io.md +96 -0
  60. package/skills/catalog.json +53 -0
  61. package/src/capabilities/index.js +7 -0
  62. package/src/capabilities/repository-snapshot-view.js +238 -0
  63. package/src/capabilities/repository-snapshot.js +28 -14
  64. package/src/cli.js +108 -56
  65. package/src/commands/context.js +14 -2
  66. package/src/commands/db.js +4 -7
  67. package/src/commands/deploy.js +17 -31
  68. package/src/commands/interactive.js +21 -0
  69. package/src/commands/knowledge.js +27 -4
  70. package/src/commands/merkle-persist.js +118 -0
  71. package/src/commands/merkle.js +32 -17
  72. package/src/commands/models.js +107 -0
  73. package/src/commands/ollama.js +259 -0
  74. package/src/commands/preferences.js +102 -0
  75. package/src/commands/product.js +86 -16
  76. package/src/commands/security.js +3 -3
  77. package/src/commands/shell.js +71 -27
  78. package/src/commands/skills.js +66 -0
  79. package/src/commands/start-local.js +1 -1
  80. package/src/commands/tunnel.js +5 -4
  81. package/src/context/budget.js +58 -0
  82. package/src/context/compact.js +28 -0
  83. package/src/context/index.js +5 -0
  84. package/src/context/resolve.js +84 -0
  85. package/src/context/result-policy.js +66 -0
  86. package/src/index.js +16 -0
  87. package/src/indexing/execution-boundary.js +144 -0
  88. package/src/indexing/index.js +8 -0
  89. package/src/indexing/provider.js +41 -0
  90. package/src/knowledge/config.js +2 -2
  91. package/src/knowledge/context-pack.js +12 -1
  92. package/src/knowledge/contracts.js +9 -4
  93. package/src/knowledge/engine.js +7 -3
  94. package/src/knowledge/service/server.js +1 -1
  95. package/src/lib/bridge-client.js +7 -5
  96. package/src/lib/cli-preferences.js +74 -0
  97. package/src/lib/core-client.js +8 -8
  98. package/src/lib/deploy-receipt/index.js +5 -2
  99. package/src/lib/detect-context.js +6 -5
  100. package/src/lib/identity-scaffold.js +1 -1
  101. package/src/lib/local-scaffold.js +35 -33
  102. package/src/lib/local-status.js +9 -17
  103. package/src/lib/merkle/cloudflare-persistence.js +321 -0
  104. package/src/lib/merkle/filemeta.js +43 -0
  105. package/src/lib/merkle/git-ignore.js +24 -0
  106. package/src/lib/merkle/hash.js +1 -0
  107. package/src/lib/merkle/index.js +22 -0
  108. package/src/lib/merkle/persistence.js +72 -0
  109. package/src/lib/merkle/semantic.js +359 -0
  110. package/src/lib/merkle/snapshot.js +9 -3
  111. package/src/lib/merkle/tree.js +11 -6
  112. package/src/lib/project-config.js +227 -0
  113. package/src/lib/project-rules.js +68 -0
  114. package/src/lib/save-sdk-token.js +1 -1
  115. package/src/lib/slash-commands.js +2 -1
  116. package/src/lib/tools.js +11 -5
  117. package/src/security/index.js +1 -0
  118. package/src/security/inventory.js +4 -1
  119. package/src/security/render.js +27 -5
  120. package/src/security/scan.js +24 -9
  121. package/src/security/trust-boundary.js +24 -0
  122. package/src/skills/index.js +64 -0
  123. package/src/tools/index.js +1 -0
  124. package/src/tools/search.js +70 -0
  125. package/src/ui/ansi.js +1 -1
  126. package/src/ui/boot.js +56 -0
  127. package/src/ui/merkle/render.js +1 -0
  128. package/src/ui/runtime-activity.js +192 -0
  129. package/src/ui/theme.js +19 -18
  130. package/test/app-building-skills.test.mjs +61 -0
  131. package/test/apps-scaffold-contract.test.mjs +56 -0
  132. package/test/capabilities.test.mjs +53 -4
  133. package/test/cli-preferences.test.mjs +25 -0
  134. package/test/context.test.mjs +95 -0
  135. package/test/indexing-provider.test.mjs +29 -0
  136. package/test/jr-dev-skill.test.mjs +26 -0
  137. package/test/knowledge-context-pack.test.mjs +19 -0
  138. package/test/knowledge.test.mjs +1 -1
  139. package/test/merkle-persistence.test.mjs +91 -0
  140. package/test/merkle.test.mjs +77 -3
  141. package/test/models.test.mjs +37 -0
  142. package/test/ollama.test.mjs +94 -0
  143. package/test/project-config.test.mjs +81 -0
  144. package/test/project-rules.test.mjs +44 -0
  145. package/test/release-hygiene.test.mjs +34 -0
  146. package/test/repository-snapshot-view.test.mjs +112 -0
  147. package/test/runtime-activity.test.mjs +98 -0
  148. package/test/sdk-worker-contract.test.mjs +68 -0
  149. package/test/security.test.mjs +46 -0
  150. package/test/shell.test.mjs +8 -1
  151. package/test/skills.test.mjs +22 -0
  152. package/test/smoke.mjs +2 -2
  153. package/test/theme-portability.test.mjs +14 -0
  154. package/test/tools-search.test.mjs +27 -0
  155. package/examples/agentsam-tui-ansi.mjs +0 -149
  156. package/src/commands/tui.js +0 -120
  157. package/src/ui/splash-xterm.js +0 -290
  158. package/src/ui/splash.js +0 -426
package/src/cli.js CHANGED
@@ -2,14 +2,17 @@
2
2
 
3
3
  import pkg from '../package.json' with { type: 'json' };
4
4
  import readline from 'readline';
5
+ import { cancel, intro, isCancel, outro, select, text } from '@clack/prompts';
5
6
  import path from 'node:path';
6
- import { buildLocalScaffoldMeta, LANE_KEYS, RUN_TARGETS } from './lib/local-scaffold.js';
7
+ import { buildLocalScaffoldMeta } from './lib/local-scaffold.js';
7
8
  import { writeScaffoldFiles } from './lib/write-files.js';
8
9
  import { initializeGitRepository } from './lib/init-git.js';
9
10
  import { initializeLocalSqlite } from './local/sqlite.js';
10
11
  import { printContextSummary } from './lib/detect-context.js';
11
12
  import { promptOptionalByokKeys } from './lib/prompt-byok.js';
12
13
  import { runStartLocal } from './commands/start-local.js';
14
+ import { runOllama } from './commands/ollama.js';
15
+ import { runModels } from './commands/models.js';
13
16
  import { runTunnel } from './commands/tunnel.js';
14
17
  import { runDeploy } from './commands/deploy.js';
15
18
  import { runIdentityPreview } from './commands/identity-preview.js';
@@ -17,7 +20,7 @@ import { runIdentityInit } from './commands/identity-init.js';
17
20
  import { runContext } from './commands/context.js';
18
21
  import { runDb } from './commands/db.js';
19
22
  import { runStatus } from './commands/status.js';
20
- import { runTui } from './commands/tui.js';
23
+ import { runInteractive } from './commands/interactive.js';
21
24
  import { runShell } from './commands/shell.js';
22
25
  import { runDockerize } from './commands/dockerize.js';
23
26
  import { runMini } from './commands/mini.js';
@@ -26,10 +29,12 @@ import { runDeployReceipt } from './commands/deploy-receipt.js';
26
29
  import { runSecurity } from './commands/security.js';
27
30
  import { runRecon } from './commands/recon.js';
28
31
  import { runCad } from './commands/cad.js';
32
+ import { runSkills } from './commands/skills.js';
29
33
  import { applyPresetSelection, runAdd, runCapabilities, runDev, runInspect } from './commands/product.js';
30
34
  import { listPresets, resolvePreset } from './presets/index.js';
31
35
  import fs from 'node:fs';
32
36
  import { repositoryRoot } from './knowledge/config.js';
37
+ import { resolveSdkKey } from '../packages/identity/src/contracts/auth-config.js';
33
38
 
34
39
  const VERSION = pkg.version;
35
40
 
@@ -46,14 +51,16 @@ function printHelp() {
46
51
  Agent Sam SDK — CLI v${VERSION}
47
52
 
48
53
  Product UX:
54
+ agentsam Enter the interactive Agent Sam experience
49
55
  agentsam create <name> --preset <fullstack|cms|prototype|data>
50
56
  agentsam add <auth|cms|knowledge|agent|deploy-cloudflare>
51
57
  agentsam dev Run this project's existing npm dev script
52
- agentsam inspect [--json] Canonical deterministic repository.snapshot
58
+ agentsam inspect [--json] Bounded repository index by default; use --view full for authority envelope
53
59
  agentsam deploy Graduate an AgentSam project intentionally
54
60
 
55
61
  Capability discovery:
56
62
  agentsam capabilities [capability-id] [--json]
63
+ agentsam skills [skill-id-or-alias] [--references] [--json]
57
64
 
58
65
  Power-user UX:
59
66
  agentsam context [--json] Git repo/revision + bridge configuration from any repo
@@ -63,15 +70,15 @@ function printHelp() {
63
70
  agentsam repo snapshot Git composition/churn; --save retains observations
64
71
  agentsam cad blender Programmatic Blender inspect/build/render/export (--help)
65
72
  agentsam mini <name> Create and preview a small local gadget (--help for options)
66
- agentsam merkle File integrity, snapshots, comparisons, and TUI (--help)
73
+ agentsam merkle File integrity, snapshots, comparisons, and interactive explorer (--help)
67
74
  agentsam deploy-receipt Merkle deploy/checkpoint capture + promote/failure receipts (--help)
68
75
  agentsam recon Bounded-worker task packets + finding-report validation (--help)
69
76
  agentsam security Dependency scan, log triage, and verified repair (--help)
70
77
  agentsam status [--json] Live local Git + DB + API + PTY status
71
78
  agentsam db init|status Manage the project-local SQLite database
72
- agentsam tui Zero-dependency ANSI Agent Sam dashboard
73
- agentsam tui rich Optional Python Rich dashboard (--install for local venv)
79
+ agentsam models Show configured providers and local model inventory
74
80
  agentsam start-local Local PTY on ws://127.0.0.1:3099 (no tunnel, no Cloudflare)
81
+ agentsam ollama Opt-in local Ollama setup/status/model management
75
82
  agentsam shell Interactive Agent Sam slash-command shell
76
83
  agentsam tunnel Explicitly expose local PTY when remote access is wanted
77
84
  agentsam deploy Graduate to Cloudflare / GCP when ready
@@ -84,6 +91,21 @@ function printHelp() {
84
91
  Context options:
85
92
  --json Machine-readable output
86
93
  --cwd <path> Resolve a different working directory
94
+
95
+ Inspect options:
96
+ --view <full|index|files> Full authority envelope, facet index, or bounded matching files (default index)
97
+ --full Explicitly request the full authority envelope
98
+ --system/--package <name> Filter semantic ownership
99
+ --category/--tag <value> Filter semantic classification
100
+ --layer/--role <value> Filter architecture role
101
+ --path <prefix|glob> Filter repository paths (supports * and **)
102
+ --symbol/--import <value> Filter AST symbols or imports
103
+ --match <text> Lexical AND-match across semantic routing metadata
104
+ --limit <1..500> Bound files view (default 50)
105
+ --facet-limit <1..200> Bound each facet list (default 48)
106
+ --snapshot-file <file> Reuse a saved canonical repository.snapshot; skips Merkle/AST rescan
107
+ --save-snapshot <file> Save the canonical full snapshot while emitting the selected view
108
+ --pretty Pretty-print JSON; machine JSON is compact by default
87
109
  --remote <name> Preferred Git remote (default origin; falls back to first remote)
88
110
 
89
111
  Init is completable with Node only — no IAM login, no OAuth, no Cloudflare.
@@ -93,7 +115,7 @@ function printHelp() {
93
115
  --quick Quick tunnel (default) — trycloudflare.com URL
94
116
  --named Named CF tunnel (needs --tunnel-name --hostname --zone-id)
95
117
  --port <n> Local PTY port (default 3099)
96
- --token <sdk_…> Use existing AGENTSAM_SDK_TOKEN (skip browser auth)
118
+ --token <sdk_…> Use existing AGENTSAM_SDK_KEY (skip browser auth)
97
119
 
98
120
  Init options:
99
121
  --name <name> Project directory name
@@ -176,7 +198,7 @@ async function runLocalInit(config) {
176
198
  ✓ Environment ${path.join(dir, '.env')}
177
199
  ✓ SQLite ${db.dbPath} (${db.tables.length} tables)
178
200
  ✓ Local API Node · http://127.0.0.1:8787
179
- ✓ Terminal UI ANSI built in · Rich optional
201
+ ✓ Agent Sam interactive CLI ready
180
202
 
181
203
  Next:`);
182
204
  console.log(` cd ${meta.projectName}`);
@@ -184,9 +206,10 @@ async function runLocalInit(config) {
184
206
  console.log(` ${step}`);
185
207
  }
186
208
 
187
- if (prompt && process.env.AGENTSAM_SDK_TOKEN) {
209
+ const sdkKey = resolveSdkKey(process.env);
210
+ if (prompt && sdkKey) {
188
211
  console.log('\n Optional — BYOK keys for IAM dashboard Agent Sam (skip with Enter):\n');
189
- await promptOptionalByokKeys(process.env.AGENTSAM_SDK_TOKEN, prompt);
212
+ await promptOptionalByokKeys(sdkKey, prompt);
190
213
  }
191
214
 
192
215
  console.log(`
@@ -196,58 +219,61 @@ async function runLocalInit(config) {
196
219
  }
197
220
 
198
221
  async function initInteractive(partial = {}) {
199
- const prompt = createPrompt();
200
-
201
- console.log(`
202
- ╔════════════════════════════════╗
203
- ║ Agent Sam SDK — Init ║
204
- ║ Local-first · Node only ║
205
- ╚════════════════════════════════╝
206
- `);
207
-
208
- const projectName =
209
- partial.projectName ||
210
- (await prompt.ask(' 1) Project name: '));
211
-
212
- if (!partial.lane) {
213
- console.log(`
214
- 2) Lane:
215
- 1) Full Stack 2) CMS 3) Data 4) CRM 5) Creative
216
- `);
217
- }
218
- const laneKey = partial.lane
219
- ? partial.lane
220
- : LANE_KEYS[await prompt.ask(' Pick lane [1-5]: ')] || 'fullstack';
222
+ const pick = (value) => {
223
+ if (!isCancel(value)) return value;
224
+ cancel('Agent Sam init cancelled.');
225
+ const error = new Error('init_cancelled');
226
+ error.code = 'AGENTSAM_INIT_CANCELLED';
227
+ throw error;
228
+ };
221
229
 
222
- if (!partial.runTarget) {
223
- console.log(`
224
- 3) Future deploy target?
230
+ intro('Create an Agent Sam project');
231
+
232
+ const projectName = partial.projectName || pick(await text({
233
+ message: 'Project name',
234
+ placeholder: 'my-agent',
235
+ validate(value) {
236
+ if (!String(value || '').trim()) return 'Project name is required';
237
+ },
238
+ }));
239
+
240
+ const laneKey = partial.lane || pick(await select({
241
+ message: 'Project type',
242
+ initialValue: 'fullstack',
243
+ options: [
244
+ { value: 'fullstack', label: 'Full Stack' },
245
+ { value: 'cms', label: 'CMS' },
246
+ { value: 'data', label: 'Data Solutions' },
247
+ { value: 'crm', label: 'Customer Management' },
248
+ { value: 'creative', label: 'Creative & Design' },
249
+ ],
250
+ }));
251
+
252
+ const runTarget = partial.runTarget || pick(await select({
253
+ message: 'Future deploy target',
254
+ initialValue: 'local',
255
+ options: [
256
+ { value: 'local', label: 'Local only / decide later' },
257
+ { value: 'cloudflare', label: 'Cloudflare later' },
258
+ { value: 'gcp', label: 'GCP later' },
259
+ ],
260
+ }));
225
261
 
226
- 1) Local only / decide later
227
- 2) Cloudflare later (Worker + D1 adapter at deploy time)
228
- 3) GCP later (your Google Cloud project)
229
- `);
230
- }
231
- const runTarget = partial.runTarget
232
- ? partial.runTarget
233
- : RUN_TARGETS[await prompt.ask(' Select [1]: ')] || 'local';
234
-
235
- // Credential detection is demand-driven, not unconditional. missingForInit()
236
- // already knows local needs nothing (`if (runTarget === 'local') return []`)
237
- // — it just never got consulted before this fix, because detectContext()
238
- // used to run before runTarget was even known. runTarget alone is enough
239
- // to decide whether detection is needed at all, so skip the subprocess
240
- // calls entirely for local instead of just hiding their output.
241
262
  if (runTarget !== 'local') {
242
263
  const { detectContext, missingForInit } = await import('./lib/detect-context.js');
243
264
  const ctx = await detectContext();
244
- if (missingForInit(ctx, process.env.AGENTSAM_SDK_TOKEN || '', { runTarget }).length) {
265
+ if (missingForInit(ctx, resolveSdkKey(process.env), { runTarget }).length) {
245
266
  printContextSummary(ctx);
246
267
  }
247
268
  }
248
269
 
249
- await runLocalInit({ projectName, lane: laneKey, runTarget, prompt });
250
- prompt.close();
270
+ const prompt = resolveSdkKey(process.env) ? createPrompt() : null;
271
+ try {
272
+ await runLocalInit({ projectName, lane: laneKey, runTarget, prompt });
273
+ } finally {
274
+ prompt?.close();
275
+ }
276
+ outro(`Created ${projectName}`);
251
277
  }
252
278
 
253
279
  async function initFromArgs(argv) {
@@ -264,8 +290,20 @@ const rest = process.argv.slice(3);
264
290
 
265
291
  if (command === '--version' || command === '-v') {
266
292
  console.log(VERSION);
267
- } else if (command === '--help' || command === '-h' || !command) {
293
+ } else if (command === '--help' || command === '-h') {
268
294
  printHelp();
295
+ } else if (!command) {
296
+ if (process.stdin.isTTY && process.stdout.isTTY) {
297
+ try { await runInteractive(); }
298
+ catch (e) {
299
+ if (e?.code !== 'AGENTSAM_SETUP_CANCELLED') {
300
+ console.error(`\n ✗ ${e?.message || e}\n`);
301
+ process.exitCode = 1;
302
+ }
303
+ }
304
+ } else {
305
+ printHelp();
306
+ }
269
307
  } else if (command === 'create') {
270
308
  try {
271
309
  const opts = parseCreateArgs(rest);
@@ -311,9 +349,9 @@ if (command === '--version' || command === '-v') {
311
349
  console.error(`\n ✗ ${e?.message || e}\n`);
312
350
  process.exit(1);
313
351
  }
314
- } else if (command === 'tui') {
352
+ } else if (command === 'models') {
315
353
  try {
316
- await runTui(rest);
354
+ await runModels(rest);
317
355
  } catch (e) {
318
356
  console.error(`\n ✗ ${e?.message || e}\n`);
319
357
  process.exit(1);
@@ -327,6 +365,13 @@ if (command === '--version' || command === '-v') {
327
365
  }
328
366
  } else if (command === 'start-local') {
329
367
  await runStartLocal({});
368
+ } else if (command === 'ollama') {
369
+ try {
370
+ await runOllama(rest);
371
+ } catch (e) {
372
+ console.error(`\n ✗ ${e?.message || e}\n`);
373
+ process.exitCode = 1;
374
+ }
330
375
  } else if (command === 'tunnel') {
331
376
  try {
332
377
  await runTunnel(rest);
@@ -357,6 +402,13 @@ if (command === '--version' || command === '-v') {
357
402
  }
358
403
  } else if (command === 'security' || command === 'sca') {
359
404
  await runSecurity(rest);
405
+ } else if (command === 'skills' || command === 'skill') {
406
+ try {
407
+ runSkills(rest);
408
+ } catch (e) {
409
+ console.error(`\n ✗ ${e?.message || e}\n`);
410
+ process.exitCode = 1;
411
+ }
360
412
  } else if (command === 'merkle') {
361
413
  await runMerkle(rest);
362
414
  } else if (command === 'deploy-receipt') {
@@ -1,5 +1,6 @@
1
1
  import { tryResolveGitContext } from '../lib/git-context.js';
2
2
  import { resolveAgentSamBaseUrl, resolveBridgeKey } from '../lib/bridge-client.js';
3
+ import { loadProjectRules } from '../lib/project-rules.js';
3
4
 
4
5
  function parseArgs(argv = []) {
5
6
  const options = { json: false, cwd: process.cwd(), remote: 'origin' };
@@ -15,10 +16,20 @@ function parseArgs(argv = []) {
15
16
 
16
17
  export function buildContextReport(options = {}) {
17
18
  const env = options.env || process.env;
18
- const git = tryResolveGitContext({ cwd: options.cwd || process.cwd(), remote: options.remote || 'origin' });
19
+ const cwd = options.cwd || process.cwd();
20
+ const git = tryResolveGitContext({ cwd, remote: options.remote || 'origin' });
21
+ const rules = loadProjectRules(cwd);
19
22
  return {
20
- schemaVersion: 'agentsam-context-v1',
23
+ schemaVersion: 'agentsam-context-v2',
21
24
  git,
25
+ rules: {
26
+ found: rules.found,
27
+ path: rules.path,
28
+ chars: rules.chars,
29
+ source_chars: rules.source_chars || 0,
30
+ truncated: rules.truncated,
31
+ hash: rules.hash,
32
+ },
22
33
  bridge: {
23
34
  baseUrl: resolveAgentSamBaseUrl(env),
24
35
  configured: Boolean(resolveBridgeKey(env)),
@@ -46,6 +57,7 @@ export async function runContext(argv = []) {
46
57
  } else {
47
58
  console.log(' repo not inside a Git repository');
48
59
  }
60
+ console.log(` rules ${report.rules.found ? `${report.rules.path} · ${report.rules.chars} chars` : 'none'}`);
49
61
  console.log(` core ${report.bridge.baseUrl}`);
50
62
  console.log(` bridge ${report.bridge.configured ? 'configured' : 'AGENTSAM_BRIDGE_KEY not set'}`);
51
63
  console.log('');
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { initializeLocalSqlite, inspectLocalSqlite } from '../local/sqlite.js';
4
+ import { getLocalDatabasePath, getLocalSchemaPath, readProjectConfig } from '../lib/project-config.js';
4
5
 
5
6
  function findProjectRoot(startDir) {
6
7
  let dir = path.resolve(startDir);
@@ -13,14 +14,10 @@ function findProjectRoot(startDir) {
13
14
  throw new Error('Not an Agent Sam project — run `agentsam init` first.');
14
15
  }
15
16
 
16
- function readConfig(root) {
17
- return JSON.parse(fs.readFileSync(path.join(root, '.agentsam', 'config.json'), 'utf8'));
18
- }
19
-
20
17
  function resolveDb(root, config) {
21
18
  return {
22
- dbPath: path.resolve(root, config.db_path || '.agentsam/data/agentsam.sqlite'),
23
- schemaPath: path.resolve(root, config.db_schema || 'db/schema.sql'),
19
+ dbPath: path.resolve(root, getLocalDatabasePath(config)),
20
+ schemaPath: path.resolve(root, getLocalSchemaPath(config)),
24
21
  };
25
22
  }
26
23
 
@@ -31,7 +28,7 @@ export async function runDb(argv = [], opts = {}) {
31
28
  }
32
29
 
33
30
  const root = findProjectRoot(opts.cwd || process.cwd());
34
- const config = readConfig(root);
31
+ const config = readProjectConfig(root);
35
32
  const paths = resolveDb(root, config);
36
33
 
37
34
  if (sub === 'init') {
@@ -6,26 +6,16 @@ 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
-
10
- function readConfig(cwd) {
11
- const configPath = path.join(cwd, '.agentsam', 'config.json');
12
- if (!fs.existsSync(configPath)) {
13
- throw new Error('Not an Agent Sam project — run agentsam init first.');
14
- }
15
- return JSON.parse(fs.readFileSync(configPath, 'utf8'));
16
- }
17
-
18
- function writeConfig(cwd, config) {
19
- const configPath = path.join(cwd, '.agentsam', 'config.json');
20
- fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
21
- }
9
+ import { resolveSdkKey } from '../../packages/identity/src/contracts/auth-config.js';
10
+ import { getDefaultProfile, getDeployTarget, getLocalSchemaPath, getProjectName, getProjectPreset, readProjectConfig, setDeployTarget, writeProjectConfig } from '../lib/project-config.js';
22
11
 
23
12
  function writeCloudflareAdapter(cwd, config, cf) {
24
- const workerName = cf.worker_name || config.project;
13
+ const projectName = getProjectName(config, path.basename(cwd));
14
+ const workerName = cf.worker_name || projectName;
25
15
  const adapterPath = path.join(cwd, 'src', 'cloudflare-worker.js');
26
16
  const migrationDir = path.join(cwd, 'migrations');
27
17
  const migrationPath = path.join(migrationDir, '0001_agentsam_core.sql');
28
- const localSchemaPath = path.join(cwd, config.db_schema || 'db/schema.sql');
18
+ const localSchemaPath = path.join(cwd, getLocalSchemaPath(config));
29
19
  const tomlPath = path.join(cwd, 'wrangler.toml');
30
20
 
31
21
  fs.mkdirSync(path.dirname(adapterPath), { recursive: true });
@@ -33,7 +23,7 @@ function writeCloudflareAdapter(cwd, config, cf) {
33
23
 
34
24
  fs.writeFileSync(
35
25
  adapterPath,
36
- `import { createAgent } from './agent.js';\n\nexport default {\n async fetch(request, env) {\n const app = createAgent({\n env,\n project: ${JSON.stringify(config.project)},\n lane: ${JSON.stringify(config.lane)},\n agent: ${JSON.stringify(config.agent)},\n });\n return app.handle(request);\n },\n};\n`,
26
+ `import { createAgent } from './agent.js';\n\nexport default {\n async fetch(request, env) {\n const app = createAgent({\n env,\n project: ${JSON.stringify(projectName)},\n lane: ${JSON.stringify(getProjectPreset(config))},\n agent: ${JSON.stringify(getDefaultProfile(config))},\n });\n return app.handle(request);\n },\n};\n`,
37
27
  'utf8',
38
28
  );
39
29
 
@@ -51,7 +41,7 @@ function writeCloudflareAdapter(cwd, config, cf) {
51
41
  if (cf.d1_database_id) {
52
42
  lines.push('', '[[d1_databases]]');
53
43
  lines.push('binding = "DB"');
54
- lines.push(`database_name = ${JSON.stringify(cf.d1_database_name || `${config.project}-db`)}`);
44
+ lines.push(`database_name = ${JSON.stringify(cf.d1_database_name || `${projectName}-db`)}`);
55
45
  lines.push(`database_id = ${JSON.stringify(cf.d1_database_id)}`);
56
46
  }
57
47
  if (cf.kv_namespace_id) {
@@ -77,7 +67,7 @@ function ask(question) {
77
67
  async function runCloudflareDeploy(cwd, config, accountId) {
78
68
  console.log('\n Cloudflare deploy — browser sign-in + resource provisioning…\n');
79
69
 
80
- let token = process.env.AGENTSAM_SDK_TOKEN || '';
70
+ let token = resolveSdkKey(process.env);
81
71
  if (!token) {
82
72
  const session = await authenticateViaBrowser();
83
73
  token = session.access_token;
@@ -92,8 +82,8 @@ async function runCloudflareDeploy(cwd, config, accountId) {
92
82
  let complete = null;
93
83
  await streamScaffold(
94
84
  {
95
- project_name: config.project,
96
- lane: config.lane,
85
+ project_name: getProjectName(config, path.basename(cwd)),
86
+ lane: getProjectPreset(config),
97
87
  hosting: 'cloudflare',
98
88
  provision_only: true,
99
89
  account_id: accountId || undefined,
@@ -114,13 +104,8 @@ async function runCloudflareDeploy(cwd, config, accountId) {
114
104
  if (!complete?.cloudflare) throw new Error('deploy incomplete — no cloudflare ids returned');
115
105
 
116
106
  const adapter = writeCloudflareAdapter(cwd, config, complete.cloudflare);
117
- writeConfig(cwd, {
118
- ...config,
119
- deploy_target: 'cloudflare',
120
- cloudflare: complete.cloudflare,
121
- cloud_adapter: path.relative(cwd, adapter.adapterPath),
122
- deployed_at: new Date().toISOString(),
123
- });
107
+ setDeployTarget(config, 'cloudflare');
108
+ writeProjectConfig(cwd, config);
124
109
 
125
110
  console.log(`
126
111
  ✓ Cloudflare resources provisioned in YOUR account
@@ -141,10 +126,10 @@ async function runCloudflareDeploy(cwd, config, accountId) {
141
126
  */
142
127
  export async function runDeploy(opts = {}) {
143
128
  const cwd = path.resolve(opts.cwd || process.cwd());
144
- const config = readConfig(cwd);
129
+ const config = readProjectConfig(cwd);
145
130
 
146
- let target = opts.target || config.deploy_target || 'cloudflare';
147
- if (!opts.target && !config.deploy_target) {
131
+ let target = opts.target || getDeployTarget(config) || 'cloudflare';
132
+ if (!opts.target && !getDeployTarget(config)) {
148
133
  console.log(`
149
134
  Where do you want to deploy?
150
135
 
@@ -166,7 +151,8 @@ export async function runDeploy(opts = {}) {
166
151
  Your local project keeps running with npm run dev.
167
152
  Container/Worker deploy scripts are project-specific — add when ready.
168
153
  `);
169
- writeConfig(cwd, { ...config, deploy_target: 'gcp' });
154
+ setDeployTarget(config, 'gcp');
155
+ writeProjectConfig(cwd, config);
170
156
  return;
171
157
  }
172
158
 
@@ -0,0 +1,21 @@
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) {
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([], {
18
+ cwd: identity.root,
19
+ intro: 'quiet',
20
+ });
21
+ }
@@ -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