@cueai/omni-reader-mcp 1.5.4 → 1.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 CHANGED
@@ -48,31 +48,25 @@ agent workspace remains the default allowed scope.
48
48
  For a local file, Bridge requests a Cube parse grant and uses the returned controlled
49
49
  granted-data-plane URL to upload the file and stream the result. A `BRIDGE_UPGRADE_REQUIRED`
50
50
  error means the running Bridge release is not accepted for direct local-file parsing —
51
- install `@cueai/omni-reader-mcp@1.5.4` using the official setup instructions, then retry the
51
+ install `@cueai/omni-reader-mcp@1.6.0` using the official setup instructions, then retry the
52
52
  same parse. Always run `doctor --json` first when diagnosing a parse failure.
53
53
 
54
54
  ## Free credits
55
55
 
56
- As of 2026-08-14:
56
+ The server-side onboarding policy is the authority for the current first-registration gift
57
+ and daily grant. When reachable, `doctor` reports those live policy values; when unavailable,
58
+ it reports that status instead of copying a stale number.
57
59
 
58
- - every account receives 10 free credits daily roughly 150 pages of ordinary documents,
59
- 75 pages of scanned images or charts, 30 minutes of audio, or 4 minutes of video;
60
- - new accounts receive a one-time 50-credit gift when obtaining `CUE_API_KEY` (60 credits
61
- available on day one, including the daily grant);
62
- - inviting a new user who registers gives both the inviter and the invitee 50 credits, with
63
- no invite limit; when an invited user subscribes, the inviter additionally receives 10% of
64
- the invitee's first-month credit quota as a bonus.
65
-
66
- When available, the `doctor` output reports the live first-registration gift and daily grant;
67
- referral rules and conversion ratios follow the server-side onboarding policy. If live values
68
- differ from the numbers above, report the live values.
60
+ Bridge does not publish static document-page or media-duration conversions. Rates can change,
61
+ and Phase 1 has no public rate response carrying an `effective_at` timestamp. Use only current
62
+ authoritative billing/policy facts rather than estimating a charge from copied arithmetic.
69
63
 
70
64
  ## Installation and configuration
71
65
 
72
66
  Always use an audited exact version, never an implicit `latest`:
73
67
 
74
68
  ```sh
75
- npx -y @cueai/omni-reader-mcp@1.5.4 setup
69
+ npx -y @cueai/omni-reader-mcp@1.6.0 setup
76
70
  ```
77
71
 
78
72
  The interactive setup supports Hermes, Cursor, Claude Desktop, and generic stdio
@@ -80,9 +74,9 @@ configuration. Non-interactive installation uses the same argument parsing and w
80
74
  logic:
81
75
 
82
76
  ```sh
83
- npx -y @cueai/omni-reader-mcp@1.5.4 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
84
- npx -y @cueai/omni-reader-mcp@1.5.4 setup --client cursor --add-root /absolute/minimum/root --yes --json
85
- npx -y @cueai/omni-reader-mcp@1.5.4 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
77
+ npx -y @cueai/omni-reader-mcp@1.6.0 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
78
+ npx -y @cueai/omni-reader-mcp@1.6.0 setup --client cursor --add-root /absolute/minimum/root --yes --json
79
+ npx -y @cueai/omni-reader-mcp@1.6.0 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
86
80
  ```
87
81
 
88
82
  When an agent or script runs under a pty (stdin is still a TTY), declare non-interactive
@@ -90,7 +84,7 @@ mode explicitly with `--headless` (alias `--non-interactive`): no `--yes` is req
90
84
  stdin is never read:
91
85
 
92
86
  ```sh
93
- npx -y @cueai/omni-reader-mcp@1.5.4 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
87
+ npx -y @cueai/omni-reader-mcp@1.6.0 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
94
88
  ```
95
89
 
96
90
  ## Windows configuration
@@ -109,14 +103,14 @@ automatically; the second is the most robust when you configure by hand.
109
103
  1. **`cmd` + `npx` (what `setup` writes on Windows)** — `cmd.exe` is a real executable,
110
104
  so a client's no-shell spawn works:
