@inneranimalmedia/agentsam-sdk 2.1.0 → 2.2.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.
- package/README.md +27 -15
- package/docs/CAPABILITIES.md +93 -0
- package/docs/CLI_SHELL.md +10 -1
- package/docs/RELEASES.md +2 -1
- package/docs/portable-knowledge.md +4 -4
- package/package.json +7 -1
- package/packages/identity/package.json +1 -1
- package/packages/identity/src/frontend/auth-portal/README.md +1 -1
- package/packages/identity/src/index.js +2 -0
- package/protocol/capabilities/capability-manifest.schema.json +36 -0
- package/protocol/capabilities/manifest.json +179 -0
- package/protocol/capabilities/repository-audit-input.schema.json +14 -0
- package/protocol/capabilities/repository-audit.schema.json +30 -0
- package/protocol/capabilities/repository-snapshot-input.schema.json +11 -0
- package/protocol/capabilities/repository-snapshot.schema.json +20 -0
- package/protocol/knowledge/chunk.schema.json +15 -73
- package/protocol/knowledge/document.schema.json +10 -48
- package/protocol/knowledge/index-config.schema.json +2 -2
- package/protocol/knowledge/repository.schema.json +11 -52
- package/protocol/knowledge/retrieval-query.schema.json +13 -63
- package/protocol/knowledge/source.schema.json +9 -43
- package/protocol/presets/catalog.json +48 -0
- package/python/agentsam_sdk/knowledge/models.py +19 -7
- package/python/agentsam_sdk/repository/__main__.py +2 -2
- package/python/tests/test_knowledge_models.py +6 -2
- package/src/agent/capability-adapter.js +50 -0
- package/src/agent/index.js +2 -0
- package/src/agent/repository-audit.js +188 -0
- package/src/capabilities/index.js +7 -0
- package/src/capabilities/manifest.js +22 -0
- package/src/capabilities/repository-snapshot.js +180 -0
- package/src/cli.js +56 -39
- package/src/commands/deploy.js +0 -1
- package/src/commands/knowledge.js +5 -6
- package/src/commands/product.js +119 -0
- package/src/commands/shell.js +253 -0
- package/src/index.js +9 -0
- package/src/knowledge/config.js +12 -6
- package/src/knowledge/contracts.js +1 -1
- package/src/knowledge/engine.js +1 -1
- package/src/knowledge/service/server.js +2 -2
- package/src/lib/git-context.js +3 -1
- package/src/lib/slash-commands.js +2 -1
- package/src/presets/index.js +20 -0
- package/src/repository/index.js +4 -0
- package/test/agent-capabilities.test.mjs +67 -0
- package/test/capabilities.test.mjs +84 -0
- package/test/portable-context.test.mjs +11 -1
- package/test/shell.test.mjs +60 -0
- package/test/smoke.mjs +2 -2
|
@@ -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,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
|
@@ -18,13 +18,15 @@ import { runContext } from './commands/context.js';
|
|
|
18
18
|
import { runDb } from './commands/db.js';
|
|
19
19
|
import { runStatus } from './commands/status.js';
|
|
20
20
|
import { runTui } from './commands/tui.js';
|
|
21
|
+
import { runShell } from './commands/shell.js';
|
|
21
22
|
import { runDockerize } from './commands/dockerize.js';
|
|
22
23
|
import { runMini } from './commands/mini.js';
|
|
23
24
|
import { runMerkle } from './commands/merkle.js';
|
|
24
25
|
import { runDeployReceipt } from './commands/deploy-receipt.js';
|
|
25
26
|
import { runSecurity } from './commands/security.js';
|
|
26
27
|
import { runRecon } from './commands/recon.js';
|
|
27
|
-
import {
|
|
28
|
+
import { applyPresetSelection, runAdd, runCapabilities, runDev, runInspect } from './commands/product.js';
|
|
29
|
+
import { listPresets, resolvePreset } from './presets/index.js';
|
|
28
30
|
import fs from 'node:fs';
|
|
29
31
|
import { repositoryRoot } from './knowledge/config.js';
|
|
30
32
|
|
|
@@ -42,7 +44,17 @@ function printHelp() {
|
|
|
42
44
|
console.log(`
|
|
43
45
|
Agent Sam SDK — CLI v${VERSION}
|
|
44
46
|
|
|
45
|
-
|
|
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)
|
|
@@ -58,7 +70,7 @@ function printHelp() {
|
|
|
58
70
|
agentsam tui Zero-dependency ANSI Agent Sam dashboard
|
|
59
71
|
agentsam tui rich Optional Python Rich dashboard (--install for local venv)
|
|
60
72
|
agentsam start-local Local PTY on ws://127.0.0.1:3099 (no tunnel, no Cloudflare)
|
|
61
|
-
agentsam shell
|
|
73
|
+
agentsam shell Interactive Agent Sam slash-command shell
|
|
62
74
|
agentsam tunnel Explicitly expose local PTY when remote access is wanted
|
|
63
75
|
agentsam deploy Graduate to Cloudflare / GCP when ready
|
|
64
76
|
agentsam dockerize Build/run app, knowledge, or CAD containers (--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 = {}) {
|
|
@@ -229,41 +257,6 @@ async function initFromArgs(argv) {
|
|
|
229
257
|
await runLocalInit({ ...opts, prompt: null });
|
|
230
258
|
}
|
|
231
259
|
|
|
232
|
-
async function runShellInfo(argv = []) {
|
|
233
|
-
const sub = argv[0] || 'list';
|
|
234
|
-
if (sub === 'demo' || sub === 'ansi') {
|
|
235
|
-
await runTui(['ansi', ...argv.slice(1)]);
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
if (sub === 'rich') {
|
|
239
|
-
await runTui(['rich', ...argv.slice(1)]);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
if (sub !== 'list' && sub !== 'status') {
|
|
243
|
-
throw new Error(`unknown shell command: ${sub}`);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const next = SHELL_PHASES.find((p) => p.status === 'next' || p.status === 'current');
|
|
247
|
-
console.log(`
|
|
248
|
-
╔═══════════════════════════════╗
|
|
249
|
-
║ Agent Sam Terminal ║
|
|
250
|
-
╚════════════════════════════════╝
|
|
251
|
-
|
|
252
|
-
Local PTY agentsam start-local ws://127.0.0.1:3099
|
|
253
|
-
ANSI TUI agentsam tui zero-dependency Node UI
|
|
254
|
-
Rich TUI agentsam tui rich optional richer Python UI
|
|
255
|
-
agentsam tui rich --install
|
|
256
|
-
DB agentsam db status local SQLite
|
|
257
|
-
|
|
258
|
-
Current milestone: ${next?.label ?? 'local terminal experience'}
|
|
259
|
-
|
|
260
|
-
Slash commands (${SLASH_COMMANDS.length} registered):
|
|
261
|
-
`);
|
|
262
|
-
for (const row of SLASH_COMMANDS) {
|
|
263
|
-
console.log(` ${row.cmd.padEnd(14)} ${row.description}`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
260
|
const command = process.argv[2];
|
|
268
261
|
const rest = process.argv.slice(3);
|
|
269
262
|
|
|
@@ -271,6 +264,30 @@ if (command === '--version' || command === '-v') {
|
|
|
271
264
|
console.log(VERSION);
|
|
272
265
|
} else if (command === '--help' || command === '-h' || !command) {
|
|
273
266
|
printHelp();
|
|
267
|
+
} else if (command === 'create') {
|
|
268
|
+
try {
|
|
269
|
+
const opts = parseCreateArgs(rest);
|
|
270
|
+
if (opts.help || !opts.projectName) {
|
|
271
|
+
console.log(`agentsam create <name> --preset <${listPresets().map((row) => row.id).join('|')}> [--target local|cloudflare|gcp]`);
|
|
272
|
+
} else {
|
|
273
|
+
const preset = resolvePreset(opts.preset);
|
|
274
|
+
const created = await runLocalInit({ projectName: opts.projectName, lane: preset.lane, runTarget: opts.runTarget, prompt: null });
|
|
275
|
+
applyPresetSelection(created.dir, preset);
|
|
276
|
+
console.log(` ✓ Preset ${preset.id}\n ✓ Features ${preset.features.join(', ') || 'none'}\n ✓ Capabilities ${preset.capabilities.length}\n`);
|
|
277
|
+
}
|
|
278
|
+
} catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
|
|
279
|
+
} else if (command === 'add') {
|
|
280
|
+
try { await runAdd(rest); }
|
|
281
|
+
catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
|
|
282
|
+
} else if (command === 'dev') {
|
|
283
|
+
try { await runDev(rest); }
|
|
284
|
+
catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
|
|
285
|
+
} else if (command === 'inspect') {
|
|
286
|
+
try { await runInspect(rest); }
|
|
287
|
+
catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
|
|
288
|
+
} else if (command === 'capabilities') {
|
|
289
|
+
try { await runCapabilities(rest); }
|
|
290
|
+
catch (e) { console.error(`\n ✗ ${e?.message || e}\n`); process.exitCode = 1; }
|
|
274
291
|
} else if (command === 'context') {
|
|
275
292
|
try {
|
|
276
293
|
await runContext(rest);
|
|
@@ -301,7 +318,7 @@ if (command === '--version' || command === '-v') {
|
|
|
301
318
|
}
|
|
302
319
|
} else if (command === 'shell') {
|
|
303
320
|
try {
|
|
304
|
-
await
|
|
321
|
+
await runShell(rest);
|
|
305
322
|
} catch (e) {
|
|
306
323
|
console.error(`\n ✗ ${e?.message || e}\n`);
|
|
307
324
|
process.exit(1);
|
package/src/commands/deploy.js
CHANGED
|
@@ -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' },
|
|
28
|
-
if (opts.help) { console.log('agentsam init [.] [--cwd PATH] [--yes] [--include src,docs] [--exclude src/generated] [--scope NAME] [--target local|production] [--
|
|
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,
|
|
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
|
-
|
|
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',
|
|
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
|
|