@inneranimalmedia/agentsam-sdk 2.1.0 → 2.2.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 (45) hide show
  1. package/README.md +27 -15
  2. package/docs/CAPABILITIES.md +93 -0
  3. package/docs/RELEASES.md +2 -1
  4. package/docs/portable-knowledge.md +4 -4
  5. package/package.json +7 -1
  6. package/packages/identity/package.json +1 -1
  7. package/packages/identity/src/frontend/auth-portal/README.md +1 -1
  8. package/packages/identity/src/index.js +2 -0
  9. package/protocol/capabilities/capability-manifest.schema.json +36 -0
  10. package/protocol/capabilities/manifest.json +179 -0
  11. package/protocol/capabilities/repository-audit-input.schema.json +14 -0
  12. package/protocol/capabilities/repository-audit.schema.json +30 -0
  13. package/protocol/capabilities/repository-snapshot-input.schema.json +11 -0
  14. package/protocol/capabilities/repository-snapshot.schema.json +20 -0
  15. package/protocol/knowledge/chunk.schema.json +15 -73
  16. package/protocol/knowledge/document.schema.json +10 -48
  17. package/protocol/knowledge/index-config.schema.json +2 -2
  18. package/protocol/knowledge/repository.schema.json +11 -52
  19. package/protocol/knowledge/retrieval-query.schema.json +13 -63
  20. package/protocol/knowledge/source.schema.json +9 -43
  21. package/protocol/presets/catalog.json +48 -0
  22. package/python/agentsam_sdk/knowledge/models.py +19 -7
  23. package/python/agentsam_sdk/repository/__main__.py +2 -2
  24. package/python/tests/test_knowledge_models.py +6 -2
  25. package/src/agent/capability-adapter.js +50 -0
  26. package/src/agent/index.js +2 -0
  27. package/src/agent/repository-audit.js +188 -0
  28. package/src/capabilities/index.js +7 -0
  29. package/src/capabilities/manifest.js +22 -0
  30. package/src/capabilities/repository-snapshot.js +180 -0
  31. package/src/cli.js +53 -1
  32. package/src/commands/deploy.js +0 -1
  33. package/src/commands/knowledge.js +5 -6
  34. package/src/commands/product.js +119 -0
  35. package/src/index.js +9 -0
  36. package/src/knowledge/config.js +12 -6
  37. package/src/knowledge/contracts.js +1 -1
  38. package/src/knowledge/engine.js +1 -1
  39. package/src/knowledge/service/server.js +2 -2
  40. package/src/lib/git-context.js +3 -1
  41. package/src/presets/index.js +20 -0
  42. package/src/repository/index.js +4 -0
  43. package/test/agent-capabilities.test.mjs +67 -0
  44. package/test/capabilities.test.mjs +84 -0
  45. package/test/portable-context.test.mjs +11 -1
