@zokizuan/satori-mcp 4.11.2 → 4.11.7
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 +55 -9
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.js +33 -4
- package/dist/cli/client.d.ts +4 -2
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.js +14 -54
- package/dist/cli/install.d.ts +7 -1
- package/dist/cli/install.js +213 -38
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/core/call-graph.d.ts +3 -1
- package/dist/core/completion-proof.d.ts +1 -0
- package/dist/core/completion-proof.js +11 -0
- package/dist/core/handlers.d.ts +187 -21
- package/dist/core/handlers.js +5266 -1452
- package/dist/core/indexing-recovery.d.ts +3 -2
- package/dist/core/indexing-recovery.js +18 -3
- package/dist/core/manage-types.d.ts +1 -1
- package/dist/core/runtime-owner.d.ts +119 -0
- package/dist/core/runtime-owner.js +465 -0
- package/dist/core/search/exact-registry.d.ts +52 -0
- package/dist/core/search/exact-registry.js +242 -0
- package/dist/core/search-constants.d.ts +1 -1
- package/dist/core/search-constants.js +3 -0
- package/dist/core/search-types.d.ts +250 -15
- package/dist/core/snapshot.d.ts +8 -2
- package/dist/core/snapshot.js +104 -13
- package/dist/core/sync.d.ts +9 -6
- package/dist/core/sync.js +61 -14
- package/dist/server/bootstrap-stdio.d.ts +2 -2
- package/dist/server/provider-runtime.d.ts +3 -0
- package/dist/server/provider-runtime.js +4 -2
- package/dist/server/start-server.d.ts +1 -0
- package/dist/server/start-server.js +30 -6
- package/dist/server/stdio-safety.d.ts +7 -1
- package/dist/telemetry/search.d.ts +1 -0
- package/dist/tools/call_graph.js +12 -3
- package/dist/tools/file_outline.js +11 -2
- package/dist/tools/list_codebases.js +21 -5
- package/dist/tools/manage_index.js +16 -6
- package/dist/tools/provider-context.d.ts +19 -0
- package/dist/tools/provider-context.js +87 -0
- package/dist/tools/read_file.js +95 -53
- package/dist/tools/registry.d.ts +6 -1
- package/dist/tools/search_codebase.js +35 -13
- package/dist/tools/setup-errors.js +11 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,16 +7,45 @@ Read-only MCP server for Satori. It gives coding agents six deterministic tools
|
|
|
7
7
|
Use the CLI installer for normal setup:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx -y @zokizuan/satori-cli@0.4.
|
|
11
|
-
npx -y @zokizuan/satori-cli@0.4.
|
|
10
|
+
npx -y @zokizuan/satori-cli@0.4.5 install --client all
|
|
11
|
+
npx -y @zokizuan/satori-cli@0.4.5 doctor
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
The CLI installer supports `codex`, `claude`, `opencode`, and `all`. It creates the runtime cache, writes the stable launcher, and writes client config for you. Avoid using `npx` as the resident MCP server command; first-run package resolution can exceed normal MCP startup timeouts.
|
|
15
15
|
|
|
16
|
+
Use `--profile default|minimal|all-text` to write repo-local `satori.toml` during install:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx -y @zokizuan/satori-cli@0.4.5 install --client all --profile minimal
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Profiles control indexing breadth, not search scope. `default` is safe-broad, `minimal` indexes source plus docs/text, and `all-text` indexes additional UTF-8 text files under the size limit. `search_codebase` still defaults to `scope=runtime`.
|
|
23
|
+
|
|
24
|
+
The repo-local config shape is:
|
|
25
|
+
|
|
26
|
+
```toml
|
|
27
|
+
[index]
|
|
28
|
+
profile = "minimal"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`satori.toml` is repository index policy, not MCP client config and not provider config. Do not put API keys, model names, Milvus endpoints, or tokens in it. Provider settings belong in the MCP client's runtime environment.
|
|
32
|
+
|
|
33
|
+
Profile behavior:
|
|
34
|
+
|
|
35
|
+
- `default`: source, docs/text, config, scripts, infra/query files, and known extensionless files such as `Dockerfile`, `Makefile`, `Justfile`, `Taskfile`, `Procfile`, `Jenkinsfile`, and `.dockerignore`.
|
|
36
|
+
- `minimal`: source plus docs/text only.
|
|
37
|
+
- `all-text`: default plus unknown UTF-8 text files under the size limit. `SATORI_ALL_TEXT_MAX_BYTES` can override the text-file cap.
|
|
38
|
+
|
|
39
|
+
All profiles still honor `.gitignore`, `.satoriignore`, and the hard denylist for secrets, lockfiles, generated output, dependency folders, binaries, bundles, logs, database dumps, source maps, and snapshots. `satori.toml` is treated as an index-policy control file; search freshness and `manage_index action="sync"` can reconcile ordinary profile/ignore changes, while incompatible fingerprints still return `requires_reindex`.
|
|
40
|
+
|
|
41
|
+
Codex installs write two companion artifacts by default: the first-party `satori` skill under `~/.codex/skills` and a marked Satori guidance block in `~/.codex/AGENTS.md`. The AGENTS block tells Codex to use Satori for semantic ownership/context discovery first, then use exact navigation and reads for proof.
|
|
42
|
+
|
|
43
|
+
For Codex, add `--install-guidance-hook` only when you want an installer-managed `SessionStart` reminder in `~/.codex/config.toml`. The hook prints guidance only, suppresses duplicate startup prints for the same working directory, and does not run indexing, search, or provider-backed work.
|
|
44
|
+
|
|
16
45
|
Advanced direct execution is available through the package bin:
|
|
17
46
|
|
|
18
47
|
```bash
|
|
19
|
-
npx -y @zokizuan/satori-mcp@4.11.
|
|
48
|
+
npx -y @zokizuan/satori-mcp@4.11.7 --help
|
|
20
49
|
```
|
|
21
50
|
|
|
22
51
|
Use direct package execution for inspection, smoke tests, or unsupported harnesses. For supported clients, prefer `satori-cli install` so startup does not depend on package-manager resolution.
|
|
@@ -36,9 +65,26 @@ Important defaults:
|
|
|
36
65
|
|
|
37
66
|
- `search_codebase` starts with runtime code, grouped by symbol.
|
|
38
67
|
- `search_codebase` runs freshness checks before returning results.
|
|
68
|
+
- Grouped search is symbol-owned: chunks are supporting evidence for an owner symbol, not the final navigation unit.
|
|
69
|
+
- Exact symbol navigation uses `symbolInstanceId`. `symbolKey` is stable-ish candidate lookup only, not exact identity.
|
|
70
|
+
- Index profiles still honor `.satoriignore`, `.gitignore`, `satori.toml`, and the hard denylist for secrets, lockfiles, generated output, dependencies, binaries, bundles, logs, and database dumps.
|
|
39
71
|
- `read_file` is bounded and can return continuation hints.
|
|
40
72
|
- `requires_reindex` means reindex first, then retry the original call.
|
|
41
73
|
- `manage_index action="clear"` is destructive and should be explicit.
|
|
74
|
+
- After changing `EMBEDDING_PROVIDER`, `EMBEDDING_MODEL`, embedding dimension, `HYBRID_MODE`, vector backend settings, or the Satori runtime version, restart all Satori MCP clients before running `manage_index create`, `reindex`, `sync`, or `clear`.
|
|
75
|
+
- Satori records live runtime owners in `~/.satori/runtime/owners.json` and blocks those index mutations with `status="blocked"` / `reason="runtime_owner_conflict"` if another live Satori MCP runtime has a different fingerprint, package version, or config identity. MCP tools do not kill processes or ask interactive cleanup questions.
|
|
76
|
+
|
|
77
|
+
## Navigation Sidecars
|
|
78
|
+
|
|
79
|
+
Completed full indexes write a derived symbol registry and relationship sidecar. Files remain the source of truth; the registry is the deterministic navigation view for the indexed snapshot.
|
|
80
|
+
|
|
81
|
+
- The symbol registry stores candidate owner keys, exact symbol instances, file-owner fallback symbols, and outline records used by grouped search, `file_outline`, and exact reads.
|
|
82
|
+
- The relationship sidecar stores conservative `CALLS v0` edges plus TypeScript/JavaScript `IMPORTS`/`EXPORTS v0` edges with manifest compatibility gates.
|
|
83
|
+
- Runtime navigation still serves canonical JSON sidecars by default. When the default shared runtime store is created at process startup, `SATORI_NAVIGATION_BACKEND=sqlite` can opt that shared store into SQLite-backed reads only after SQLite proves parity with the canonical JSON symbol registry and relationship sidecars. If canonical JSON is missing or incompatible, SQLite is not served as truth; if SQLite is missing, stale, incompatible, or parity-mismatched while JSON is compatible, runtime falls back to JSON with a warning. When the default shared runtime store is created with `SATORI_NAVIGATION_DUAL_READ=1`, JSON remains the serving backend and the runtime emits once-per-root parity mismatch warnings without changing the served result.
|
|
84
|
+
- `CALLS v0` is heuristic/name-based. Same-file unique targets can be high confidence, cross-file name-only targets start low confidence, and ambiguous same-name targets are skipped.
|
|
85
|
+
- `IMPORTS`/`EXPORTS v0` records only resolvable relative module edges and unambiguous local export declarations. Package imports, unresolved paths, ambiguous exports, and multiline module syntax are skipped.
|
|
86
|
+
- `call_graph` uses compatible relationship sidecars as the canonical source for symbol-owned traversal.
|
|
87
|
+
- Successful incremental sync reuses changed-file symbol output, preserves unchanged registry state, and recomputes relationships against the merged registry without re-splitting unchanged files. If changed-file indexing stops early, navigation state is cleared instead of publishing a mixed generation.
|
|
42
88
|
|
|
43
89
|
## Runtime Requirements
|
|
44
90
|
|
|
@@ -85,7 +131,7 @@ The full generated tool reference below is kept in the npm README for MCP client
|
|
|
85
131
|
|
|
86
132
|
### `manage_index`
|
|
87
133
|
|
|
88
|
-
Manage index lifecycle operations (create/reindex/sync/status/clear) for a codebase path. Ignore-rule edits in repo-root .satoriignore/.gitignore reconcile automatically in the normal sync path. Use action="sync" for immediate convergence and action="reindex" for full rebuild recovery (preflight may block unnecessary ignore-only reindex churn unless allowUnnecessaryReindex=true).
|
|
134
|
+
Manage index lifecycle operations (create/reindex/sync/status/clear) for a codebase path. Ignore-rule edits in repo-root .satoriignore/.gitignore reconcile automatically in the normal sync path. Use action="sync" for immediate convergence and action="reindex" for full rebuild recovery (preflight may block unnecessary ignore-only reindex churn unless allowUnnecessaryReindex=true). create/reindex return the kickoff response immediately and do not poll to terminal state; use action="status" to observe progress.
|
|
89
135
|
|
|
90
136
|
| Parameter | Type | Required | Default | Description |
|
|
91
137
|
|---|---|---|---|---|
|
|
@@ -99,7 +145,7 @@ Manage index lifecycle operations (create/reindex/sync/status/clear) for a codeb
|
|
|
99
145
|
|
|
100
146
|
### `search_codebase`
|
|
101
147
|
|
|
102
|
-
Unified semantic search with runtime-first defaults (start with scope="runtime"), grouped/raw output modes, and deterministic ranking/freshness behavior. Operators are parsed from a query prefix block: lang:, path:, -path:, must:, exclude: (escape with \\ to keep literals).
|
|
148
|
+
Unified semantic search with runtime-first defaults (start with scope="runtime"), grouped/raw output modes, and deterministic ranking/freshness behavior. Operators are parsed from a query prefix block: lang:, path:, -path:, must:, exclude: (escape with \\ to keep literals). For high-precision queries such as exact identifiers, quoted literal phrases, and strict path filters, search_codebase can use an exact registry fast path or add a bounded tracked-file lexical recovery pass when semantic retrieval under-delivers. Grouped results expose legacy span plus explicit previewSpan/symbolSpan metadata, structured warnings, recommendedNextAction, per-result capabilities/fallbacks, executable nextActions/navigationFallbacks, and remediation hints such as .satoriignore noise handling. Use debug:true for explainability payloads, including debugSummary, exactRegistry, phaseTimingsMs, trackedLexical, and ranking provenance.
|
|
103
149
|
|
|
104
150
|
| Parameter | Type | Required | Default | Description |
|
|
105
151
|
|---|---|---|---|---|
|
|
@@ -114,7 +160,7 @@ Unified semantic search with runtime-first defaults (start with scope="runtime")
|
|
|
114
160
|
|
|
115
161
|
### `call_graph`
|
|
116
162
|
|
|
117
|
-
Traverse
|
|
163
|
+
Traverse registry-resolved caller/callee relationships for indexed TS/JS/Python code. On symbol-owned indexes, call_graph uses compatible relationship sidecars for conservative CALLS v0 traversal and upgrades low-confidence cross-file calls only when current IMPORTS/EXPORTS evidence deterministically supports the target symbol. In successful traversal responses, sidecar.nodeCount and sidecar.edgeCount report the counts returned in that response, not whole-sidecar totals for the indexed codebase.
|
|
118
164
|
|
|
119
165
|
| Parameter | Type | Required | Default | Description |
|
|
120
166
|
|---|---|---|---|---|
|
|
@@ -136,7 +182,7 @@ Return a sidecar-backed symbol outline for one file, including call_graph jump h
|
|
|
136
182
|
| `end_line` | integer | no | | Optional end line filter (1-based, inclusive). |
|
|
137
183
|
| `limitSymbols` | integer | no | `500` | Maximum number of returned symbols after line filtering. |
|
|
138
184
|
| `resolveMode` | enum("outline", "exact") | no | `"outline"` | Outline mode returns all symbols (windowed/limited). Exact mode resolves deterministic symbol matches in this file. |
|
|
139
|
-
| `symbolIdExact` | string | no | | Used with resolveMode="exact": exact
|
|
185
|
+
| `symbolIdExact` | string | no | | Used with resolveMode="exact": exact symbol identifier match in the target file. On symbol-owned flows, pass the symbol's symbolInstanceId. |
|
|
140
186
|
| `symbolLabelExact` | string | no | | Used with resolveMode="exact": exact symbol label match in the target file. |
|
|
141
187
|
|
|
142
188
|
### `read_file`
|
|
@@ -149,7 +195,7 @@ Read file content from the local filesystem, with optional 1-based inclusive lin
|
|
|
149
195
|
| `start_line` | integer | no | | Optional start line (1-based, inclusive). |
|
|
150
196
|
| `end_line` | integer | no | | Optional end line (1-based, inclusive). |
|
|
151
197
|
| `mode` | enum("plain", "annotated") | no | `"plain"` | Output mode. plain returns text only; annotated returns content plus sidecar-backed outline metadata. |
|
|
152
|
-
| `open_symbol` | object | no | | Optional deterministic symbol jump request for this file path. Uses exact symbol resolution within `path` when symbolId/symbolLabel is provided. |
|
|
198
|
+
| `open_symbol` | object | no | | Optional deterministic symbol jump request for this file path. Uses exact symbol resolution within `path` when symbolId/symbolLabel is provided, and only uses direct span opens when no symbol identity fields are supplied. On symbol-owned flows, symbolId should carry the symbolInstanceId. |
|
|
153
199
|
|
|
154
200
|
### `list_codebases`
|
|
155
201
|
|
|
@@ -162,7 +208,7 @@ No parameters.
|
|
|
162
208
|
|
|
163
209
|
## Notes
|
|
164
210
|
|
|
165
|
-
- `open_symbol` resolves exact symbols inside the same file passed to `read_file.path`.
|
|
211
|
+
- `open_symbol` resolves exact symbols inside the same file passed to `read_file.path`. On symbol-owned flows, `symbolId`/`symbolIdExact` should carry `symbolInstanceId`.
|
|
166
212
|
- `MILVUS_TOKEN` is optional auth; local unauthenticated Milvus only needs `MILVUS_ADDRESS`.
|
|
167
213
|
- MCP startup does not require provider credentials or a live Milvus backend. Provider-backed calls report `MISSING_PROVIDER_CONFIG` when setup is incomplete.
|
|
168
214
|
- `MISSING_PROVIDER_CONFIG` is an active setup failure only when it appears as a tool response `code` or `reason`.
|
package/dist/cli/args.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export type ParsedCommand = {
|
|
|
23
23
|
kind: "install";
|
|
24
24
|
client: InstallClient;
|
|
25
25
|
dryRun: boolean;
|
|
26
|
+
installGuidanceHook: boolean;
|
|
27
|
+
profile?: InstallProfile;
|
|
26
28
|
} | {
|
|
27
29
|
kind: "uninstall";
|
|
28
30
|
client: InstallClient;
|
|
@@ -48,6 +50,7 @@ export interface ResolveRawArgsOptions {
|
|
|
48
50
|
stdinTimeoutMs: number;
|
|
49
51
|
}
|
|
50
52
|
export type InstallClient = "all" | "claude" | "codex" | "opencode";
|
|
53
|
+
export type InstallProfile = "default" | "minimal" | "all-text";
|
|
51
54
|
export declare function parseCliArgs(argv: string[]): ParsedCliInput;
|
|
52
55
|
export declare function resolveRawArguments(rawArgsMode: RawArgsMode, options: ResolveRawArgsOptions): Promise<Record<string, unknown>>;
|
|
53
56
|
export declare function parseWrapperArgumentsFromSchema(toolName: string, inputSchema: unknown, wrapperArgs: string[]): Record<string, unknown>;
|
package/dist/cli/args.js
CHANGED
|
@@ -2,6 +2,9 @@ import fs from "node:fs";
|
|
|
2
2
|
import { CliError } from "./errors.js";
|
|
3
3
|
const RESERVED_SUBCOMMANDS = new Set(["tools", "tool", "help", "version", "install", "uninstall"]);
|
|
4
4
|
const PRIMITIVE_TYPES = new Set(["string", "number", "integer", "boolean"]);
|
|
5
|
+
function isWrapperJsonSchema(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
5
8
|
function parsePositiveInteger(value, flagName) {
|
|
6
9
|
const parsed = Number.parseInt(value, 10);
|
|
7
10
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
@@ -112,6 +115,8 @@ function parseRawArgsMode(args) {
|
|
|
112
115
|
function parseInstallCommand(kind, args) {
|
|
113
116
|
let client = "all";
|
|
114
117
|
let dryRun = false;
|
|
118
|
+
let installGuidanceHook = false;
|
|
119
|
+
let profile;
|
|
115
120
|
for (let i = 0; i < args.length; i += 1) {
|
|
116
121
|
const token = args[i];
|
|
117
122
|
if (token === "--client") {
|
|
@@ -127,9 +132,24 @@ function parseInstallCommand(kind, args) {
|
|
|
127
132
|
dryRun = true;
|
|
128
133
|
continue;
|
|
129
134
|
}
|
|
135
|
+
if (kind === "install" && token === "--install-guidance-hook") {
|
|
136
|
+
installGuidanceHook = true;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (kind === "install" && token === "--profile") {
|
|
140
|
+
const next = args[i + 1];
|
|
141
|
+
if (next !== "default" && next !== "minimal" && next !== "all-text") {
|
|
142
|
+
throw new CliError("E_USAGE", "--profile must be one of: default, minimal, all-text.", 2);
|
|
143
|
+
}
|
|
144
|
+
profile = next;
|
|
145
|
+
i += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
130
148
|
throw new CliError("E_USAGE", `Unknown arguments for ${kind}: ${args.slice(i).join(" ")}`, 2);
|
|
131
149
|
}
|
|
132
|
-
return
|
|
150
|
+
return kind === "install"
|
|
151
|
+
? { kind, client, dryRun, installGuidanceHook, profile }
|
|
152
|
+
: { kind, client, dryRun };
|
|
133
153
|
}
|
|
134
154
|
export function parseCliArgs(argv) {
|
|
135
155
|
const { globals, rest } = parseGlobalOptions(argv);
|
|
@@ -282,7 +302,7 @@ function isPrimitiveEnum(enumValues) {
|
|
|
282
302
|
});
|
|
283
303
|
}
|
|
284
304
|
function unsupportedSchemaReason(schema) {
|
|
285
|
-
if (!schema
|
|
305
|
+
if (!isWrapperJsonSchema(schema)) {
|
|
286
306
|
return "schema is not an object";
|
|
287
307
|
}
|
|
288
308
|
if ("oneOf" in schema) {
|
|
@@ -305,7 +325,7 @@ function unsupportedSchemaReason(schema) {
|
|
|
305
325
|
}
|
|
306
326
|
if (schema.type === "array") {
|
|
307
327
|
const itemSchema = schema.items;
|
|
308
|
-
if (!itemSchema
|
|
328
|
+
if (!isWrapperJsonSchema(itemSchema)) {
|
|
309
329
|
return "array items schema is missing";
|
|
310
330
|
}
|
|
311
331
|
const itemReason = unsupportedSchemaReason(itemSchema);
|
|
@@ -377,7 +397,7 @@ function parsePrimitive(schema, raw) {
|
|
|
377
397
|
}
|
|
378
398
|
}
|
|
379
399
|
export function parseWrapperArgumentsFromSchema(toolName, inputSchema, wrapperArgs) {
|
|
380
|
-
if (!inputSchema
|
|
400
|
+
if (!isWrapperJsonSchema(inputSchema)) {
|
|
381
401
|
throw new CliError("E_SCHEMA_UNSUPPORTED", `${toolName} schema is missing or invalid. Use --args-json/--args-file.`, 2);
|
|
382
402
|
}
|
|
383
403
|
const schema = inputSchema;
|
|
@@ -395,6 +415,9 @@ export function parseWrapperArgumentsFromSchema(toolName, inputSchema, wrapperAr
|
|
|
395
415
|
const normalizedToCanonical = new Map();
|
|
396
416
|
const propertySchemas = new Map();
|
|
397
417
|
for (const [propertyName, propertySchema] of Object.entries(properties)) {
|
|
418
|
+
if (!isWrapperJsonSchema(propertySchema)) {
|
|
419
|
+
throw new CliError("E_SCHEMA_UNSUPPORTED", `${toolName}.${propertyName} uses unsupported schema (schema is not an object). Use --args-json/--args-file.`, 2);
|
|
420
|
+
}
|
|
398
421
|
const unsupportedReason = unsupportedSchemaReason(propertySchema);
|
|
399
422
|
if (unsupportedReason) {
|
|
400
423
|
throw new CliError("E_SCHEMA_UNSUPPORTED", `${toolName}.${propertyName} uses unsupported schema (${unsupportedReason}). Use --args-json/--args-file.`, 2);
|
|
@@ -417,6 +440,9 @@ export function parseWrapperArgumentsFromSchema(toolName, inputSchema, wrapperAr
|
|
|
417
440
|
throw new CliError("E_USAGE", `Unknown flag '${token}' for tool '${toolName}'.`, 2);
|
|
418
441
|
}
|
|
419
442
|
const propertySchema = propertySchemas.get(canonicalName);
|
|
443
|
+
if (!propertySchema) {
|
|
444
|
+
throw new CliError("E_SCHEMA_UNSUPPORTED", `${toolName}.${canonicalName} schema is missing. Use --args-json/--args-file.`, 2);
|
|
445
|
+
}
|
|
420
446
|
if (isJsonFlag) {
|
|
421
447
|
const next = wrapperArgs[i + 1];
|
|
422
448
|
if (!next) {
|
|
@@ -435,6 +461,9 @@ export function parseWrapperArgumentsFromSchema(toolName, inputSchema, wrapperAr
|
|
|
435
461
|
throw new CliError("E_USAGE", `Missing value for ${token}.`, 2);
|
|
436
462
|
}
|
|
437
463
|
const itemSchema = propertySchema.items;
|
|
464
|
+
if (!isWrapperJsonSchema(itemSchema)) {
|
|
465
|
+
throw new CliError("E_SCHEMA_UNSUPPORTED", `${toolName}.${canonicalName} array items schema is missing. Use --args-json/--args-file.`, 2);
|
|
466
|
+
}
|
|
438
467
|
const parsedValue = parsePrimitive(itemSchema, next);
|
|
439
468
|
const existing = parsed[canonicalName];
|
|
440
469
|
if (!Array.isArray(existing)) {
|
package/dist/cli/client.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
2
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
export type ListToolsResult = Awaited<ReturnType<Client["listTools"]>>;
|
|
4
|
+
export type CallToolResult = Awaited<ReturnType<Client["callTool"]>>;
|
|
3
5
|
interface SessionOptions {
|
|
4
6
|
command: string;
|
|
5
7
|
args: string[];
|
|
@@ -15,8 +17,8 @@ export declare class CliMcpSession {
|
|
|
15
17
|
private readonly callTimeoutMs;
|
|
16
18
|
private readonly writeStderr;
|
|
17
19
|
constructor(client: Client, transport: StdioClientTransport, callTimeoutMs: number, writeStderr: (text: string) => void);
|
|
18
|
-
listTools(): Promise<
|
|
19
|
-
callTool(name: string, args: Record<string, unknown>): Promise<
|
|
20
|
+
listTools(): Promise<ListToolsResult>;
|
|
21
|
+
callTool(name: string, args: Record<string, unknown>): Promise<CallToolResult>;
|
|
20
22
|
close(): Promise<void>;
|
|
21
23
|
logProtocolFailure(error: unknown): never;
|
|
22
24
|
wireStderr(): void;
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { type CallToolResult, type ListToolsResult } from "./client.js";
|
|
2
3
|
import { type ManagedRuntimeCommand } from "./install.js";
|
|
3
4
|
interface RunCliOptions {
|
|
4
5
|
writeStdout?: (text: string) => void;
|
|
@@ -24,8 +25,8 @@ interface RunCliOptions {
|
|
|
24
25
|
}) => Promise<CliSession>;
|
|
25
26
|
}
|
|
26
27
|
interface CliSession {
|
|
27
|
-
listTools(): Promise<
|
|
28
|
-
callTool(name: string, args: Record<string, unknown>): Promise<
|
|
28
|
+
listTools(): Promise<ListToolsResult>;
|
|
29
|
+
callTool(name: string, args: Record<string, unknown>): Promise<CallToolResult>;
|
|
29
30
|
close(): Promise<void>;
|
|
30
31
|
}
|
|
31
32
|
export declare function runCli(argv: string[], options?: RunCliOptions): Promise<number>;
|
package/dist/cli/index.js
CHANGED
|
@@ -5,18 +5,20 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { parseCliArgs, parseWrapperArgumentsFromSchema, resolveRawArguments } from "./args.js";
|
|
6
6
|
import { connectCliMcpSession } from "./client.js";
|
|
7
7
|
import { asCliError, CliError } from "./errors.js";
|
|
8
|
-
import { emitError, emitJson,
|
|
8
|
+
import { emitError, emitJson, parseStructuredEnvelope } from "./format.js";
|
|
9
9
|
import { executeInstallCommand } from "./install.js";
|
|
10
10
|
import { verifyManagedPackageInstallability } from "./package-installability.js";
|
|
11
11
|
import { resolveServerEntryPath } from "./resolve-server-entry.js";
|
|
12
|
-
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === "object" && value !== null;
|
|
14
|
+
}
|
|
13
15
|
function firstText(result) {
|
|
14
|
-
const content = result
|
|
16
|
+
const content = isRecord(result) ? result.content : undefined;
|
|
15
17
|
if (!Array.isArray(content)) {
|
|
16
18
|
return null;
|
|
17
19
|
}
|
|
18
|
-
const entry = content.find((item) => item
|
|
19
|
-
return entry?.text
|
|
20
|
+
const entry = content.find((item) => (isRecord(item) && item.type === "text" && typeof item.text === "string"));
|
|
21
|
+
return entry?.text ?? null;
|
|
20
22
|
}
|
|
21
23
|
function readPackageVersion() {
|
|
22
24
|
try {
|
|
@@ -44,7 +46,7 @@ function buildHelpPayload() {
|
|
|
44
46
|
return {
|
|
45
47
|
usage: "satori-cli <command>",
|
|
46
48
|
commands: [
|
|
47
|
-
"install [--client all|codex|claude|opencode] [--dry-run]",
|
|
49
|
+
"install [--client all|codex|claude|opencode] [--profile default|minimal|all-text] [--dry-run] [--install-guidance-hook]",
|
|
48
50
|
"uninstall [--client all|codex|claude|opencode] [--dry-run]",
|
|
49
51
|
"tools list",
|
|
50
52
|
"tool call <toolName> --args-json '<json>'",
|
|
@@ -60,8 +62,10 @@ function buildHelpPayload() {
|
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
function resolveToolSchema(toolsResult, toolName) {
|
|
63
|
-
const tools =
|
|
64
|
-
|
|
65
|
+
const tools = isRecord(toolsResult) && Array.isArray(toolsResult.tools)
|
|
66
|
+
? toolsResult.tools
|
|
67
|
+
: [];
|
|
68
|
+
const tool = tools.find((entry) => (isRecord(entry) && typeof entry.name === "string" && entry.name === toolName));
|
|
65
69
|
if (!tool) {
|
|
66
70
|
throw new CliError("E_USAGE", `Unknown tool '${toolName}'.`, 2);
|
|
67
71
|
}
|
|
@@ -71,39 +75,6 @@ function resolveToolSchema(toolsResult, toolName) {
|
|
|
71
75
|
}
|
|
72
76
|
return schema;
|
|
73
77
|
}
|
|
74
|
-
function sleep(ms) {
|
|
75
|
-
return new Promise((resolve) => {
|
|
76
|
-
const timer = setTimeout(resolve, ms);
|
|
77
|
-
timer.unref();
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
async function pollManageIndexUntilTerminal(pathArg, session, timeoutMs) {
|
|
81
|
-
const startedAt = Date.now();
|
|
82
|
-
const pollIntervalMs = 500;
|
|
83
|
-
while (Date.now() - startedAt < timeoutMs) {
|
|
84
|
-
await sleep(pollIntervalMs);
|
|
85
|
-
const statusResult = await session.callTool("manage_index", {
|
|
86
|
-
action: "status",
|
|
87
|
-
path: pathArg
|
|
88
|
-
});
|
|
89
|
-
const state = inferManageStatusState(statusResult);
|
|
90
|
-
if (state === "indexing" || state === "unknown") {
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
return { result: statusResult, state };
|
|
94
|
-
}
|
|
95
|
-
throw new CliError("E_CALL_TIMEOUT", `Timed out after ${timeoutMs}ms while waiting for manage_index status.`, 3);
|
|
96
|
-
}
|
|
97
|
-
function shouldWaitManageIndex(toolName, args) {
|
|
98
|
-
if (toolName !== "manage_index") {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
const action = args.action;
|
|
102
|
-
if (action !== "create" && action !== "reindex") {
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
return typeof args.path === "string" && args.path.length > 0;
|
|
106
|
-
}
|
|
107
78
|
function summarizeEnvelopeError(writers, envelope) {
|
|
108
79
|
if (!envelope) {
|
|
109
80
|
return;
|
|
@@ -120,7 +91,7 @@ function maybeEmitTextSummary(writers, result) {
|
|
|
120
91
|
}
|
|
121
92
|
}
|
|
122
93
|
function evaluateToolResultForError(result, writers) {
|
|
123
|
-
if (result
|
|
94
|
+
if (isRecord(result) && result.isError === true) {
|
|
124
95
|
const message = firstText(result) || "tool call failed";
|
|
125
96
|
writers.writeStderr(`E_TOOL_ERROR ${message}\n`);
|
|
126
97
|
return 1;
|
|
@@ -132,9 +103,8 @@ function evaluateToolResultForError(result, writers) {
|
|
|
132
103
|
}
|
|
133
104
|
return null;
|
|
134
105
|
}
|
|
135
|
-
async function invokeTool(toolName, args, session,
|
|
106
|
+
async function invokeTool(toolName, args, session, _callTimeoutMs, writers, format) {
|
|
136
107
|
let result = await session.callTool(toolName, args);
|
|
137
|
-
let manageWaitState = null;
|
|
138
108
|
const initialErrorExit = evaluateToolResultForError(result, writers);
|
|
139
109
|
if (initialErrorExit !== null) {
|
|
140
110
|
emitJson(writers, result);
|
|
@@ -143,12 +113,6 @@ async function invokeTool(toolName, args, session, callTimeoutMs, writers, forma
|
|
|
143
113
|
}
|
|
144
114
|
return initialErrorExit;
|
|
145
115
|
}
|
|
146
|
-
if (shouldWaitManageIndex(toolName, args)) {
|
|
147
|
-
const effectiveManagePollTimeoutMs = Math.max(callTimeoutMs, MANAGE_INDEX_MIN_POLL_TIMEOUT_MS);
|
|
148
|
-
const polled = await pollManageIndexUntilTerminal(args.path, session, effectiveManagePollTimeoutMs);
|
|
149
|
-
result = polled.result;
|
|
150
|
-
manageWaitState = polled.state;
|
|
151
|
-
}
|
|
152
116
|
emitJson(writers, result);
|
|
153
117
|
if (format === "text") {
|
|
154
118
|
maybeEmitTextSummary(writers, result);
|
|
@@ -157,10 +121,6 @@ async function invokeTool(toolName, args, session, callTimeoutMs, writers, forma
|
|
|
157
121
|
if (finalErrorExit !== null) {
|
|
158
122
|
return finalErrorExit;
|
|
159
123
|
}
|
|
160
|
-
if (manageWaitState === "indexfailed" || manageWaitState === "requires_reindex" || manageWaitState === "not_indexed") {
|
|
161
|
-
emitError(writers, "E_TOOL_ERROR", `manage_index terminal state=${manageWaitState}`);
|
|
162
|
-
return 1;
|
|
163
|
-
}
|
|
164
124
|
return 0;
|
|
165
125
|
}
|
|
166
126
|
export async function runCli(argv, options = {}) {
|
package/dist/cli/install.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import type { InstallClient } from "./args.js";
|
|
2
|
+
import type { InstallClient, InstallProfile } from "./args.js";
|
|
3
3
|
type ExecFileSyncLike = typeof execFileSync;
|
|
4
4
|
type ClientName = Exclude<InstallClient, "all">;
|
|
5
5
|
export interface ManagedRuntimeCommand {
|
|
@@ -10,9 +10,12 @@ export interface InstallCommandInput {
|
|
|
10
10
|
kind: "install" | "uninstall";
|
|
11
11
|
client: InstallClient;
|
|
12
12
|
dryRun: boolean;
|
|
13
|
+
installGuidanceHook?: boolean;
|
|
14
|
+
profile?: InstallProfile;
|
|
13
15
|
}
|
|
14
16
|
export interface InstallCommandOptions {
|
|
15
17
|
homeDir?: string;
|
|
18
|
+
repoDir?: string;
|
|
16
19
|
packageSpecifier?: string;
|
|
17
20
|
skillAssetRoot?: string;
|
|
18
21
|
runtimeCommand?: ManagedRuntimeCommand;
|
|
@@ -33,6 +36,9 @@ export interface InstallCommandResult {
|
|
|
33
36
|
action: "install" | "uninstall";
|
|
34
37
|
client: InstallClient;
|
|
35
38
|
dryRun: boolean;
|
|
39
|
+
profile?: InstallProfile;
|
|
40
|
+
profileConfigPath?: string;
|
|
41
|
+
profileConfigChanged?: boolean;
|
|
36
42
|
results: ClientInstallResult[];
|
|
37
43
|
}
|
|
38
44
|
export declare function executeInstallCommand(command: InstallCommandInput, options?: InstallCommandOptions): InstallCommandResult;
|