scoutline 0.1.0 → 0.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/README.md +394 -7
- package/bin/scoutline.js +36 -15
- package/dist/capabilities/diagnostics.d.ts +135 -0
- package/dist/capabilities/diagnostics.d.ts.map +1 -0
- package/dist/capabilities/diagnostics.js +139 -0
- package/dist/capabilities/diagnostics.js.map +1 -0
- package/dist/capabilities/quota.d.ts +99 -0
- package/dist/capabilities/quota.d.ts.map +1 -0
- package/dist/capabilities/quota.js +129 -0
- package/dist/capabilities/quota.js.map +1 -0
- package/dist/capabilities/reader.d.ts +227 -0
- package/dist/capabilities/reader.d.ts.map +1 -0
- package/dist/capabilities/reader.js +100 -0
- package/dist/capabilities/reader.js.map +1 -0
- package/dist/capabilities/repository.d.ts +221 -0
- package/dist/capabilities/repository.d.ts.map +1 -0
- package/dist/capabilities/repository.js +172 -0
- package/dist/capabilities/repository.js.map +1 -0
- package/dist/capabilities/search.d.ts +110 -0
- package/dist/capabilities/search.d.ts.map +1 -0
- package/dist/capabilities/search.js +21 -0
- package/dist/capabilities/search.js.map +1 -0
- package/dist/capabilities/vision.d.ts +156 -0
- package/dist/capabilities/vision.d.ts.map +1 -0
- package/dist/capabilities/vision.js +111 -0
- package/dist/capabilities/vision.js.map +1 -0
- package/dist/command-invocation.d.ts +58 -0
- package/dist/command-invocation.d.ts.map +1 -0
- package/dist/command-invocation.js +95 -0
- package/dist/command-invocation.js.map +1 -0
- package/dist/commands/cache.d.ts +106 -0
- package/dist/commands/cache.d.ts.map +1 -0
- package/dist/commands/cache.js +203 -0
- package/dist/commands/cache.js.map +1 -0
- package/dist/commands/code.d.ts +11 -4
- package/dist/commands/code.d.ts.map +1 -1
- package/dist/commands/code.js +16 -42
- package/dist/commands/code.js.map +1 -1
- package/dist/commands/doctor.d.ts +69 -3
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +179 -52
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/quota.d.ts +45 -3
- package/dist/commands/quota.d.ts.map +1 -1
- package/dist/commands/quota.js +116 -60
- package/dist/commands/quota.js.map +1 -1
- package/dist/commands/read.d.ts +76 -2
- package/dist/commands/read.d.ts.map +1 -1
- package/dist/commands/read.js +260 -121
- package/dist/commands/read.js.map +1 -1
- package/dist/commands/repo.d.ts +55 -4
- package/dist/commands/repo.d.ts.map +1 -1
- package/dist/commands/repo.js +115 -172
- package/dist/commands/repo.js.map +1 -1
- package/dist/commands/repository-explorer.d.ts +147 -0
- package/dist/commands/repository-explorer.d.ts.map +1 -0
- package/dist/commands/repository-explorer.js +550 -0
- package/dist/commands/repository-explorer.js.map +1 -0
- package/dist/commands/search.d.ts +27 -2
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +95 -103
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/tools.d.ts +9 -3
- package/dist/commands/tools.d.ts.map +1 -1
- package/dist/commands/tools.js +29 -59
- package/dist/commands/tools.js.map +1 -1
- package/dist/commands/vision.d.ts +35 -9
- package/dist/commands/vision.d.ts.map +1 -1
- package/dist/commands/vision.js +184 -131
- package/dist/commands/vision.js.map +1 -1
- package/dist/index.d.ts +62 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +580 -223
- package/dist/index.js.map +1 -1
- package/dist/lib/cache.d.ts +178 -12
- package/dist/lib/cache.d.ts.map +1 -1
- package/dist/lib/cache.js +377 -39
- package/dist/lib/cache.js.map +1 -1
- package/dist/lib/code-mode.d.ts +13 -0
- package/dist/lib/code-mode.d.ts.map +1 -1
- package/dist/lib/code-mode.js +26 -6
- package/dist/lib/code-mode.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +15 -18
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/errors.d.ts +89 -11
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +168 -21
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/execution.d.ts +190 -0
- package/dist/lib/execution.d.ts.map +1 -0
- package/dist/lib/execution.js +383 -0
- package/dist/lib/execution.js.map +1 -0
- package/dist/lib/index.d.ts +10 -2
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +10 -2
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/mcp-client.d.ts +75 -16
- package/dist/lib/mcp-client.d.ts.map +1 -1
- package/dist/lib/mcp-client.js +173 -108
- package/dist/lib/mcp-client.js.map +1 -1
- package/dist/lib/mcp-config.d.ts +17 -0
- package/dist/lib/mcp-config.d.ts.map +1 -1
- package/dist/lib/mcp-config.js +29 -0
- package/dist/lib/mcp-config.js.map +1 -1
- package/dist/lib/monitor-client.d.ts +17 -3
- package/dist/lib/monitor-client.d.ts.map +1 -1
- package/dist/lib/monitor-client.js +23 -69
- package/dist/lib/monitor-client.js.map +1 -1
- package/dist/lib/output.d.ts +53 -7
- package/dist/lib/output.d.ts.map +1 -1
- package/dist/lib/output.js +126 -39
- package/dist/lib/output.js.map +1 -1
- package/dist/lib/redact.d.ts +62 -3
- package/dist/lib/redact.d.ts.map +1 -1
- package/dist/lib/redact.js +165 -31
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/tool-cache.d.ts +86 -0
- package/dist/lib/tool-cache.d.ts.map +1 -0
- package/dist/lib/tool-cache.js +123 -0
- package/dist/lib/tool-cache.js.map +1 -0
- package/dist/lib/tty.d.ts +7 -20
- package/dist/lib/tty.d.ts.map +1 -1
- package/dist/lib/tty.js +51 -30
- package/dist/lib/tty.js.map +1 -1
- package/dist/node-command-invocation-adapter.d.ts +28 -0
- package/dist/node-command-invocation-adapter.d.ts.map +1 -0
- package/dist/node-command-invocation-adapter.js +88 -0
- package/dist/node-command-invocation-adapter.js.map +1 -0
- package/dist/providers/minimax/adapter.d.ts +49 -0
- package/dist/providers/minimax/adapter.d.ts.map +1 -0
- package/dist/providers/minimax/adapter.js +450 -0
- package/dist/providers/minimax/adapter.js.map +1 -0
- package/dist/providers/minimax/coding-plan-client.d.ts +60 -0
- package/dist/providers/minimax/coding-plan-client.d.ts.map +1 -0
- package/dist/providers/minimax/coding-plan-client.js +204 -0
- package/dist/providers/minimax/coding-plan-client.js.map +1 -0
- package/dist/providers/minimax/config.d.ts +32 -0
- package/dist/providers/minimax/config.d.ts.map +1 -0
- package/dist/providers/minimax/config.js +84 -0
- package/dist/providers/minimax/config.js.map +1 -0
- package/dist/providers/minimax/media.d.ts +88 -0
- package/dist/providers/minimax/media.d.ts.map +1 -0
- package/dist/providers/minimax/media.js +232 -0
- package/dist/providers/minimax/media.js.map +1 -0
- package/dist/providers/minimax/quota-client.d.ts +44 -0
- package/dist/providers/minimax/quota-client.d.ts.map +1 -0
- package/dist/providers/minimax/quota-client.js +83 -0
- package/dist/providers/minimax/quota-client.js.map +1 -0
- package/dist/providers/minimax/quota.d.ts +48 -0
- package/dist/providers/minimax/quota.d.ts.map +1 -0
- package/dist/providers/minimax/quota.js +120 -0
- package/dist/providers/minimax/quota.js.map +1 -0
- package/dist/providers/minimax/sdk-client.d.ts +29 -0
- package/dist/providers/minimax/sdk-client.d.ts.map +1 -0
- package/dist/providers/minimax/sdk-client.js +50 -0
- package/dist/providers/minimax/sdk-client.js.map +1 -0
- package/dist/providers/minimax/vision-attestations.d.ts +52 -0
- package/dist/providers/minimax/vision-attestations.d.ts.map +1 -0
- package/dist/providers/minimax/vision-attestations.js +79 -0
- package/dist/providers/minimax/vision-attestations.js.map +1 -0
- package/dist/providers/minimax/vision-conformance.d.ts +186 -0
- package/dist/providers/minimax/vision-conformance.d.ts.map +1 -0
- package/dist/providers/minimax/vision-conformance.js +371 -0
- package/dist/providers/minimax/vision-conformance.js.map +1 -0
- package/dist/providers/minimax/vision-mappings/chart.d.ts +35 -0
- package/dist/providers/minimax/vision-mappings/chart.d.ts.map +1 -0
- package/dist/providers/minimax/vision-mappings/chart.js +55 -0
- package/dist/providers/minimax/vision-mappings/chart.js.map +1 -0
- package/dist/providers/minimax/vision-mappings/common.d.ts +60 -0
- package/dist/providers/minimax/vision-mappings/common.d.ts.map +1 -0
- package/dist/providers/minimax/vision-mappings/common.js +75 -0
- package/dist/providers/minimax/vision-mappings/common.js.map +1 -0
- package/dist/providers/minimax/vision-mappings/diagnose-error.d.ts +35 -0
- package/dist/providers/minimax/vision-mappings/diagnose-error.d.ts.map +1 -0
- package/dist/providers/minimax/vision-mappings/diagnose-error.js +55 -0
- package/dist/providers/minimax/vision-mappings/diagnose-error.js.map +1 -0
- package/dist/providers/minimax/vision-mappings/diagram.d.ts +35 -0
- package/dist/providers/minimax/vision-mappings/diagram.d.ts.map +1 -0
- package/dist/providers/minimax/vision-mappings/diagram.js +55 -0
- package/dist/providers/minimax/vision-mappings/diagram.js.map +1 -0
- package/dist/providers/minimax/vision-mappings/extract-text.d.ts +35 -0
- package/dist/providers/minimax/vision-mappings/extract-text.d.ts.map +1 -0
- package/dist/providers/minimax/vision-mappings/extract-text.js +55 -0
- package/dist/providers/minimax/vision-mappings/extract-text.js.map +1 -0
- package/dist/providers/minimax/vision-mappings/ui-artifact.d.ts +35 -0
- package/dist/providers/minimax/vision-mappings/ui-artifact.d.ts.map +1 -0
- package/dist/providers/minimax/vision-mappings/ui-artifact.js +69 -0
- package/dist/providers/minimax/vision-mappings/ui-artifact.js.map +1 -0
- package/dist/providers/minimax/vision-mappings.generated.d.ts +29 -0
- package/dist/providers/minimax/vision-mappings.generated.d.ts.map +1 -0
- package/dist/providers/minimax/vision-mappings.generated.js +29 -0
- package/dist/providers/minimax/vision-mappings.generated.js.map +1 -0
- package/dist/providers/minimax/vision-revisions.d.ts +29 -0
- package/dist/providers/minimax/vision-revisions.d.ts.map +1 -0
- package/dist/providers/minimax/vision-revisions.js +34 -0
- package/dist/providers/minimax/vision-revisions.js.map +1 -0
- package/dist/providers/registry.d.ts +36 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +45 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/selection.d.ts +44 -0
- package/dist/providers/selection.d.ts.map +1 -0
- package/dist/providers/selection.js +85 -0
- package/dist/providers/selection.js.map +1 -0
- package/dist/providers/types.d.ts +281 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +127 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/providers/zai/adapter.d.ts +35 -0
- package/dist/providers/zai/adapter.d.ts.map +1 -0
- package/dist/providers/zai/adapter.js +685 -0
- package/dist/providers/zai/adapter.js.map +1 -0
- package/dist/providers/zai/credentials.d.ts +38 -0
- package/dist/providers/zai/credentials.d.ts.map +1 -0
- package/dist/providers/zai/credentials.js +60 -0
- package/dist/providers/zai/credentials.js.map +1 -0
- package/dist/providers/zai/encoded-error.d.ts +90 -0
- package/dist/providers/zai/encoded-error.d.ts.map +1 -0
- package/dist/providers/zai/encoded-error.js +169 -0
- package/dist/providers/zai/encoded-error.js.map +1 -0
- package/dist/providers/zai/media.d.ts +38 -0
- package/dist/providers/zai/media.d.ts.map +1 -0
- package/dist/providers/zai/media.js +107 -0
- package/dist/providers/zai/media.js.map +1 -0
- package/dist/providers/zai/monitor-client.d.ts +53 -0
- package/dist/providers/zai/monitor-client.d.ts.map +1 -0
- package/dist/providers/zai/monitor-client.js +117 -0
- package/dist/providers/zai/monitor-client.js.map +1 -0
- package/dist/providers/zai/quota.d.ts +47 -0
- package/dist/providers/zai/quota.d.ts.map +1 -0
- package/dist/providers/zai/quota.js +127 -0
- package/dist/providers/zai/quota.js.map +1 -0
- package/dist/providers/zai/reader.d.ts +82 -0
- package/dist/providers/zai/reader.d.ts.map +1 -0
- package/dist/providers/zai/reader.js +490 -0
- package/dist/providers/zai/reader.js.map +1 -0
- package/dist/providers/zai/repository.d.ts +76 -0
- package/dist/providers/zai/repository.d.ts.map +1 -0
- package/dist/providers/zai/repository.js +715 -0
- package/dist/providers/zai/repository.js.map +1 -0
- package/package.json +11 -6
package/dist/index.js
CHANGED
|
@@ -6,34 +6,60 @@ import { search, SEARCH_HELP } from "./commands/search.js";
|
|
|
6
6
|
import { read, READ_HELP } from "./commands/read.js";
|
|
7
7
|
import { repoSearch, repoTree, repoRead, REPO_HELP } from "./commands/repo.js";
|
|
8
8
|
import { listTools, showTool, callTool, TOOLS_HELP, CALL_HELP } from "./commands/tools.js";
|
|
9
|
-
import { doctor, DOCTOR_HELP } from "./commands/doctor.js";
|
|
10
|
-
import { quota, QUOTA_HELP } from "./commands/quota.js";
|
|
9
|
+
import { doctor, buildDiagnosticsReport, DOCTOR_HELP } from "./commands/doctor.js";
|
|
10
|
+
import { quota, buildQuotaDashboard, QUOTA_HELP } from "./commands/quota.js";
|
|
11
|
+
import { cacheStatsCommand, cacheClearCommand, formatDoctorCacheSummary, CACHE_HELP, } from "./commands/cache.js";
|
|
12
|
+
import { cacheStats, clearAllCaches } from "./lib/cache.js";
|
|
11
13
|
import { isExtractMode } from "./lib/extract.js";
|
|
12
14
|
import { runCodeFile, evalCode, printInterfaces, printPromptTemplate, CODE_HELP, } from "./commands/code.js";
|
|
13
|
-
import {
|
|
15
|
+
import { isOutputMode, OUTPUT_MODES } from "./lib/output.js";
|
|
16
|
+
import { formatErrorOutput } from "./lib/output.js";
|
|
17
|
+
import { ConfigurationError, ValidationError, UnsupportedCapabilityError, getErrorExitCode, } from "./lib/errors.js";
|
|
18
|
+
import { invokeCommand } from "./command-invocation.js";
|
|
19
|
+
import { defaultResponseCache } from "./lib/cache.js";
|
|
20
|
+
import { configuredSecrets } from "./lib/redact.js";
|
|
21
|
+
import { resolveProviderId } from "./providers/selection.js";
|
|
22
|
+
import { BUILT_IN_PROVIDER_DESCRIPTORS, getProviderDescriptor } from "./providers/registry.js";
|
|
23
|
+
import { visionOperationToCapability } from "./capabilities/vision.js";
|
|
14
24
|
import { createRequire } from "node:module";
|
|
15
25
|
const require = createRequire(import.meta.url);
|
|
16
26
|
const { version: VERSION } = require("../package.json");
|
|
17
27
|
const MAIN_HELP = `
|
|
18
|
-
scoutline v${VERSION} -
|
|
28
|
+
scoutline v${VERSION} - Multimodal source investigation CLI
|
|
19
29
|
|
|
20
30
|
Usage: scoutline <command> [args] [options]
|
|
21
31
|
|
|
22
32
|
Commands:
|
|
23
|
-
vision Image and video analysis (
|
|
24
|
-
search Real-time web search
|
|
25
|
-
read Fetch and parse web pages
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
vision Image and video analysis (Z.AI; MiniMax for interpret-image)
|
|
34
|
+
search Real-time web search (shared: Z.AI + MiniMax)
|
|
35
|
+
read Fetch and parse web pages (Provider Capability; Z.AI supports it,
|
|
36
|
+
MiniMax returns UNSUPPORTED_CAPABILITY)
|
|
37
|
+
repo GitHub repository exploration (Provider Capability; Z.AI supports it,
|
|
38
|
+
MiniMax returns UNSUPPORTED_CAPABILITY)
|
|
39
|
+
quota Provider-aware plan usage (calls remaining, reset time)
|
|
40
|
+
tools List available MCP tools (Z.AI)
|
|
41
|
+
tool Show a tool schema (Z.AI)
|
|
42
|
+
call Call a tool directly (Z.AI)
|
|
43
|
+
doctor Provider-aware environment + connectivity checks
|
|
44
|
+
cache Inspect or clear the local cache (stats / clear)
|
|
45
|
+
code Execute TypeScript tool chains (Code Mode, Z.AI)
|
|
46
|
+
|
|
47
|
+
Provider selection (precedence: --provider, then SCOUTLINE_PROVIDER, then zai):
|
|
48
|
+
--provider <zai|minimax> Select the active Provider for shared capabilities
|
|
49
|
+
SCOUTLINE_PROVIDER=<id> Fallback when --provider is not passed
|
|
50
|
+
|
|
51
|
+
Shared capabilities accept --provider. The 'repo' and 'read' commands
|
|
52
|
+
participate in Provider selection: Z.AI advertises and supplies the
|
|
53
|
+
repository-exploration and reader Capabilities; MiniMax advertises and
|
|
54
|
+
supplies neither, so selecting MiniMax (explicitly or via
|
|
55
|
+
SCOUTLINE_PROVIDER) returns UNSUPPORTED_CAPABILITY with no fallback.
|
|
56
|
+
Z.AI-only commands (tools, tool, call, code) carry the flag but ignore
|
|
57
|
+
it. Quota and doctor report per-Provider; --provider picks the effective
|
|
58
|
+
Provider for metadata.
|
|
33
59
|
|
|
34
60
|
Global Options:
|
|
35
|
-
--output-format <data|json|pretty|compact|markdown|refs> Output mode (default: data)
|
|
36
|
-
-O <mode>
|
|
61
|
+
--output-format <data|json|pretty|compact|markdown|refs|tty> Output mode (default: data)
|
|
62
|
+
-O <mode> Alias for --output-format
|
|
37
63
|
|
|
38
64
|
Help:
|
|
39
65
|
scoutline --help
|
|
@@ -44,6 +70,7 @@ Help:
|
|
|
44
70
|
scoutline tools --help
|
|
45
71
|
scoutline call --help
|
|
46
72
|
scoutline code --help
|
|
73
|
+
scoutline cache --help
|
|
47
74
|
`.trim();
|
|
48
75
|
function parseArgs(args) {
|
|
49
76
|
const flags = {};
|
|
@@ -93,6 +120,7 @@ function extractGlobalOptions(args) {
|
|
|
93
120
|
let outputFormat;
|
|
94
121
|
let forcePretty = false;
|
|
95
122
|
let forceRaw = false;
|
|
123
|
+
let provider;
|
|
96
124
|
for (let i = 0; i < args.length; i += 1) {
|
|
97
125
|
const arg = args[i];
|
|
98
126
|
if (arg === "--output-format" || arg === "-O") {
|
|
@@ -108,90 +136,208 @@ function extractGlobalOptions(args) {
|
|
|
108
136
|
forceRaw = true;
|
|
109
137
|
continue;
|
|
110
138
|
}
|
|
139
|
+
if (arg === "--provider") {
|
|
140
|
+
// Global option: accepted before OR after the command token. It is
|
|
141
|
+
// removed from the rest stream so command-local positional parsing
|
|
142
|
+
// never observes it. Only shared Search resolves/validates it; the
|
|
143
|
+
// Z.AI-only command families carry it but never consult it.
|
|
144
|
+
provider = args[i + 1];
|
|
145
|
+
i += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
111
148
|
rest.push(arg);
|
|
112
149
|
}
|
|
113
|
-
return { outputFormat, forcePretty, forceRaw, rest };
|
|
150
|
+
return { outputFormat, forcePretty, forceRaw, provider, rest };
|
|
114
151
|
}
|
|
115
|
-
function
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
152
|
+
function resolveOutputMode(explicit, forcePretty, forceRaw, adapter) {
|
|
153
|
+
if (explicit !== undefined) {
|
|
154
|
+
if (!isOutputMode(explicit)) {
|
|
155
|
+
throw new ValidationError(`Invalid output format: ${explicit}`, `Use one of: ${OUTPUT_MODES.join(", ")}`);
|
|
156
|
+
}
|
|
157
|
+
return explicit;
|
|
158
|
+
}
|
|
159
|
+
if (forcePretty)
|
|
160
|
+
return "tty";
|
|
161
|
+
if (forceRaw)
|
|
162
|
+
return "data";
|
|
163
|
+
const envMode = adapter.environmentOutputMode;
|
|
164
|
+
if (typeof envMode === "string" && isOutputMode(envMode)) {
|
|
165
|
+
return envMode;
|
|
166
|
+
}
|
|
167
|
+
if (adapter.stdoutIsTTY)
|
|
168
|
+
return "tty";
|
|
169
|
+
return "data";
|
|
124
170
|
}
|
|
125
|
-
async function handleVision(args) {
|
|
171
|
+
async function handleVision(args, outputMode, deps) {
|
|
126
172
|
const { flags, positional } = parseArgs(args);
|
|
127
173
|
if (flags.help || flags.h || positional.length === 0) {
|
|
128
|
-
|
|
129
|
-
return;
|
|
174
|
+
deps.invocation.writeStdout(vision.VISION_HELP);
|
|
175
|
+
return 0;
|
|
130
176
|
}
|
|
131
177
|
const command = positional[0];
|
|
132
178
|
const source = positional[1];
|
|
133
179
|
const prompt = positional[2];
|
|
180
|
+
// Map the subcommand to its Vision operation. Unknown subcommands are
|
|
181
|
+
// a parse-time VALIDATION_ERROR and are rejected before any Provider
|
|
182
|
+
// resolution, support check, or media access.
|
|
183
|
+
const operation = visionOperationForCommand(command);
|
|
184
|
+
// Resolve the effective Provider for Vision (DESIGN.md §6). Invalid
|
|
185
|
+
// explicit/env input fails here with VALIDATION_ERROR before any Vision
|
|
186
|
+
// support check or media access. Selection never consults credentials
|
|
187
|
+
// (FR-003); the configured check below is the caller's responsibility.
|
|
188
|
+
const providerId = resolveProviderId(deps.provider, deps.env);
|
|
189
|
+
const descriptor = getProviderDescriptor(providerId, deps.providerDescriptors);
|
|
190
|
+
// Gate the operation on descriptor metadata BEFORE Adapter construction.
|
|
191
|
+
// An unsupported operation (e.g. MiniMax for any specialized op, diff, or
|
|
192
|
+
// video) fails with UNSUPPORTED_CAPABILITY before credentials, media,
|
|
193
|
+
// transport, cache, or any Z.AI fallback (FR-023, FR-024). No command
|
|
194
|
+
// branches on a Provider ID: the support check alone decides availability.
|
|
195
|
+
const capabilityId = visionOperationToCapability(operation);
|
|
196
|
+
if (!descriptor.capabilities().has(capabilityId)) {
|
|
197
|
+
throw new UnsupportedCapabilityError(providerId, capabilityId);
|
|
198
|
+
}
|
|
199
|
+
// FR-003: selection returns the default zai even when unconfigured. The
|
|
200
|
+
// dispatch layer surfaces a missing credential as ConfigurationError
|
|
201
|
+
// (exit 3), AFTER the capability support check (FR-023) but before any
|
|
202
|
+
// Adapter construction or media access (Fixup A — B5).
|
|
203
|
+
if (!descriptor.isConfigured(deps.env)) {
|
|
204
|
+
throw new ConfigurationError(`Provider "${providerId}" is not configured. Set the required API key.`);
|
|
205
|
+
}
|
|
206
|
+
const adapter = descriptor.create({ env: deps.env });
|
|
207
|
+
const visionCapability = adapter.vision;
|
|
208
|
+
if (!visionCapability || !visionCapability.supports(operation)) {
|
|
209
|
+
throw new UnsupportedCapabilityError(providerId, capabilityId);
|
|
210
|
+
}
|
|
211
|
+
// Vision bypasses the response cache (FR-022). The shared execution
|
|
212
|
+
// primitives (sleep/random) are the same ones Search consumes; they
|
|
213
|
+
// drive retry backoff deterministically under test.
|
|
214
|
+
const visionDeps = {
|
|
215
|
+
capability: visionCapability,
|
|
216
|
+
sleep: deps.searchSleep,
|
|
217
|
+
random: deps.searchRandom,
|
|
218
|
+
};
|
|
134
219
|
switch (command) {
|
|
135
220
|
case "analyze":
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
await vision.analyze(source, prompt);
|
|
140
|
-
break;
|
|
141
|
-
case "ui-to-code":
|
|
142
|
-
if (!source) {
|
|
143
|
-
outputError("Missing image source", "INVALID_ARGS", "Usage: scoutline vision ui-to-code <image> [prompt]");
|
|
144
|
-
}
|
|
221
|
+
return invokeCommand(deps.invocation, (context) => vision.analyze(source, prompt, visionDeps, context), outputMode, deps.now, deps.secrets);
|
|
222
|
+
case "ui-to-code": {
|
|
145
223
|
const outputType = flags.output || "code";
|
|
146
|
-
|
|
147
|
-
|
|
224
|
+
return invokeCommand(deps.invocation, (context) => vision.uiToCode(source, prompt, outputType, visionDeps, context), outputMode, deps.now, deps.secrets);
|
|
225
|
+
}
|
|
148
226
|
case "extract-text":
|
|
149
|
-
|
|
150
|
-
outputError("Missing image source", "INVALID_ARGS", "Usage: scoutline vision extract-text <image> [prompt] [--language <lang>]");
|
|
151
|
-
}
|
|
152
|
-
await vision.extractText(source, prompt, flags.language);
|
|
153
|
-
break;
|
|
227
|
+
return invokeCommand(deps.invocation, (context) => vision.extractText(source, prompt, flags.language, visionDeps, context), outputMode, deps.now, deps.secrets);
|
|
154
228
|
case "diagnose-error":
|
|
155
|
-
|
|
156
|
-
outputError("Missing image source", "INVALID_ARGS", "Usage: scoutline vision diagnose-error <image> [prompt] [--context <ctx>]");
|
|
157
|
-
}
|
|
158
|
-
await vision.diagnoseError(source, prompt, flags.context);
|
|
159
|
-
break;
|
|
229
|
+
return invokeCommand(deps.invocation, (context) => vision.diagnoseError(source, prompt, flags.context, visionDeps, context), outputMode, deps.now, deps.secrets);
|
|
160
230
|
case "diagram":
|
|
161
|
-
|
|
162
|
-
outputError("Missing image source", "INVALID_ARGS", "Usage: scoutline vision diagram <image> [prompt] [--type <type>]");
|
|
163
|
-
}
|
|
164
|
-
await vision.diagram(source, prompt, flags.type);
|
|
165
|
-
break;
|
|
231
|
+
return invokeCommand(deps.invocation, (context) => vision.diagram(source, prompt, flags.type, visionDeps, context), outputMode, deps.now, deps.secrets);
|
|
166
232
|
case "chart":
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
await vision.chart(source, prompt, flags.focus);
|
|
171
|
-
break;
|
|
172
|
-
case "diff":
|
|
233
|
+
return invokeCommand(deps.invocation, (context) => vision.chart(source, prompt, flags.focus, visionDeps, context), outputMode, deps.now, deps.secrets);
|
|
234
|
+
case "diff": {
|
|
173
235
|
const actual = positional[2];
|
|
174
236
|
const diffPrompt = positional[3];
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
await vision.diff(source, actual, diffPrompt);
|
|
179
|
-
break;
|
|
237
|
+
return invokeCommand(deps.invocation, (context) => vision.diff(source, actual, diffPrompt, visionDeps, context), outputMode, deps.now, deps.secrets);
|
|
238
|
+
}
|
|
180
239
|
case "video":
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
240
|
+
return invokeCommand(deps.invocation, (context) => vision.video(source, prompt, visionDeps, context), outputMode, deps.now, deps.secrets);
|
|
241
|
+
default:
|
|
242
|
+
throw new ValidationError(`Unknown vision command: ${command}`, 'Run "scoutline vision --help" for available commands');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Map a Vision subcommand to its discriminated operation id. Used by
|
|
247
|
+
* `handleVision` to gate the operation against descriptor metadata before
|
|
248
|
+
* Adapter construction. Unknown subcommands throw `ValidationError`.
|
|
249
|
+
*/
|
|
250
|
+
function visionOperationForCommand(command) {
|
|
251
|
+
switch (command) {
|
|
252
|
+
case "analyze":
|
|
253
|
+
return "interpret-image";
|
|
254
|
+
case "ui-to-code":
|
|
255
|
+
return "ui-artifact";
|
|
256
|
+
case "extract-text":
|
|
257
|
+
return "extract-text";
|
|
258
|
+
case "diagnose-error":
|
|
259
|
+
return "diagnose-error";
|
|
260
|
+
case "diagram":
|
|
261
|
+
return "diagram";
|
|
262
|
+
case "chart":
|
|
263
|
+
return "chart";
|
|
264
|
+
case "diff":
|
|
265
|
+
return "diff";
|
|
266
|
+
case "video":
|
|
267
|
+
return "video";
|
|
186
268
|
default:
|
|
187
|
-
|
|
269
|
+
throw new ValidationError(`Unknown vision command: ${command}`, 'Run "scoutline vision --help" for available commands');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Parse and validate the `--count` flag value (Fixup C — B11, Fixup D). Per
|
|
274
|
+
* DESIGN.md §7, count must be a safe integer >= 0. Invalid values (NaN,
|
|
275
|
+
* negative, non-integer, Infinity, values above Number.MAX_SAFE_INTEGER)
|
|
276
|
+
* throw `ValidationError` BEFORE any Provider resolution or invocation.
|
|
277
|
+
*
|
|
278
|
+
* Fixup D hardens two gaps:
|
|
279
|
+
* - `--count` without a value parses to `true`; that is a user error,
|
|
280
|
+
* not an absent flag, and now throws VALIDATION_ERROR instead of being
|
|
281
|
+
* silently treated as absent.
|
|
282
|
+
* - Uses `Number.isSafeInteger` instead of `Number.isFinite` +
|
|
283
|
+
* `Number.isInteger` so values above 2^53-1 are rejected rather than
|
|
284
|
+
* silently rounded.
|
|
285
|
+
*
|
|
286
|
+
* Exported for testing so the validation can be exercised without going
|
|
287
|
+
* through the CLI parser (which does not deliver negative numbers as flag
|
|
288
|
+
* values today).
|
|
289
|
+
*/
|
|
290
|
+
export function parseAndValidateCount(raw) {
|
|
291
|
+
if (raw === undefined || raw === "")
|
|
292
|
+
return undefined;
|
|
293
|
+
if (raw === true) {
|
|
294
|
+
throw new ValidationError("Count requires a numeric value.", "Use a non-negative integer (e.g. --count 5).");
|
|
188
295
|
}
|
|
296
|
+
const str = typeof raw === "string" ? raw : String(raw);
|
|
297
|
+
if (!/^\d+$/.test(str)) {
|
|
298
|
+
throw new ValidationError(`Invalid --count value "${str}": must be a non-negative integer`, "Use a non-negative integer (e.g. --count 5).");
|
|
299
|
+
}
|
|
300
|
+
const parsed = Number(str);
|
|
301
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
302
|
+
throw new ValidationError(`Invalid --count value "${str}": must be a non-negative safe integer`, "Use a non-negative integer (e.g. --count 5).");
|
|
303
|
+
}
|
|
304
|
+
return parsed;
|
|
189
305
|
}
|
|
190
|
-
async function handleSearch(args) {
|
|
306
|
+
async function handleSearch(args, outputMode, deps) {
|
|
191
307
|
const { flags, positional } = parseArgs(args);
|
|
192
308
|
if (flags.help || flags.h || positional.length === 0) {
|
|
193
|
-
|
|
194
|
-
return;
|
|
309
|
+
deps.invocation.writeStdout(SEARCH_HELP);
|
|
310
|
+
return 0;
|
|
311
|
+
}
|
|
312
|
+
// Fixup D — B11: validate --count BEFORE Provider resolution and the
|
|
313
|
+
// configured/credential check. A syntax error in a CLI argument must
|
|
314
|
+
// not depend on whether credentials are present: `search q --count nope`
|
|
315
|
+
// with NO credentials must surface VALIDATION_ERROR (exit 1), not
|
|
316
|
+
// CONFIGURATION_ERROR (exit 3). Order: parse global options -> validate
|
|
317
|
+
// count -> resolve provider -> check configured -> dispatch.
|
|
318
|
+
const count = parseAndValidateCount(flags.count);
|
|
319
|
+
// Resolve the Provider ONLY inside shared Search (DESIGN.md §6). Other
|
|
320
|
+
// command families carry the parsed flag but never resolve or validate
|
|
321
|
+
// it. An invalid explicit/env value throws VALIDATION_ERROR here, before
|
|
322
|
+
// any Adapter construction or invocation. Selection never consults
|
|
323
|
+
// credentials (FR-003); the configured check below is the caller's
|
|
324
|
+
// responsibility (Fixup A — B5).
|
|
325
|
+
const providerId = resolveProviderId(deps.provider, deps.env);
|
|
326
|
+
const descriptor = getProviderDescriptor(providerId, deps.providerDescriptors);
|
|
327
|
+
if (!descriptor.capabilities().has("search")) {
|
|
328
|
+
throw new UnsupportedCapabilityError(providerId, "search");
|
|
329
|
+
}
|
|
330
|
+
// FR-003: selection returns the default zai even when unconfigured. The
|
|
331
|
+
// dispatch layer surfaces a missing credential as ConfigurationError
|
|
332
|
+
// (exit 3), AFTER the capability support check (FR-023) but before any
|
|
333
|
+
// Adapter construction (Fixup A — B5).
|
|
334
|
+
if (!descriptor.isConfigured(deps.env)) {
|
|
335
|
+
throw new ConfigurationError(`Provider "${providerId}" is not configured. Set the required API key.`);
|
|
336
|
+
}
|
|
337
|
+
const adapter = descriptor.create({ env: deps.env });
|
|
338
|
+
const capability = adapter.search;
|
|
339
|
+
if (!capability) {
|
|
340
|
+
throw new UnsupportedCapabilityError(providerId, "search");
|
|
195
341
|
}
|
|
196
342
|
const query = positional.join(" ");
|
|
197
343
|
const fieldsRaw = flags.fields;
|
|
@@ -201,26 +347,87 @@ async function handleSearch(args) {
|
|
|
201
347
|
.map((f) => f.trim())
|
|
202
348
|
.filter(Boolean)
|
|
203
349
|
: undefined;
|
|
204
|
-
|
|
205
|
-
count
|
|
350
|
+
return invokeCommand(deps.invocation, (context) => search(query, {
|
|
351
|
+
count,
|
|
206
352
|
domain: flags.domain,
|
|
207
353
|
recency: flags.recency,
|
|
208
354
|
contentSize: flags["content-size"],
|
|
209
355
|
location: flags.location,
|
|
210
|
-
maxSummary: flags["max-summary"]
|
|
356
|
+
maxSummary: flags["max-summary"]
|
|
357
|
+
? parseInt(flags["max-summary"], 10)
|
|
358
|
+
: undefined,
|
|
211
359
|
fields: fields && fields.length > 0 ? fields : undefined,
|
|
212
360
|
noCache: flags["no-cache"] === true,
|
|
213
361
|
merge: flags.merge === true,
|
|
214
|
-
}
|
|
362
|
+
}, {
|
|
363
|
+
capability,
|
|
364
|
+
cache: deps.searchCache,
|
|
365
|
+
sleep: deps.searchSleep,
|
|
366
|
+
random: deps.searchRandom,
|
|
367
|
+
}, context), outputMode, deps.now, deps.secrets);
|
|
215
368
|
}
|
|
216
|
-
async function handleRead(args) {
|
|
369
|
+
async function handleRead(args, outputMode, deps) {
|
|
217
370
|
const { flags, positional } = parseArgs(args);
|
|
218
371
|
if (flags.help || flags.h || positional.length === 0) {
|
|
219
|
-
|
|
220
|
-
return;
|
|
372
|
+
deps.invocation.writeStdout(READ_HELP);
|
|
373
|
+
return 0;
|
|
221
374
|
}
|
|
222
375
|
const url = positional[0];
|
|
223
|
-
|
|
376
|
+
// Parse-level validation BEFORE Provider resolution. URL scheme and
|
|
377
|
+
// --extract mode are validated at parse time so an invalid value
|
|
378
|
+
// surfaces VALIDATION_ERROR regardless of which Provider would have
|
|
379
|
+
// been selected, because parse-level validation fires before the
|
|
380
|
+
// support/configuration gates. The handler re-runs an identical
|
|
381
|
+
// check as defensive backstop. The pre-dispatch
|
|
382
|
+
// configuredSecrets(env) redaction read in `main` is the only
|
|
383
|
+
// permitted credential-related read before this point.
|
|
384
|
+
if (typeof url !== "string" || (!url.startsWith("http://") && !url.startsWith("https://"))) {
|
|
385
|
+
throw new ValidationError("URL must start with http:// or https://");
|
|
386
|
+
}
|
|
387
|
+
const extractFlag = flags.extract;
|
|
388
|
+
if (extractFlag !== undefined && !isExtractMode(extractFlag)) {
|
|
389
|
+
throw new ValidationError(`Invalid --extract mode: ${extractFlag}. Use one of: code, links, tables, headings`);
|
|
390
|
+
}
|
|
391
|
+
const extract = extractFlag !== undefined ? extractFlag : undefined;
|
|
392
|
+
// Resolve the effective Provider (DESIGN.md §6, FR-001–FR-005):
|
|
393
|
+
// explicit --provider > SCOUTLINE_PROVIDER > default zai. Selection
|
|
394
|
+
// never consults credentials (FR-003) and never branches on Provider
|
|
395
|
+
// ID; an unknown explicit/env value throws VALIDATION_ERROR here.
|
|
396
|
+
const providerId = resolveProviderId(deps.provider, deps.env);
|
|
397
|
+
const descriptor = getProviderDescriptor(providerId, deps.providerDescriptors);
|
|
398
|
+
// Capability support check BEFORE `descriptor.isConfigured`,
|
|
399
|
+
// `descriptor.create`, Adapter access, operation validation/
|
|
400
|
+
// cacheIdentity, credential use, cache, or transport. Unsupported
|
|
401
|
+
// MiniMax (explicit or environment) returns UNSUPPORTED_CAPABILITY
|
|
402
|
+
// with no fallback to Z.AI; zero selected-Provider work occurs.
|
|
403
|
+
if (!descriptor.capabilities().has("reader")) {
|
|
404
|
+
throw new UnsupportedCapabilityError(providerId, "reader");
|
|
405
|
+
}
|
|
406
|
+
// FR-003: selection returns the default zai even when unconfigured.
|
|
407
|
+
// Supported-but-unconfigured Z.AI surfaces a missing credential as
|
|
408
|
+
// ConfigurationError (exit 3) AFTER the support metadata check and
|
|
409
|
+
// BEFORE `descriptor.create`.
|
|
410
|
+
if (!descriptor.isConfigured(deps.env)) {
|
|
411
|
+
throw new ConfigurationError(`Provider "${providerId}" is not configured. Set the required API key.`);
|
|
412
|
+
}
|
|
413
|
+
const adapter = descriptor.create({ env: deps.env });
|
|
414
|
+
const capability = adapter.reader;
|
|
415
|
+
// Defensive fail-closed: the descriptor advertised support but the
|
|
416
|
+
// Adapter omitted the handle. Treat as unsupported so a registry
|
|
417
|
+
// mismatch can never reach transport.
|
|
418
|
+
if (!capability) {
|
|
419
|
+
throw new UnsupportedCapabilityError(providerId, "reader");
|
|
420
|
+
}
|
|
421
|
+
// Shared Reader execution dependencies. The cache/sleep/random
|
|
422
|
+
// default to the same production values as Search and Repository
|
|
423
|
+
// but are kept as separate optional MainDependencies so reader
|
|
424
|
+
// tests can inject isolated in-memory doubles.
|
|
425
|
+
const executionDeps = {
|
|
426
|
+
cache: deps.readerCache,
|
|
427
|
+
sleep: deps.readerSleep,
|
|
428
|
+
random: deps.readerRandom,
|
|
429
|
+
};
|
|
430
|
+
return invokeCommand(deps.invocation, (context) => read(url, {
|
|
224
431
|
format: flags.format,
|
|
225
432
|
noImages: flags["no-images"] === true,
|
|
226
433
|
noCache: flags["no-cache"] === true,
|
|
@@ -231,112 +438,231 @@ async function handleRead(args) {
|
|
|
231
438
|
withImagesSummary: flags["with-images-summary"] === true,
|
|
232
439
|
maxChars: flags["max-chars"] ? parseInt(flags["max-chars"], 10) : undefined,
|
|
233
440
|
fullEnvelope: flags["full-envelope"] === true,
|
|
234
|
-
extract
|
|
235
|
-
});
|
|
441
|
+
extract,
|
|
442
|
+
}, { capability, execution: executionDeps }, context), outputMode, deps.now, deps.secrets);
|
|
236
443
|
}
|
|
237
|
-
async function handleRepo(args) {
|
|
444
|
+
async function handleRepo(args, outputMode, deps) {
|
|
238
445
|
const { flags, positional } = parseArgs(args);
|
|
239
446
|
if (flags.help || flags.h || positional.length === 0) {
|
|
240
|
-
|
|
241
|
-
return;
|
|
447
|
+
deps.invocation.writeStdout(REPO_HELP);
|
|
448
|
+
return 0;
|
|
242
449
|
}
|
|
243
450
|
const command = positional[0];
|
|
244
451
|
const repo = positional[1];
|
|
452
|
+
// Parse-level validation. Subcommand grammar and required positionals
|
|
453
|
+
// are validated BEFORE Provider resolution, capability support check,
|
|
454
|
+
// configuration check, Adapter construction, or any
|
|
455
|
+
// operation/cacheIdentity/credential/cache/transport work. The
|
|
456
|
+
// pre-dispatch `configuredSecrets(env)` redaction read in `main` is
|
|
457
|
+
// the only permitted credential-related read before this point; it is
|
|
458
|
+
// not selected-Provider resolution and is covered separately by the
|
|
459
|
+
// ordering tests.
|
|
460
|
+
let searchQuery;
|
|
461
|
+
let readPath;
|
|
462
|
+
if (command === "search") {
|
|
463
|
+
searchQuery = positional.slice(2).join(" ");
|
|
464
|
+
if (!repo || !searchQuery) {
|
|
465
|
+
throw new ValidationError("Missing repo or query", "Usage: scoutline repo search <owner/repo> <query>");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
else if (command === "tree") {
|
|
469
|
+
if (!repo) {
|
|
470
|
+
throw new ValidationError("Missing repo", "Usage: scoutline repo tree <owner/repo>");
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
else if (command === "read") {
|
|
474
|
+
readPath = positional[2];
|
|
475
|
+
if (!repo || !readPath) {
|
|
476
|
+
throw new ValidationError("Missing repo or path", "Usage: scoutline repo read <owner/repo> <path>");
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
throw new ValidationError(`Unknown repo command: ${command}`, 'Run "scoutline repo --help" for available commands');
|
|
481
|
+
}
|
|
482
|
+
// Resolve the effective Provider (DESIGN.md §6, FR-001–FR-005):
|
|
483
|
+
// explicit --provider > SCOUTLINE_PROVIDER > default zai. Selection
|
|
484
|
+
// never consults credentials (FR-003) and never branches on Provider
|
|
485
|
+
// ID; an unknown explicit/env value throws VALIDATION_ERROR here.
|
|
486
|
+
const providerId = resolveProviderId(deps.provider, deps.env);
|
|
487
|
+
const descriptor = getProviderDescriptor(providerId, deps.providerDescriptors);
|
|
488
|
+
// Capability support check BEFORE `descriptor.isConfigured`,
|
|
489
|
+
// `descriptor.create`, Adapter access, operation validation/
|
|
490
|
+
// cacheIdentity, credential use, cache, or transport. Unsupported
|
|
491
|
+
// MiniMax (explicit or environment) returns UNSUPPORTED_CAPABILITY
|
|
492
|
+
// with no fallback to Z.AI; zero selected-Provider work occurs.
|
|
493
|
+
if (!descriptor.capabilities().has("repository-exploration")) {
|
|
494
|
+
throw new UnsupportedCapabilityError(providerId, "repository-exploration");
|
|
495
|
+
}
|
|
496
|
+
// FR-003: selection returns the default zai even when unconfigured.
|
|
497
|
+
// Supported-but-unconfigured Z.AI surfaces a missing credential as
|
|
498
|
+
// ConfigurationError (exit 3) AFTER the support metadata check and
|
|
499
|
+
// BEFORE `descriptor.create`.
|
|
500
|
+
if (!descriptor.isConfigured(deps.env)) {
|
|
501
|
+
throw new ConfigurationError(`Provider "${providerId}" is not configured. Set the required API key.`);
|
|
502
|
+
}
|
|
503
|
+
const adapter = descriptor.create({ env: deps.env });
|
|
504
|
+
const capability = adapter.repository;
|
|
505
|
+
// Defensive fail-closed: the descriptor advertised support but the
|
|
506
|
+
// Adapter omitted the handle. Treat as unsupported so a registry
|
|
507
|
+
// mismatch can never reach transport.
|
|
508
|
+
if (!capability) {
|
|
509
|
+
throw new UnsupportedCapabilityError(providerId, "repository-exploration");
|
|
510
|
+
}
|
|
511
|
+
// Shared Repository execution dependencies. The cache/sleep/random
|
|
512
|
+
// default to the same production values as Search but are kept as
|
|
513
|
+
// separate optional MainDependencies so repository tests can inject
|
|
514
|
+
// isolated in-memory doubles.
|
|
515
|
+
const executionDeps = {
|
|
516
|
+
cache: deps.repositoryCache,
|
|
517
|
+
sleep: deps.repositorySleep,
|
|
518
|
+
random: deps.repositoryRandom,
|
|
519
|
+
};
|
|
245
520
|
switch (command) {
|
|
246
|
-
case "search":
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
depth: flags.depth ? parseInt(flags.depth, 10) : undefined,
|
|
264
|
-
noCache: flags["no-cache"] === true,
|
|
265
|
-
});
|
|
266
|
-
break;
|
|
267
|
-
case "read":
|
|
268
|
-
const path = positional[2];
|
|
269
|
-
if (!repo || !path) {
|
|
270
|
-
outputError("Missing repo or path", "INVALID_ARGS", "Usage: scoutline repo read <owner/repo> <path>");
|
|
271
|
-
}
|
|
272
|
-
await repoRead(repo, path, {
|
|
273
|
-
maxChars: flags["max-chars"] ? parseInt(flags["max-chars"], 10) : undefined,
|
|
274
|
-
noCache: flags["no-cache"] === true,
|
|
275
|
-
});
|
|
276
|
-
break;
|
|
521
|
+
case "search": {
|
|
522
|
+
const language = flags.language;
|
|
523
|
+
const maxChars = flags["max-chars"] ? parseInt(flags["max-chars"], 10) : undefined;
|
|
524
|
+
const noCache = flags["no-cache"] === true;
|
|
525
|
+
return invokeCommand(deps.invocation, () => repoSearch(repo, searchQuery, { language, maxChars, noCache }, { capability, execution: executionDeps }), outputMode, deps.now, deps.secrets);
|
|
526
|
+
}
|
|
527
|
+
case "tree": {
|
|
528
|
+
const treePath = flags.path;
|
|
529
|
+
const depth = flags.depth ? parseInt(flags.depth, 10) : undefined;
|
|
530
|
+
const noCache = flags["no-cache"] === true;
|
|
531
|
+
return invokeCommand(deps.invocation, () => repoTree(repo, { path: treePath, depth, noCache }, { capability, execution: executionDeps }), outputMode, deps.now, deps.secrets);
|
|
532
|
+
}
|
|
533
|
+
case "read": {
|
|
534
|
+
const maxChars = flags["max-chars"] ? parseInt(flags["max-chars"], 10) : undefined;
|
|
535
|
+
const noCache = flags["no-cache"] === true;
|
|
536
|
+
return invokeCommand(deps.invocation, () => repoRead(repo, readPath, { maxChars, noCache }, { capability, execution: executionDeps }), outputMode, deps.now, deps.secrets);
|
|
537
|
+
}
|
|
277
538
|
default:
|
|
278
|
-
|
|
539
|
+
// Unreachable: the parse-level validation above already rejected
|
|
540
|
+
// unknown subcommands. Keep a defensive throw so the dispatch
|
|
541
|
+
// table stays total.
|
|
542
|
+
throw new ValidationError(`Unknown repo command: ${command}`, 'Run "scoutline repo --help" for available commands');
|
|
279
543
|
}
|
|
280
544
|
}
|
|
281
|
-
async function handleTools(args) {
|
|
545
|
+
async function handleTools(args, outputMode, deps) {
|
|
282
546
|
const { flags } = parseArgs(args);
|
|
283
547
|
if (flags.help || flags.h) {
|
|
284
|
-
|
|
285
|
-
return;
|
|
548
|
+
deps.invocation.writeStdout(TOOLS_HELP);
|
|
549
|
+
return 0;
|
|
286
550
|
}
|
|
287
|
-
|
|
551
|
+
return invokeCommand(deps.invocation, (context) => listTools({
|
|
288
552
|
filter: flags.filter,
|
|
289
553
|
full: flags.full === true,
|
|
290
554
|
typescript: flags.typescript === true || flags.ts === true,
|
|
291
555
|
enableVision: flags.vision !== false,
|
|
292
|
-
});
|
|
556
|
+
}, context), outputMode, deps.now, deps.secrets);
|
|
293
557
|
}
|
|
294
|
-
async function handleTool(args) {
|
|
558
|
+
async function handleTool(args, outputMode, deps) {
|
|
295
559
|
const { flags, positional } = parseArgs(args);
|
|
296
560
|
if (flags.help || flags.h || positional.length === 0) {
|
|
297
|
-
|
|
298
|
-
return;
|
|
561
|
+
deps.invocation.writeStdout(TOOLS_HELP);
|
|
562
|
+
return 0;
|
|
299
563
|
}
|
|
300
|
-
|
|
564
|
+
return invokeCommand(deps.invocation, (context) => showTool(positional[0], { enableVision: flags.vision !== false }, context), outputMode, deps.now, deps.secrets);
|
|
301
565
|
}
|
|
302
|
-
async function handleCall(args) {
|
|
566
|
+
async function handleCall(args, outputMode, deps) {
|
|
303
567
|
const { flags, positional } = parseArgs(args);
|
|
304
568
|
if (flags.help || flags.h || positional.length === 0) {
|
|
305
|
-
|
|
306
|
-
return;
|
|
569
|
+
deps.invocation.writeStdout(CALL_HELP);
|
|
570
|
+
return 0;
|
|
307
571
|
}
|
|
308
|
-
|
|
572
|
+
return invokeCommand(deps.invocation, (context) => callTool(positional[0], {
|
|
309
573
|
json: flags.json,
|
|
310
574
|
file: flags.file,
|
|
311
575
|
stdin: flags.stdin === true,
|
|
312
576
|
dryRun: flags["dry-run"] === true,
|
|
313
577
|
enableVision: flags.vision !== false,
|
|
314
|
-
});
|
|
578
|
+
}, context), outputMode, deps.now, deps.secrets);
|
|
315
579
|
}
|
|
316
|
-
async function handleDoctor(args) {
|
|
580
|
+
async function handleDoctor(args, outputMode, deps) {
|
|
317
581
|
const { flags } = parseArgs(args);
|
|
318
582
|
if (flags.help || flags.h) {
|
|
319
|
-
|
|
320
|
-
return;
|
|
583
|
+
deps.invocation.writeStdout(DOCTOR_HELP);
|
|
584
|
+
return 0;
|
|
585
|
+
}
|
|
586
|
+
const noTools = flags["no-tools"] === true;
|
|
587
|
+
// Resolve the effective Provider ID for report metadata, mirroring
|
|
588
|
+
// Search/Vision/quota. Descriptors are intentionally NOT passed here so
|
|
589
|
+
// the report always lists every built-in Provider even when the
|
|
590
|
+
// effective Provider is unconfigured.
|
|
591
|
+
const effectiveProvider = resolveProviderId(deps.provider, deps.env);
|
|
592
|
+
// Cache Module Unification Ticket 03 — Doctor's one-line cache summary.
|
|
593
|
+
// The dispatcher formats the summary here (L1 fix) from `cacheStats()`
|
|
594
|
+
// output; the report builder only embeds the pre-formatted string.
|
|
595
|
+
// `cacheStats()` never throws (it catches all I/O internally); on a
|
|
596
|
+
// missing directory it returns zeros, which still format correctly.
|
|
597
|
+
const cacheSummary = formatDoctorCacheSummary(await cacheStats());
|
|
598
|
+
return invokeCommand(deps.invocation, () => doctor({
|
|
599
|
+
buildReport: () => buildDiagnosticsReport({
|
|
600
|
+
noTools,
|
|
601
|
+
effectiveProvider,
|
|
602
|
+
descriptors: deps.providerDescriptors,
|
|
603
|
+
env: deps.env,
|
|
604
|
+
sleep: deps.searchSleep,
|
|
605
|
+
random: deps.searchRandom,
|
|
606
|
+
cacheSummary,
|
|
607
|
+
}),
|
|
608
|
+
}), outputMode, deps.now, deps.secrets);
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* `scoutline cache <stats|clear>` — local cache utility. Like Doctor,
|
|
612
|
+
* it bypasses Provider resolution entirely (no descriptor lookup, no
|
|
613
|
+
* Adapter, no transport). The command surfaces the inventory and clear
|
|
614
|
+
* helpers owned by `src/lib/cache.ts` (Ticket 01) through the
|
|
615
|
+
* presentation-only handlers in `src/commands/cache.ts`.
|
|
616
|
+
*/
|
|
617
|
+
async function handleCache(args, outputMode, deps) {
|
|
618
|
+
const { flags, positional } = parseArgs(args);
|
|
619
|
+
if (flags.help || flags.h || positional.length === 0) {
|
|
620
|
+
deps.invocation.writeStdout(CACHE_HELP);
|
|
621
|
+
return 0;
|
|
622
|
+
}
|
|
623
|
+
const subcommand = positional[0];
|
|
624
|
+
switch (subcommand) {
|
|
625
|
+
case "stats":
|
|
626
|
+
return invokeCommand(deps.invocation, () => cacheStatsCommand({
|
|
627
|
+
getStats: () => cacheStats(),
|
|
628
|
+
}), outputMode, deps.now, deps.secrets);
|
|
629
|
+
case "clear":
|
|
630
|
+
return invokeCommand(deps.invocation, () => cacheClearCommand({
|
|
631
|
+
clear: () => clearAllCaches(),
|
|
632
|
+
}), outputMode, deps.now, deps.secrets);
|
|
633
|
+
default:
|
|
634
|
+
throw new ValidationError(`Unknown cache command: ${subcommand}`, 'Run "scoutline cache --help" for available commands');
|
|
321
635
|
}
|
|
322
|
-
await doctor({
|
|
323
|
-
noTools: flags["no-tools"] === true,
|
|
324
|
-
enableVision: flags.vision !== false,
|
|
325
|
-
});
|
|
326
636
|
}
|
|
327
|
-
async function handleQuota(args) {
|
|
637
|
+
async function handleQuota(args, outputMode, deps) {
|
|
328
638
|
const { flags } = parseArgs(args);
|
|
329
639
|
if (flags.help || flags.h) {
|
|
330
|
-
|
|
331
|
-
return;
|
|
640
|
+
deps.invocation.writeStdout(QUOTA_HELP);
|
|
641
|
+
return 0;
|
|
332
642
|
}
|
|
333
|
-
|
|
643
|
+
const allProviders = flags["all-providers"] === true;
|
|
644
|
+
// Resolve the effective Provider ID for dashboard metadata. Config
|
|
645
|
+
// validation is owned by the dashboard builder (ConfigurationError,
|
|
646
|
+
// exit 3) so an unconfigured default is reported as configuration, not
|
|
647
|
+
// a registry error. Descriptors are intentionally NOT passed here so
|
|
648
|
+
// all-provider mode is not blocked by an unconfigured effective.
|
|
649
|
+
const effectiveProvider = resolveProviderId(deps.provider, deps.env);
|
|
650
|
+
return invokeCommand(deps.invocation, () => quota({
|
|
651
|
+
buildDashboard: () => buildQuotaDashboard({
|
|
652
|
+
allProviders,
|
|
653
|
+
effectiveProvider,
|
|
654
|
+
descriptors: deps.providerDescriptors,
|
|
655
|
+
env: deps.env,
|
|
656
|
+
sleep: deps.searchSleep,
|
|
657
|
+
random: deps.searchRandom,
|
|
658
|
+
}),
|
|
659
|
+
}), outputMode, deps.now, deps.secrets);
|
|
334
660
|
}
|
|
335
|
-
async function handleCode(args) {
|
|
661
|
+
async function handleCode(args, outputMode, deps) {
|
|
336
662
|
const { flags, positional } = parseArgs(args);
|
|
337
663
|
if (flags.help || flags.h || positional.length === 0) {
|
|
338
|
-
|
|
339
|
-
return;
|
|
664
|
+
deps.invocation.writeStdout(CODE_HELP);
|
|
665
|
+
return 0;
|
|
340
666
|
}
|
|
341
667
|
const command = positional[0];
|
|
342
668
|
const timeout = flags.timeout ? parseInt(flags.timeout, 10) : undefined;
|
|
@@ -345,99 +671,130 @@ async function handleCode(args) {
|
|
|
345
671
|
case "run": {
|
|
346
672
|
const filePath = positional[1];
|
|
347
673
|
if (!filePath) {
|
|
348
|
-
|
|
674
|
+
throw new ValidationError("Missing code file", "Usage: scoutline code run <file>");
|
|
349
675
|
}
|
|
350
|
-
|
|
351
|
-
break;
|
|
676
|
+
return invokeCommand(deps.invocation, (context) => runCodeFile(filePath, { timeout, includeLogs }, context), outputMode, deps.now, deps.secrets);
|
|
352
677
|
}
|
|
353
678
|
case "eval": {
|
|
354
679
|
const code = positional.slice(1).join(" ");
|
|
355
680
|
if (!code) {
|
|
356
|
-
|
|
681
|
+
throw new ValidationError("Missing code string", "Usage: scoutline code eval <code>");
|
|
357
682
|
}
|
|
358
|
-
|
|
359
|
-
break;
|
|
683
|
+
return invokeCommand(deps.invocation, (context) => evalCode(code, { timeout, includeLogs }, context), outputMode, deps.now, deps.secrets);
|
|
360
684
|
}
|
|
361
685
|
case "interfaces":
|
|
362
|
-
|
|
363
|
-
break;
|
|
686
|
+
return invokeCommand(deps.invocation, (context) => printInterfaces(context), outputMode, deps.now, deps.secrets);
|
|
364
687
|
case "prompt":
|
|
365
|
-
printPromptTemplate();
|
|
366
|
-
break;
|
|
688
|
+
return invokeCommand(deps.invocation, async (context) => printPromptTemplate(context), outputMode, deps.now, deps.secrets);
|
|
367
689
|
default:
|
|
368
|
-
|
|
690
|
+
throw new ValidationError(`Unknown code command: ${command}`, 'Run "scoutline code --help" for available commands');
|
|
369
691
|
}
|
|
370
692
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
693
|
+
const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
694
|
+
export async function main(args, dependencies) {
|
|
695
|
+
const { invocation, env, now } = dependencies;
|
|
696
|
+
const providerDescriptors = dependencies.providerDescriptors ?? BUILT_IN_PROVIDER_DESCRIPTORS;
|
|
697
|
+
const searchCache = dependencies.searchCache ?? defaultResponseCache;
|
|
698
|
+
const searchSleep = dependencies.searchSleep ?? realSleep;
|
|
699
|
+
const searchRandom = dependencies.searchRandom ?? Math.random;
|
|
700
|
+
// P6-07: Repository execution defaults to the same production values
|
|
701
|
+
// as Search but stays as separate optional MainDependencies so
|
|
702
|
+
// repository tests can inject isolated in-memory doubles.
|
|
703
|
+
const repositoryCache = dependencies.repositoryCache ?? defaultResponseCache;
|
|
704
|
+
const repositorySleep = dependencies.repositorySleep ?? realSleep;
|
|
705
|
+
const repositoryRandom = dependencies.repositoryRandom ?? Math.random;
|
|
706
|
+
// Reader Migration Ticket 04: Reader execution defaults to the same
|
|
707
|
+
// production values as Search/Repository but stays as separate
|
|
708
|
+
// optional MainDependencies so reader tests can inject isolated
|
|
709
|
+
// in-memory doubles.
|
|
710
|
+
const readerCache = dependencies.readerCache ?? defaultResponseCache;
|
|
711
|
+
const readerSleep = dependencies.readerSleep ?? realSleep;
|
|
712
|
+
const readerRandom = dependencies.readerRandom ?? Math.random;
|
|
713
|
+
// Resolve configured Provider credentials from the INJECTED env (B3) so
|
|
714
|
+
// redaction follows the same environment the handlers see — a secret
|
|
715
|
+
// that exists only in MainDependencies.env is still redacted from output.
|
|
716
|
+
const secrets = configuredSecrets(env);
|
|
717
|
+
const { outputFormat, forcePretty, forceRaw, provider, rest } = extractGlobalOptions([...args]);
|
|
718
|
+
// Fixup C — B10: resolve the output mode BEFORE the dispatch try/catch.
|
|
719
|
+
// An invalid explicit mode still surfaces as a typed ValidationError,
|
|
720
|
+
// but the surface formatter uses the user's REQUESTED mode (or the
|
|
721
|
+
// best deterministic fallback) so the envelope matches what the user
|
|
722
|
+
// asked for. Pre-invocation validation errors (provider resolution,
|
|
723
|
+
// missing credentials, count parsing, etc.) MUST honour the requested
|
|
724
|
+
// output mode the same way handler errors do.
|
|
725
|
+
let outputMode;
|
|
726
|
+
try {
|
|
727
|
+
outputMode = resolveOutputMode(outputFormat, forcePretty ?? false, forceRaw ?? false, invocation);
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
// The explicit mode is invalid — fall back to a deterministic
|
|
731
|
+
// compact form so we can still surface a structured error envelope.
|
|
732
|
+
invocation.writeStderr(formatErrorOutput(error, "data", secrets));
|
|
733
|
+
return getErrorExitCode(error);
|
|
734
|
+
}
|
|
388
735
|
if (rest.length === 0 || rest[0] === "--help" || rest[0] === "-h") {
|
|
389
|
-
|
|
390
|
-
return;
|
|
736
|
+
invocation.writeStdout(MAIN_HELP);
|
|
737
|
+
return 0;
|
|
391
738
|
}
|
|
392
739
|
if (rest[0] === "--version" || rest[0] === "-v") {
|
|
393
|
-
|
|
394
|
-
return;
|
|
740
|
+
invocation.writeStdout(VERSION);
|
|
741
|
+
return 0;
|
|
395
742
|
}
|
|
396
743
|
const command = rest[0];
|
|
397
744
|
const commandArgs = rest.slice(1);
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
745
|
+
const handlerDeps = {
|
|
746
|
+
invocation,
|
|
747
|
+
env,
|
|
748
|
+
secrets,
|
|
749
|
+
now,
|
|
750
|
+
provider,
|
|
751
|
+
providerDescriptors,
|
|
752
|
+
searchCache,
|
|
753
|
+
searchSleep,
|
|
754
|
+
searchRandom,
|
|
755
|
+
repositoryCache,
|
|
756
|
+
repositorySleep,
|
|
757
|
+
repositoryRandom,
|
|
758
|
+
readerCache,
|
|
759
|
+
readerSleep,
|
|
760
|
+
readerRandom,
|
|
761
|
+
};
|
|
762
|
+
try {
|
|
763
|
+
switch (command) {
|
|
764
|
+
case "vision":
|
|
765
|
+
return await handleVision(commandArgs, outputMode, handlerDeps);
|
|
766
|
+
case "search":
|
|
767
|
+
return await handleSearch(commandArgs, outputMode, handlerDeps);
|
|
768
|
+
case "read":
|
|
769
|
+
return await handleRead(commandArgs, outputMode, handlerDeps);
|
|
770
|
+
case "repo":
|
|
771
|
+
return await handleRepo(commandArgs, outputMode, handlerDeps);
|
|
772
|
+
case "tools":
|
|
773
|
+
return await handleTools(commandArgs, outputMode, handlerDeps);
|
|
774
|
+
case "tool":
|
|
775
|
+
return await handleTool(commandArgs, outputMode, handlerDeps);
|
|
776
|
+
case "call":
|
|
777
|
+
return await handleCall(commandArgs, outputMode, handlerDeps);
|
|
778
|
+
case "doctor":
|
|
779
|
+
return await handleDoctor(commandArgs, outputMode, handlerDeps);
|
|
780
|
+
case "cache":
|
|
781
|
+
return await handleCache(commandArgs, outputMode, handlerDeps);
|
|
782
|
+
case "quota":
|
|
783
|
+
return await handleQuota(commandArgs, outputMode, handlerDeps);
|
|
784
|
+
case "code":
|
|
785
|
+
return await handleCode(commandArgs, outputMode, handlerDeps);
|
|
786
|
+
default:
|
|
787
|
+
invocation.writeStderr(formatErrorOutput(new ValidationError(`Unknown command: ${command}`, 'Run "scoutline --help" for available commands'), outputMode, secrets));
|
|
788
|
+
return 1;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
catch (error) {
|
|
792
|
+
// Fixup C — B10: pre-invocation validation errors (provider
|
|
793
|
+
// resolution, missing credential, count parsing, etc.) MUST be
|
|
794
|
+
// formatted in the resolved output mode — they used to be hardcoded
|
|
795
|
+
// to "data" regardless of what the user asked for.
|
|
796
|
+
invocation.writeStderr(formatErrorOutput(error, outputMode, secrets));
|
|
797
|
+
return getErrorExitCode(error);
|
|
431
798
|
}
|
|
432
799
|
}
|
|
433
|
-
main(process.argv)
|
|
434
|
-
.then(() => process.exit(0))
|
|
435
|
-
.catch((error) => {
|
|
436
|
-
console.error(JSON.stringify({
|
|
437
|
-
success: false,
|
|
438
|
-
error: error.message,
|
|
439
|
-
code: "FATAL_ERROR",
|
|
440
|
-
}, null, 2));
|
|
441
|
-
process.exit(1);
|
|
442
|
-
});
|
|
443
800
|
//# sourceMappingURL=index.js.map
|