111
105
  ```json
112
- { "command": "cmd", "args": ["/d", "/c", "npx", "-y", "@cueai/omni-reader-mcp@1.5.4"] }
106
+ { "command": "cmd", "args": ["/d", "/c", "npx", "-y", "@cueai/omni-reader-mcp@1.6.0"] }
113
107
  ```
114
108
  2. **`node` + the package entry (most robust)** — skips `npx` entirely; the absolute
115
109
  path must be a stable install you control:
116
110
  ```json
117
111
  { "command": "node", "args": ["C:\\path\\to\\node_modules\\@cueai\\omni-reader-mcp\\dist\\index.js"] }
118
112
  ```
119
- A deliberate local `npm install @cueai/omni-reader-mcp@1.5.4` gives you that stable
113
+ A deliberate local `npm install @cueai/omni-reader-mcp@1.6.0` gives you that stable
120
114
  path. Do not point the entry at an `_npx` cache directory — its hash subdirectory
121
115
  changes between installs.
122
116
  3. **Never use a session/timestamp directory** — such paths are cleaned up, and the
@@ -165,6 +159,31 @@ Agents pass the user-provided source string directly to `parse(source)`:
165
159
  - Prefer the existing `operation_id` for status recovery; never re-upload or duplicate a
166
160
  parse for a timeout.
167
161
 
162
+ Use the result contract in this order:
163
+
164
+ ```text
165
+ Answer directly → use inline text when present; otherwise read_result
166
+ Find one section → read_outline, then pass its cursor to read_result
167
+ Read all content → read_result until next_cursor is absent
168
+ Deliver a file → save_result
169
+ ```
170
+
171
+ Outline navigation does not require `save_result`: call `read_outline`, then pass a selected
172
+ node cursor directly to `read_result`. `result_delivery="artifact"` is appropriate for saving, section navigation, multiple documents, or strict context control; omitted or `auto` preserves
173
+ inline delivery when possible. For multiple sources, use bounded concurrent independent `parse` calls and keep each operation/result handle separate. There is no `batch_parse` tool,
174
+ and Bridge does not invent one universal concurrency number before client evidence exists.
175
+
176
+ Client capabilities are classified exactly as `supported | unsupported | unknown`; they are
177
+ discovered from the actual initialize exchange, client documentation/settings, a local
178
+ non-billable harness, or explicit redacted config facts—not guessed from another MCP host.
179
+ Safe fallbacks are:
180
+
181
+ - Tasks unknown → ordinary `parse` / `get_parse_status` polling.
182
+ - Roots unknown → process cwd and explicitly configured roots only.
183
+ - Host timeout unknown → retain the bounded 20-second status wait.
184
+ - Process cwd versus active workspace unknown → do not widen authorization; use cwd and
185
+ explicit roots until the client provides evidence.
186
+
168
187
  The public tools are fixed:
169
188
 
170
189
  - `parse`
@@ -261,37 +280,50 @@ satisfies the constraints.
261
280
  ## Commands
262
281
 
263
282
  ```sh
264
- npx -y @cueai/omni-reader-mcp@1.5.4 doctor
265
- npx -y @cueai/omni-reader-mcp@1.5.4 doctor --json
266
- npx -y @cueai/omni-reader-mcp@1.5.4 clean
267
- npx -y @cueai/omni-reader-mcp@1.5.4 uninstall --yes --json
283
+ npx -y @cueai/omni-reader-mcp@1.6.0 doctor
284
+ npx -y @cueai/omni-reader-mcp@1.6.0 doctor --json
285
+ npx -y @cueai/omni-reader-mcp@1.6.0 clean
286
+ npx -y @cueai/omni-reader-mcp@1.6.0 uninstall --yes --json
268
287
  ```
269
288
 
270
289
  Running the pinned version without a command starts the stdio MCP server:
271
290
 
272
291
  ```sh
273
- npx -y @cueai/omni-reader-mcp@1.5.4
292
+ npx -y @cueai/omni-reader-mcp@1.6.0
293
+ ```
294
+
295
+ Default `doctor --json` reports `inspection_scope: "current_process_env"`: its Key and
296
+ allowed-root facts describe the doctor process, not a separately running agent child. To
297
+ inspect one caller-selected JSON entry without scanning or writing configuration, use:
298
+
299
+ ```sh
300
+ npx -y @cueai/omni-reader-mcp@1.6.0 doctor --json --config-path /absolute/client-config.json --server-name omni-reader
274
301
  ```
