@inneranimalmedia/agentsam-sdk 2.4.1 → 2.6.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/AGENTSAM.md +55 -0
- package/README.md +17 -12
- package/bin/agentsam +2 -0
- package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
- 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 +167 -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 +35 -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 +35 -10
- package/packages/connectors/cloudflare/package.json +10 -0
- package/packages/connectors/cloudflare/src/index.js +127 -0
- package/packages/connectors/cloudflare/src/owner.js +76 -0
- package/packages/connectors/cloudflare/src/routes.js +223 -0
- package/packages/connectors/cloudflare/src/vault.js +80 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
- package/packages/identity/package.json +10 -1
- package/packages/identity/src/contracts/auth-config.js +109 -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 +61 -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/README.md +1 -0
- package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
- package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
- package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
- package/protocol/capabilities/manifest.json +79 -3
- package/protocol/capabilities/repository-snapshot.schema.json +1 -0
- package/protocol/context/context-budget.schema.json +36 -0
- package/protocol/context/context-item.schema.json +19 -0
- package/protocol/context/resolved-context-pack.schema.json +47 -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/protocol/models/README.md +373 -0
- package/protocol/models/model-inventory-v2.schema.json +212 -0
- 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-cloudflare-workers/SKILL.md +53 -0
- package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
- package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
- package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -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 +71 -0
- package/src/agent/capability-adapter.js +25 -13
- package/src/agent/index.js +1 -0
- package/src/agent/responses-runner.js +325 -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 +198 -76
- package/src/cloudflare/cpu-profile.js +115 -0
- package/src/cloudflare/index.js +14 -0
- package/src/cloudflare/wrangler.js +132 -0
- package/src/commands/account-auth.js +47 -0
- package/src/commands/cloudflare.js +58 -0
- package/src/commands/connections.js +93 -0
- package/src/commands/context-economics.js +114 -0
- package/src/commands/context.js +14 -2
- package/src/commands/db.js +4 -7
- package/src/commands/deploy.js +54 -32
- package/src/commands/eval.js +63 -0
- package/src/commands/interactive.js +18 -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 +152 -0
- package/src/commands/ollama.js +259 -0
- package/src/commands/preferences.js +144 -0
- package/src/commands/product.js +86 -16
- package/src/commands/resume.js +67 -0
- package/src/commands/security.js +8 -6
- package/src/commands/shell.js +405 -100
- package/src/commands/skills.js +66 -0
- package/src/commands/start-local.js +1 -1
- package/src/commands/tunnel.js +5 -4
- package/src/commands/whoami.js +86 -0
- package/src/context/budget.js +120 -0
- package/src/context/compact.js +28 -0
- package/src/context/index.js +7 -0
- package/src/context/rehydrate.js +35 -0
- package/src/context/resolve.js +116 -0
- package/src/context/result-policy.js +66 -0
- package/src/errors/diagnostic.js +160 -0
- package/src/errors/index.js +9 -0
- package/src/eval/context.js +191 -0
- package/src/eval/index.js +1 -0
- package/src/index.js +70 -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/account-session.js +98 -0
- package/src/lib/agent-instructions.js +73 -0
- package/src/lib/auth.js +4 -0
- package/src/lib/bridge-client.js +7 -5
- package/src/lib/cli-preferences.js +78 -0
- package/src/lib/core-client.js +8 -8
- package/src/lib/deploy/git-guard.js +69 -0
- package/src/lib/deploy/health.js +57 -0
- package/src/lib/deploy/local-studio.js +283 -0
- package/src/lib/deploy/secret-scan.js +65 -0
- package/src/lib/deploy-receipt/index.js +5 -2
- package/src/lib/detect-context.js +6 -5
- package/src/lib/execution-approvals.js +59 -0
- package/src/lib/identity-scaffold.js +1 -1
- package/src/lib/local-scaffold.js +35 -33
- package/src/lib/local-sessions.js +127 -0
- 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/project-config.js +227 -0
- package/src/lib/project-rules.js +68 -0
- package/src/lib/provider-credentials.js +83 -0
- package/src/lib/save-sdk-token.js +1 -1
- package/src/lib/scaffold/templates/worker-api/index.js +101 -20
- package/src/lib/scaffold/wizards/worker-api.js +27 -11
- package/src/lib/slash-commands.js +22 -15
- package/src/lib/tools.js +11 -5
- package/src/models/catalog.js +135 -0
- package/src/models/index.js +7 -0
- package/src/providers/index.js +5 -0
- package/src/providers/openai-responses.js +275 -0
- package/src/security/index.js +1 -0
- package/src/security/inventory.js +4 -1
- package/src/security/process.js +35 -9
- 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/telemetry/contracts.js +203 -0
- package/src/telemetry/events.js +48 -0
- package/src/telemetry/index.js +8 -0
- package/src/tools/hydrate.js +35 -0
- package/src/tools/index.js +2 -0
- package/src/tools/search.js +70 -0
- package/src/ui/ansi.js +1 -1
- package/src/ui/boot.js +54 -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/account-session.test.mjs +36 -0
- 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 +46 -0
- package/test/cloudflare-connector.test.mjs +96 -0
- package/test/cloudflare-runtime.test.mjs +75 -0
- package/test/context.test.mjs +144 -0
- package/test/deploy-health-scan.test.mjs +67 -0
- package/test/error-diagnostics.test.mjs +59 -0
- package/test/eval-context.test.mjs +37 -0
- package/test/execution-approvals.test.mjs +27 -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/local-sessions.test.mjs +42 -0
- package/test/local-studio-deploy.test.mjs +83 -0
- package/test/merkle-persistence.test.mjs +91 -0
- package/test/merkle.test.mjs +77 -3
- package/test/model-catalog.test.mjs +43 -0
- package/test/models.test.mjs +51 -0
- package/test/npm10-lock.test.mjs +29 -0
- package/test/ollama.test.mjs +94 -0
- package/test/openai-responses.test.mjs +95 -0
- package/test/project-config.test.mjs +81 -0
- package/test/project-rules.test.mjs +44 -0
- package/test/provider-credentials.test.mjs +52 -0
- package/test/rehydrate.test.mjs +25 -0
- package/test/release-hygiene.test.mjs +34 -0
- package/test/repository-snapshot-view.test.mjs +112 -0
- package/test/responses-runner.test.mjs +148 -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 +55 -21
- package/test/skills.test.mjs +22 -0
- package/test/smoke.mjs +5 -2
- package/test/telemetry.test.mjs +79 -0
- package/test/theme-portability.test.mjs +14 -0
- package/test/tools-search.test.mjs +40 -0
- package/test/whoami-resume.test.mjs +56 -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,63 @@
|
|
|
1
|
+
import { evaluateContextFixture, listContextEvalFixtures } from '../eval/index.js';
|
|
2
|
+
|
|
3
|
+
function parse(argv) {
|
|
4
|
+
const out = { subcommand: argv[0] || '', fixture: '', strategy: 'all', model: 'gpt-6-astra', json: false, list: false };
|
|
5
|
+
for (let i = 1; i < argv.length; i += 1) {
|
|
6
|
+
const arg = argv[i];
|
|
7
|
+
if (arg === '--fixture') out.fixture = argv[++i] || '';
|
|
8
|
+
else if (arg === '--strategy') out.strategy = argv[++i] || 'all';
|
|
9
|
+
else if (arg === '--model') out.model = argv[++i] || 'gpt-6-astra';
|
|
10
|
+
else if (arg === '--json') out.json = true;
|
|
11
|
+
else if (arg === '--list') out.list = true;
|
|
12
|
+
else if (arg === '--help' || arg === '-h') out.help = true;
|
|
13
|
+
else throw new Error(`unknown eval option: ${arg}`);
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function render(report) {
|
|
19
|
+
const rows = [
|
|
20
|
+
'',
|
|
21
|
+
' AgentSam · Context Eval',
|
|
22
|
+
'',
|
|
23
|
+
` fixture ${report.fixture}`,
|
|
24
|
+
` model ${report.model}`,
|
|
25
|
+
` provider call no · deterministic/offline`,
|
|
26
|
+
'',
|
|
27
|
+
];
|
|
28
|
+
for (const row of report.strategies) {
|
|
29
|
+
rows.push(` ${row.strategy.toUpperCase()}`);
|
|
30
|
+
rows.push(` result ${row.result}`);
|
|
31
|
+
rows.push(` evidence ${row.required_found} / ${row.required_evidence} required · ${row.sources_selected} / ${row.sources_considered} selected`);
|
|
32
|
+
rows.push(` active context ~${row.active_context_tokens.toLocaleString('en-US')}`);
|
|
33
|
+
rows.push(` window ${row.window_tokens.toLocaleString('en-US')}`);
|
|
34
|
+
rows.push(` price threshold ${row.pricing_threshold_tokens.toLocaleString('en-US')}`);
|
|
35
|
+
rows.push(` remaining ${row.tokens_until_pricing_threshold.toLocaleString('en-US')}`);
|
|
36
|
+
rows.push(` tool schemas ${row.hydrated_tools} · ${row.tool_schema_chars.toLocaleString('en-US')} chars`);
|
|
37
|
+
rows.push(` compacted ${row.compacted_chars.toLocaleString('en-US')} chars`);
|
|
38
|
+
rows.push(` rehydrated ${row.rehydrated_refs.length ? row.rehydrated_refs.join(', ') : 'none'}`);
|
|
39
|
+
rows.push(` est input cost $${row.estimated_input_cost_usd.toFixed(4)}`);
|
|
40
|
+
rows.push('');
|
|
41
|
+
}
|
|
42
|
+
rows.push(` winner ${report.winner}`);
|
|
43
|
+
rows.push(` scoring ${report.scoring.join(' → ')}`);
|
|
44
|
+
rows.push('');
|
|
45
|
+
return rows.join('\n');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function runEval(argv = [], options = {}) {
|
|
49
|
+
const args = parse(argv);
|
|
50
|
+
const write = options.write || (text => process.stdout.write(text));
|
|
51
|
+
if (args.help || args.subcommand !== 'context') {
|
|
52
|
+
write('agentsam eval context --fixture <name> [--strategy bounded|discovery|compact|all] [--model gpt-6-astra] [--json]\n');
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (args.list) {
|
|
56
|
+
const value = { fixtures: listContextEvalFixtures() };
|
|
57
|
+
write(args.json ? `${JSON.stringify(value)}\n` : `${value.fixtures.join('\n')}\n`);
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
const report = await evaluateContextFixture({ fixture: args.fixture || 'exact-symbol-callers', strategy: args.strategy, model: args.model });
|
|
61
|
+
write(args.json ? `${JSON.stringify(report)}\n` : render(report));
|
|
62
|
+
return report;
|
|
63
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { configureCliPreferences } from './preferences.js';
|
|
2
|
+
import { runShell } from './shell.js';
|
|
3
|
+
import { detectCliProject, readCliPreferences } from '../lib/cli-preferences.js';
|
|
4
|
+
import { runBootScene } from '../ui/boot.js';
|
|
5
|
+
|
|
6
|
+
export async function runInteractive(options = {}) {
|
|
7
|
+
let identity = detectCliProject(options.cwd || process.cwd());
|
|
8
|
+
let preferences = readCliPreferences(identity.root);
|
|
9
|
+
|
|
10
|
+
if (!preferences || preferences.trustedDirectory !== true) {
|
|
11
|
+
const configured = await configureCliPreferences({ cwd: identity.root, firstRun: true });
|
|
12
|
+
identity = configured.identity;
|
|
13
|
+
preferences = configured.preferences;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
await runBootScene({ identity, preferences, animate: options.animate !== false });
|
|
17
|
+
await runShell([], { cwd: identity.root, intro: 'quiet' });
|
|
18
|
+
}
|
|
@@ -6,12 +6,15 @@ import { execFile } from 'node:child_process';
|
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import pkg from '../../package.json' with { type: 'json' };
|
|
9
10
|
import { CONFIG_PATH, repositoryRoot, initRepository, readConfig, scopeKey, cacheNamespace } from '../knowledge/config.js';
|
|
10
11
|
import { openSqliteStore } from '../knowledge/stores/sqlite.js';
|
|
11
12
|
import { openPostgresStore } from '../knowledge/stores/postgres.js';
|
|
12
13
|
import { planIndex, runIndex, retrieve } from '../knowledge/engine.js';
|
|
13
14
|
import { createGeminiEmbedder } from '../knowledge/providers/gemini.js';
|
|
14
15
|
import { compareObservations } from '../knowledge/evolution.js';
|
|
16
|
+
import { ensureProjectManifest, getProjectName, getRepositoryId } from '../lib/project-config.js';
|
|
17
|
+
import { ensureProjectRules } from '../lib/project-rules.js';
|
|
15
18
|
|
|
16
19
|
const execute = promisify(execFile);
|
|
17
20
|
const split = text => text.split(',').map(s => s.trim()).filter(Boolean);
|
|
@@ -28,7 +31,20 @@ export async function runRepositoryInit(argv) {
|
|
|
28
31
|
if (opts.help) { console.log('agentsam init [.] [--cwd PATH] [--yes] [--include src,docs] [--exclude src/generated] [--scope NAME] [--target local|production] [--dimensions 768]'); return; }
|
|
29
32
|
if (positionals.length > 1 || (positionals[0] && positionals[0] !== '.')) throw new Error('Use init . --cwd PATH to adopt an existing repository, or init --name NAME to scaffold.');
|
|
30
33
|
const root = repositoryRoot(opts.cwd);
|
|
31
|
-
|
|
34
|
+
const knowledgePath = path.join(root, CONFIG_PATH);
|
|
35
|
+
const existingKnowledge = fs.existsSync(knowledgePath) ? readConfig(root) : null;
|
|
36
|
+
const projectManifest = ensureProjectManifest(root, {
|
|
37
|
+
repositoryId: existingKnowledge?.repository_id || undefined,
|
|
38
|
+
sdkVersion: pkg.version,
|
|
39
|
+
});
|
|
40
|
+
ensureProjectRules(root, getProjectName(projectManifest));
|
|
41
|
+
const projectRepositoryId = getRepositoryId(projectManifest);
|
|
42
|
+
if (existingKnowledge) {
|
|
43
|
+
if (projectRepositoryId && existingKnowledge.repository_id !== projectRepositoryId) {
|
|
44
|
+
throw new Error(`repository_identity_mismatch: project=${projectRepositoryId} knowledge=${existingKnowledge.repository_id}`);
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`${CONFIG_PATH} already exists; edit it to change scope/profile. Existing configuration was preserved.`);
|
|
47
|
+
}
|
|
32
48
|
let include = opts.include, exclude = opts.exclude, target = opts.target, dimensions = opts.dimensions;
|
|
33
49
|
if (!opts.yes) {
|
|
34
50
|
if (!process.stdin.isTTY) throw new Error('Existing-repository setup needs a terminal or --yes with explicit options.');
|
|
@@ -41,7 +57,14 @@ export async function runRepositoryInit(argv) {
|
|
|
41
57
|
dimensions ??= await prompt.question('4) Gemini Embedding 2 dimensions [768]: ') || '768';
|
|
42
58
|
} finally { prompt.close(); }
|
|
43
59
|
}
|
|
44
|
-
const config = initRepository(root, {
|
|
60
|
+
const config = initRepository(root, {
|
|
61
|
+
include: split(include || '.'),
|
|
62
|
+
exclude: split(exclude || ''),
|
|
63
|
+
scope: opts.scope || 'default',
|
|
64
|
+
target: target || 'local',
|
|
65
|
+
dimensions: Number(dimensions || 768),
|
|
66
|
+
repositoryId: projectRepositoryId,
|
|
67
|
+
});
|
|
45
68
|
show({ root, config: CONFIG_PATH, storage: config.storage.driver, next: config.storage.driver === 'postgres' ? ['agentsam index setup-store', 'agentsam index plan', 'agentsam index run'] : ['agentsam index plan', 'agentsam index run', 'agentsam search "your symbol"'], note: 'No indexing, credentials, network calls, or source-file changes during setup.' });
|
|
46
69
|
}
|
|
47
70
|
|
|
@@ -73,9 +96,9 @@ export async function runKnowledge(argv) {
|
|
|
73
96
|
|
|
74
97
|
export async function runSearch(argv) {
|
|
75
98
|
const { values: opts, positionals } = flags(argv, { semantic: { type: 'boolean' }, 'top-k': { type: 'string' }, 'token-budget': { type: 'string' }, generation: { type: 'string' } });
|
|
76
|
-
if (opts.help) { console.log('agentsam search "query" [--cwd PATH] [--semantic] [--top-k 8] [--token-budget
|
|
99
|
+
if (opts.help) { console.log('agentsam search "query" [--cwd PATH] [--semantic] [--top-k 8] [--token-budget 6000] [--generation ID]'); return; }
|
|
77
100
|
const root = repositoryRoot(opts.cwd), config = readConfig(root), store = await openStore(root, config, true);
|
|
78
|
-
try { show(await retrieve({ store, config, text: positionals.join(' '), semantic: opts.semantic, embedder: opts.semantic ? provider() : null, topK: Number(opts['top-k'] || 8), tokenBudget: Number(opts['token-budget'] ||
|
|
101
|
+
try { show(await retrieve({ store, config, text: positionals.join(' '), semantic: opts.semantic, embedder: opts.semantic ? provider() : null, topK: Number(opts['top-k'] || 8), tokenBudget: Number(opts['token-budget'] || 6000), generationId: opts.generation })); }
|
|
79
102
|
finally { await store?.close(); }
|
|
80
103
|
}
|
|
81
104
|
|
|
@@ -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,152 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { probeOllama, resolveOllamaConfig } from './ollama.js';
|
|
3
|
+
import { listModelCatalog } from '../models/index.js';
|
|
4
|
+
import { resolveProviderCredential } from '../lib/provider-credentials.js';
|
|
5
|
+
|
|
6
|
+
const API_PROVIDERS = Object.freeze([
|
|
7
|
+
{ id: 'openai', label: 'OpenAI', credential: 'OPENAI_API_KEY' },
|
|
8
|
+
{ id: 'gemini', label: 'Gemini', credential: 'GEMINI_API_KEY' },
|
|
9
|
+
{ id: 'grok', label: 'Grok', credential: 'XAI_API_KEY' },
|
|
10
|
+
{ id: 'anthropic', label: 'Anthropic', credential: 'ANTHROPIC_API_KEY' },
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function clean(value) { return value == null ? '' : String(value).trim(); }
|
|
14
|
+
function configured(value) { return Boolean(clean(value)); }
|
|
15
|
+
|
|
16
|
+
async function discoverOpenAIModels(apiKey, fetchImpl) {
|
|
17
|
+
if (!apiKey) return { attempted: false, ok: false, models: [], error: null };
|
|
18
|
+
try {
|
|
19
|
+
const response = await fetchImpl('https://api.openai.com/v1/models', {
|
|
20
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
21
|
+
signal: typeof AbortSignal?.timeout === 'function' ? AbortSignal.timeout(8_000) : undefined,
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) return { attempted: true, ok: false, models: [], error: `HTTP ${response.status}` };
|
|
24
|
+
const body = await response.json();
|
|
25
|
+
const models = Array.isArray(body?.data) ? body.data.map((row) => clean(row?.id)).filter(Boolean) : [];
|
|
26
|
+
return { attempted: true, ok: true, models, error: null };
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return { attempted: true, ok: false, models: [], error: error?.message || String(error) };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function collectModelsStatus(options = {}) {
|
|
33
|
+
const env = options.env || process.env;
|
|
34
|
+
const ollamaFetchImpl = options.fetchImpl || fetch;
|
|
35
|
+
const providerFetchImpl = options.providerFetchImpl || fetch;
|
|
36
|
+
const ollamaConfig = resolveOllamaConfig({}, env);
|
|
37
|
+
const ollama = await probeOllama(ollamaConfig, ollamaFetchImpl);
|
|
38
|
+
const credentials = new Map(API_PROVIDERS.map((provider) => [provider.id, resolveProviderCredential(provider.id, { env, home: options.home })]));
|
|
39
|
+
const providers = API_PROVIDERS.map((provider) => {
|
|
40
|
+
const credential = credentials.get(provider.id);
|
|
41
|
+
return {
|
|
42
|
+
...provider,
|
|
43
|
+
configured: credential?.configured === true,
|
|
44
|
+
source: credential?.source || null,
|
|
45
|
+
credentialError: credential?.error || null,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const openaiCredential = credentials.get('openai');
|
|
50
|
+
const shouldDiscover = options.discoverRemote !== false && Boolean(openaiCredential?.configured);
|
|
51
|
+
const openai = shouldDiscover
|
|
52
|
+
? await discoverOpenAIModels(clean(openaiCredential?.value), providerFetchImpl)
|
|
53
|
+
: { attempted: false, ok: false, models: [], error: null };
|
|
54
|
+
const availableIds = new Set(openai.models);
|
|
55
|
+
const catalogModels = listModelCatalog().map((record) => ({
|
|
56
|
+
model_key: record.model_key,
|
|
57
|
+
provider: record.provider,
|
|
58
|
+
provider_model_id: record.provider_model_id,
|
|
59
|
+
label: record.label,
|
|
60
|
+
availability: record.provider === 'openai' && openai.ok
|
|
61
|
+
? (availableIds.has(record.provider_model_id) ? 'available' : 'unavailable')
|
|
62
|
+
: 'unverified',
|
|
63
|
+
source: record.source,
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
schemaVersion: 'agentsam-model-inventory-v2',
|
|
68
|
+
providers,
|
|
69
|
+
discovery: {
|
|
70
|
+
openai: {
|
|
71
|
+
attempted: openai.attempted,
|
|
72
|
+
ok: openai.ok,
|
|
73
|
+
error: openai.error,
|
|
74
|
+
returnedModelCount: openai.models.length,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
catalogModels,
|
|
78
|
+
availableModels: catalogModels.filter((row) => row.availability === 'available'),
|
|
79
|
+
local: {
|
|
80
|
+
provider: 'ollama',
|
|
81
|
+
configured: ollama.online,
|
|
82
|
+
online: ollama.online,
|
|
83
|
+
endpoint: ollama.endpoint,
|
|
84
|
+
chatModel: ollamaConfig.model,
|
|
85
|
+
embedModel: ollamaConfig.embedModel,
|
|
86
|
+
models: ollama.models || [],
|
|
87
|
+
error: ollama.error || null,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function statusMark(ok) { return ok ? pc.green('●') : pc.dim('○'); }
|
|
93
|
+
function writeLine(write, value = '') { write(`${value}\n`); }
|
|
94
|
+
|
|
95
|
+
export function renderModelsStatus(status) {
|
|
96
|
+
const lines = [];
|
|
97
|
+
lines.push('');
|
|
98
|
+
lines.push(` ${pc.bold('Agent Sam · models')}`);
|
|
99
|
+
lines.push(` ${pc.dim('Credential presence is local evidence; exact hosted-model availability is provider-verified when discovery succeeds.')}`);
|
|
100
|
+
lines.push('');
|
|
101
|
+
|
|
102
|
+
for (const provider of status.providers) {
|
|
103
|
+
const state = provider.configured ? pc.green('configured') : pc.dim(provider.credentialError ? 'blocked' : 'not configured');
|
|
104
|
+
const detail = provider.configured ? `credential available · ${provider.source || 'runtime'}` : provider.credentialError ? `${provider.credential} · ${provider.credentialError}` : provider.credential;
|
|
105
|
+
lines.push(` ${statusMark(provider.configured)} ${pc.cyan(provider.label.padEnd(10))} ${state.padEnd(20)} ${pc.dim(detail)}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const exact = status.availableModels || [];
|
|
109
|
+
if (exact.length) {
|
|
110
|
+
lines.push('');
|
|
111
|
+
lines.push(` ${pc.dim('provider-verified selectable models')}`);
|
|
112
|
+
for (const model of exact) lines.push(` ${pc.green('•')} ${model.provider_model_id}`);
|
|
113
|
+
} else if (status.discovery?.openai?.attempted) {
|
|
114
|
+
lines.push('');
|
|
115
|
+
lines.push(` ${pc.dim(`OpenAI discovery ${status.discovery.openai.ok ? 'completed; no catalog models matched' : `failed: ${status.discovery.openai.error || 'unknown error'}`}`)}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const local = status.local;
|
|
119
|
+
const localState = local.online ? pc.green('online') : pc.dim('offline');
|
|
120
|
+
lines.push(` ${statusMark(local.online)} ${pc.cyan('Ollama'.padEnd(10))} ${localState.padEnd(20)} ${pc.dim('local only')}`);
|
|
121
|
+
if (local.online) {
|
|
122
|
+
const names = local.models.map((row) => row.name).filter(Boolean);
|
|
123
|
+
lines.push('');
|
|
124
|
+
lines.push(` ${pc.dim('local models')}`);
|
|
125
|
+
if (names.length) for (const name of names) lines.push(` ${pc.green('•')} ${name}`);
|
|
126
|
+
else lines.push(` ${pc.dim('no models reported')}`);
|
|
127
|
+
lines.push('');
|
|
128
|
+
lines.push(` ${pc.dim('chat default')} ${local.chatModel}`);
|
|
129
|
+
lines.push(` ${pc.dim('embed default')} ${local.embedModel}`);
|
|
130
|
+
}
|
|
131
|
+
lines.push('');
|
|
132
|
+
lines.push(` ${pc.dim('Use /model inside Agent Sam to choose an exact verified model, reasoning effort, and processing tier.')}`);
|
|
133
|
+
lines.push('');
|
|
134
|
+
return lines.join('\n');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function runModels(argv = [], options = {}) {
|
|
138
|
+
if (argv.some((arg) => arg === '--help' || arg === '-h')) {
|
|
139
|
+
const text = 'agentsam models [--json] [--no-discover]\n\nShow configured providers, provider-verified known models when available, and local Ollama inventory.\n';
|
|
140
|
+
(options.write || process.stdout.write.bind(process.stdout))(text);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const allowed = new Set(['--json', '--no-discover']);
|
|
144
|
+
const unknown = argv.filter((arg) => !allowed.has(arg));
|
|
145
|
+
if (unknown.length) throw new Error(`unknown models option: ${unknown[0]}`);
|
|
146
|
+
|
|
147
|
+
const status = await collectModelsStatus({ ...options, discoverRemote: !argv.includes('--no-discover') });
|
|
148
|
+
const write = options.write || ((text) => process.stdout.write(text));
|
|
149
|
+
if (argv.includes('--json')) writeLine(write, JSON.stringify(status, null, 2));
|
|
150
|
+
else write(renderModelsStatus(status));
|
|
151
|
+
return status;
|
|
152
|
+
}
|