@inneranimalmedia/agentsam-sdk 2.4.0 → 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.
- package/README.md +7 -6
- package/docs/AGENTSAM_WORKBENCH.md +30 -0
- package/docs/AUTH_IDENTITY_CONTRACT.md +52 -0
- package/docs/CAPABILITIES.md +5 -3
- package/docs/CLI_SHELL.md +57 -80
- package/docs/CMS_STUDIO.md +101 -0
- package/docs/CONTEXT.md +170 -0
- package/docs/LOCAL_OLLAMA.md +58 -0
- package/docs/MERKLE.md +19 -19
- package/docs/PORTABLE_CONTEXT.md +4 -3
- package/docs/PROJECT_CONFIG.md +72 -0
- package/docs/RELEASES.md +26 -2
- package/docs/REPOSITORY_INTELLIGENCE.md +1 -1
- package/docs/REPOSITORY_KNOWLEDGE.md +114 -0
- package/docs/SDK_WORKER.md +86 -0
- package/docs/SECURITY.md +59 -22
- package/docs/client-cms-editor.md +15 -0
- package/docs/local-studio/WORKMODE_DONOR_NOTES.md +485 -0
- package/package.json +17 -5
- package/packages/identity/package.json +11 -2
- package/packages/identity/src/contracts/auth-config.js +98 -0
- package/packages/identity/src/index.js +1 -0
- package/packages/identity/src/oauth/README.md +2 -2
- package/packages/identity/src/oauth/credentials.js +11 -4
- package/packages/identity/src/oauth/iam-platform.js +3 -3
- package/packages/identity/src/providers/iam/index.js +7 -7
- package/packages/identity/src/providers/iam/oauth.js +6 -4
- package/packages/identity/src/providers/iam/profile.js +5 -5
- package/packages/identity/tests/auth-config.test.mjs +57 -0
- package/packages/identity/tests/oauth-credentials.test.mjs +13 -2
- package/protocol/FILEMETA_V1.md +95 -0
- package/protocol/INSPECT_VIEWS_V1.md +40 -0
- package/protocol/MERKLE_PERSISTENCE_V1.md +50 -0
- package/protocol/MERKLE_V1.md +3 -1
- package/protocol/capabilities/manifest.json +32 -3
- package/protocol/capabilities/repository-snapshot.schema.json +1 -0
- package/protocol/context/context-budget.schema.json +41 -0
- package/protocol/context/context-item.schema.json +20 -0
- package/protocol/context/resolved-context-pack.schema.json +42 -0
- package/protocol/context/result-policy.schema.json +17 -0
- package/protocol/knowledge/context-pack.schema.json +33 -0
- package/protocol/knowledge/retrieval-query.schema.json +68 -13
- package/python/README.md +15 -7
- package/python/agentsam_sdk/cli.py +0 -21
- package/python/agentsam_sdk/tui/README.md +17 -12
- package/python/agentsam_sdk/tui/bootstrap.py +2 -2
- package/python/agentsam_sdk/tui/demo.py +25 -10
- package/python/agentsam_sdk/tui/onboarding.py +208 -0
- package/python/tests/test_tui_cli.py +7 -6
- package/skills/README.md +21 -0
- package/skills/agentsam-app-fundamentals/SKILL.md +165 -0
- package/skills/agentsam-app-fundamentals/references/graphs-contracts-ast-merkle.md +89 -0
- package/skills/agentsam-app-fundamentals/references/trust-credentials-and-destinations.md +99 -0
- package/skills/agentsam-jr-dev/SKILL.md +232 -0
- package/skills/agentsam-jr-dev/references/real-application-logic.md +156 -0
- package/skills/agentsam-jr-dev/references/web-application-fundamentals.md +240 -0
- package/skills/agentsam-progression-guard/SKILL.md +197 -0
- package/skills/agentsam-progression-guard/references/checkpoint-chain.md +111 -0
- package/skills/agentsam-progression-guard/references/hooks-operational-io.md +96 -0
- package/skills/catalog.json +53 -0
- package/src/capabilities/index.js +7 -0
- package/src/capabilities/repository-snapshot-view.js +238 -0
- package/src/capabilities/repository-snapshot.js +28 -14
- package/src/cli.js +108 -56
- package/src/commands/cad.js +56 -6
- package/src/commands/context.js +14 -2
- package/src/commands/db.js +4 -7
- package/src/commands/deploy.js +17 -31
- package/src/commands/interactive.js +21 -0
- package/src/commands/knowledge.js +27 -4
- package/src/commands/merkle-persist.js +118 -0
- package/src/commands/merkle.js +32 -17
- package/src/commands/models.js +107 -0
- package/src/commands/ollama.js +259 -0
- package/src/commands/preferences.js +102 -0
- package/src/commands/product.js +86 -16
- package/src/commands/security.js +3 -3
- package/src/commands/shell.js +71 -27
- package/src/commands/skills.js +66 -0
- package/src/commands/start-local.js +1 -1
- package/src/commands/tunnel.js +5 -4
- package/src/context/budget.js +58 -0
- package/src/context/compact.js +28 -0
- package/src/context/index.js +5 -0
- package/src/context/resolve.js +84 -0
- package/src/context/result-policy.js +66 -0
- package/src/index.js +16 -0
- package/src/indexing/execution-boundary.js +144 -0
- package/src/indexing/index.js +8 -0
- package/src/indexing/provider.js +41 -0
- package/src/knowledge/config.js +2 -2
- package/src/knowledge/context-pack.js +12 -1
- package/src/knowledge/contracts.js +9 -4
- package/src/knowledge/engine.js +7 -3
- package/src/knowledge/service/server.js +1 -1
- package/src/lib/auth.js +10 -19
- package/src/lib/bridge-client.js +7 -5
- package/src/lib/cli-preferences.js +74 -0
- package/src/lib/core-client.js +8 -8
- package/src/lib/deploy-receipt/index.js +5 -2
- package/src/lib/detect-context.js +6 -5
- package/src/lib/identity-scaffold.js +1 -1
- package/src/lib/local-scaffold.js +35 -33
- package/src/lib/local-status.js +9 -17
- package/src/lib/merkle/cloudflare-persistence.js +321 -0
- package/src/lib/merkle/filemeta.js +43 -0
- package/src/lib/merkle/git-ignore.js +24 -0
- package/src/lib/merkle/hash.js +1 -0
- package/src/lib/merkle/index.js +22 -0
- package/src/lib/merkle/persistence.js +72 -0
- package/src/lib/merkle/semantic.js +359 -0
- package/src/lib/merkle/snapshot.js +9 -3
- package/src/lib/merkle/tree.js +11 -6
- package/src/lib/open-url.js +66 -0
- package/src/lib/project-config.js +227 -0
- package/src/lib/project-rules.js +68 -0
- package/src/lib/save-sdk-token.js +1 -1
- package/src/lib/slash-commands.js +2 -1
- package/src/lib/tools.js +11 -5
- package/src/security/index.js +1 -0
- package/src/security/inventory.js +4 -1
- package/src/security/render.js +27 -5
- package/src/security/scan.js +24 -9
- package/src/security/trust-boundary.js +24 -0
- package/src/skills/index.js +64 -0
- package/src/tools/index.js +1 -0
- package/src/tools/search.js +70 -0
- package/src/ui/ansi.js +1 -1
- package/src/ui/boot.js +56 -0
- package/src/ui/merkle/render.js +1 -0
- package/src/ui/runtime-activity.js +192 -0
- package/src/ui/theme.js +19 -18
- package/test/app-building-skills.test.mjs +61 -0
- package/test/apps-scaffold-contract.test.mjs +56 -0
- package/test/capabilities.test.mjs +53 -4
- package/test/cli-preferences.test.mjs +25 -0
- package/test/context.test.mjs +95 -0
- package/test/indexing-provider.test.mjs +29 -0
- package/test/jr-dev-skill.test.mjs +26 -0
- package/test/knowledge-context-pack.test.mjs +19 -0
- package/test/knowledge.test.mjs +1 -1
- package/test/merkle-persistence.test.mjs +91 -0
- package/test/merkle.test.mjs +77 -3
- package/test/models.test.mjs +37 -0
- package/test/ollama.test.mjs +94 -0
- package/test/open-url.test.mjs +64 -0
- package/test/project-config.test.mjs +81 -0
- package/test/project-rules.test.mjs +44 -0
- package/test/release-hygiene.test.mjs +34 -0
- package/test/repository-snapshot-view.test.mjs +112 -0
- package/test/runtime-activity.test.mjs +98 -0
- package/test/sdk-worker-contract.test.mjs +68 -0
- package/test/security.test.mjs +46 -0
- package/test/shell.test.mjs +8 -1
- package/test/skills.test.mjs +22 -0
- package/test/smoke.mjs +2 -2
- package/test/theme-portability.test.mjs +14 -0
- package/test/tools-search.test.mjs +27 -0
- package/examples/agentsam-tui-ansi.mjs +0 -149
- package/src/commands/tui.js +0 -120
- package/src/ui/splash-xterm.js +0 -290
- package/src/ui/splash.js +0 -426
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readSnapshot } from '../lib/merkle/snapshot.js';
|
|
3
|
+
import {
|
|
4
|
+
buildMerklePersistencePlan,
|
|
5
|
+
persistMerkleSnapshotCloudflare,
|
|
6
|
+
resolveWranglerMerklePersistence,
|
|
7
|
+
} from '../lib/merkle/cloudflare-persistence.js';
|
|
8
|
+
|
|
9
|
+
function value(args, index, flag) {
|
|
10
|
+
const next = args[index + 1];
|
|
11
|
+
if (!next || next.startsWith('--')) throw new Error(`Missing value for ${flag}`);
|
|
12
|
+
return next;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function printMerklePersistHelp() {
|
|
16
|
+
console.log(`
|
|
17
|
+
agentsam merkle persist <snapshot.json> — publish a saved Merkle snapshot through host bindings
|
|
18
|
+
|
|
19
|
+
--wrangler-config <file> Worker config containing WEBSITE_ASSETS and optionally DB
|
|
20
|
+
--owner-user-id <id> Snapshot owner authority (or AGENTSAM_OWNER_USER_ID)
|
|
21
|
+
--repo-id <id> Canonical repo id; inferred for GitHub/GitLab/Bitbucket when possible
|
|
22
|
+
--capture-kind <kind> deploy|manual|agent|index (default manual)
|
|
23
|
+
--connection-id <id> Execution provenance for non-deploy captures
|
|
24
|
+
--runtime-lease-id <id> Alternative execution provenance for non-deploy captures
|
|
25
|
+
--deployment-id <id> Optional deployment linkage
|
|
26
|
+
--worker-version <id> Optional provider worker version linkage
|
|
27
|
+
--reference-label <label> Optional human/audit label
|
|
28
|
+
--source <source> github|gitlab|bitbucket|local|upload (default local)
|
|
29
|
+
--prefix <prefix> Object prefix (default agentsam_fs_merkle_snapshots)
|
|
30
|
+
--r2-binding <name> Logical R2 role (default WEBSITE_ASSETS)
|
|
31
|
+
--d1-binding <name> Logical D1 role (default DB)
|
|
32
|
+
--environment <name> Wrangler environment
|
|
33
|
+
--wrangler-bin <path> Wrangler executable (default WRANGLER_BIN or wrangler)
|
|
34
|
+
--r2-only Upload object without indexing in D1
|
|
35
|
+
--dry-run Resolve bindings and emit the exact storage/index plan without writes
|
|
36
|
+
--json Machine-readable output
|
|
37
|
+
|
|
38
|
+
The SDK never infers owner identity. Hosts should pass authenticated owner_user_id.
|
|
39
|
+
Physical bucket/database names come from Wrangler bindings, so customer installs keep
|
|
40
|
+
WEBSITE_ASSETS/DB while selecting their own storage resources.
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parse(args) {
|
|
45
|
+
if (!args.length || args.includes('--help') || args.includes('-h')) return { help: true };
|
|
46
|
+
const opts = {
|
|
47
|
+
snapshotPath: '', json: false, dryRun: false, r2Only: false,
|
|
48
|
+
captureKind: 'manual', source: 'local', r2Binding: 'WEBSITE_ASSETS', d1Binding: 'DB',
|
|
49
|
+
storagePrefix: 'agentsam_fs_merkle_snapshots',
|
|
50
|
+
};
|
|
51
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
52
|
+
const arg = args[i];
|
|
53
|
+
if (!arg.startsWith('-') && !opts.snapshotPath) { opts.snapshotPath = arg; continue; }
|
|
54
|
+
if (arg === '--json') { opts.json = true; continue; }
|
|
55
|
+
if (arg === '--dry-run') { opts.dryRun = true; continue; }
|
|
56
|
+
if (arg === '--r2-only') { opts.r2Only = true; continue; }
|
|
57
|
+
const map = {
|
|
58
|
+
'--wrangler-config': 'wranglerConfig', '--owner-user-id': 'ownerUserId', '--repo-id': 'repoId',
|
|
59
|
+
'--capture-kind': 'captureKind', '--connection-id': 'connectionId', '--runtime-lease-id': 'runtimeLeaseId',
|
|
60
|
+
'--deployment-id': 'deploymentId', '--worker-version': 'workerVersionId', '--reference-label': 'referenceLabel',
|
|
61
|
+
'--source': 'source', '--prefix': 'storagePrefix', '--r2-binding': 'r2Binding', '--d1-binding': 'd1Binding',
|
|
62
|
+
'--environment': 'environment', '--wrangler-bin': 'wranglerBin', '--root': 'root',
|
|
63
|
+
};
|
|
64
|
+
if (map[arg]) { opts[map[arg]] = value(args, i, arg); i += 1; continue; }
|
|
65
|
+
throw new Error(`Unknown merkle persist option: ${arg}`);
|
|
66
|
+
}
|
|
67
|
+
if (!opts.snapshotPath) throw new Error('snapshot_file_required');
|
|
68
|
+
opts.ownerUserId ||= process.env.AGENTSAM_OWNER_USER_ID || '';
|
|
69
|
+
opts.wranglerConfig ||= process.env.AGENTSAM_WRANGLER_CONFIG || '';
|
|
70
|
+
opts.connectionId ||= process.env.AGENTSAM_CONNECTION_ID || '';
|
|
71
|
+
opts.runtimeLeaseId ||= process.env.AGENTSAM_RUNTIME_LEASE_ID || '';
|
|
72
|
+
return opts;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function runMerklePersist(args = []) {
|
|
76
|
+
const opts = parse(args);
|
|
77
|
+
if (opts.help) { printMerklePersistHelp(); return null; }
|
|
78
|
+
const snapshotPath = path.resolve(opts.snapshotPath);
|
|
79
|
+
const snapshot = await readSnapshot(snapshotPath);
|
|
80
|
+
const root = path.resolve(opts.root || snapshot.rootPath || process.cwd());
|
|
81
|
+
const wrangler = resolveWranglerMerklePersistence({
|
|
82
|
+
configPath: opts.wranglerConfig,
|
|
83
|
+
environment: opts.environment || null,
|
|
84
|
+
r2Binding: opts.r2Binding,
|
|
85
|
+
d1Binding: opts.d1Binding,
|
|
86
|
+
r2Only: opts.r2Only,
|
|
87
|
+
});
|
|
88
|
+
const plan = buildMerklePersistencePlan({
|
|
89
|
+
snapshot,
|
|
90
|
+
root,
|
|
91
|
+
ownerUserId: opts.ownerUserId,
|
|
92
|
+
repoId: opts.repoId,
|
|
93
|
+
source: opts.source,
|
|
94
|
+
captureKind: opts.captureKind,
|
|
95
|
+
connectionId: opts.connectionId,
|
|
96
|
+
runtimeLeaseId: opts.runtimeLeaseId,
|
|
97
|
+
deploymentId: opts.deploymentId,
|
|
98
|
+
workerVersionId: opts.workerVersionId,
|
|
99
|
+
referenceLabel: opts.referenceLabel,
|
|
100
|
+
storagePrefix: opts.storagePrefix,
|
|
101
|
+
wrangler,
|
|
102
|
+
});
|
|
103
|
+
const result = persistMerkleSnapshotCloudflare({
|
|
104
|
+
snapshotPath,
|
|
105
|
+
snapshot,
|
|
106
|
+
plan,
|
|
107
|
+
wranglerBin: opts.wranglerBin,
|
|
108
|
+
r2Only: opts.r2Only,
|
|
109
|
+
dryRun: opts.dryRun,
|
|
110
|
+
});
|
|
111
|
+
if (opts.json) process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
112
|
+
else {
|
|
113
|
+
console.log(`Persisted ${result.snapshot_id}${result.dry_run ? ' (dry run)' : ''}`);
|
|
114
|
+
console.log(` R2 ${result.binding} -> ${result.storage_bucket}/${result.storage_key}`);
|
|
115
|
+
if (result.table) console.log(` D1 ${result.database_binding} -> ${result.database_name}.${result.table}`);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
package/src/commands/merkle.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { buildMerkleTree, saveSnapshot, readSnapshot, diffTrees, normalizePolicy } from '../lib/merkle/index.js';
|
|
4
4
|
import { renderSummary, safeText } from '../ui/merkle/render.js';
|
|
5
5
|
import { runMerkleExplorer } from '../ui/merkle/explorer.js';
|
|
6
|
+
import { runMerklePersist } from './merkle-persist.js';
|
|
6
7
|
|
|
7
8
|
export function printMerkleHelp() {
|
|
8
9
|
console.log(`
|
|
@@ -13,24 +14,26 @@ export function printMerkleHelp() {
|
|
|
13
14
|
verify <snapshot> Check current files against a saved manifest
|
|
14
15
|
diff <a> <b> Compare two directories or snapshot files
|
|
15
16
|
inspect [path] Show every path and hash (also accepts a snapshot)
|
|
16
|
-
|
|
17
|
+
persist <snapshot> Publish a saved snapshot through WEBSITE_ASSETS/DB bindings
|
|
18
|
+
explore [path] Open the interactive tree explorer
|
|
17
19
|
|
|
18
20
|
--out <file> Snapshot destination
|
|
19
21
|
--force Replace an existing snapshot file
|
|
22
|
+
--semantic Add agentsam-filemeta classification + AST metadata
|
|
20
23
|
--root <path> Verify a snapshot against a different directory
|
|
21
24
|
--include <default-rule> Include an ignored category, e.g. --include dist
|
|
22
25
|
--exclude <path-or-name> Ignore an additional name or relative subtree
|
|
23
|
-
--
|
|
24
|
-
--json Machine-readable output; never opens
|
|
26
|
+
--interactive Explore root/inspect/verify/diff interactively
|
|
27
|
+
--json Machine-readable output; never opens the interactive explorer
|
|
25
28
|
|
|
26
29
|
Examples:
|
|
27
30
|
agentsam merkle root .
|
|
28
|
-
agentsam merkle snapshot . --out .agentsam/merkle.json
|
|
29
|
-
agentsam merkle verify .agentsam/merkle.json --
|
|
30
|
-
agentsam merkle diff ./mac-copy ./vm-copy --
|
|
31
|
-
agentsam
|
|
31
|
+
agentsam merkle snapshot . --semantic --out .agentsam/merkle.json
|
|
32
|
+
agentsam merkle verify .agentsam/merkle.json --interactive
|
|
33
|
+
agentsam merkle diff ./mac-copy ./vm-copy --interactive
|
|
34
|
+
agentsam merkle explore .
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
Explorer: arrows or j/k to move; Enter/right to expand; left to collapse;
|
|
34
37
|
c to filter changes; r to rescan; q/Esc/Ctrl+C to close.
|
|
35
38
|
Exit codes: 0 success/match, 1 differences, 2 error, 130 interrupted scan.
|
|
36
39
|
Includes/excludes are literal rules, not globs or .gitignore syntax.
|
|
@@ -39,14 +42,16 @@ export function printMerkleHelp() {
|
|
|
39
42
|
|
|
40
43
|
function parse(argv) {
|
|
41
44
|
const [command, ...args] = argv;
|
|
42
|
-
if (!['root', 'snapshot', 'verify', 'diff', 'inspect', 'tui'].includes(command)) throw new Error(`Unknown Merkle command: ${command}`);
|
|
43
|
-
const
|
|
45
|
+
if (!['root', 'snapshot', 'verify', 'diff', 'inspect', 'explore', 'tui'].includes(command)) throw new Error(`Unknown Merkle command: ${command}`);
|
|
46
|
+
const publicCommand = command === 'tui' ? 'explore' : command;
|
|
47
|
+
const opts = { command: publicCommand, paths: [], include: [], exclude: [], tui: publicCommand === 'explore', json: false, force: false, semantic: false };
|
|
44
48
|
let positionalOnly = false;
|
|
45
49
|
for (let i = 0; i < args.length; i++) {
|
|
46
50
|
const arg = args[i];
|
|
47
51
|
if (arg === '--' && !positionalOnly) { positionalOnly = true; continue; }
|
|
48
52
|
if (positionalOnly || !arg.startsWith('-')) opts.paths.push(arg);
|
|
49
|
-
else if (
|
|
53
|
+
else if (arg === '--interactive' || arg === '--tui') opts.tui = true;
|
|
54
|
+
else if (['--json', '--force', '--semantic'].includes(arg)) opts[arg.slice(2)] = true;
|
|
50
55
|
else if (['--out', '--root', '--include', '--exclude'].includes(arg)) {
|
|
51
56
|
const value = args[++i];
|
|
52
57
|
if (!value || value.startsWith('--')) throw new Error(`Missing value for ${arg}`);
|
|
@@ -59,7 +64,8 @@ function parse(argv) {
|
|
|
59
64
|
if (!count && !opts.paths.length) opts.paths.push('.');
|
|
60
65
|
if ((opts.out || opts.force) && command !== 'snapshot') throw new Error('--out and --force apply only to snapshot.');
|
|
61
66
|
if (opts.root && command !== 'verify') throw new Error('--root applies only to verify.');
|
|
62
|
-
if (command === 'snapshot' && opts.tui) throw new Error('Use inspect --
|
|
67
|
+
if (command === 'snapshot' && opts.tui) throw new Error('Use inspect --interactive after saving a snapshot.');
|
|
68
|
+
if (opts.semantic && !['root', 'snapshot', 'inspect'].includes(command)) throw new Error('--semantic applies only to root, snapshot, or inspect.');
|
|
63
69
|
if (command === 'verify' && (opts.include.length || opts.exclude.length)) throw new Error('Verification uses the snapshot\'s saved ignore rules.');
|
|
64
70
|
opts.policy = normalizePolicy(opts);
|
|
65
71
|
return opts;
|
|
@@ -78,6 +84,14 @@ async function comparison(opts, progress) {
|
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
export async function runMerkle(argv = []) {
|
|
87
|
+
if (argv[0] === 'persist') {
|
|
88
|
+
try { return await runMerklePersist(argv.slice(1)); }
|
|
89
|
+
catch (error) {
|
|
90
|
+
process.exitCode = 2;
|
|
91
|
+
process.stderr.write(argv.includes('--json') ? JSON.stringify({ error: error.message }) + '\n' : `Merkle persist: ${safeText(error.message)}\n`);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
81
95
|
if (!argv.length || argv.includes('--help') || argv.includes('-h')) { printMerkleHelp(); return; }
|
|
82
96
|
let opts;
|
|
83
97
|
try {
|
|
@@ -91,12 +105,12 @@ export async function runMerkle(argv = []) {
|
|
|
91
105
|
}
|
|
92
106
|
if (opts.command === 'diff') return comparison(opts, progress);
|
|
93
107
|
if (opts.command === 'snapshot') {
|
|
94
|
-
const { snapshot: tree, output } = await saveSnapshot(target, { ...progress, policy: opts.policy, out: opts.out, force: opts.force });
|
|
108
|
+
const { snapshot: tree, output } = await saveSnapshot(target, { ...progress, policy: opts.policy, out: opts.out, force: opts.force, semantic: opts.semantic });
|
|
95
109
|
return { title: 'Merkle snapshot', tree, output };
|
|
96
110
|
}
|
|
97
|
-
const isSnapshot = ['inspect', '
|
|
111
|
+
const isSnapshot = ['inspect', 'explore'].includes(opts.command) && !(await fs.stat(target)).isDirectory();
|
|
98
112
|
if (isSnapshot && (opts.include.length || opts.exclude.length)) throw new Error('Snapshot inspection uses saved ignore rules.');
|
|
99
|
-
const tree = isSnapshot ? await readSnapshot(target) : await buildMerkleTree(target, { ...progress, policy: opts.policy });
|
|
113
|
+
const tree = isSnapshot ? await readSnapshot(target) : await buildMerkleTree(target, { ...progress, policy: opts.policy, semantic: opts.semantic });
|
|
100
114
|
return { title: isSnapshot ? 'Saved Merkle tree' : 'Merkle root', tree };
|
|
101
115
|
};
|
|
102
116
|
let result;
|
|
@@ -108,9 +122,10 @@ export async function runMerkle(argv = []) {
|
|
|
108
122
|
try { result = await load({ signal: controller.signal }); }
|
|
109
123
|
finally { process.removeListener('SIGINT', stop); process.removeListener('SIGTERM', stop); }
|
|
110
124
|
if (opts.json) {
|
|
111
|
-
const { rootPath, rootHash, stats, policy } = result.tree;
|
|
125
|
+
const { rootPath, rootHash, stats, policy, policyHash, semantic } = result.tree;
|
|
126
|
+
const semanticSummary = semantic ? { metadataRoot: semantic.rootHash, classifier: semantic.classifier, semanticStats: semantic.stats } : {};
|
|
112
127
|
const value = result.diff ? { ...result.diff, rootPath, policy } : opts.command === 'snapshot' ? { ...result.tree, output: result.output }
|
|
113
|
-
: opts.command === 'root' ? { rootPath, rootHash, stats, policy } : result.tree;
|
|
128
|
+
: opts.command === 'root' ? { rootPath, rootHash, stats, policy, policyHash, ...semanticSummary } : result.tree;
|
|
114
129
|
process.stdout.write(JSON.stringify(value, null, 2) + '\n');
|
|
115
130
|
} else process.stdout.write(renderSummary(result, { inspect: opts.command === 'inspect', color: process.stdout.isTTY && !Object.hasOwn(process.env, 'NO_COLOR') }));
|
|
116
131
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { probeOllama, resolveOllamaConfig } from './ollama.js';
|
|
3
|
+
|
|
4
|
+
const API_PROVIDERS = Object.freeze([
|
|
5
|
+
{ id: 'openai', label: 'OpenAI', credential: 'OPENAI_API_KEY' },
|
|
6
|
+
{ id: 'gemini', label: 'Gemini', credential: 'GEMINI_API_KEY' },
|
|
7
|
+
{ id: 'grok', label: 'Grok', credential: 'XAI_API_KEY' },
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
function clean(value) {
|
|
11
|
+
return value == null ? '' : String(value).trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function configured(value) {
|
|
15
|
+
return Boolean(clean(value));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function collectModelsStatus(options = {}) {
|
|
19
|
+
const env = options.env || process.env;
|
|
20
|
+
const fetchImpl = options.fetchImpl || fetch;
|
|
21
|
+
const ollamaConfig = resolveOllamaConfig({}, env);
|
|
22
|
+
const ollama = await probeOllama(ollamaConfig, fetchImpl);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
schemaVersion: 'agentsam-model-inventory-v1',
|
|
26
|
+
providers: API_PROVIDERS.map((provider) => ({
|
|
27
|
+
...provider,
|
|
28
|
+
configured: configured(env[provider.credential]),
|
|
29
|
+
source: 'environment',
|
|
30
|
+
})),
|
|
31
|
+
local: {
|
|
32
|
+
provider: 'ollama',
|
|
33
|
+
configured: ollama.online,
|
|
34
|
+
online: ollama.online,
|
|
35
|
+
endpoint: ollama.endpoint,
|
|
36
|
+
chatModel: ollamaConfig.model,
|
|
37
|
+
embedModel: ollamaConfig.embedModel,
|
|
38
|
+
models: ollama.models || [],
|
|
39
|
+
error: ollama.error || null,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function statusMark(ok) {
|
|
45
|
+
return ok ? pc.green('●') : pc.dim('○');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function writeLine(write, value = '') {
|
|
49
|
+
write(`${value}\n`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function renderModelsStatus(status) {
|
|
53
|
+
const lines = [];
|
|
54
|
+
lines.push('');
|
|
55
|
+
lines.push(` ${pc.bold('Agent Sam · models')}`);
|
|
56
|
+
lines.push(` ${pc.dim('Detected from this terminal session. Secrets are never printed.')}`);
|
|
57
|
+
lines.push('');
|
|
58
|
+
|
|
59
|
+
for (const provider of status.providers) {
|
|
60
|
+
const state = provider.configured ? pc.green('configured') : pc.dim('not configured');
|
|
61
|
+
const detail = provider.configured ? 'credential available' : provider.credential;
|
|
62
|
+
lines.push(` ${statusMark(provider.configured)} ${pc.cyan(provider.label.padEnd(10))} ${state.padEnd(20)} ${pc.dim(detail)}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const local = status.local;
|
|
66
|
+
const localState = local.online ? pc.green('online') : pc.dim('offline');
|
|
67
|
+
lines.push(` ${statusMark(local.online)} ${pc.cyan('Ollama'.padEnd(10))} ${localState.padEnd(20)} ${pc.dim('local only')}`);
|
|
68
|
+
|
|
69
|
+
if (local.online) {
|
|
70
|
+
const names = local.models.map((row) => row.name).filter(Boolean);
|
|
71
|
+
lines.push('');
|
|
72
|
+
lines.push(` ${pc.dim('local models')}`);
|
|
73
|
+
if (names.length) {
|
|
74
|
+
for (const name of names) lines.push(` ${pc.green('•')} ${name}`);
|
|
75
|
+
} else {
|
|
76
|
+
lines.push(` ${pc.dim('no models reported')}`);
|
|
77
|
+
}
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push(` ${pc.dim('chat default')} ${local.chatModel}`);
|
|
80
|
+
lines.push(` ${pc.dim('embed default')} ${local.embedModel}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
lines.push('');
|
|
84
|
+
lines.push(` ${pc.dim('Provider model catalogs and model selection belong to the connected host/runtime;')}`);
|
|
85
|
+
lines.push(` ${pc.dim('this command only reports what this local CLI can prove is configured or available.')}`);
|
|
86
|
+
lines.push('');
|
|
87
|
+
return lines.join('\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function runModels(argv = [], options = {}) {
|
|
91
|
+
if (argv.some((arg) => arg === '--help' || arg === '-h')) {
|
|
92
|
+
const text = 'agentsam models [--json]\n\nShow model providers configured in this terminal session plus models reported by local Ollama.\n';
|
|
93
|
+
(options.write || process.stdout.write.bind(process.stdout))(text);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const unknown = argv.filter((arg) => arg !== '--json');
|
|
97
|
+
if (unknown.length) throw new Error(`unknown models option: ${unknown[0]}`);
|
|
98
|
+
|
|
99
|
+
const status = await collectModelsStatus(options);
|
|
100
|
+
const write = options.write || ((text) => process.stdout.write(text));
|
|
101
|
+
if (argv.includes('--json')) {
|
|
102
|
+
writeLine(write, JSON.stringify(status, null, 2));
|
|
103
|
+
} else {
|
|
104
|
+
write(renderModelsStatus(status));
|
|
105
|
+
}
|
|
106
|
+
return status;
|
|
107
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
4
|
+
import { readProjectConfig, setLocalModelCapability, writeProjectConfig } from '../lib/project-config.js';
|
|
5
|
+
|
|
6
|
+
export const OLLAMA_DEFAULTS = Object.freeze({
|
|
7
|
+
baseUrl: 'http://127.0.0.1:11434',
|
|
8
|
+
model: 'qwen2.5-coder',
|
|
9
|
+
embedModel: 'mxbai-embed-large',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function clean(value) {
|
|
13
|
+
return value == null ? '' : String(value).trim();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function boolFlag(argv, name) {
|
|
17
|
+
return argv.includes(name);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function parseOllamaArgs(argv = []) {
|
|
21
|
+
const first = argv[0] && !String(argv[0]).startsWith('-') ? String(argv[0]) : 'status';
|
|
22
|
+
const opts = {
|
|
23
|
+
command: first,
|
|
24
|
+
cwd: process.cwd(),
|
|
25
|
+
envFile: '',
|
|
26
|
+
baseUrl: '',
|
|
27
|
+
model: '',
|
|
28
|
+
embedModel: '',
|
|
29
|
+
install: boolFlag(argv, '--install'),
|
|
30
|
+
start: boolFlag(argv, '--start'),
|
|
31
|
+
pull: boolFlag(argv, '--pull'),
|
|
32
|
+
json: boolFlag(argv, '--json'),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
for (let i = first === argv[0] ? 1 : 0; i < argv.length; i += 1) {
|
|
36
|
+
const arg = argv[i];
|
|
37
|
+
if (['--install', '--start', '--pull', '--json'].includes(arg)) continue;
|
|
38
|
+
if (arg === '--cwd') opts.cwd = argv[++i] || opts.cwd;
|
|
39
|
+
else if (arg === '--env-file') opts.envFile = argv[++i] || '';
|
|
40
|
+
else if (arg === '--base-url') opts.baseUrl = argv[++i] || '';
|
|
41
|
+
else if (arg === '--model') opts.model = argv[++i] || '';
|
|
42
|
+
else if (arg === '--embed-model') opts.embedModel = argv[++i] || '';
|
|
43
|
+
else if (!String(arg).startsWith('-') && opts.command === 'pull') {
|
|
44
|
+
opts.model = opts.model || arg;
|
|
45
|
+
} else if (arg === '--help' || arg === '-h') opts.command = 'help';
|
|
46
|
+
else throw new Error(`unknown ollama option: ${arg}`);
|
|
47
|
+
}
|
|
48
|
+
return opts;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function resolveOllamaConfig(opts = {}, env = process.env) {
|
|
52
|
+
return {
|
|
53
|
+
baseUrl: clean(opts.baseUrl || env.OLLAMA_BASE_URL || OLLAMA_DEFAULTS.baseUrl).replace(/\/+$/, ''),
|
|
54
|
+
model: clean(opts.model || env.OLLAMA_MODEL || OLLAMA_DEFAULTS.model),
|
|
55
|
+
embedModel: clean(opts.embedModel || env.OLLAMA_EMBED_MODEL || OLLAMA_DEFAULTS.embedModel),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function replaceEnvLine(text, key, value) {
|
|
60
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
61
|
+
const line = `${key}=${value}`;
|
|
62
|
+
const re = new RegExp(`^${escaped}=.*$`, 'm');
|
|
63
|
+
if (re.test(text)) return text.replace(re, line);
|
|
64
|
+
const prefix = text && !text.endsWith('\n') ? `${text}\n` : text;
|
|
65
|
+
return `${prefix}${line}\n`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function upsertOllamaEnvFile(filename, config) {
|
|
69
|
+
const target = path.resolve(filename);
|
|
70
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
71
|
+
let text = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : '';
|
|
72
|
+
text = replaceEnvLine(text, 'OLLAMA_BASE_URL', config.baseUrl);
|
|
73
|
+
text = replaceEnvLine(text, 'OLLAMA_MODEL', config.model);
|
|
74
|
+
text = replaceEnvLine(text, 'OLLAMA_EMBED_MODEL', config.embedModel);
|
|
75
|
+
fs.writeFileSync(target, text, 'utf8');
|
|
76
|
+
return target;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function updateProjectOllamaConfig(cwd, config) {
|
|
80
|
+
const root = path.resolve(cwd);
|
|
81
|
+
const filename = path.join(root, '.agentsam', 'config.json');
|
|
82
|
+
if (!fs.existsSync(filename)) return null;
|
|
83
|
+
const project = readProjectConfig(root);
|
|
84
|
+
setLocalModelCapability(project, 'ollama');
|
|
85
|
+
writeProjectConfig(root, project);
|
|
86
|
+
return filename;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function commandResult(command, args, options = {}) {
|
|
90
|
+
return spawnSync(command, args, {
|
|
91
|
+
encoding: 'utf8',
|
|
92
|
+
stdio: options.inherit ? 'inherit' : ['ignore', 'pipe', 'pipe'],
|
|
93
|
+
timeout: options.timeout || 15_000,
|
|
94
|
+
...options,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function commandAvailable(command, spawnSyncImpl = spawnSync) {
|
|
99
|
+
const finder = process.platform === 'win32' ? 'where' : 'which';
|
|
100
|
+
const result = spawnSyncImpl(finder, [command], { encoding: 'utf8', stdio: 'ignore' });
|
|
101
|
+
return result.status === 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function ollamaInstallPlan(platform = process.platform, available = {}) {
|
|
105
|
+
if ((platform === 'darwin' || platform === 'linux') && available.brew) {
|
|
106
|
+
return { command: 'brew', args: ['install', 'ollama'], manager: 'homebrew' };
|
|
107
|
+
}
|
|
108
|
+
if (platform === 'win32' && available.winget) {
|
|
109
|
+
return {
|
|
110
|
+
command: 'winget',
|
|
111
|
+
args: ['install', '--id', 'Ollama.Ollama', '--exact', '--accept-source-agreements', '--accept-package-agreements'],
|
|
112
|
+
manager: 'winget',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function installOllama() {
|
|
119
|
+
const available = {
|
|
120
|
+
brew: commandAvailable('brew'),
|
|
121
|
+
winget: commandAvailable('winget'),
|
|
122
|
+
};
|
|
123
|
+
const plan = ollamaInstallPlan(process.platform, available);
|
|
124
|
+
if (!plan) {
|
|
125
|
+
throw new Error('automatic Ollama install is unavailable on this host; install Ollama with your platform package manager, then rerun `agentsam ollama setup`');
|
|
126
|
+
}
|
|
127
|
+
const result = commandResult(plan.command, plan.args, { inherit: true, timeout: 20 * 60_000 });
|
|
128
|
+
if (result.status !== 0) throw new Error(`Ollama install failed via ${plan.manager}`);
|
|
129
|
+
return plan.manager;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function startOllama() {
|
|
133
|
+
if (process.platform === 'darwin' && commandAvailable('brew')) {
|
|
134
|
+
const result = commandResult('brew', ['services', 'start', 'ollama'], { inherit: true, timeout: 60_000 });
|
|
135
|
+
if (result.status === 0) return 'homebrew-service';
|
|
136
|
+
}
|
|
137
|
+
const child = spawn('ollama', ['serve'], { detached: true, stdio: 'ignore' });
|
|
138
|
+
child.unref();
|
|
139
|
+
return 'ollama-serve';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function normalizeModelName(value) {
|
|
143
|
+
return clean(value).toLowerCase().replace(/:latest$/, '');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function hasModel(models, desired) {
|
|
147
|
+
const want = normalizeModelName(desired);
|
|
148
|
+
return models.some((row) => normalizeModelName(row.name || row.model) === want);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function probeOllama(config, fetchImpl = fetch) {
|
|
152
|
+
const endpoint = `${config.baseUrl}/api/tags`;
|
|
153
|
+
try {
|
|
154
|
+
const response = await fetchImpl(endpoint, { signal: AbortSignal.timeout(2000) });
|
|
155
|
+
if (!response.ok) return { online: false, status: response.status, endpoint, models: [] };
|
|
156
|
+
const body = await response.json();
|
|
157
|
+
const models = Array.isArray(body?.models) ? body.models.map((row) => ({ name: row.name || row.model || '' })).filter((row) => row.name) : [];
|
|
158
|
+
return {
|
|
159
|
+
online: true,
|
|
160
|
+
status: response.status,
|
|
161
|
+
endpoint,
|
|
162
|
+
models,
|
|
163
|
+
chat_ready: hasModel(models, config.model),
|
|
164
|
+
embed_ready: hasModel(models, config.embedModel),
|
|
165
|
+
};
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return { online: false, status: null, endpoint, models: [], error: error?.message || String(error) };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function binaryInfo() {
|
|
172
|
+
if (!commandAvailable('ollama')) return { installed: false, version: null };
|
|
173
|
+
const result = commandResult('ollama', ['--version']);
|
|
174
|
+
return {
|
|
175
|
+
installed: result.status === 0,
|
|
176
|
+
version: result.status === 0 ? clean(result.stdout || result.stderr) : null,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function pullModel(model) {
|
|
181
|
+
const result = commandResult('ollama', ['pull', model], { inherit: true, timeout: 60 * 60_000 });
|
|
182
|
+
if (result.status !== 0) throw new Error(`ollama pull failed for ${model}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function help() {
|
|
186
|
+
return `agentsam ollama <setup|status|list|pull> [options]\n\nLocal-only Ollama development kit. Nothing here configures an edge model.\n\n setup Write OLLAMA_* values into the local project .env\n --install Explicitly install Ollama with a supported package manager\n --start Start the local Ollama service\n --pull Pull both configured chat + embedding models\n status Probe the local Ollama API and configured models\n list Alias for status with model inventory\n pull [model] Pull one model, or both configured defaults when omitted\n\n --base-url <url> Default http://127.0.0.1:11434\n --model <name> Default qwen2.5-coder\n --embed-model <name> Default mxbai-embed-large\n --cwd <path> Project root (default current directory)\n --env-file <path> Env file to update (default <cwd>/.env)\n --json Machine-readable output\n\nRemote AgentSam sessions should use the existing user-hosted terminal tunnel to execute this CLI on the local machine; the Worker should not try to reach 127.0.0.1.\n`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function printResult(result, json) {
|
|
190
|
+
if (json) {
|
|
191
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
console.log(`Ollama local development`);
|
|
195
|
+
console.log(` base URL ${result.config.baseUrl}`);
|
|
196
|
+
console.log(` chat model ${result.config.model}`);
|
|
197
|
+
console.log(` embed model ${result.config.embedModel}`);
|
|
198
|
+
console.log(` binary ${result.binary.installed ? result.binary.version || 'installed' : 'not installed'}`);
|
|
199
|
+
console.log(` API ${result.api.online ? 'online' : 'offline'}`);
|
|
200
|
+
if (result.env_file) console.log(` env ${result.env_file}`);
|
|
201
|
+
if (result.project_config) console.log(` project ${result.project_config}`);
|
|
202
|
+
if (result.api.online) {
|
|
203
|
+
console.log(` chat ready ${result.api.chat_ready ? 'yes' : 'no'}`);
|
|
204
|
+
console.log(` embed ready ${result.api.embed_ready ? 'yes' : 'no'}`);
|
|
205
|
+
}
|
|
206
|
+
if (!result.binary.installed) console.log(` next agentsam ollama setup --install --start --pull`);
|
|
207
|
+
else if (!result.api.online) console.log(` next agentsam ollama setup --start`);
|
|
208
|
+
else if (!result.api.chat_ready || !result.api.embed_ready) console.log(` next agentsam ollama pull`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function runOllama(argv = []) {
|
|
212
|
+
const opts = parseOllamaArgs(argv);
|
|
213
|
+
if (opts.command === 'help') {
|
|
214
|
+
process.stdout.write(help());
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (!['setup', 'status', 'list', 'pull'].includes(opts.command)) {
|
|
218
|
+
throw new Error(`unknown ollama command: ${opts.command}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const root = path.resolve(opts.cwd || process.cwd());
|
|
222
|
+
const config = resolveOllamaConfig(opts);
|
|
223
|
+
let envFile = null;
|
|
224
|
+
let projectConfig = null;
|
|
225
|
+
let installedBy = null;
|
|
226
|
+
let startedBy = null;
|
|
227
|
+
|
|
228
|
+
if (opts.command === 'setup') {
|
|
229
|
+
envFile = upsertOllamaEnvFile(opts.envFile || path.join(root, '.env'), config);
|
|
230
|
+
projectConfig = updateProjectOllamaConfig(root, config);
|
|
231
|
+
if (!commandAvailable('ollama') && opts.install) installedBy = installOllama();
|
|
232
|
+
if (commandAvailable('ollama') && opts.start) startedBy = startOllama();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (opts.command === 'pull') {
|
|
236
|
+
if (!commandAvailable('ollama')) throw new Error('Ollama is not installed; run `agentsam ollama setup --install` first');
|
|
237
|
+
const requested = opts.model && opts.model !== OLLAMA_DEFAULTS.model ? [opts.model] : [config.model, config.embedModel];
|
|
238
|
+
for (const model of [...new Set(requested.filter(Boolean))]) pullModel(model);
|
|
239
|
+
} else if (opts.command === 'setup' && opts.pull) {
|
|
240
|
+
if (!commandAvailable('ollama')) throw new Error('Ollama is not installed; rerun setup with --install before --pull');
|
|
241
|
+
for (const model of [...new Set([config.model, config.embedModel])]) pullModel(model);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (startedBy) await new Promise((resolve) => setTimeout(resolve, 600));
|
|
245
|
+
const result = {
|
|
246
|
+
schemaVersion: 'agentsam-ollama-local-v1',
|
|
247
|
+
local_only: true,
|
|
248
|
+
edge_model: false,
|
|
249
|
+
config,
|
|
250
|
+
env_file: envFile,
|
|
251
|
+
project_config: projectConfig,
|
|
252
|
+
installed_by: installedBy,
|
|
253
|
+
started_by: startedBy,
|
|
254
|
+
binary: binaryInfo(),
|
|
255
|
+
api: await probeOllama(config),
|
|
256
|
+
};
|
|
257
|
+
printResult(result, opts.json);
|
|
258
|
+
return result;
|
|
259
|
+
}
|