275
302
 
276
- `doctor --json` returns package/npm/client adapter, Key present/absent, allowed-root
277
- safety status, the authenticated Cube control/configuration facts, artifacts, cache
278
- mode, onboarding, and reload status; it never prints the Key, private source paths,
279
- or content. The `endpoints.direct_upload` field is a fact about the release, not a
280
- probe: the granted data plane is reached only through a Cube grant and is never
303
+ The optional `explicit_config` block reports only closed redacted facts: entry/transport
304
+ shape, command basename/category, exact package version when derivable, Key configuration
305
+ shape, and allowed-root count/safety. It never resolves or prints a Key, uses a configured
306
+ Key for a network call, prints the config path, scans unrelated entries, or claims that the
307
+ running child process can use the selected entry.
308
+
309
+ `doctor --json` also returns package/npm/client adapter, authenticated Cube
310
+ control/configuration facts, artifacts, cache mode, onboarding, and reload status. The
311
+ `endpoints.direct_upload` field is a fact about the release, not a probe: the granted data
312
+ plane is reached only through a Cube grant and is never
281
313
  definitively "available" until a real local-file parse validates the route.
282
314
 
283
315
 
284
316
  ## Network diagnostics
285
317
 
286
- Run `npx -y @cueai/omni-reader-mcp@1.5.4 doctor --json` first. Diagnose failures by
318
+ Run `npx -y @cueai/omni-reader-mcp@1.6.0 doctor --json` first. Diagnose failures by
287
319
  stage instead of guessing an endpoint:
288
320
 
289
- | Code | Stage | Meaning |
321
+ | Failure | Stage | Meaning |
290
322
  |---|---|---|
291
323
  | `CUBE_UNAVAILABLE` | before upload | the control plane could not create a parse grant |
292
324
  | `PRINCIPAL_CONCURRENCY_LIMIT` | before upload | the principal already has the maximum active parses; wait for `retry_after`, then retry the same parse |
293
- | `BRIDGE_UPGRADE_REQUIRED` | before upload | this Bridge release is too old; install `@cueai/omni-reader-mcp@1.5.4` via the official setup, then retry the same parse |
294
- | `IIIS_UNAVAILABLE` | after grant creation | the granted upload stage could not complete |
325
+ | `BRIDGE_UPGRADE_REQUIRED` | before upload | this Bridge release is too old; install `@cueai/omni-reader-mcp@1.6.0` via the official setup, then retry the same parse |
326
+ | a secure upload-stage failure | after grant creation | the file was granted, but the secure upload stage did not complete |
295
327
  | `CUBE_PROTOCOL_ERROR` | control-plane response | the response did not match the required contract |
296
328
  | `MCP error -32000: Connection closed` | before Bridge starts | the client launcher shape is invalid; see Windows configuration |
297
329
 
@@ -309,15 +341,16 @@ the same way from every client.
309
341
 
310
342
  ## Uninstall and rollback
311
343
 
312
- `uninstall --yes --json` removes only a trusted 1.5.3 or 1.5.4 Bridge entry, plus
344
+ `uninstall --yes --json` removes only a trusted 1.5.5 or 1.6.0 Bridge entry, plus
313
345
  the exact broken bare-`npx` Windows entry written by 1.5.1. When a matching trusted
314
346
  backup exists, it restores the original URL-only `omni-reader` entry. Uninstall never
315
347
  deletes user source files and never silently removes unexpired local results.
316
348
 
317
- To roll back from 1.5.4:
349
+ To roll back from 1.6.0 to 1.5.5:
318
350
 
319
- 1. stop recommending or installing that version;
351
+ 1. stop recommending or installing 1.6.0;
320
352
  2. run `uninstall --yes --json` to restore the trusted URL-only entry;
321
- 3. for already-uploaded operations, continue using status recovery so settlement and
353
+ 3. if the local facade is still required, run `npx -y @cueai/omni-reader-mcp@1.5.5 setup`;
354
+ 4. for already-uploaded operations, continue using status recovery so settlement and
322
355
  cleanup complete;
