@inneranimalmedia/agentsam-sdk 2.5.0 → 2.6.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/AGENTSAM.md +55 -0
- package/README.md +12 -8
- package/bin/agentsam +2 -0
- package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
- package/docs/CLI_SHELL.md +163 -53
- package/docs/PLATFORM_RUNTIME_EVENTS.md +48 -0
- package/docs/RELEASES.md +16 -7
- package/docs/SOURCE_ARCHITECTURE.md +58 -0
- package/docs/TEST_TIERS.md +26 -0
- package/migrations/runtime/0001_cli_runtime.sql +298 -0
- package/package.json +45 -12
- package/packages/agentsam-repository/README.md +15 -0
- package/packages/agentsam-repository/package.json +25 -0
- package/packages/agentsam-repository/src/contracts.js +113 -0
- package/packages/agentsam-repository/src/index.js +3 -0
- package/{src/lib → packages/agentsam-repository/src}/merkle/cloudflare-persistence.js +14 -24
- package/{src/lib → packages/agentsam-repository/src}/merkle/index.js +1 -0
- package/{src/lib → packages/agentsam-repository/src}/merkle/persistence.js +6 -4
- package/{src/lib → packages/agentsam-repository/src}/merkle/policy.js +1 -0
- package/packages/agentsam-repository/test/contracts.test.mjs +40 -0
- package/packages/agentsam-repository/test/git-context.test.mjs +24 -0
- package/{test/merkle.test.mjs → packages/agentsam-repository/test/merkle-core.test.mjs} +2 -32
- package/{test → packages/agentsam-repository/test}/merkle-persistence.test.mjs +11 -6
- 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 +2 -2
- package/packages/identity/src/contracts/auth-config.js +18 -7
- package/packages/identity/tests/auth-config.test.mjs +9 -5
- package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
- package/protocol/COMPANY_REPOSITORY_GRAPH_V1.md +91 -0
- package/protocol/MERKLE_PERSISTENCE_V1.md +2 -0
- package/protocol/MERKLE_PERSISTENCE_V2.md +40 -0
- 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 +47 -0
- package/protocol/context/context-budget.schema.json +10 -15
- package/protocol/context/context-item.schema.json +4 -5
- package/protocol/context/resolved-context-pack.schema.json +19 -14
- package/protocol/models/README.md +373 -0
- package/protocol/models/model-inventory-v2.schema.json +212 -0
- package/protocol/repository/repository-contract.schema.json +24 -0
- package/protocol/repository/repository-dependency.schema.json +24 -0
- package/protocol/repository/repository-identity.schema.json +17 -0
- package/protocol/rpc/v1/common.proto +16 -0
- package/protocol/rpc/v1/errors.proto +35 -0
- package/protocol/rpc/v1/knowledge.proto +77 -0
- package/services/knowledge/package-lock.json +333 -0
- package/services/knowledge/package.json +5 -1
- 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/catalog.json +18 -0
- package/src/agent/capability-adapter.js +25 -13
- package/src/agent/index.js +1 -0
- package/src/agent/responses-runner.js +353 -0
- package/src/capabilities/repository-snapshot.js +3 -3
- package/src/cli.js +118 -31
- 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 +129 -0
- package/src/commands/context.js +1 -1
- package/src/commands/db.js +20 -3
- package/src/commands/deploy.js +39 -3
- package/src/commands/env.js +90 -0
- package/src/commands/eval.js +63 -0
- package/src/commands/interactive.js +2 -5
- package/src/commands/knowledge.js +12 -4
- package/src/commands/merkle-persist.js +30 -11
- package/src/commands/merkle.js +1 -1
- package/src/commands/models.js +149 -46
- package/src/commands/ollama.js +26 -0
- package/src/commands/preferences.js +130 -61
- package/src/commands/resume.js +67 -0
- package/src/commands/security.js +5 -3
- package/src/commands/shell.js +568 -119
- package/src/commands/tunnel.js +2 -2
- package/src/commands/whoami.js +86 -0
- package/src/context/budget.js +68 -6
- package/src/context/index.js +3 -1
- package/src/context/rehydrate.js +35 -0
- package/src/context/resolve.js +44 -12
- package/src/errors/contract.js +236 -0
- package/src/errors/diagnostic.js +160 -0
- package/src/errors/index.js +23 -0
- package/src/eval/context.js +191 -0
- package/src/eval/index.js +1 -0
- package/src/index.js +68 -2
- package/src/knowledge/service/auth.js +13 -0
- package/src/knowledge/service/grpc-client.js +115 -0
- package/src/knowledge/service/grpc-codec.js +237 -0
- package/src/knowledge/service/grpc-server.js +83 -0
- package/src/knowledge/service/job-engine.js +248 -0
- package/src/knowledge/service/server.js +87 -135
- package/src/knowledge/source.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/cli-preferences.js +55 -24
- 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 +2 -2
- package/src/lib/detect-context.js +2 -2
- package/src/lib/execution-approvals.js +59 -0
- package/src/lib/knowledge-docker.js +6 -3
- package/src/lib/local-sessions.js +148 -0
- package/src/lib/local-status.js +1 -1
- package/src/lib/project-config.js +1 -1
- package/src/lib/provider-credentials.js +183 -0
- 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 +23 -16
- package/src/local/migrations.js +93 -0
- package/src/local/runtime-store.js +141 -0
- package/src/local/sqlite.js +2 -0
- package/src/local-pty/server.js +113 -51
- package/src/models/catalog.js +135 -0
- package/src/models/discovery.js +292 -0
- package/src/models/index.js +7 -0
- package/src/providers/anthropic-messages.js +192 -0
- package/src/providers/cloudflare-chat.js +183 -0
- package/src/providers/factory.js +69 -0
- package/src/providers/gemini-generate-content.js +208 -0
- package/src/providers/index.js +10 -0
- package/src/providers/ollama-chat.js +148 -0
- package/src/providers/openai-responses.js +426 -0
- package/src/repository/index.js +14 -2
- package/src/rpc/generated/common_grpc_pb.js +1 -0
- package/src/rpc/generated/common_pb.js +536 -0
- package/src/rpc/generated/errors_grpc_pb.js +1 -0
- package/src/rpc/generated/errors_pb.js +482 -0
- package/src/rpc/generated/knowledge_grpc_pb.js +135 -0
- package/src/rpc/generated/knowledge_pb.js +2168 -0
- package/src/rpc/generated/package.json +3 -0
- package/src/security/process.js +35 -9
- package/src/security/trust-boundary.js +2 -2
- package/src/telemetry/contracts.js +203 -0
- package/src/telemetry/events.js +51 -0
- package/src/telemetry/index.js +8 -0
- package/src/tools/hydrate.js +35 -0
- package/src/tools/index.js +1 -0
- package/src/ui/boot.js +15 -17
- package/src/ui/cli/activity.js +76 -0
- package/src/ui/cli/compaction.js +15 -0
- package/src/ui/cli/footer.js +39 -0
- package/src/ui/cli/help.js +192 -0
- package/src/ui/cli/plan.js +20 -0
- package/src/ui/cli/runtime-events.js +110 -0
- package/src/ui/cli/waiting.js +16 -0
- package/src/ui/merkle/render.js +1 -1
- package/test/account-session.test.mjs +36 -0
- package/test/cli/preferences-runtime.test.mjs +11 -0
- package/test/cli/runtime-ui.test.mjs +74 -0
- package/test/cli-preferences.test.mjs +26 -5
- package/test/cloudflare-connector.test.mjs +96 -0
- package/test/cloudflare-runtime.test.mjs +75 -0
- package/test/context.test.mjs +61 -12
- package/test/deploy-health-scan.test.mjs +67 -0
- package/test/error-diagnostics.test.mjs +115 -0
- package/test/eval-context.test.mjs +37 -0
- package/test/execution-approvals.test.mjs +27 -0
- package/test/fixtures/knowledge-rpc-worker.mjs +16 -0
- package/test/integration/cli-help.test.mjs +37 -0
- package/test/integration/knowledge-rpc.test.mjs +112 -0
- package/test/integration/merkle-cli.test.mjs +61 -0
- package/test/integration/merkle-persistence-identity.test.mjs +48 -0
- package/test/integration/provider-env-cli.test.mjs +49 -0
- package/test/integration/provider-factory.test.mjs +197 -0
- package/test/integration/repository-company-graph.test.mjs +90 -0
- package/test/integration/runtime-migrations.test.mjs +82 -0
- package/test/knowledge-service.test.mjs +5 -0
- package/test/knowledge.test.mjs +16 -0
- package/test/live/terminal-transport.live.test.mjs +24 -0
- package/test/local-sessions.test.mjs +48 -0
- package/test/local-studio-deploy.test.mjs +83 -0
- package/test/model-catalog.test.mjs +43 -0
- package/test/models.test.mjs +127 -16
- package/test/npm10-lock.test.mjs +29 -0
- package/test/ollama.test.mjs +21 -0
- package/test/openai-responses.test.mjs +95 -0
- package/test/portable-context.test.mjs +1 -1
- package/test/provider-credentials.test.mjs +96 -0
- package/test/rehydrate.test.mjs +25 -0
- package/test/release-hygiene.test.mjs +13 -5
- package/test/responses-runner.test.mjs +150 -0
- package/test/shell.test.mjs +92 -23
- package/test/smoke.mjs +4 -1
- package/test/telemetry.test.mjs +79 -0
- package/test/terminal/local-pty.mock.test.mjs +151 -0
- package/test/tools-search.test.mjs +14 -1
- package/test/whoami-resume.test.mjs +56 -0
- /package/{src/lib → packages/agentsam-repository/src}/git-context.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/diff.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/filemeta.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/git-ignore.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/hash.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/semantic.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/snapshot.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/tree.js +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { readSnapshot } from '
|
|
2
|
+
import { readSnapshot } from '../../packages/agentsam-repository/src/merkle/snapshot.js';
|
|
3
|
+
import { readAccountSession } from '../lib/account-session.js';
|
|
4
|
+
import { getRepositoryId, portableRepositoryIdFromGit, tryReadProjectConfig } from '../lib/project-config.js';
|
|
3
5
|
import {
|
|
4
6
|
buildMerklePersistencePlan,
|
|
5
7
|
persistMerkleSnapshotCloudflare,
|
|
6
8
|
resolveWranglerMerklePersistence,
|
|
7
|
-
} from '
|
|
9
|
+
} from '../../packages/agentsam-repository/src/merkle/cloudflare-persistence.js';
|
|
8
10
|
|
|
9
11
|
function value(args, index, flag) {
|
|
10
12
|
const next = args[index + 1];
|
|
@@ -17,8 +19,6 @@ export function printMerklePersistHelp() {
|
|
|
17
19
|
agentsam merkle persist <snapshot.json> — publish a saved Merkle snapshot through host bindings
|
|
18
20
|
|
|
19
21
|
--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
22
|
--capture-kind <kind> deploy|manual|agent|index (default manual)
|
|
23
23
|
--connection-id <id> Execution provenance for non-deploy captures
|
|
24
24
|
--runtime-lease-id <id> Alternative execution provenance for non-deploy captures
|
|
@@ -35,9 +35,11 @@ export function printMerklePersistHelp() {
|
|
|
35
35
|
--dry-run Resolve bindings and emit the exact storage/index plan without writes
|
|
36
36
|
--json Machine-readable output
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
CLI ownership comes from the authenticated AgentSam session, while repository identity is
|
|
39
|
+
derived from Git/provider identity (with the committed project manifest as local fallback).
|
|
40
|
+
Programmatic hosts pass account_id + repository_id directly to the persistence plan. Physical
|
|
41
|
+
bucket/database names come from Wrangler bindings, so customer installs keep WEBSITE_ASSETS/DB
|
|
42
|
+
while selecting their own storage resources.
|
|
41
43
|
`);
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -55,7 +57,7 @@ function parse(args) {
|
|
|
55
57
|
if (arg === '--dry-run') { opts.dryRun = true; continue; }
|
|
56
58
|
if (arg === '--r2-only') { opts.r2Only = true; continue; }
|
|
57
59
|
const map = {
|
|
58
|
-
'--wrangler-config': 'wranglerConfig',
|
|
60
|
+
'--wrangler-config': 'wranglerConfig',
|
|
59
61
|
'--capture-kind': 'captureKind', '--connection-id': 'connectionId', '--runtime-lease-id': 'runtimeLeaseId',
|
|
60
62
|
'--deployment-id': 'deploymentId', '--worker-version': 'workerVersionId', '--reference-label': 'referenceLabel',
|
|
61
63
|
'--source': 'source', '--prefix': 'storagePrefix', '--r2-binding': 'r2Binding', '--d1-binding': 'd1Binding',
|
|
@@ -65,19 +67,36 @@ function parse(args) {
|
|
|
65
67
|
throw new Error(`Unknown merkle persist option: ${arg}`);
|
|
66
68
|
}
|
|
67
69
|
if (!opts.snapshotPath) throw new Error('snapshot_file_required');
|
|
68
|
-
opts.ownerUserId ||= process.env.AGENTSAM_OWNER_USER_ID || '';
|
|
69
70
|
opts.wranglerConfig ||= process.env.AGENTSAM_WRANGLER_CONFIG || '';
|
|
70
71
|
opts.connectionId ||= process.env.AGENTSAM_CONNECTION_ID || '';
|
|
71
72
|
opts.runtimeLeaseId ||= process.env.AGENTSAM_RUNTIME_LEASE_ID || '';
|
|
72
73
|
return opts;
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
export function resolveMerklePersistenceIdentity(root, options = {}) {
|
|
77
|
+
const session = options.session ?? readAccountSession(options.sessionOptions || {});
|
|
78
|
+
const accountId = String(session?.account_id || '').trim();
|
|
79
|
+
if (!accountId) throw new Error('agentsam_login_required_for_merkle_persistence');
|
|
80
|
+
|
|
81
|
+
const projectConfig = options.projectConfig ?? tryReadProjectConfig(root);
|
|
82
|
+
const gitRepositoryId = portableRepositoryIdFromGit(root);
|
|
83
|
+
const repositoryId = gitRepositoryId || getRepositoryId(projectConfig);
|
|
84
|
+
if (!repositoryId) throw new Error('repository_identity_unresolved');
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
accountId,
|
|
88
|
+
repositoryId,
|
|
89
|
+
repositoryIdentitySource: gitRepositoryId ? 'git' : 'project_manifest',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
75
93
|
export async function runMerklePersist(args = []) {
|
|
76
94
|
const opts = parse(args);
|
|
77
95
|
if (opts.help) { printMerklePersistHelp(); return null; }
|
|
78
96
|
const snapshotPath = path.resolve(opts.snapshotPath);
|
|
79
97
|
const snapshot = await readSnapshot(snapshotPath);
|
|
80
98
|
const root = path.resolve(opts.root || snapshot.rootPath || process.cwd());
|
|
99
|
+
const identity = resolveMerklePersistenceIdentity(root);
|
|
81
100
|
const wrangler = resolveWranglerMerklePersistence({
|
|
82
101
|
configPath: opts.wranglerConfig,
|
|
83
102
|
environment: opts.environment || null,
|
|
@@ -88,8 +107,8 @@ export async function runMerklePersist(args = []) {
|
|
|
88
107
|
const plan = buildMerklePersistencePlan({
|
|
89
108
|
snapshot,
|
|
90
109
|
root,
|
|
91
|
-
|
|
92
|
-
|
|
110
|
+
accountId: identity.accountId,
|
|
111
|
+
repositoryId: identity.repositoryId,
|
|
93
112
|
source: opts.source,
|
|
94
113
|
captureKind: opts.captureKind,
|
|
95
114
|
connectionId: opts.connectionId,
|
package/src/commands/merkle.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { buildMerkleTree, saveSnapshot, readSnapshot, diffTrees, normalizePolicy } from '
|
|
3
|
+
import { buildMerkleTree, saveSnapshot, readSnapshot, diffTrees, normalizePolicy } from '../../packages/agentsam-repository/src/merkle/index.js';
|
|
4
4
|
import { renderSummary, safeText } from '../ui/merkle/render.js';
|
|
5
5
|
import { runMerkleExplorer } from '../ui/merkle/explorer.js';
|
|
6
6
|
import { runMerklePersist } from './merkle-persist.js';
|
package/src/commands/models.js
CHANGED
|
@@ -1,33 +1,109 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
2
|
import { probeOllama, resolveOllamaConfig } from './ollama.js';
|
|
3
|
+
import { listModelCatalog } from '../models/index.js';
|
|
4
|
+
import { discoverProviderModels } from '../models/discovery.js';
|
|
5
|
+
import { resolveProviderCredential } from '../lib/provider-credentials.js';
|
|
3
6
|
|
|
4
|
-
const API_PROVIDERS = Object.freeze([
|
|
7
|
+
export const API_PROVIDERS = Object.freeze([
|
|
5
8
|
{ id: 'openai', label: 'OpenAI', credential: 'OPENAI_API_KEY' },
|
|
9
|
+
{ id: 'anthropic', label: 'Anthropic', credential: 'ANTHROPIC_API_KEY' },
|
|
6
10
|
{ id: 'gemini', label: 'Gemini', credential: 'GEMINI_API_KEY' },
|
|
7
|
-
{ id: 'grok', label: 'Grok', credential: 'XAI_API_KEY' },
|
|
11
|
+
{ id: 'grok', label: 'Grok / xAI', credential: 'XAI_API_KEY' },
|
|
12
|
+
{ id: 'cloudflare', label: 'Cloudflare', credential: 'CLOUDFLARE_API_TOKEN' },
|
|
8
13
|
]);
|
|
9
14
|
|
|
10
|
-
function clean(value) {
|
|
11
|
-
|
|
15
|
+
function clean(value) { return value == null ? '' : String(value).trim(); }
|
|
16
|
+
|
|
17
|
+
function emptyDiscovery() {
|
|
18
|
+
return { attempted: false, ok: false, models: [], error: null };
|
|
12
19
|
}
|
|
13
20
|
|
|
14
|
-
function
|
|
15
|
-
return
|
|
21
|
+
function providerSummary(result = {}) {
|
|
22
|
+
return {
|
|
23
|
+
attempted: result.attempted === true,
|
|
24
|
+
ok: result.ok === true,
|
|
25
|
+
error: result.error || null,
|
|
26
|
+
returnedModelCount: Array.isArray(result.models) ? result.models.length : 0,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function mergeStaticFallbacks(discovered = []) {
|
|
31
|
+
const byKey = new Map(discovered.map((row) => [row.model_key, row]));
|
|
32
|
+
const out = [...discovered];
|
|
33
|
+
for (const record of listModelCatalog()) {
|
|
34
|
+
if (byKey.has(record.model_key)) continue;
|
|
35
|
+
out.push({
|
|
36
|
+
model_key: record.model_key,
|
|
37
|
+
provider: record.provider,
|
|
38
|
+
provider_model_id: record.provider_model_id,
|
|
39
|
+
label: record.label,
|
|
40
|
+
availability: 'unverified',
|
|
41
|
+
availability_source: 'sdk_reference',
|
|
42
|
+
context_window: record.context_window,
|
|
43
|
+
context_window_source: 'sdk_reference',
|
|
44
|
+
max_output_tokens: record.max_output_tokens,
|
|
45
|
+
max_output_tokens_source: 'sdk_reference',
|
|
46
|
+
reasoning_efforts: [...record.reasoning_efforts],
|
|
47
|
+
service_tiers: [...record.service_tiers],
|
|
48
|
+
capabilities: { ...record.capabilities },
|
|
49
|
+
pricing: record.pricing || null,
|
|
50
|
+
context_policy: record.context_policy || null,
|
|
51
|
+
batch: record.batch || null,
|
|
52
|
+
source: record.source || null,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
16
56
|
}
|
|
17
57
|
|
|
18
58
|
export async function collectModelsStatus(options = {}) {
|
|
19
59
|
const env = options.env || process.env;
|
|
20
|
-
const
|
|
60
|
+
const ollamaFetchImpl = options.fetchImpl || fetch;
|
|
61
|
+
const providerFetchImpl = options.providerFetchImpl || fetch;
|
|
21
62
|
const ollamaConfig = resolveOllamaConfig({}, env);
|
|
22
|
-
const ollama = await probeOllama(ollamaConfig,
|
|
63
|
+
const ollama = await probeOllama(ollamaConfig, ollamaFetchImpl);
|
|
64
|
+
const credentials = new Map(
|
|
65
|
+
API_PROVIDERS.map((provider) => [
|
|
66
|
+
provider.id,
|
|
67
|
+
resolveProviderCredential(provider.id, { env, home: options.home }),
|
|
68
|
+
]),
|
|
69
|
+
);
|
|
23
70
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
71
|
+
const providers = API_PROVIDERS.map((provider) => {
|
|
72
|
+
const credential = credentials.get(provider.id);
|
|
73
|
+
return {
|
|
27
74
|
...provider,
|
|
28
|
-
configured: configured
|
|
29
|
-
source:
|
|
30
|
-
|
|
75
|
+
configured: credential?.configured === true,
|
|
76
|
+
source: credential?.source || null,
|
|
77
|
+
credentialError: credential?.error || null,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const shouldDiscover = options.discoverRemote !== false;
|
|
82
|
+
const discovery = {};
|
|
83
|
+
const providerModels = {};
|
|
84
|
+
|
|
85
|
+
await Promise.all(API_PROVIDERS.map(async (provider) => {
|
|
86
|
+
const credential = credentials.get(provider.id);
|
|
87
|
+
const result = shouldDiscover && credential?.configured
|
|
88
|
+
? await discoverProviderModels(provider.id, credential, { fetchImpl: providerFetchImpl })
|
|
89
|
+
: emptyDiscovery();
|
|
90
|
+
discovery[provider.id] = providerSummary(result);
|
|
91
|
+
providerModels[provider.id] = result.models || [];
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
const discovered = Object.values(providerModels).flat();
|
|
95
|
+
const exactAvailable = discovered.filter((row) => row.availability === 'available');
|
|
96
|
+
const catalogModels = mergeStaticFallbacks(discovered);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
schemaVersion: 'agentsam-model-inventory-v3',
|
|
100
|
+
generatedAt: new Date().toISOString(),
|
|
101
|
+
authority: 'per_credential_provider_discovery',
|
|
102
|
+
providers,
|
|
103
|
+
discovery,
|
|
104
|
+
providerModels,
|
|
105
|
+
catalogModels,
|
|
106
|
+
availableModels: exactAvailable,
|
|
31
107
|
local: {
|
|
32
108
|
provider: 'ollama',
|
|
33
109
|
configured: ollama.online,
|
|
@@ -41,67 +117,94 @@ export async function collectModelsStatus(options = {}) {
|
|
|
41
117
|
};
|
|
42
118
|
}
|
|
43
119
|
|
|
44
|
-
function statusMark(ok) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
120
|
+
function statusMark(ok) { return ok ? pc.green('●') : pc.dim('○'); }
|
|
121
|
+
function writeLine(write, value = '') { write(`${value}\n`); }
|
|
122
|
+
function contextLabel(model) {
|
|
123
|
+
const value = Number(model?.context_window);
|
|
124
|
+
if (!Number.isFinite(value) || value <= 0) return 'ctx unknown';
|
|
125
|
+
return `ctx ${Math.round(value / 1000).toLocaleString('en-US')}k`;
|
|
50
126
|
}
|
|
51
127
|
|
|
52
128
|
export function renderModelsStatus(status) {
|
|
53
129
|
const lines = [];
|
|
54
130
|
lines.push('');
|
|
55
131
|
lines.push(` ${pc.bold('Agent Sam · models')}`);
|
|
56
|
-
lines.push(` ${pc.dim('
|
|
132
|
+
lines.push(` ${pc.dim('Availability is verified with this machine\'s own provider credentials. Limits are provider-derived when exposed; otherwise marked unknown/reference.')}`);
|
|
57
133
|
lines.push('');
|
|
58
134
|
|
|
59
135
|
for (const provider of status.providers) {
|
|
60
|
-
const state = provider.configured
|
|
61
|
-
|
|
62
|
-
|
|
136
|
+
const state = provider.configured
|
|
137
|
+
? pc.green('configured')
|
|
138
|
+
: pc.dim(provider.credentialError ? 'blocked' : 'not configured');
|
|
139
|
+
const discovery = status.discovery?.[provider.id];
|
|
140
|
+
const detail = provider.configured
|
|
141
|
+
? discovery?.attempted
|
|
142
|
+
? discovery.ok
|
|
143
|
+
? `${discovery.returnedModelCount} account-visible models`
|
|
144
|
+
: `discovery failed · ${discovery.error || 'unknown error'}`
|
|
145
|
+
: `credential available · ${provider.source || 'runtime'}`
|
|
146
|
+
: provider.credentialError
|
|
147
|
+
? `${provider.credential} · ${provider.credentialError}`
|
|
148
|
+
: provider.credential;
|
|
149
|
+
lines.push(` ${statusMark(provider.configured)} ${pc.cyan(provider.label.padEnd(12))} ${state.padEnd(20)} ${pc.dim(detail)}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const provider of API_PROVIDERS) {
|
|
153
|
+
const models = status.providerModels?.[provider.id] || [];
|
|
154
|
+
if (!models.length) continue;
|
|
155
|
+
lines.push('');
|
|
156
|
+
lines.push(` ${pc.dim(`${provider.label} · provider-verified for this credential`)}`);
|
|
157
|
+
const visible = models.slice(0, 30);
|
|
158
|
+
for (const model of visible) {
|
|
159
|
+
const source = model.context_window_source === 'provider_api'
|
|
160
|
+
? contextLabel(model)
|
|
161
|
+
: model.context_window_source === 'sdk_reference'
|
|
162
|
+
? `${contextLabel(model)} · reference`
|
|
163
|
+
: 'ctx unknown';
|
|
164
|
+
lines.push(` ${pc.green('•')} ${model.provider_model_id} ${pc.dim('· ' + source)}`);
|
|
165
|
+
}
|
|
166
|
+
if (models.length > visible.length) lines.push(` ${pc.dim(`… ${models.length - visible.length} more`)}`);
|
|
63
167
|
}
|
|
64
168
|
|
|
65
169
|
const local = status.local;
|
|
66
170
|
const localState = local.online ? pc.green('online') : pc.dim('offline');
|
|
67
|
-
lines.push(
|
|
68
|
-
|
|
171
|
+
lines.push('');
|
|
172
|
+
lines.push(` ${statusMark(local.online)} ${pc.cyan('Ollama'.padEnd(12))} ${localState.padEnd(20)} ${pc.dim('local only')}`);
|
|
69
173
|
if (local.online) {
|
|
70
174
|
const names = local.models.map((row) => row.name).filter(Boolean);
|
|
71
|
-
lines.push('');
|
|
72
|
-
lines.push(`
|
|
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}`);
|
|
175
|
+
for (const name of names.slice(0, 30)) lines.push(` ${pc.green('•')} ${name}`);
|
|
176
|
+
if (names.length > 30) lines.push(` ${pc.dim(`… ${names.length - 30} more`)}`);
|
|
81
177
|
}
|
|
82
178
|
|
|
83
179
|
lines.push('');
|
|
84
|
-
lines.push(` ${pc.dim('
|
|
85
|
-
lines.push(` ${pc.dim('this command only reports what this local CLI can prove is configured or available.')}`);
|
|
180
|
+
lines.push(` ${pc.dim('Use /model inside Agent Sam. The picker is built from the models visible to your own connected provider credentials.')}`);
|
|
86
181
|
lines.push('');
|
|
87
182
|
return lines.join('\n');
|
|
88
183
|
}
|
|
89
184
|
|
|
90
185
|
export async function runModels(argv = [], options = {}) {
|
|
91
186
|
if (argv.some((arg) => arg === '--help' || arg === '-h')) {
|
|
92
|
-
const text =
|
|
187
|
+
const text = [
|
|
188
|
+
'agentsam models [--json] [--no-discover]',
|
|
189
|
+
'',
|
|
190
|
+
'Probe the provider accounts configured on this machine and list the models visible to those exact credentials.',
|
|
191
|
+
'Context/output limits are taken from provider metadata when available; otherwise Agent Sam reports unknown or a clearly-labeled SDK reference.',
|
|
192
|
+
'',
|
|
193
|
+
].join('\n');
|
|
93
194
|
(options.write || process.stdout.write.bind(process.stdout))(text);
|
|
94
195
|
return;
|
|
95
196
|
}
|
|
96
|
-
|
|
197
|
+
|
|
198
|
+
const allowed = new Set(['--json', '--no-discover']);
|
|
199
|
+
const unknown = argv.filter((arg) => !allowed.has(arg));
|
|
97
200
|
if (unknown.length) throw new Error(`unknown models option: ${unknown[0]}`);
|
|
98
201
|
|
|
99
|
-
const status = await collectModelsStatus(
|
|
202
|
+
const status = await collectModelsStatus({
|
|
203
|
+
...options,
|
|
204
|
+
discoverRemote: !argv.includes('--no-discover'),
|
|
205
|
+
});
|
|
100
206
|
const write = options.write || ((text) => process.stdout.write(text));
|
|
101
|
-
if (argv.includes('--json'))
|
|
102
|
-
|
|
103
|
-
} else {
|
|
104
|
-
write(renderModelsStatus(status));
|
|
105
|
-
}
|
|
207
|
+
if (argv.includes('--json')) writeLine(write, JSON.stringify(status, null, 2));
|
|
208
|
+
else write(renderModelsStatus(status));
|
|
106
209
|
return status;
|
|
107
210
|
}
|
package/src/commands/ollama.js
CHANGED
|
@@ -148,6 +148,32 @@ function hasModel(models, desired) {
|
|
|
148
148
|
return models.some((row) => normalizeModelName(row.name || row.model) === want);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
export async function probeOllamaModel(model, config, fetchImpl = fetch) {
|
|
152
|
+
const endpoint = `${config.baseUrl}/api/show`;
|
|
153
|
+
try {
|
|
154
|
+
const response = await fetchImpl(endpoint, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: { 'content-type': 'application/json' },
|
|
157
|
+
body: JSON.stringify({ model }),
|
|
158
|
+
signal: AbortSignal.timeout(3000),
|
|
159
|
+
});
|
|
160
|
+
if (!response.ok) return { ok: false, model, context_window: null, error: `HTTP ${response.status}` };
|
|
161
|
+
const body = await response.json();
|
|
162
|
+
const info = body?.model_info && typeof body.model_info === 'object' ? body.model_info : {};
|
|
163
|
+
const contextEntry = Object.entries(info).find(([key, value]) => key.endsWith('.context_length') && Number(value) > 0);
|
|
164
|
+
return {
|
|
165
|
+
ok: true,
|
|
166
|
+
model,
|
|
167
|
+
context_window: contextEntry ? Math.floor(Number(contextEntry[1])) : null,
|
|
168
|
+
context_window_source: contextEntry ? 'local_runtime' : 'unknown',
|
|
169
|
+
capabilities: Array.isArray(body?.capabilities) ? body.capabilities : [],
|
|
170
|
+
details: body?.details || null,
|
|
171
|
+
};
|
|
172
|
+
} catch (error) {
|
|
173
|
+
return { ok: false, model, context_window: null, error: error?.message || String(error) };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
151
177
|
export async function probeOllama(config, fetchImpl = fetch) {
|
|
152
178
|
const endpoint = `${config.baseUrl}/api/tags`;
|
|
153
179
|
try {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
|
-
import { cancel, intro, isCancel, outro, select, text } from '@clack/prompts';
|
|
4
|
+
import { cancel, confirm, intro, isCancel, outro, select, text } from '@clack/prompts';
|
|
5
5
|
import { collectModelsStatus } from './models.js';
|
|
6
|
+
import { getModelRecord } from '../models/index.js';
|
|
6
7
|
import { detectCliProject, readCliPreferences, writeCliPreferences } from '../lib/cli-preferences.js';
|
|
8
|
+
import { readAccountSession } from '../lib/account-session.js';
|
|
7
9
|
|
|
8
10
|
function stopIfCancelled(value) {
|
|
9
11
|
if (!isCancel(value)) return value;
|
|
@@ -18,85 +20,152 @@ function commandExists(command) {
|
|
|
18
20
|
return spawnSync(probe[0], probe[1], { stdio: 'ignore' }).status === 0;
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
export function runtimeOptions({ accountConnected = false } = {}) {
|
|
24
|
+
const rows = [{ value: 'local', label: 'Local machine', hint: 'standalone · real shell + filesystem' }];
|
|
25
|
+
if (accountConnected) {
|
|
26
|
+
rows.push(
|
|
27
|
+
{ value: 'remote', label: 'Remote', hint: 'IAM-connected enrolled runtime' },
|
|
28
|
+
{ value: 'sandbox', label: 'Sandbox', hint: 'IAM-connected isolated runtime' },
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return rows;
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
export function availableShells(env = process.env) {
|
|
22
35
|
const values = [];
|
|
23
36
|
const detected = path.basename(env.SHELL || env.ComSpec || '').trim();
|
|
24
37
|
if (detected) values.push(detected);
|
|
25
|
-
for (const shell of ['zsh', 'bash', 'fish', 'pwsh'])
|
|
26
|
-
if (!values.includes(shell) && commandExists(shell)) values.push(shell);
|
|
27
|
-
}
|
|
38
|
+
for (const shell of ['zsh', 'bash', 'fish', 'pwsh']) if (!values.includes(shell) && commandExists(shell)) values.push(shell);
|
|
28
39
|
return values.length ? values : ['shell'];
|
|
29
40
|
}
|
|
30
41
|
|
|
31
|
-
function modelOptions(status) {
|
|
32
|
-
const options = [{ value: 'auto', label: 'Automatic', hint: '
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
export function modelOptions(status) {
|
|
43
|
+
const options = [{ value: 'auto', label: 'Automatic', hint: 'runtime chooses from this credential\'s verified inventory', model: null }];
|
|
44
|
+
for (const model of status.availableModels || []) {
|
|
45
|
+
const context = Number(model.context_window) > 0 ? ` · ctx ${Math.round(Number(model.context_window) / 1000)}k` : ' · ctx unknown';
|
|
46
|
+
options.push({
|
|
47
|
+
value: model.model_key,
|
|
48
|
+
label: `${model.provider} · ${model.label}`,
|
|
49
|
+
hint: `${model.provider_model_id}${context} · provider verified`,
|
|
50
|
+
model,
|
|
51
|
+
});
|
|
37
52
|
}
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
if (status.local?.online) {
|
|
54
|
+
for (const row of status.local.models || []) if (row?.name) options.push({
|
|
55
|
+
value: `ollama:${row.name}`,
|
|
56
|
+
label: `Ollama · ${row.name}`,
|
|
57
|
+
hint: 'local',
|
|
58
|
+
model: {
|
|
59
|
+
model_key: `ollama:${row.name}`, provider: 'ollama', provider_model_id: row.name, label: row.name,
|
|
60
|
+
availability: 'available', availability_source: 'local_runtime', context_window: null, context_window_source: 'unknown',
|
|
61
|
+
max_output_tokens: null, max_output_tokens_source: 'unknown', reasoning_efforts: ['auto'], service_tiers: ['default'], capabilities: { chat: true },
|
|
62
|
+
},
|
|
63
|
+
});
|
|
40
64
|
}
|
|
41
65
|
return options;
|
|
42
66
|
}
|
|
43
67
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const projectChoice = stopIfCancelled(await select({
|
|
50
|
-
message: 'Where should Agent Sam work?',
|
|
51
|
-
initialValue: 'current',
|
|
52
|
-
options: [
|
|
53
|
-
{ value: 'current', label: `This project · ${identity.project}`, hint: identity.root },
|
|
54
|
-
{ value: 'other', label: 'Choose another folder' },
|
|
55
|
-
],
|
|
56
|
-
}));
|
|
57
|
-
|
|
58
|
-
if (projectChoice === 'other') {
|
|
59
|
-
const folder = stopIfCancelled(await text({
|
|
60
|
-
message: 'Project folder',
|
|
61
|
-
placeholder: identity.root,
|
|
62
|
-
defaultValue: identity.root,
|
|
63
|
-
validate(value) {
|
|
64
|
-
const resolved = path.resolve(String(value || ''));
|
|
65
|
-
if (!fs.existsSync(resolved)) return 'Folder does not exist';
|
|
66
|
-
if (!fs.statSync(resolved).isDirectory()) return 'Path is not a directory';
|
|
67
|
-
},
|
|
68
|
-
}));
|
|
69
|
-
identity = detectCliProject(path.resolve(String(folder)));
|
|
70
|
-
}
|
|
68
|
+
function resolvedModel(modelPreference, modelSnapshot) {
|
|
69
|
+
if (modelSnapshot?.model_key === modelPreference) return modelSnapshot;
|
|
70
|
+
return getModelRecord(modelPreference);
|
|
71
|
+
}
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
],
|
|
73
|
+
function reasoningOptions(modelPreference, modelSnapshot) {
|
|
74
|
+
const record = resolvedModel(modelPreference, modelSnapshot);
|
|
75
|
+
if (!record) return [{ value: 'auto', label: 'Automatic', hint: 'runtime/provider default' }];
|
|
76
|
+
return record.reasoning_efforts.map((value) => ({
|
|
77
|
+
value,
|
|
78
|
+
label: value === 'xhigh' ? 'Extra high' : value === 'max' ? 'Max' : value[0].toUpperCase() + value.slice(1),
|
|
79
|
+
hint: value === 'low' ? 'lighter reasoning' : value === 'max' ? 'highest supported reasoning depth' : undefined,
|
|
80
80
|
}));
|
|
81
|
+
}
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
function serviceTierOptions(modelPreference, modelSnapshot) {
|
|
84
|
+
const record = resolvedModel(modelPreference, modelSnapshot);
|
|
85
|
+
if (!record) return [{ value: 'default', label: 'Standard', hint: 'default provider processing' }];
|
|
86
|
+
return record.service_tiers.map((value) => {
|
|
87
|
+
if (value === 'fast') return { value, label: 'Fast', hint: 'lower latency · 2× applicable token rates for Astra' };
|
|
88
|
+
if (value === 'flex') return { value, label: 'Flex', hint: 'slower / capacity-sensitive · 50% of Standard for Astra' };
|
|
89
|
+
return { value, label: 'Standard', hint: 'standard pricing and latency' };
|
|
90
|
+
});
|
|
91
|
+
}
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
async function promptModelPreferences(identity, existing, options = {}) {
|
|
94
|
+
let status = { providers: [], availableModels: [], local: { online: false, models: [] } };
|
|
95
|
+
try { status = await collectModelsStatus(options.modelStatusOptions || {}); } catch { /* inventory remains best-effort */ }
|
|
91
96
|
const models = modelOptions(status);
|
|
92
97
|
const initialModel = models.some((row) => row.value === existing.modelPreference) ? existing.modelPreference : 'auto';
|
|
93
|
-
const modelPreference = stopIfCancelled(await select({
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
const modelPreference = stopIfCancelled(await select({ message: 'Model', initialValue: initialModel, options: models.map(({ model, ...row }) => row) }));
|
|
99
|
+
const selected = models.find((row) => row.value === modelPreference)?.model || null;
|
|
100
|
+
const modelSnapshot = selected || (existing.modelSnapshot?.model_key === modelPreference ? existing.modelSnapshot : null);
|
|
101
|
+
|
|
102
|
+
const reasoning = reasoningOptions(modelPreference, modelSnapshot);
|
|
103
|
+
const initialReasoning = reasoning.some((row) => row.value === existing.reasoningEffort) ? existing.reasoningEffort : reasoning[0].value;
|
|
104
|
+
const reasoningEffort = stopIfCancelled(await select({ message: 'Reasoning level', initialValue: initialReasoning, options: reasoning }));
|
|
105
|
+
|
|
106
|
+
const tiers = serviceTierOptions(modelPreference, modelSnapshot);
|
|
107
|
+
const initialTier = tiers.some((row) => row.value === existing.serviceTier) ? existing.serviceTier : tiers[0].value;
|
|
108
|
+
const serviceTier = stopIfCancelled(await select({ message: 'Processing', initialValue: initialTier, options: tiers }));
|
|
109
|
+
|
|
110
|
+
return { modelPreference, modelSnapshot, reasoningEffort, serviceTier };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function configureCliPreferences({ cwd = process.cwd(), firstRun = false, section = 'all', modelStatusOptions, home, env = process.env } = {}) {
|
|
114
|
+
let identity = detectCliProject(cwd);
|
|
115
|
+
const existing = readCliPreferences(identity.root) || {};
|
|
116
|
+
intro(firstRun ? `You are in ${identity.root}` : section === 'model' ? 'Agent Sam model' : 'Agent Sam settings');
|
|
117
|
+
|
|
118
|
+
let trustedDirectory = existing.trustedDirectory === true;
|
|
119
|
+
if (firstRun && !trustedDirectory) {
|
|
120
|
+
trustedDirectory = stopIfCancelled(await confirm({
|
|
121
|
+
message: 'Do you trust the contents of this directory? Project-local instructions, hooks, and execution policy may load.',
|
|
122
|
+
initialValue: false,
|
|
123
|
+
}));
|
|
124
|
+
if (!trustedDirectory) {
|
|
125
|
+
cancel('Directory not trusted. Agent Sam did not load project-local configuration.');
|
|
126
|
+
const error = new Error('directory_not_trusted');
|
|
127
|
+
error.code = 'AGENTSAM_SETUP_CANCELLED';
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const accountConnected = Boolean(readAccountSession({ home, env })?.account_id);
|
|
133
|
+
const runtimes = runtimeOptions({ accountConnected });
|
|
134
|
+
let runtime = runtimes.some((row) => row.value === existing.runtime) ? existing.runtime : 'local';
|
|
135
|
+
let terminal = existing.terminal || availableShells(env)[0];
|
|
136
|
+
if (!firstRun && section === 'all') {
|
|
137
|
+
const projectChoice = stopIfCancelled(await select({
|
|
138
|
+
message: 'Where should Agent Sam work?',
|
|
139
|
+
initialValue: 'current',
|
|
140
|
+
options: [
|
|
141
|
+
{ value: 'current', label: `This project · ${identity.project}`, hint: identity.root },
|
|
142
|
+
{ value: 'other', label: 'Choose another folder' },
|
|
143
|
+
],
|
|
144
|
+
}));
|
|
145
|
+
if (projectChoice === 'other') {
|
|
146
|
+
const folder = stopIfCancelled(await text({
|
|
147
|
+
message: 'Project folder', placeholder: identity.root, defaultValue: identity.root,
|
|
148
|
+
validate(value) {
|
|
149
|
+
const resolved = path.resolve(String(value || ''));
|
|
150
|
+
if (!fs.existsSync(resolved)) return 'Folder does not exist';
|
|
151
|
+
if (!fs.statSync(resolved).isDirectory()) return 'Path is not a directory';
|
|
152
|
+
},
|
|
153
|
+
}));
|
|
154
|
+
identity = detectCliProject(path.resolve(String(folder)));
|
|
155
|
+
}
|
|
156
|
+
runtime = stopIfCancelled(await select({
|
|
157
|
+
message: 'Runtime', initialValue: runtime,
|
|
158
|
+
options: runtimes,
|
|
159
|
+
}));
|
|
160
|
+
const shells = availableShells(env);
|
|
161
|
+
terminal = stopIfCancelled(await select({
|
|
162
|
+
message: 'Terminal', initialValue: terminal && shells.includes(terminal) ? terminal : shells[0],
|
|
163
|
+
options: shells.map((shell) => ({ value: shell, label: shell, hint: shell === shells[0] ? 'detected/default' : undefined })),
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
98
166
|
|
|
99
|
-
const
|
|
167
|
+
const model = await promptModelPreferences(identity, existing, { modelStatusOptions: { home, env, ...(modelStatusOptions || {}) } });
|
|
168
|
+
const preferences = writeCliPreferences(identity.root, { trustedDirectory, runtime, terminal, ...model });
|
|
100
169
|
outro(`Ready · ${identity.project}`);
|
|
101
170
|
return { identity, preferences };
|
|
102
171
|
}
|