@@ -0,0 +1,188 @@
1
+ import { repositorySnapshot } from '../capabilities/repository-snapshot.js';
2
+
3
+ const DEPTHS = new Set(['quick', 'standard', 'deep']);
4
+ const FORBIDDEN_OUTPUT_KEYS = new Set(['jobs', 'executions', 'commits', 'deployments', 'mutations', 'patches']);
5
+ const ARRAY_FIELDS = [
6
+ 'packages',
7
+ 'binaries',
8
+ 'commands',
9
+ 'capabilities',
10
+ 'services',
11
+ 'protocols',
12
+ 'notable_combinations',
13
+ 'findings',
14
+ 'cleanup_candidates',
15
+ 'recommended_routes',
16
+ 'evidence_refs',
17
+ 'artifacts',
18
+ ];
19
+
20
+ function boundedStrings(values = [], max = 32, field = 'value') {
21
+ if (!Array.isArray(values)) throw new TypeError(`${field} must be an array`);
22
+ return values.slice(0, max).map((value) => String(value).trim()).filter(Boolean);
23
+ }
24
+
25
+ function shrinkEvidence(evidence, characterBudget) {
26
+ const out = structuredClone(evidence);
27
+ const shrinkable = [
28
+ ['pressure_points', 12, 6, 3],
29
+ ['top_level', 20, 10, 5],
30
+ ['file_paths', 400, 200, 100],
31
+ ['packages', 40, 20, 10],
32
+ ['languages', 30, 15, 8],
33
+ ['manifests', 40, 20, 10],
34
+ ];
35
+ for (const [key, ...limits] of shrinkable) {
36
+ const original = Array.isArray(out[key]) ? out[key] : [];
37
+ for (const limit of limits) {
38
+ if (JSON.stringify(out).length <= characterBudget) break;
39
+ out[key] = original.slice(0, limit);
40
+ }
41
+ }
42
+ if (JSON.stringify(out).length > characterBudget) {
43
+ out.top_level = [];
44
+ out.pressure_points = [];
45
+ }
46
+ if (JSON.stringify(out).length > characterBudget) {
47
+ out.file_paths = [];
48
+ out.packages = [];
49
+ out.languages = [];
50
+ out.manifests = [];
51
+ }
52
+ return out;
53
+ }
54
+
55
+ export function buildRepositoryAuditPacket({
56
+ snapshot,
57
+ focus = [],
58
+ depth = 'standard',
59
+ requestedSections = [],
60
+ evidenceBudget = 8000,
61
+ } = {}) {
62
+ if (snapshot?.capability !== 'repository.snapshot' || !snapshot.snapshot_id) {
63
+ throw new TypeError('repository.audit requires a repository.snapshot result');
64
+ }
65
+ if (!DEPTHS.has(depth)) throw new RangeError(`depth must be one of: ${[...DEPTHS].join(', ')}`);
66
+ if (!Number.isInteger(evidenceBudget) || evidenceBudget < 1000 || evidenceBudget > 64000) {
67
+ throw new RangeError('evidenceBudget must be an integer from 1000..64000');
68
+ }
69
+
70
+ const evidence = shrinkEvidence({
71
+ repository: snapshot.repository,
72
+ tree: { merkle_root: snapshot.tree?.merkle_root, stats: snapshot.tree?.stats },
73
+ file_paths: snapshot.tree?.paths || [],
74
+ summary: snapshot.intelligence?.summary || {},
75
+ languages: snapshot.intelligence?.languages || [],
76
+ manifests: snapshot.intelligence?.manifests || [],
77
+ top_level: snapshot.intelligence?.top_level || [],
78
+ pressure_points: snapshot.intelligence?.pressure_points || [],
79
+ packages: snapshot.packages || [],
80
+ knowledge: snapshot.knowledge || { configured: false },
81
+ deploy: snapshot.deploy || null,
82
+ }, evidenceBudget * 4);
83
+
84
+ const prefix = `snapshot:${snapshot.snapshot_id}`;
85
+ return Object.freeze({
86
+ schema_version: 1,
87
+ primitive: 'repository.audit',
88
+ snapshot_id: snapshot.snapshot_id,
89
+ snapshot_content_hash: snapshot.content_hash,
90
+ focus: boundedStrings(focus, 24, 'focus'),
91
+ depth,
92
+ requested_sections: boundedStrings(requestedSections, 24, 'requestedSections'),
93
+ evidence_budget: evidenceBudget,
94
+ evidence,
95
+ evidence_index: Object.freeze({
96
+ repository: `${prefix}#repository`,
97
+ tree: `${prefix}#tree`,
98
+ file_paths: `${prefix}#tree.paths`,
99
+ summary: `${prefix}#intelligence.summary`,
100
+ languages: `${prefix}#intelligence.languages`,
101
+ manifests: `${prefix}#intelligence.manifests`,
102
+ packages: `${prefix}#packages`,
103
+ knowledge: `${prefix}#knowledge`,
104
+ deploy: `${prefix}#deploy`,
105
+ }),
106
+ rules: Object.freeze({
107
+ read_only: true,
108
+ may_edit: false,
109
+ may_deploy: false,
110
+ may_create_jobs: false,
111
+ evidence_must_come_from_packet: true,
112
+ }),
113
+ });
114
+ }
115
+
116
+ function normalizeAnalysis(value, snapshotId) {
117
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new TypeError('repository.audit reasoner must return an object');
118
+ for (const key of FORBIDDEN_OUTPUT_KEYS) {
119
+ if (Object.hasOwn(value, key)) throw new Error(`repository.audit output may not contain mutation field: ${key}`);
120
+ }
121
+ if (typeof value.summary !== 'string' || !value.summary.trim()) throw new TypeError('repository.audit output requires summary');
122
+ const prefix = `snapshot:${snapshotId}#`;
123
+ const result = {
124
+ summary: value.summary.trim(),
125
+ repository_map: value.repository_map ?? null,
126
+ file_tree: value.file_tree ?? null,
127
+ };
128
+ for (const field of ARRAY_FIELDS) result[field] = Array.isArray(value[field]) ? structuredClone(value[field]) : [];
129
+ const validateRef = (ref) => {
130
+ if (typeof ref !== 'string' || !ref.startsWith(prefix)) throw new Error(`repository.audit invalid evidence ref: ${ref}`);
131
+ };
132
+ for (const ref of result.evidence_refs) validateRef(ref);
133
+ for (const finding of result.findings) {
134
+ if (finding && typeof finding === 'object' && Array.isArray(finding.evidence_refs)) {
135
+ for (const ref of finding.evidence_refs) validateRef(ref);
136
+ }
137
+ }
138
+ return result;
139
+ }
140
+
141
+ /**
142
+ * Optional AgentSam/LLM layer over deterministic repository evidence.
143
+ * The SDK owns evidence collection and output validation; the caller injects reasoning.
144
+ */
145
+ export async function runRepositoryAudit({
146
+ cwd = process.cwd(),
147
+ snapshot,
148
+ focus = [],
149
+ depth = 'standard',
150
+ requestedSections = [],
151
+ evidenceBudget = 8000,
152
+ reasoner,
153
+ } = {}) {
154
+ if (typeof reasoner !== 'function') throw new TypeError('repository.audit requires an injected reasoner(packet) function');
155
+ const resolvedSnapshot = snapshot || await repositorySnapshot({ cwd });
156
+ const packet = buildRepositoryAuditPacket({ snapshot: resolvedSnapshot, focus, depth, requestedSections, evidenceBudget });
157
+ const analysis = normalizeAnalysis(await reasoner(structuredClone(packet)), resolvedSnapshot.snapshot_id);
158
+ return {
159
+ schema_version: 1,
160
+ primitive: 'repository.audit',
161
+ snapshot_id: resolvedSnapshot.snapshot_id,
162
+ snapshot_content_hash: resolvedSnapshot.content_hash,
163
+ generated_at: new Date().toISOString(),
164
+ ...analysis,
165
+ };
166
+ }
167
+
168
+ export function renderRepositoryAuditMarkdown(audit) {
169
+ if (audit?.primitive !== 'repository.audit') throw new TypeError('repository.audit result required');
170
+ const lines = [`# Repository audit`, '', audit.summary, '', `Snapshot: \`${audit.snapshot_id}\``];
171
+ const sections = [
172
+ ['Packages', audit.packages],
173
+ ['Commands', audit.commands],
174
+ ['Capabilities', audit.capabilities],
175
+ ['Services', audit.services],
176
+ ['Protocols', audit.protocols],
177
+ ['Notable combinations', audit.notable_combinations],
178
+ ['Findings', audit.findings],
179
+ ['Cleanup candidates', audit.cleanup_candidates],
180
+ ['Recommended routes', audit.recommended_routes],
181
+ ];
182
+ for (const [title, rows] of sections) {
183
+ if (!Array.isArray(rows) || rows.length === 0) continue;
184
+ lines.push('', `## ${title}`, '');
185
+ for (const row of rows) lines.push(`- ${typeof row === 'string' ? row : JSON.stringify(row)}`);
186
+ }
187
+ return `${lines.join('\n')}\n`;
188
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ CAPABILITY_MANIFEST_VERSION,
3
+ getCapability,
4
+ getCapabilityManifest,
5
+ listCapabilities,
6
+ } from './manifest.js';
7
+ export { repositorySnapshot } from './repository-snapshot.js';
@@ -0,0 +1,22 @@
1
+ import manifest from '../../protocol/capabilities/manifest.json' with { type: 'json' };
2
+
3
+ export const CAPABILITY_MANIFEST_VERSION = manifest.schema_version;
4
+
5
+ function rows() {
6
+ return Object.values(manifest.capabilities);
7
+ }
8
+
9
+ export function listCapabilities({ domain, status = 'stable', kind } = {}) {
10
+ return rows()
11
+ .filter((row) => (!domain || row.domain === domain) && (!status || row.status === status) && (!kind || row.kind === kind))
12
+ .map((row) => structuredClone(row));
13
+ }
14
+
15
+ export function getCapability(id) {
16
+ const row = manifest.capabilities[String(id || '').trim()];
17
+ return row ? structuredClone(row) : null;
18
+ }
19
+
20
+ export function getCapabilityManifest() {
21
+ return structuredClone(manifest);
22
+ }
@@ -0,0 +1,180 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFile } from 'node:child_process';
4
+ import { promisify } from 'node:util';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { createHash } from 'node:crypto';
7
+ import { resolveGitContext } from '../lib/git-context.js';
8
+ import { buildMerkleTree } from '../lib/merkle/index.js';
9
+ import { showLatestDeployReceipt } from '../lib/deploy-receipt/index.js';
10
+ import { CONFIG_PATH, canonical, readConfig, scopeKey } from '../knowledge/config.js';
11
+ import { openSqliteStore } from '../knowledge/stores/sqlite.js';
12
+
13
+ const execute = promisify(execFile);
14
+
15
+ function sha256(value) {
16
+ return createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex');
17
+ }
18
+
19
+ function providerForHost(host) {
20
+ const value = String(host || '').toLowerCase();
21
+ if (value === 'github.com') return 'github';
22
+ if (value === 'gitlab.com') return 'gitlab';
23
+ if (value === 'bitbucket.org') return 'bitbucket';
24
+ return value ? 'git' : null;
25
+ }
26
+
27
+ async function gitIgnoredPaths(root) {
28
+ const result = await execute('git', ['status', '--porcelain=v1', '--ignored=matching'], {
29
+ cwd: root,
30
+ maxBuffer: 16 * 1024 * 1024,
31
+ });
32
+ return [...new Set(result.stdout.split('\n')
33
+ .filter((line) => line.startsWith('!! '))
34
+ .map((line) => line.slice(3).trim().replace(/\/$/, ''))
35
+ .filter((value) => value && !value.includes('\\') && !value.split('/').includes('..')))];
36
+ }
37
+
38
+ async function runRepositoryIntelligence(root, churnDays) {
39
+ const pythonRoot = fileURLToPath(new URL('../../python', import.meta.url));
40
+ const result = await execute(process.platform === 'win32' ? 'python' : 'python3', [
41
+ '-B', '-m', 'agentsam_sdk.repository.intelligence', '--repo-root', root,
42
+ '--churn-days', String(churnDays), '--json',
43
+ ], {
44
+ env: { ...process.env, PYTHONPATH: [pythonRoot, process.env.PYTHONPATH].filter(Boolean).join(path.delimiter) },
45
+ maxBuffer: 64 * 1024 * 1024,
46
+ });
47
+ return JSON.parse(result.stdout);
48
+ }
49
+
50
+ function readPackageInventory(root, manifests = []) {
51
+ const packages = [];
52
+ for (const row of manifests) {
53
+ if (row.kind !== 'node' || path.basename(row.path) !== 'package.json') continue;
54
+ const filename = path.join(root, row.path);
55
+ try {
56
+ const parsed = JSON.parse(fs.readFileSync(filename, 'utf8'));
57
+ packages.push({
58
+ path: row.path,
59
+ name: parsed.name || null,
60
+ version: parsed.version || null,
61
+ private: parsed.private === true,
62
+ type: parsed.type || null,
63
+ bins: parsed.bin || null,
64
+ exports: parsed.exports && typeof parsed.exports === 'object' ? Object.keys(parsed.exports) : [],
65
+ scripts: parsed.scripts && typeof parsed.scripts === 'object' ? Object.keys(parsed.scripts).sort() : [],
66
+ dependencies: parsed.dependencies && typeof parsed.dependencies === 'object' ? Object.keys(parsed.dependencies).sort() : [],
67
+ dev_dependencies: parsed.devDependencies && typeof parsed.devDependencies === 'object' ? Object.keys(parsed.devDependencies).sort() : [],
68
+ workspaces: parsed.workspaces || null,
69
+ });
70
+ } catch {
71
+ packages.push({ path: row.path, invalid: true });
72
+ }
73
+ }
74
+ return packages.sort((a, b) => a.path.localeCompare(b.path));
75
+ }
76
+
77
+ async function readKnowledgeState(root) {
78
+ const filename = path.join(root, CONFIG_PATH);
79
+ if (!fs.existsSync(filename)) return { configured: false };
80
+ try {
81
+ const config = readConfig(root);
82
+ const state = {
83
+ configured: true,
84
+ repository_id: config.repository_id,
85
+ scope: config.scope,
86
+ storage: config.storage.driver,
87
+ embedding_profile: config.embedding,
88
+ legacy_workspace_id: config.workspace_id || null,
89
+ };
90
+ if (config.storage.driver !== 'sqlite') return state;
91
+ const store = await openSqliteStore(path.join(root, '.agentsam', 'knowledge', 'index.sqlite'), { readOnly: true });
92
+ if (!store) return { ...state, indexed: false };
93
+ try {
94
+ const generation = await store.active(scopeKey(config));
95
+ if (!generation) return { ...state, indexed: false };
96
+ return {
97
+ ...state,
98
+ indexed: true,
99
+ generation_id: generation.id,
100
+ created_at: generation.created_at,
101
+ source_hash: generation.source_hash,
102
+ profile_id: generation.profile_id || null,
103
+ receipt: generation.receipt || null,
104
+ };
105
+ } finally {
106
+ await store.close();
107
+ }
108
+ } catch (error) {
109
+ return { configured: true, error: error?.message || String(error) };
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Canonical deterministic repository evidence snapshot.
115
+ * Read-only: no source/config/index/deploy state is created or modified.
116
+ */
117
+ export async function repositorySnapshot({ cwd = process.cwd(), churnDays = 30 } = {}) {
118
+ if (!Number.isInteger(churnDays) || churnDays < 1 || churnDays > 3650) {
119
+ throw new RangeError('churnDays must be an integer from 1..3650');
120
+ }
121
+ const git = resolveGitContext({ cwd });
122
+ const root = git.root;
123
+ const ignored = await gitIgnoredPaths(root);
124
+ const [intelligence, merkle, knowledge, deployReceipt] = await Promise.all([
125
+ runRepositoryIntelligence(root, churnDays),
126
+ buildMerkleTree(root, { exclude: ignored }),
127
+ readKnowledgeState(root),
128
+ showLatestDeployReceipt({ root }),
129
+ ]);
130
+ const provider = providerForHost(git.remoteHost);
131
+ const portableRepositoryId = provider && git.repoFullName ? `${provider}:${git.repoFullName}` : null;
132
+ const repositoryId = knowledge.repository_id || portableRepositoryId;
133
+ const packages = readPackageInventory(root, intelligence.manifests || []);
134
+
135
+ const evidence = {
136
+ repository: {
137
+ repository_id: repositoryId,
138
+ identity_source: knowledge.repository_id ? 'knowledge-config' : portableRepositoryId ? 'git-remote' : 'unresolved',
139
+ provider,
140
+ full_name: git.repoFullName,
141
+ remote_url: git.remoteUrl || null,
142
+ branch: git.branch,
143
+ revision_sha: git.revisionSha,
144
+ dirty: git.dirty,
145
+ },
146
+ tree: {
147
+ merkle_root: merkle.rootHash,
148
+ stats: merkle.stats,
149
+ paths: merkle.entries
150
+ .filter((entry) => entry.type === 'file' || entry.type === 'symlink')
151
+ .map((entry) => entry.path),
152
+ },
153
+ intelligence: {
154
+ summary: intelligence.summary,
155
+ languages: intelligence.languages || [],
156
+ manifests: intelligence.manifests || [],
157
+ top_level: intelligence.top_level || [],
158
+ pressure_points: intelligence.pressure_points || [],
159
+ },
160
+ packages,
161
+ knowledge,
162
+ deploy: deployReceipt ? {
163
+ status: deployReceipt.status,
164
+ root_hash: deployReceipt.root_hash,
165
+ git_sha: deployReceipt.git_sha,
166
+ deployment_id: deployReceipt.deployment_id || null,
167
+ worker_version_id: deployReceipt.worker_version_id || null,
168
+ completed_at: deployReceipt.completed_at || null,
169
+ } : null,
170
+ };
171
+ const contentHash = sha256(evidence);
172
+ return {
173
+ schema_version: 1,
174
+ capability: 'repository.snapshot',
175
+ snapshot_id: `rsnap_${contentHash.slice(0, 24)}`,
176
+ created_at: new Date().toISOString(),
177
+ content_hash: `sha256:${contentHash}`,
178
+ ...evidence,
179
+ };
180
+ }
package/src/cli.js CHANGED
@@ -24,6 +24,8 @@ import { runMerkle } from './commands/merkle.js';
24
24
  import { runDeployReceipt } from './commands/deploy-receipt.js';
25
25
  import { runSecurity } from './commands/security.js';
26
26
  import { runRecon } from './commands/recon.js';
27
+ import { applyPresetSelection, runAdd, runCapabilities, runDev, runInspect } from './commands/product.js';
28
+ import { listPresets, resolvePreset } from './presets/index.js';
27
29
  import { SLASH_COMMANDS, SHELL_PHASES } from './lib/slash-commands.js';
28
30
  import fs from 'node:fs';
29
31
  import { repositoryRoot } from './knowledge/config.js';
@@ -42,7 +44,17 @@ function printHelp() {
42
44
  console.log(`
43
45
  Agent Sam SDK — CLI v${VERSION}
44
46
 
45
- Usage:
47
+ Product UX:
48
+ agentsam create <name> --preset <fullstack|cms|prototype|data>
49
+ agentsam add <auth|cms|knowledge|agent|deploy-cloudflare>
50
+ agentsam dev Run this project's existing npm dev script
51
+ agentsam inspect [--json] Canonical deterministic repository.snapshot
52
+ agentsam deploy Graduate an AgentSam project intentionally
53
+
54
+ Capability discovery:
55
+ agentsam capabilities [capability-id] [--json]
56
+
57
+ Power-user UX:
46
58
  agentsam context [--json] Git repo/revision + bridge configuration from any repo
47
59
  agentsam init Configure knowledge in this repo; --name scaffolds a new project
48
60
  agentsam index Plan/run incremental AST and optional embeddings (--help)
@@ -116,6 +128,21 @@ function parseDeployArgs(argv) {
116
128
  return opts;
117
129
  }
118
130
 
131
+ function parseCreateArgs(argv) {
132
+ const opts = { projectName: '', preset: 'fullstack', runTarget: 'local', help: false };
133
+ for (let i = 0; i < argv.length; i += 1) {
134
+ const arg = argv[i];
135
+ if (arg === '--help' || arg === '-h') opts.help = true;
136
+ else if (arg === '--preset') opts.preset = argv[++i] || 'fullstack';
137
+ else if (arg === '--run-target' || arg === '--target') opts.runTarget = argv[++i] || 'local';
138
+ else if (arg === '--yes' || arg === '-y') continue;
139
+ else if (arg.startsWith('-')) throw new Error(`unknown create option: ${arg}`);
140
+ else if (!opts.projectName) opts.projectName = arg;
141
+ else throw new Error(`unexpected create argument: ${arg}`);
142
+ }
143
+ return opts;
144
+ }
145
+
119
146
  async function runLocalInit(config) {
120
147
  const { projectName, lane, runTarget, prompt } = config;
121
148
 
@@ -163,6 +190,7 @@ async function runLocalInit(config) {
163
190
  console.log(`
164
191
  Local means local: no Worker, tunnel, IAM login, or cloud database is required.
165
192
  `);
193
+ return { dir, meta };
166
194
  }
167
195
 
168
196
  async function initInteractive(partial = {}) {
@@ -271,6 +299,30 @@ if (command === '--version' || command === '-v') {
271
299
  console.log(VERSION);
272
300
  } else if (command === '--help' || command === '-h' || !command) {
273
301
  printHelp();
302
+ } else if (command === 'create') {
303
+ try {
304
+ const opts = parseCreateArgs(rest);
305
+ if (opts.help || !opts.projectName) {
306
+ console.log(`agentsam create <name> --preset <${listPresets().map((row) => row.id).join('|')}> [--target local|cloudflare|gcp]`);
307
+ } else {
308
+ const preset = resolvePreset(opts.preset);
309
+ const created = await runLocalInit({ projectName: opts.projectName, lane: preset.lane, runTarget: opts.runTarget, prompt: null });
310
+ applyPresetSelection(created.dir, preset);
311
+ console.log(` ✓ Preset ${preset.id}\n ✓ Features ${preset.features.join(', ') || 'none'}\n ✓ Capabilities ${preset.capabilities.length}\n`);
312
+ }
313
+ } catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
314
+ } else if (command === 'add') {
315
+ try { await runAdd(rest); }
316
+ catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
317
+ } else if (command === 'dev') {
318
+ try { await runDev(rest); }
319
+ catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
320
+ } else if (command === 'inspect') {
321
+ try { await runInspect(rest); }
322
+ catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
323
+ } else if (command === 'capabilities') {
324
+ try { await runCapabilities(rest); }
325
+ catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
274
326
  } else if (command === 'context') {
275
327
  try {
276
328
  await runContext(rest);
@@ -97,7 +97,6 @@ async function runCloudflareDeploy(cwd, config, accountId) {
97
97
  hosting: 'cloudflare',
98
98
  provision_only: true,
99
99
  account_id: accountId || undefined,
100
- workspace_id: ctx.workspace_id,
101
100
  },
102
101
  token,
103
102
  async (evt) => {
@@ -24,12 +24,12 @@ async function openStore(root, config, readOnly = false) {
24
24
  function provider() { return createGeminiEmbedder({ apiKey: process.env.GEMINI_API_KEY }); }
25
25
 
26
26
  export async function runRepositoryInit(argv) {
27
- const { values: opts, positionals } = flags(argv, { existing: { type: 'boolean' }, yes: { type: 'boolean', short: 'y' }, include: { type: 'string' }, exclude: { type: 'string' }, scope: { type: 'string' }, workspace: { type: 'string' }, target: { type: 'string' }, dimensions: { type: 'string' } });
28
- if (opts.help) { console.log('agentsam init [.] [--cwd PATH] [--yes] [--include src,docs] [--exclude src/generated] [--scope NAME] [--target local|production] [--workspace ID] [--dimensions 768]'); return; }
27
+ 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' } });
28
+ 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
29
  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
30
  const root = repositoryRoot(opts.cwd);
31
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.`);
32
- let include = opts.include, exclude = opts.exclude, target = opts.target, workspace = opts.workspace, dimensions = opts.dimensions;
32
+ let include = opts.include, exclude = opts.exclude, target = opts.target, dimensions = opts.dimensions;
33
33
  if (!opts.yes) {
34
34
  if (!process.stdin.isTTY) throw new Error('Existing-repository setup needs a terminal or --yes with explicit options.');
35
35
  const prompt = createInterface({ input: process.stdin, output: process.stdout });
@@ -38,11 +38,10 @@ export async function runRepositoryInit(argv) {
38
38
  include ??= await prompt.question('1) Include files/directories, comma-separated [.]: ') || '.';
39
39
  exclude ??= await prompt.question('2) Exclude files/directories [none]: ') || '';
40
40
  target ??= await prompt.question('3) Storage: local or production [local]: ') || 'local';
41
- if (target === 'production') workspace ??= await prompt.question('4) Workspace identifier: ');
42
- dimensions ??= await prompt.question('5) Gemini Embedding 2 dimensions [768]: ') || '768';
41
+ dimensions ??= await prompt.question('4) Gemini Embedding 2 dimensions [768]: ') || '768';
43
42
  } finally { prompt.close(); }
44
43
  }
45
- const config = initRepository(root, { include: split(include || '.'), exclude: split(exclude || ''), scope: opts.scope || 'default', target: target || 'local', workspace: workspace || 'local', dimensions: Number(dimensions || 768) });
44
+ const config = initRepository(root, { include: split(include || '.'), exclude: split(exclude || ''), scope: opts.scope || 'default', target: target || 'local', dimensions: Number(dimensions || 768) });
46
45
  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.' });
47
46
  }
48
47
 
@@ -0,0 +1,119 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawn } from 'node:child_process';
4
+ import { getCapability, getCapabilityManifest, listCapabilities, repositorySnapshot } from '../capabilities/index.js';
5
+ import { getAddon, listAddons, listPresets } from '../presets/index.js';
6
+
7
+ function parseCommon(argv = []) {
8
+ const out = { json: false, cwd: process.cwd(), positionals: [] };
9
+ for (let i = 0; i < argv.length; i += 1) {
10
+ const arg = argv[i];
11
+ if (arg === '--json') out.json = true;
12
+ else if (arg === '--cwd') out.cwd = argv[++i] || out.cwd;
13
+ else out.positionals.push(arg);
14
+ }
15
+ return out;
16
+ }
17
+
18
+ export async function runInspect(argv = []) {
19
+ const opts = parseCommon(argv);
20
+ let churnDays = 30;
21
+ for (let i = 0; i < opts.positionals.length; i += 1) {
22
+ if (opts.positionals[i] === '--churn-days') churnDays = Number(opts.positionals[++i] || 30);
23
+ else if (opts.positionals[i] === 'repository') continue;
24
+ else throw new Error(`unknown inspect option: ${opts.positionals[i]}`);
25
+ }
26
+ const result = await repositorySnapshot({ cwd: opts.cwd, churnDays });
27
+ if (opts.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
28
+ else {
29
+ console.log(`\nRepository snapshot ${result.snapshot_id}`);
30
+ console.log(` repo ${result.repository.full_name || result.repository.repository_id || '(local)'}`);
31
+ console.log(` revision ${result.repository.revision_sha}`);
32
+ console.log(` merkle ${result.tree.merkle_root}`);
33
+ console.log(` files ${result.intelligence.summary?.file_count ?? result.tree.stats?.files ?? 'unknown'}`);
34
+ console.log(` knowledge ${result.knowledge?.indexed ? result.knowledge.generation_id : result.knowledge?.configured ? 'configured / not indexed' : 'not configured'}`);
35
+ console.log(` deploy ${result.deploy?.status || 'no trusted receipt'}`);
36
+ console.log(` content ${result.content_hash}\n`);
37
+ }
38
+ return result;
39
+ }
40
+
41
+ export async function runCapabilities(argv = []) {
42
+ const opts = parseCommon(argv);
43
+ const id = opts.positionals[0] || '';
44
+ const result = id ? getCapability(id) : getCapabilityManifest();
45
+ if (id && !result) throw new Error(`unknown_capability:${id}`);
46
+ if (opts.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
47
+ else if (id) console.log(`${result.id}\n ${result.description}\n cli: ${result.cli || '(library only)'}\n model required: ${result.model_required ? 'yes' : 'no'}`);
48
+ else {
49
+ console.log('\nAgentSam deterministic capabilities\n');
50
+ for (const row of listCapabilities()) console.log(` ${row.id.padEnd(26)} ${row.description}`);
51
+ console.log('');
52
+ }
53
+ return result;
54
+ }
55
+
56
+ function projectConfigPath(cwd) { return path.join(cwd, '.agentsam', 'config.json'); }
57
+ function featureStatePath(cwd) { return path.join(cwd, '.agentsam', 'features.json'); }
58
+
59
+ export function applyPresetSelection(cwd, preset) {
60
+ const filename = projectConfigPath(cwd);
61
+ if (!fs.existsSync(filename)) throw new Error('not_agentsam_project');
62
+ const config = JSON.parse(fs.readFileSync(filename, 'utf8'));
63
+ const next = { ...config, preset: preset.id, features: [...preset.features], capabilities: [...preset.capabilities] };
64
+ fs.writeFileSync(filename, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
65
+ return next;
66
+ }
67
+
68
+ export async function runAdd(argv = []) {
69
+ const opts = parseCommon(argv);
70
+ const id = opts.positionals[0];
71
+ if (!id || id === '--help') {
72
+ const rows = listAddons();
73
+ console.log(`agentsam add <${rows.map(x => x.id).join('|')}> [--cwd PATH] [--json]`);
74
+ return null;
75
+ }
76
+ const addon = getAddon(id);
77
+ if (!addon) throw new Error(`unknown_addon:${id}`);
78
+ const cwd = path.resolve(opts.cwd);
79
+ if (!fs.existsSync(projectConfigPath(cwd))) throw new Error('not_agentsam_project');
80
+ fs.mkdirSync(path.dirname(featureStatePath(cwd)), { recursive: true });
81
+ let state = { schema_version: 1, features: {} };
82
+ if (fs.existsSync(featureStatePath(cwd))) state = JSON.parse(fs.readFileSync(featureStatePath(cwd), 'utf8'));
83
+ state.features ||= {};
84
+ state.features[addon.id] = { selected: true, capabilities: addon.capabilities, selected_at: new Date().toISOString() };
85
+ fs.writeFileSync(featureStatePath(cwd), `${JSON.stringify(state, null, 2)}\n`, 'utf8');
86
+ if (addon.id === 'deploy-cloudflare') {
87
+ const config = JSON.parse(fs.readFileSync(projectConfigPath(cwd), 'utf8'));
88
+ config.deploy_target = 'cloudflare';
89
+ fs.writeFileSync(projectConfigPath(cwd), `${JSON.stringify(config, null, 2)}\n`, 'utf8');
90
+ }
91
+ const result = { ok: true, feature: addon.id, capabilities: addon.capabilities, description: addon.description, state_file: '.agentsam/features.json' };
92
+ if (opts.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
93
+ else console.log(`\nAdded ${addon.id}\n ${addon.description}\n state: ${result.state_file}\n`);
94
+ return result;
95
+ }
96
+
97
+ export async function runDev(argv = []) {
98
+ const opts = parseCommon(argv);
99
+ if (opts.positionals.length) throw new Error(`unknown dev option: ${opts.positionals[0]}`);
100
+ const cwd = path.resolve(opts.cwd);
101
+ const pkgFile = path.join(cwd, 'package.json');
102
+ if (!fs.existsSync(pkgFile)) throw new Error('package_json_not_found');
103
+ const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf8'));
104
+ if (!pkg.scripts?.dev) throw new Error('dev_script_not_found');
105
+ if (/\bagentsam\s+dev\b/.test(pkg.scripts.dev)) throw new Error('recursive_agentsam_dev_script');
106
+ const child = spawn(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'dev'], { cwd, stdio: 'inherit', env: process.env });
107
+ return new Promise((resolve, reject) => {
108
+ child.once('error', reject);
109
+ child.once('exit', (code, signal) => {
110
+ if (signal) return reject(new Error(`dev_process_signal:${signal}`));
111
+ process.exitCode = code || 0;
112
+ resolve(code || 0);
113
+ });
114
+ });
115
+ }
116
+
117
+ export function printProductCatalog() {
118
+ return { presets: listPresets(), addons: listAddons(), capabilities: listCapabilities() };
119
+ }
package/src/index.js CHANGED
@@ -31,12 +31,21 @@ export {
31
31
  SHELL_PHASES,
32
32
  listSlashCommands,
33
33
  } from './lib/slash-commands.js';
34
+ export {
35
+ CAPABILITY_MANIFEST_VERSION,
36
+ getCapability,
37
+ getCapabilityManifest,
38
+ listCapabilities,
39
+ repositorySnapshot,
40
+ } from './capabilities/index.js';
41
+ export { getPreset, listPresets, resolvePreset, getAddon, listAddons } from './presets/index.js';
34
42
 
35
43
  export {
36
44
  createIdentityClient,
37
45
  createIdentity,
38
46
  GoogleProvider,
39
47
  GithubProvider,
48
+ IamProvider,
40
49
  GcpProvider,
41
50
  EmailProvider,
42
51
  getIdentityProvider,