323
- 4. keep local results, or have the user explicitly run `discard_result` / `clean`.
356
+ 5. keep local results, or have the user explicitly run `discard_result` / `clean`.
@@ -109,8 +109,10 @@ export declare class ArtifactStore {
109
109
  static open(options?: ArtifactStoreOptions): Promise<ArtifactStore>;
110
110
  get rootDirectory(): string;
111
111
  createRetention(): LocalResultRetention;
112
+ preflightWrite(): Promise<void>;
112
113
  read(resultId: string, cursor?: string, maxBytes?: number): Promise<ArtifactReadResult>;
113
114
  readOutline(resultId: string): Promise<OutlineResult>;
115
+ mintReadCursor(resultId: string, byteOffset: number): Promise<string>;
114
116
  mintOutlineCursor(resultId: string, byteOffset: number): Promise<string>;
115
117
  exportResult(resultId: string): Promise<{
116
118
  path: string;
@@ -5,7 +5,7 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { z } from "zod";
7
7
  import { ARTIFACT_TTL_MS, INLINE_RESULT_MAX_BYTES, RESULT_CHUNK_MAX_BYTES } from "./constants.js";
8
- import { CursorCodec } from "./cursor.js";
8
+ import { CursorCodec, CursorCodecError } from "./cursor.js";
9
9
  import { OmniBridgeError } from "./errors.js";
10
10
  import { extractOutline } from "./outline.js";
11
11
  import { GROUNDING_SCHEMA_VERSION, RESULT_BUNDLE_PROTOCOL_VERSION } from "./protocol.js";
@@ -28,11 +28,11 @@ function artifactError(code, message, retryable = false) {
28
28
  return new OmniBridgeError({
29
29
  code,
30
30
  message,
31
- operationCreated: true,
32
- fileUploaded: true,
33
- parserStarted: true,
34
- billed: true,
35
- contentReleased: true,
31
+ operationCreated: false,
32
+ fileUploaded: false,
33
+ parserStarted: false,
34
+ billed: false,
35
+ contentReleased: false,
36
36
  retryable,
37
37
  });
38
38
  }
@@ -48,6 +48,17 @@ function cacheError(code, message) {
48
48
  retryable: false,
49
49
  });
50
50
  }
51
+ function decodeCursor(action) {
52
+ try {
53
+ return action();
54
+ }
55
+ catch (error) {
56
+ if (error instanceof CursorCodecError) {
57
+ throw artifactError(error.code, error.message);
58
+ }
59
+ throw error;
60
+ }
61
+ }
51
62
  function errno(error, code) {
52
63
  return error instanceof Error && error.code === code;
53
64
  }
@@ -475,6 +486,27 @@ export class ArtifactStore {
475
486
  this.#requireOpen();
476
487
  return new LocalResultRetention(this);
477
488
  }
489
+ async preflightWrite() {
490
+ this.#requireOpen();
491
+ const probePath = path.join(this.#resultsDirectory, `.tmp-preflight-${randomBytes(18).toString("base64url")}`);
492
+ let handle;
493
+ try {
494
+ handle = await open(probePath, "wx", 0o600);
495
+ await handle.writeFile("omni-artifact-preflight-v1\n", "utf8");
496
+ await handle.sync();
497
+ await handle.close();
498
+ handle = undefined;
499
+ await removeIfPresent(probePath);
500
+ await syncDirectory(this.#resultsDirectory);
501
+ }
502
+ catch (error) {
503
+ await handle?.close().catch(() => undefined);
504
+ await removeIfPresent(probePath).catch(() => false);
505
+ if (error instanceof OmniBridgeError)
506
+ throw error;
507
+ throw artifactError("ARTIFACT_PREFLIGHT_FAILED", "The local result cache is not writable.", true);
508
+ }
509
+ }
478
510
  async read(resultId, cursor, maxBytes = RESULT_CHUNK_MAX_BYTES) {
479
511
  this.#requireOpen();
480
512
  let metadata;
@@ -496,7 +528,7 @@ export class ArtifactStore {
496
528
  }
497
529
  let offset = 0;
498
530
  if (cursor !== undefined) {
499
- const payload = this.#cursor.decode(cursor);
531
+ const payload = decodeCursor(() => this.#cursor.decode(cursor));
500
532
  if (payload.resultId !== resultId || payload.expiresAt !== metadata.expiresAt) {
501
533
  throw artifactError("RESULT_CURSOR_MISMATCH", "The result cursor does not match this artifact.");
502
534
  }
@@ -559,7 +591,7 @@ export class ArtifactStore {
559
591
  }
560
592
  return extractOutline(fullText);
561
593
  }
562
- async mintOutlineCursor(resultId, byteOffset) {
594
+ async mintReadCursor(resultId, byteOffset) {
563
595
  this.#requireOpen();
564
596
  const metadata = await this.#loadMetadata(resultId);
565
597
  if (Date.parse(metadata.expiresAt) <= this.#now().getTime()) {
@@ -567,10 +599,13 @@ export class ArtifactStore {
567
599
  throw artifactError("RESULT_EXPIRED", "The local result artifact has expired.");
568
600
  }
569
601
  if (!Number.isSafeInteger(byteOffset) || byteOffset < 0 || byteOffset > metadata.resultBytes) {
570
- throw artifactError("INVALID_RESULT_CURSOR", "The outline node offset is invalid.");
602
+ throw artifactError("INVALID_RESULT_CURSOR", "The result cursor offset is invalid.");
571
603
  }
572
604
  return this.#cursor.encode({ resultId, offset: byteOffset, expiresAt: metadata.expiresAt });
573
605
  }
606
+ async mintOutlineCursor(resultId, byteOffset) {
607
+ return this.mintReadCursor(resultId, byteOffset);
608
+ }
574
609
  // The full content text in one read, regardless of storage shape (a flat
575
610
  // v1 artifact, or a v2 bundle's "content" part). save_result's only
576
611
  // consumer: read_result's chunked/cursor contract stays the single way an
@@ -657,7 +692,7 @@ export class ArtifactStore {
657
692
  // against the actual bytes before any chunk is returned.
658
693
  async readBundlePart(resultId, cursor, maxBytes = RESULT_CHUNK_MAX_BYTES) {
659
694
  this.#requireOpen();
660
- const payload = this.#cursor.decodeBundle(cursor);
695
+ const payload = decodeCursor(() => this.#cursor.decodeBundle(cursor));
661
696
  if (payload.resultId !== resultId) {
662
697
  throw artifactError("RESULT_CURSOR_MISMATCH", "The result cursor does not match this artifact.");
663
698
  }
@@ -7,7 +7,7 @@ const PACKAGE_SPEC = `@cueai/omni-reader-mcp@${BRIDGE_RELEASE_VERSION}`;
7
7
  // The release that the current version is a trusted upgrade from: normal upgrade,
8
8
  // uninstall, and rollback recognize exactly {previous, current}. Bump this to the
9
9
  // version we just published when BRIDGE_RELEASE_VERSION advances.
10
- const PREVIOUS_RELEASE_VERSION = "1.5.3";
10
+ const PREVIOUS_RELEASE_VERSION = "1.5.5";
11
11
  const PREVIOUS_PACKAGE_SPEC = `@cueai/omni-reader-mcp@${PREVIOUS_RELEASE_VERSION}`;
12
12
  // Preserve the one evidenced two-release migration exception: 1.5.1 setup wrote a
13
13
  // bare `npx` launcher on Windows, so later setup/uninstall versions must still be able
@@ -641,7 +641,7 @@ export function expectedBridgeVersion(target, value, platform) {
641
641
  }
642
642
  function isLegacyBridgeEntry(target, value, platform) {
643
643
  // Defer the entire invocation-shape decision to the single platform policy:
644
- // exact pinned {1.5.3 previous, 1.5.4 current} plus the exact Windows 1.5.1
644
+ // exact pinned {1.5.5 previous, 1.6.0 current} plus the exact Windows 1.5.1
645
645
  // bare-npx migration source. The historical platform-agnostic acceptance of a
646
646
  // bare unpinned `npx -y @cueai/omni-reader-mcp` is removed: it widened the
647
647
  // frozen trust set to a floating latest on every OS, which expectedBridgeVersion
@@ -7,18 +7,22 @@ export interface SetupArguments {
7
7
  readonly headless: boolean;
8
8
  readonly json: boolean;
9
9
  }
10
+ export interface DoctorArguments {
11
+ readonly json: boolean;
12
+ readonly configPath?: string;
13
+ readonly serverName?: string;
14
+ }
10
15
  export type ParsedCommand = {
11
16
  readonly command: "server";
12
17
  } | {
13
18
  readonly command: "help";
14
19
  } | {
15
20
  readonly command: "version";
16
- } | {
21
+ } | ({
22
+ readonly command: "doctor";
23
+ } & DoctorArguments) | {
17
24
  readonly command: "setup";
18
25
  readonly arguments: SetupArguments;
19
- } | {
20
- readonly command: "doctor";
21
- readonly json: boolean;
22
26
  } | {
23
27
  readonly command: "clean";
24
28
  } | {
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { PathNormalizationError, normalizePlatformPath, } from "../path-normalization.js";
2
3
  import { parseAgentTarget } from "./agent-config.js";
3
4
  export class CliUsageError extends Error {
4
5
  exitCode = 2;
@@ -10,6 +11,26 @@ export class CliUsageError extends Error {
10
11
  function pathsFor(platform) {
11
12
  return platform === "win32" ? path.win32 : path.posix;
12
13
  }
14
+ function normalizeAbsoluteCliPath(raw, flag, platform) {
15
+ if (raw.length === 0 || raw.includes("\0")) {
16
+ throw new CliUsageError(`${flag} requires one valid absolute path.`);
17
+ }
18
+ let normalizedInput;
19
+ try {
20
+ normalizedInput = normalizePlatformPath(raw, platform);
21
+ }
22
+ catch (error) {
23
+ if (error instanceof PathNormalizationError) {
24
+ throw new CliUsageError(error.message);
25
+ }
26
+ throw error;
27
+ }
28
+ const paths = pathsFor(platform);
29
+ if (!paths.isAbsolute(normalizedInput)) {
30
+ throw new CliUsageError(`${flag} requires an absolute path.`);
31
+ }
32
+ return paths.normalize(normalizedInput);
33
+ }
13
34
  function requireNoArguments(command, values) {
14
35
  if (values.length > 0)
15
36
  throw new CliUsageError(`${command} does not accept arguments.`);
@@ -35,7 +56,6 @@ function parseSetupArguments(values, platform) {
35
56
  let headless = false;
36
57
  let json = false;
37
58
  const seen = new Set();
38
- const paths = pathsFor(platform);
39
59
  for (let index = 0; index < values.length; index += 1) {
40
60
  const flag = values[index];
41
61
  if (!["--client", "--allowed-root", "--add-root", "--yes", "--headless", "--non-interactive", "--json"].includes(flag)) {
@@ -71,10 +91,7 @@ function parseSetupArguments(values, platform) {
71
91
  client = parsed;
72
92
  continue;
73
93
  }
74
- if (!paths.isAbsolute(raw)) {
75
- throw new CliUsageError(`${flag} requires an absolute path.`);
76
- }
77
- const normalized = paths.normalize(raw);
94
+ const normalized = normalizeAbsoluteCliPath(raw, flag, platform);
78
95
  if (flag === "--allowed-root")
79
96
  allowedRoot = normalized;
80
97
  if (flag === "--add-root")
@@ -92,6 +109,45 @@ function parseSetupArguments(values, platform) {
92
109
  json,
93
110
  };
94
111
  }
112
+ function parseDoctorArguments(values, platform) {
113
+ let json = false;
114
+ let configPath;
115
+ let serverName;
116
+ const seen = new Set();
117
+ for (let index = 0; index < values.length; index += 1) {
118
+ const flag = values[index];
119
+ if (!["--json", "--config-path", "--server-name"].includes(flag)) {
120
+ throw new CliUsageError(`Unknown doctor flag: ${flag}`);
121
+ }
122
+ if (seen.has(flag))
123
+ throw new CliUsageError(`Duplicate doctor flag: ${flag}`);
124
+ seen.add(flag);
125
+ if (flag === "--json") {
126
+ json = true;
127
+ continue;
128
+ }
129
+ const raw = values[index + 1];
130
+ if (raw === undefined || raw.startsWith("--")) {
131
+ throw new CliUsageError(`${flag} requires a value.`);
132
+ }
133
+ index += 1;
134
+ if (flag === "--config-path") {
135
+ configPath = normalizeAbsoluteCliPath(raw, flag, platform);
136
+ continue;
137
+ }
138
+ if (raw.length === 0 || raw.includes("\0")) {
139
+ throw new CliUsageError("--server-name requires one valid entry name.");
140
+ }
141
+ serverName = raw;
142
+ }
143
+ if ((configPath === undefined) !== (serverName === undefined)) {
144
+ throw new CliUsageError("--config-path and --server-name must be provided together.");
145
+ }
146
+ return {
147
+ json,
148
+ ...(configPath === undefined ? {} : { configPath, serverName: serverName }),
149
+ };
150
+ }
95
151
  export function parseCliArguments(argv, platform = process.platform) {
96
152
  if (argv.length === 0)
97
153
  return { command: "server" };
@@ -108,8 +164,7 @@ export function parseCliArguments(argv, platform = process.platform) {
108
164
  return { command: "setup", arguments: parseSetupArguments(values, platform) };
109
165
  }
110
166
  if (command === "doctor") {
111
- const flags = parseBooleanFlags("doctor", values, new Set(["--json"]));
112
- return { command: "doctor", json: flags.has("--json") };
167
+ return { command: "doctor", ...parseDoctorArguments(values, platform) };
113
168
  }
114
169
  if (command === "clean") {
115
170
  requireNoArguments("clean", values);
@@ -0,0 +1,59 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ export interface ConfigInspectionHandle {
3
+ stat(options: {
4
+ bigint: true;
5
+ }): Promise<BigIntStats>;
6
+ read(buffer: Buffer, offset: number, length: number, position: number): Promise<{
7
+ bytesRead: number;
8
+ buffer: Buffer;
9
+ }>;
10
+ close(): Promise<void>;
11
+ }
12
+ export interface ConfigInspectionFileSystem {
13
+ lstat(candidate: string): Promise<BigIntStats>;
14
+ open(candidate: string, flags: number): Promise<ConfigInspectionHandle>;
15
+ }
16
+ export declare class ConfigInspectionError extends Error {
17
+ readonly code: string;
18
+ readonly constraints?: {
19
+ readonly max_bytes: number;
20
+ };
21
+ constructor(code: string, message: string, constraints?: {
22
+ readonly max_bytes: number;
23
+ });
24
+ }
25
+ export type ConfigEntryStatus = "present" | "missing" | "invalid";
26
+ export type ConfigTransportShape = "local_command" | "remote_url" | "unknown";
27
+ export type ConfiguredApiKeyStatus = "literal_present" | "reference_present" | "absent" | "unknown";
28
+ export type AllowedRootSafety = "safe" | "unsafe" | "unknown";
29
+ export type CommandCategory = "package_runner" | "shell" | "runtime" | "executable";
30
+ export interface ExplicitConfigInspection {
31
+ readonly inspection_scope: "explicit_config";
32
+ readonly entry: {
33
+ readonly status: ConfigEntryStatus;
34
+ };
35
+ readonly transport: {
36
+ readonly shape: ConfigTransportShape;
37
+ readonly command?: {
38
+ readonly basename: string;
39
+ readonly category: CommandCategory;
40
+ };
41
+ };
42
+ readonly package_version?: string;
43
+ readonly api_key: {
44
+ readonly status: ConfiguredApiKeyStatus;
45
+ };
46
+ readonly allowed_roots: {
47
+ readonly count: number;
48
+ readonly safety: Exclude<AllowedRootSafety, "unknown">;
49
+ } | {
50
+ readonly safety: "unknown";
51
+ };
52
+ }
53
+ export interface InspectExplicitConfigOptions {
54
+ readonly configPath: string;
55
+ readonly serverName: string;
56
+ readonly platform?: NodeJS.Platform;
57
+ readonly fileSystem?: ConfigInspectionFileSystem;
58
+ }
59
+ export declare function inspectExplicitConfig(options: InspectExplicitConfigOptions): Promise<ExplicitConfigInspection>;