@levr-one/cli 0.9.11 → 0.9.13
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/dist/{addHandler-Brr4ndcR.js → addHandler-BlZQkDtP.js} +837 -49
- package/dist/cli.js +12 -12
- package/dist/{importHandler-DIQ73h08.js → importHandler-C3w-uUQ_.js} +2 -2
- package/dist/{listHandler-BJxEIkd6.js → listHandler-CKsZ1_8V.js} +1 -1
- package/dist/{loginHandler-CORhO7SH.js → loginHandler-B7ggggGI.js} +2 -2
- package/dist/{pushHandler-C0U3Q8Tp.js → pushHandler-C7s-Ki18.js} +2 -2
- package/dist/{resolve-workspace-DCnQrM2H.js → resolve-workspace-febNMGo2.js} +1 -1
- package/dist/{sdk-client-DXyc1fWZ.js → sdk-client-QtfUKoae.js} +3 -0
- package/dist/{selectHandler-Asn9ElGw.js → selectHandler-CdQ4t9Zd.js} +1 -1
- package/dist/{statusHandler-DC1bfcrN.js → statusHandler-D5n1x-lz.js} +1 -1
- package/package.json +2 -1
|
@@ -1,23 +1,119 @@
|
|
|
1
1
|
import { getApiUrl } from "./env-CHeKHu5S.js";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { delimiter, dirname, join, resolve } from "node:path";
|
|
2
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, delimiter, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
5
|
+
import { applyEdits, modify, parse, printParseErrorCode } from "jsonc-parser";
|
|
6
|
+
import { TomlError, parse as parse$1 } from "smol-toml";
|
|
6
7
|
import { execFileSync } from "node:child_process";
|
|
7
8
|
|
|
8
|
-
//#region ../mcp-harnesses/dist/
|
|
9
|
+
//#region ../mcp-harnesses/dist/format/toml-render.js
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* Value → TOML text, ISOMORPHIC (browser + Node), dependency-free.
|
|
12
|
+
*
|
|
13
|
+
* Two consumers, one implementation (ENG-5264 D5, audit C1):
|
|
14
|
+
* - `buildHarnessConfig` in the isomorphic catalog renders the pasteable
|
|
15
|
+
* snippet a TOML client's card shows — so it must run in the client SPA,
|
|
16
|
+
* which forbids `node:` imports and the `/node/` subpath (catalog.test.ts
|
|
17
|
+
* isomorphic guard).
|
|
18
|
+
* - The node `tomlAdapter` renders the body of the table it writes into the
|
|
19
|
+
* user's config file.
|
|
11
20
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* MUST stay free of Node built-ins (`node:fs`, `node:os`, `node:path`) so the
|
|
15
|
-
* client SPA can import it without pulling `node:fs` into the bundle. Detection
|
|
16
|
-
* and config-write live in the `@levr/mcp-harnesses/node` subpath.
|
|
21
|
+
* Written once here so the snippet a user pastes by hand and the bytes the
|
|
22
|
+
* installer writes can never drift apart.
|
|
17
23
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
24
|
+
* Scope is deliberately what `buildServerEntry` produces: string, number,
|
|
25
|
+
* boolean, arrays of those, and nested plain objects. Anything else throws —
|
|
26
|
+
* a TOML file is never the place to discover a serializer guessed.
|
|
27
|
+
*/
|
|
28
|
+
/** Keys TOML lets us write without quotes. */
|
|
29
|
+
const BARE_KEY = /^[A-Za-z0-9_-]+$/;
|
|
30
|
+
/** A TOML key, quoted only when it has to be. */
|
|
31
|
+
function renderTomlKey(key) {
|
|
32
|
+
return BARE_KEY.test(key) ? key : renderTomlString(key);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A TOML basic string with every escape TOML requires: backslash, double
|
|
36
|
+
* quote, the C0 controls, and DEL. Everything else (including non-ASCII) is
|
|
37
|
+
* written verbatim, which TOML permits in basic strings.
|
|
38
|
+
*/
|
|
39
|
+
function renderTomlString(value) {
|
|
40
|
+
let out = "\"";
|
|
41
|
+
for (const ch of value) {
|
|
42
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
43
|
+
if (ch === "\"") out += "\\\"";
|
|
44
|
+
else if (ch === "\\") out += "\\\\";
|
|
45
|
+
else if (ch === "\n") out += "\\n";
|
|
46
|
+
else if (ch === "\r") out += "\\r";
|
|
47
|
+
else if (ch === " ") out += "\\t";
|
|
48
|
+
else if (ch === "\b") out += "\\b";
|
|
49
|
+
else if (ch === "\f") out += "\\f";
|
|
50
|
+
else if (code < 32 || code === 127) out += `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
51
|
+
else out += ch;
|
|
52
|
+
}
|
|
53
|
+
return out + "\"";
|
|
54
|
+
}
|
|
55
|
+
/** A scalar or array value on the right-hand side of `key = …`. */
|
|
56
|
+
function renderTomlValue(value) {
|
|
57
|
+
if (typeof value === "string") return renderTomlString(value);
|
|
58
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
59
|
+
if (typeof value === "number") {
|
|
60
|
+
if (!Number.isFinite(value)) throw new Error(`cannot render non-finite number ${String(value)} as TOML`);
|
|
61
|
+
return String(value);
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
if (value.length === 0) return "[]";
|
|
65
|
+
return `[ ${value.map((v) => renderTomlValue(v)).join(", ")} ]`;
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`cannot render a ${value === null ? "null" : typeof value} as a TOML value`);
|
|
68
|
+
}
|
|
69
|
+
function isPlainObject(value) {
|
|
70
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The `key = value` lines for one table body, in the order the object's keys
|
|
74
|
+
* were declared, followed by sub-tables for any nested objects. Nested objects
|
|
75
|
+
* become `[path.key]` sub-tables rather than inline tables so the output stays
|
|
76
|
+
* one key per line — the shape both Codex and Grok Build write themselves.
|
|
77
|
+
*
|
|
78
|
+
* `pathPrefix` is the table this body belongs to, needed to name sub-tables.
|
|
79
|
+
* Returns lines WITHOUT line terminators; the caller picks `\n` or `\r\n`.
|
|
80
|
+
*/
|
|
81
|
+
function renderTomlBodyLines(value, pathPrefix) {
|
|
82
|
+
const lines = [];
|
|
83
|
+
const nested = [];
|
|
84
|
+
for (const [key, v] of Object.entries(value)) {
|
|
85
|
+
if (v === void 0) continue;
|
|
86
|
+
if (isPlainObject(v)) {
|
|
87
|
+
nested.push([key, v]);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
lines.push(`${renderTomlKey(key)} = ${renderTomlValue(v)}`);
|
|
91
|
+
}
|
|
92
|
+
for (const [key, v] of nested) {
|
|
93
|
+
const path = [...pathPrefix, key];
|
|
94
|
+
lines.push("", `[${path.map(renderTomlKey).join(".")}]`);
|
|
95
|
+
lines.push(...renderTomlBodyLines(v, path));
|
|
96
|
+
}
|
|
97
|
+
return lines;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region ../mcp-harnesses/dist/catalog.js
|
|
102
|
+
/** The `{ url, type: "http" }` shape Claude Code and Gemini CLI write. */
|
|
103
|
+
const DEFAULT_NATIVE_HTTP_ENTRY = {
|
|
104
|
+
urlKey: "url",
|
|
105
|
+
constants: { type: "http" }
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Provenance sentinel for entries that predate the invariant (ENG-5264 D1,
|
|
109
|
+
* decision ENG-5327). No observation record exists for the harnesses that
|
|
110
|
+
* carry it, and inventing a version or date would be exactly what the
|
|
111
|
+
* invariant forbids — so they say so, greppably: `grep unverified-legacy`
|
|
112
|
+
* names every entry still owing a real observation. An entry is upgraded to a
|
|
113
|
+
* real version + date whenever someone next installs that client, and is never
|
|
114
|
+
* downgraded back. A NEW entry must never be born carrying this value.
|
|
20
115
|
*/
|
|
116
|
+
const UNVERIFIED_LEGACY = "unverified-legacy";
|
|
21
117
|
/** Stable server key written into every harness config (used by detect/remove).
|
|
22
118
|
* Renamed from the legacy brand key pre-first-publish (ENG-2515) — this key
|
|
23
119
|
* is a persisted identity in end-users' client config files, so it must not
|
|
@@ -59,11 +155,12 @@ const CLAUDE_CODE_LOCATIONS = [
|
|
|
59
155
|
/**
|
|
60
156
|
* The catalog. Order is presentation order (most common first).
|
|
61
157
|
*
|
|
62
|
-
* `comingSoon` clients (VS Code
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
158
|
+
* `comingSoon` clients (VS Code) are listed but not installable: their config
|
|
159
|
+
* format differs enough (VS Code's `servers`/native-http schema) that a
|
|
160
|
+
* faithful write is deferred. Their SCOPES are declared as data regardless, so
|
|
161
|
+
* the support matrix stays complete and turning them on later is a builder
|
|
162
|
+
* change, not a catalog change. Codex left this list in ENG-5264 once the TOML
|
|
163
|
+
* adapter existed.
|
|
67
164
|
*/
|
|
68
165
|
const HARNESSES = [
|
|
69
166
|
{
|
|
@@ -78,6 +175,8 @@ const HARNESSES = [
|
|
|
78
175
|
transport: "mcp-remote",
|
|
79
176
|
docsUrl: "https://modelcontextprotocol.io/quickstart/user",
|
|
80
177
|
comingSoon: false,
|
|
178
|
+
verifiedVersion: UNVERIFIED_LEGACY,
|
|
179
|
+
verifiedOn: UNVERIFIED_LEGACY,
|
|
81
180
|
detectSignals: [
|
|
82
181
|
{
|
|
83
182
|
platform: "darwin",
|
|
@@ -124,6 +223,8 @@ const HARNESSES = [
|
|
|
124
223
|
transport: "native-http",
|
|
125
224
|
docsUrl: "https://docs.anthropic.com/en/docs/claude-code/mcp",
|
|
126
225
|
comingSoon: false,
|
|
226
|
+
verifiedVersion: UNVERIFIED_LEGACY,
|
|
227
|
+
verifiedOn: UNVERIFIED_LEGACY,
|
|
127
228
|
detectSignals: [
|
|
128
229
|
{
|
|
129
230
|
platform: "darwin",
|
|
@@ -179,6 +280,8 @@ const HARNESSES = [
|
|
|
179
280
|
transport: "mcp-remote",
|
|
180
281
|
docsUrl: "https://cursor.com/docs/mcp",
|
|
181
282
|
comingSoon: false,
|
|
283
|
+
verifiedVersion: UNVERIFIED_LEGACY,
|
|
284
|
+
verifiedOn: UNVERIFIED_LEGACY,
|
|
182
285
|
detectSignals: [
|
|
183
286
|
{
|
|
184
287
|
platform: "darwin",
|
|
@@ -228,6 +331,8 @@ const HARNESSES = [
|
|
|
228
331
|
transport: "mcp-remote",
|
|
229
332
|
docsUrl: "https://docs.windsurf.com/windsurf/mcp",
|
|
230
333
|
comingSoon: false,
|
|
334
|
+
verifiedVersion: UNVERIFIED_LEGACY,
|
|
335
|
+
verifiedOn: UNVERIFIED_LEGACY,
|
|
231
336
|
detectSignals: [
|
|
232
337
|
{
|
|
233
338
|
platform: "darwin",
|
|
@@ -273,6 +378,8 @@ const HARNESSES = [
|
|
|
273
378
|
transport: "mcp-remote",
|
|
274
379
|
docsUrl: "https://zed.dev/docs/ai/mcp",
|
|
275
380
|
comingSoon: false,
|
|
381
|
+
verifiedVersion: UNVERIFIED_LEGACY,
|
|
382
|
+
verifiedOn: UNVERIFIED_LEGACY,
|
|
276
383
|
detectSignals: [
|
|
277
384
|
{
|
|
278
385
|
platform: "darwin",
|
|
@@ -314,6 +421,161 @@ const HARNESSES = [
|
|
|
314
421
|
projectPath: ".zed/settings.json"
|
|
315
422
|
}]
|
|
316
423
|
},
|
|
424
|
+
{
|
|
425
|
+
id: "gemini",
|
|
426
|
+
label: "Gemini CLI",
|
|
427
|
+
matchers: ["gemini cli", "gemini-cli"],
|
|
428
|
+
serverPropertyName: "mcpServers",
|
|
429
|
+
transport: "native-http",
|
|
430
|
+
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
431
|
+
comingSoon: false,
|
|
432
|
+
verifiedVersion: "0.35.1",
|
|
433
|
+
verifiedOn: "2026-09-17",
|
|
434
|
+
detectSignals: [
|
|
435
|
+
{
|
|
436
|
+
platform: "darwin",
|
|
437
|
+
signals: ["which:gemini", "~/.gemini/settings.json"]
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
platform: "linux",
|
|
441
|
+
signals: ["which:gemini", "~/.gemini/settings.json"]
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
platform: "win32",
|
|
445
|
+
signals: ["which:gemini", "~/.gemini/settings.json"]
|
|
446
|
+
}
|
|
447
|
+
],
|
|
448
|
+
scopes: [{
|
|
449
|
+
scope: "user",
|
|
450
|
+
installKind: "config-file",
|
|
451
|
+
locations: [
|
|
452
|
+
{
|
|
453
|
+
platform: "darwin",
|
|
454
|
+
configPath: "~/.gemini/settings.json"
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
platform: "linux",
|
|
458
|
+
configPath: "~/.gemini/settings.json"
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
platform: "win32",
|
|
462
|
+
configPath: "~/.gemini/settings.json"
|
|
463
|
+
}
|
|
464
|
+
]
|
|
465
|
+
}, {
|
|
466
|
+
scope: "project",
|
|
467
|
+
installKind: "config-file",
|
|
468
|
+
projectPath: ".gemini/settings.json"
|
|
469
|
+
}]
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
id: "grok",
|
|
473
|
+
label: "Grok Build",
|
|
474
|
+
matchers: ["grok build", "grok-build"],
|
|
475
|
+
serverPropertyName: "mcp_servers",
|
|
476
|
+
transport: "native-http",
|
|
477
|
+
nativeHttpEntry: {
|
|
478
|
+
urlKey: "url",
|
|
479
|
+
constants: { enabled: true }
|
|
480
|
+
},
|
|
481
|
+
configFormat: "toml",
|
|
482
|
+
docsUrl: "https://docs.x.ai/build/overview",
|
|
483
|
+
comingSoon: false,
|
|
484
|
+
verifiedVersion: "1.0.34",
|
|
485
|
+
verifiedOn: "2026-09-17",
|
|
486
|
+
detectSignals: [
|
|
487
|
+
{
|
|
488
|
+
platform: "darwin",
|
|
489
|
+
signals: ["~/.grok/config.toml", "~/.grok/bin/grok"]
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
platform: "linux",
|
|
493
|
+
signals: ["~/.grok/config.toml", "~/.grok/bin/grok"]
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
platform: "win32",
|
|
497
|
+
signals: ["~/.grok/config.toml", "~/.grok/bin/grok.exe"]
|
|
498
|
+
}
|
|
499
|
+
],
|
|
500
|
+
scopes: [{
|
|
501
|
+
scope: "user",
|
|
502
|
+
installKind: "config-file",
|
|
503
|
+
locations: [
|
|
504
|
+
{
|
|
505
|
+
platform: "darwin",
|
|
506
|
+
configPath: "~/.grok/config.toml"
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
platform: "linux",
|
|
510
|
+
configPath: "~/.grok/config.toml"
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
platform: "win32",
|
|
514
|
+
configPath: "~/.grok/config.toml"
|
|
515
|
+
}
|
|
516
|
+
]
|
|
517
|
+
}, {
|
|
518
|
+
scope: "project",
|
|
519
|
+
installKind: "config-file",
|
|
520
|
+
projectPath: ".grok/config.toml"
|
|
521
|
+
}]
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
id: "antigravity",
|
|
525
|
+
label: "Antigravity",
|
|
526
|
+
matchers: ["antigravity"],
|
|
527
|
+
serverPropertyName: "mcpServers",
|
|
528
|
+
transport: "native-http",
|
|
529
|
+
nativeHttpEntry: {
|
|
530
|
+
urlKey: "serverUrl",
|
|
531
|
+
constants: { disabled: false }
|
|
532
|
+
},
|
|
533
|
+
docsUrl: "https://antigravity.google/docs/mcp",
|
|
534
|
+
comingSoon: false,
|
|
535
|
+
verifiedVersion: "1.2.5",
|
|
536
|
+
verifiedOn: "2026-09-17",
|
|
537
|
+
detectSignals: [
|
|
538
|
+
{
|
|
539
|
+
platform: "darwin",
|
|
540
|
+
signals: [
|
|
541
|
+
"which:agy",
|
|
542
|
+
"~/.local/bin/agy",
|
|
543
|
+
"/Applications/Antigravity IDE.app",
|
|
544
|
+
"~/.gemini/config/mcp_config.json"
|
|
545
|
+
]
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
platform: "linux",
|
|
549
|
+
signals: [
|
|
550
|
+
"which:agy",
|
|
551
|
+
"~/.local/bin/agy",
|
|
552
|
+
"~/.gemini/config/mcp_config.json"
|
|
553
|
+
]
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
platform: "win32",
|
|
557
|
+
signals: ["which:agy", "~/.gemini/config/mcp_config.json"]
|
|
558
|
+
}
|
|
559
|
+
],
|
|
560
|
+
scopes: [{
|
|
561
|
+
scope: "user",
|
|
562
|
+
installKind: "config-file",
|
|
563
|
+
locations: [
|
|
564
|
+
{
|
|
565
|
+
platform: "darwin",
|
|
566
|
+
configPath: "~/.gemini/config/mcp_config.json"
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
platform: "linux",
|
|
570
|
+
configPath: "~/.gemini/config/mcp_config.json"
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
platform: "win32",
|
|
574
|
+
configPath: "~/.gemini/config/mcp_config.json"
|
|
575
|
+
}
|
|
576
|
+
]
|
|
577
|
+
}]
|
|
578
|
+
},
|
|
317
579
|
{
|
|
318
580
|
id: "vscode",
|
|
319
581
|
label: "VS Code",
|
|
@@ -367,10 +629,14 @@ const HARNESSES = [
|
|
|
367
629
|
id: "codex",
|
|
368
630
|
label: "Codex CLI",
|
|
369
631
|
matchers: ["codex"],
|
|
370
|
-
serverPropertyName: "
|
|
371
|
-
transport: "
|
|
632
|
+
serverPropertyName: "mcp_servers",
|
|
633
|
+
transport: "native-http",
|
|
634
|
+
nativeHttpEntry: { urlKey: "url" },
|
|
635
|
+
configFormat: "toml",
|
|
372
636
|
docsUrl: "https://github.com/openai/codex",
|
|
373
|
-
comingSoon:
|
|
637
|
+
comingSoon: false,
|
|
638
|
+
verifiedVersion: "0.154.0",
|
|
639
|
+
verifiedOn: "2026-09-17",
|
|
374
640
|
detectSignals: [
|
|
375
641
|
{
|
|
376
642
|
platform: "darwin",
|
|
@@ -402,10 +668,6 @@ const HARNESSES = [
|
|
|
402
668
|
configPath: "~/.codex/config.toml"
|
|
403
669
|
}
|
|
404
670
|
]
|
|
405
|
-
}, {
|
|
406
|
-
scope: "project",
|
|
407
|
-
installKind: "config-file",
|
|
408
|
-
projectPath: ".codex/config.toml"
|
|
409
671
|
}]
|
|
410
672
|
}
|
|
411
673
|
];
|
|
@@ -473,7 +735,8 @@ function mcpRemoteInvocation(mcpUrl) {
|
|
|
473
735
|
/**
|
|
474
736
|
* The structured server entry to merge under the scope's server property,
|
|
475
737
|
* keyed by {@link SERVER_NAME}. Shape is per-transport, then per-property:
|
|
476
|
-
* - `native-http
|
|
738
|
+
* - `native-http`: the entry's declared {@link NativeHttpEntryShape} —
|
|
739
|
+
* `{ url, type: 'http' }` by default (Claude Code's `.mcp.json`, Gemini).
|
|
477
740
|
* - `mcpServers` (Claude Desktop, Cursor, Windsurf): flat `{ command, args }`.
|
|
478
741
|
* - `context_servers` (Zed): nested `{ source, command: { path, args } }`.
|
|
479
742
|
*
|
|
@@ -481,10 +744,13 @@ function mcpRemoteInvocation(mcpUrl) {
|
|
|
481
744
|
*/
|
|
482
745
|
function buildServerEntry(harness, mcpUrl, scope = defaultScope(harness)) {
|
|
483
746
|
requireScope(harness, scope);
|
|
484
|
-
if (harness.transport === "native-http")
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
747
|
+
if (harness.transport === "native-http") {
|
|
748
|
+
const shape = harness.nativeHttpEntry ?? DEFAULT_NATIVE_HTTP_ENTRY;
|
|
749
|
+
return { [SERVER_NAME]: {
|
|
750
|
+
[shape.urlKey]: mcpUrl,
|
|
751
|
+
...shape.constants ?? {}
|
|
752
|
+
} };
|
|
753
|
+
}
|
|
488
754
|
const { command, args } = mcpRemoteInvocation(mcpUrl);
|
|
489
755
|
if (serverPropertyFor(harness, scope) === "context_servers") return { [SERVER_NAME]: {
|
|
490
756
|
source: "custom",
|
|
@@ -724,6 +990,428 @@ function getAtPath(obj, path) {
|
|
|
724
990
|
return cur;
|
|
725
991
|
}
|
|
726
992
|
|
|
993
|
+
//#endregion
|
|
994
|
+
//#region ../mcp-harnesses/dist/node/format/port.js
|
|
995
|
+
/**
|
|
996
|
+
* Outbound port: a config DOCUMENT the installer can read a value out of and
|
|
997
|
+
* surgically edit, without the domain knowing what syntax the file is in.
|
|
998
|
+
*
|
|
999
|
+
* `installHarnessSync` / `removeHarnessSync` (`../install.ts`) and detection
|
|
1000
|
+
* (`../detect.ts`) speak only this interface; each concrete format lives in a
|
|
1001
|
+
* sibling adapter and is selected by the DECLARATIVE `configFormat` field on
|
|
1002
|
+
* the catalog entry (`adapterFor`, `./index.ts`). No harness id, and no format
|
|
1003
|
+
* name, ever reaches the domain — plan ENG-5264 §2.
|
|
1004
|
+
*
|
|
1005
|
+
* ## The failure channel is part of the contract (review F-002)
|
|
1006
|
+
*
|
|
1007
|
+
* A read has THREE outcomes, not two. `absent` is the state the domain answers
|
|
1008
|
+
* by writing a fresh entry into an empty document; `unsupported` is the state
|
|
1009
|
+
* it must REFUSE on — a non-empty file that does not parse, or a document in
|
|
1010
|
+
* which our key sits in a shape the adapter cannot edit in place. Collapsing
|
|
1011
|
+
* the two (returning `undefined` for both) is how a real user config gets
|
|
1012
|
+
* treated as empty and overwritten. That is why `readAt` returns a tagged
|
|
1013
|
+
* union rather than `unknown`, and why the mutators throw a typed error
|
|
1014
|
+
* instead of best-effort restructuring a document they did not understand.
|
|
1015
|
+
*/
|
|
1016
|
+
/**
|
|
1017
|
+
* Thrown by {@link ConfigDocumentAdapter.setAt} / `removeAt` when the edit
|
|
1018
|
+
* cannot be made without restructuring content the adapter does not fully
|
|
1019
|
+
* understand. The domain catches it and refuses the install — fail closed.
|
|
1020
|
+
*/
|
|
1021
|
+
var UnsupportedConfigShapeError = class extends Error {
|
|
1022
|
+
detail;
|
|
1023
|
+
constructor(detail) {
|
|
1024
|
+
super(detail);
|
|
1025
|
+
this.detail = detail;
|
|
1026
|
+
this.name = "UnsupportedConfigShapeError";
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
|
|
1030
|
+
//#endregion
|
|
1031
|
+
//#region ../mcp-harnesses/dist/node/format/jsonc.js
|
|
1032
|
+
const FORMAT = {
|
|
1033
|
+
insertSpaces: true,
|
|
1034
|
+
tabSize: 2,
|
|
1035
|
+
eol: "\n"
|
|
1036
|
+
};
|
|
1037
|
+
/**
|
|
1038
|
+
* Parse strictly enough to be safe: any syntax error is a refusal, never a
|
|
1039
|
+
* partial document. Returns the root, or the reason it cannot be used.
|
|
1040
|
+
*/
|
|
1041
|
+
function parseDocument$1(text) {
|
|
1042
|
+
const errors = [];
|
|
1043
|
+
const root = parse(text, errors, {
|
|
1044
|
+
allowTrailingComma: true,
|
|
1045
|
+
disallowComments: false
|
|
1046
|
+
});
|
|
1047
|
+
const first = errors[0];
|
|
1048
|
+
if (first) return {
|
|
1049
|
+
ok: false,
|
|
1050
|
+
detail: `document does not parse (${printParseErrorCode(first.error)} at offset ${first.offset})`
|
|
1051
|
+
};
|
|
1052
|
+
if (root === null || typeof root !== "object" || Array.isArray(root)) return {
|
|
1053
|
+
ok: false,
|
|
1054
|
+
detail: "document root is not an object"
|
|
1055
|
+
};
|
|
1056
|
+
return {
|
|
1057
|
+
ok: true,
|
|
1058
|
+
root
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
const jsoncAdapter = {
|
|
1062
|
+
empty: "{}",
|
|
1063
|
+
readAt(text, path) {
|
|
1064
|
+
if (text.trim() === "") return { kind: "absent" };
|
|
1065
|
+
const doc = parseDocument$1(text);
|
|
1066
|
+
if (!doc.ok) return {
|
|
1067
|
+
kind: "unsupported",
|
|
1068
|
+
detail: doc.detail
|
|
1069
|
+
};
|
|
1070
|
+
const value = getAtPath(doc.root, path);
|
|
1071
|
+
return value === void 0 ? { kind: "absent" } : {
|
|
1072
|
+
kind: "value",
|
|
1073
|
+
value
|
|
1074
|
+
};
|
|
1075
|
+
},
|
|
1076
|
+
setAt(text, path, value) {
|
|
1077
|
+
return edit(text, path, value);
|
|
1078
|
+
},
|
|
1079
|
+
removeAt(text, path) {
|
|
1080
|
+
return edit(text, path, void 0);
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
/** Shared by set and remove: jsonc-parser removes a key when `value` is `undefined`. */
|
|
1084
|
+
function edit(text, path, value) {
|
|
1085
|
+
const doc = parseDocument$1(text);
|
|
1086
|
+
if (!doc.ok) throw new UnsupportedConfigShapeError(doc.detail);
|
|
1087
|
+
try {
|
|
1088
|
+
return applyEdits(text, modify(text, path, value, { formattingOptions: FORMAT }));
|
|
1089
|
+
} catch (err) {
|
|
1090
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1091
|
+
throw new UnsupportedConfigShapeError(`cannot edit path ${path.join(".")}: ${message}`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
//#endregion
|
|
1096
|
+
//#region ../mcp-harnesses/dist/node/format/toml.js
|
|
1097
|
+
/**
|
|
1098
|
+
* Suffix of the one-shot sibling backup the domain writes before the FIRST
|
|
1099
|
+
* modifying write to a TOML target (review F-005): `config.toml.levr-bak`.
|
|
1100
|
+
* Declared here, applied format-generically by the installer.
|
|
1101
|
+
*/
|
|
1102
|
+
const TOML_BACKUP_SUFFIX = ".levr-bak";
|
|
1103
|
+
const BOM = "";
|
|
1104
|
+
function parseDocument(text) {
|
|
1105
|
+
try {
|
|
1106
|
+
return {
|
|
1107
|
+
ok: true,
|
|
1108
|
+
root: parse$1(stripBom(text))
|
|
1109
|
+
};
|
|
1110
|
+
} catch (err) {
|
|
1111
|
+
if (err instanceof TomlError) return {
|
|
1112
|
+
ok: false,
|
|
1113
|
+
detail: `document does not parse: ${err.message.split("\n")[0] ?? err.message} (line ${err.line}, col ${err.column})`
|
|
1114
|
+
};
|
|
1115
|
+
return {
|
|
1116
|
+
ok: false,
|
|
1117
|
+
detail: `document does not parse: ${err instanceof Error ? err.message : String(err)}`
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* `smol-toml` does not strip a UTF-8 byte-order mark, and Windows editors
|
|
1123
|
+
* write one (code review F-006). The BOM is document decoration: parse and
|
|
1124
|
+
* edit the text behind it, put it back on the way out.
|
|
1125
|
+
*/
|
|
1126
|
+
function stripBom(text) {
|
|
1127
|
+
return text.startsWith(BOM) ? text.slice(1) : text;
|
|
1128
|
+
}
|
|
1129
|
+
function splitLines(text) {
|
|
1130
|
+
const lines = [];
|
|
1131
|
+
const eols = [];
|
|
1132
|
+
let crlf = 0;
|
|
1133
|
+
let lf = 0;
|
|
1134
|
+
let from = 0;
|
|
1135
|
+
for (let i = 0; i < text.length; i++) {
|
|
1136
|
+
if (text[i] !== "\n") continue;
|
|
1137
|
+
const cr = i > 0 && text[i - 1] === "\r";
|
|
1138
|
+
lines.push(text.slice(from, cr ? i - 1 : i));
|
|
1139
|
+
eols.push(cr ? "\r\n" : "\n");
|
|
1140
|
+
if (cr) crlf++;
|
|
1141
|
+
else lf++;
|
|
1142
|
+
from = i + 1;
|
|
1143
|
+
}
|
|
1144
|
+
lines.push(text.slice(from));
|
|
1145
|
+
eols.push("");
|
|
1146
|
+
return {
|
|
1147
|
+
lines,
|
|
1148
|
+
eols,
|
|
1149
|
+
eol: crlf > lf ? "\r\n" : "\n"
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
function joinLines(lines, eols) {
|
|
1153
|
+
let out = "";
|
|
1154
|
+
for (let i = 0; i < lines.length; i++) out += lines[i] + (eols[i] ?? "");
|
|
1155
|
+
return out;
|
|
1156
|
+
}
|
|
1157
|
+
/** Leading whitespace then `[` — the shape every table header shares. */
|
|
1158
|
+
function isBracketLed(line) {
|
|
1159
|
+
return /^[ \t]*\[/.test(line);
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* The key path of a `[table.header]` line, or `undefined` when the line is
|
|
1163
|
+
* not a standard-table header this scanner can read. Array-of-tables
|
|
1164
|
+
* (`[[x]]`) and comment lines never match; a trailing `# comment` may
|
|
1165
|
+
* contain anything, brackets included. Segments may be bare, `"basic"` (with
|
|
1166
|
+
* escapes) or `'literal'` keys.
|
|
1167
|
+
*
|
|
1168
|
+
* `undefined` means "not OUR table" and nothing more — see the module
|
|
1169
|
+
* comment for why an unreadable header still ends a body.
|
|
1170
|
+
*/
|
|
1171
|
+
function headerPathOf(line) {
|
|
1172
|
+
let i = 0;
|
|
1173
|
+
while (line[i] === " " || line[i] === " ") i++;
|
|
1174
|
+
if (line[i] !== "[" || line[i + 1] === "[") return void 0;
|
|
1175
|
+
i++;
|
|
1176
|
+
const segments = [];
|
|
1177
|
+
let current = "";
|
|
1178
|
+
let sawSegment = false;
|
|
1179
|
+
let closed = false;
|
|
1180
|
+
while (i < line.length) {
|
|
1181
|
+
const ch = line[i];
|
|
1182
|
+
if (ch === " " || ch === " ") {
|
|
1183
|
+
i++;
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
if (ch === "]") {
|
|
1187
|
+
closed = true;
|
|
1188
|
+
i++;
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
if (ch === "\"") {
|
|
1192
|
+
const end = scanBasicString(line, i);
|
|
1193
|
+
if (end === -1) return void 0;
|
|
1194
|
+
const unescaped = unescapeBasic(line.slice(i + 1, end));
|
|
1195
|
+
if (unescaped === void 0) return void 0;
|
|
1196
|
+
current += unescaped;
|
|
1197
|
+
i = end + 1;
|
|
1198
|
+
sawSegment = true;
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
if (ch === "'") {
|
|
1202
|
+
const close = line.indexOf("'", i + 1);
|
|
1203
|
+
if (close === -1) return void 0;
|
|
1204
|
+
current += line.slice(i + 1, close);
|
|
1205
|
+
i = close + 1;
|
|
1206
|
+
sawSegment = true;
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
if (ch === ".") {
|
|
1210
|
+
if (!sawSegment) return void 0;
|
|
1211
|
+
segments.push(current);
|
|
1212
|
+
current = "";
|
|
1213
|
+
sawSegment = false;
|
|
1214
|
+
i++;
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
if (/[A-Za-z0-9_-]/.test(ch)) {
|
|
1218
|
+
current += ch;
|
|
1219
|
+
sawSegment = true;
|
|
1220
|
+
i++;
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
if (!closed || !sawSegment) return void 0;
|
|
1226
|
+
const rest = line.slice(i);
|
|
1227
|
+
if (!/^[ \t]*(#.*)?$/.test(rest)) return void 0;
|
|
1228
|
+
segments.push(current);
|
|
1229
|
+
return segments;
|
|
1230
|
+
}
|
|
1231
|
+
/** Index of the closing quote of the basic string opening at `open`, or -1. */
|
|
1232
|
+
function scanBasicString(line, open) {
|
|
1233
|
+
for (let i = open + 1; i < line.length; i++) {
|
|
1234
|
+
if (line[i] === "\\") {
|
|
1235
|
+
i++;
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
if (line[i] === "\"") return i;
|
|
1239
|
+
}
|
|
1240
|
+
return -1;
|
|
1241
|
+
}
|
|
1242
|
+
/** TOML basic-string escapes are a subset of JSON's, so JSON can decode them. */
|
|
1243
|
+
function unescapeBasic(raw) {
|
|
1244
|
+
try {
|
|
1245
|
+
const value = JSON.parse(`"${raw}"`);
|
|
1246
|
+
return typeof value === "string" ? value : void 0;
|
|
1247
|
+
} catch {
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
const PROBE_KEY = "__levr_header_probe__";
|
|
1252
|
+
/**
|
|
1253
|
+
* What is the bracket-led line at `idx`? Swap it for a probe header and ask
|
|
1254
|
+
* the parser:
|
|
1255
|
+
*
|
|
1256
|
+
* - `'real'`: the probe table appeared at the root — the line was a genuine
|
|
1257
|
+
* header at document level.
|
|
1258
|
+
* - `'text'`: the document still parses and the probe did NOT appear — the
|
|
1259
|
+
* line is content inside a multi-line string. It changed the string, not
|
|
1260
|
+
* the document's shape.
|
|
1261
|
+
* - `'unknown'`: the swap broke the document. Nothing is proven either way.
|
|
1262
|
+
*/
|
|
1263
|
+
function probeHeader(doc, idx) {
|
|
1264
|
+
const probe = [...doc.lines];
|
|
1265
|
+
probe[idx] = `[${PROBE_KEY}]`;
|
|
1266
|
+
const parsed = parseDocument(joinLines(probe, doc.eols));
|
|
1267
|
+
if (!parsed.ok) return "unknown";
|
|
1268
|
+
return Object.prototype.hasOwnProperty.call(parsed.root, PROBE_KEY) ? "real" : "text";
|
|
1269
|
+
}
|
|
1270
|
+
function samePath(a, b) {
|
|
1271
|
+
return a.length === b.length && a.every((s, i) => s === b[i]);
|
|
1272
|
+
}
|
|
1273
|
+
function isSubPath(path, prefix) {
|
|
1274
|
+
return path.length > prefix.length && prefix.every((s, i) => s === path[i]);
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Locate our table: the header line index and the index just past its body.
|
|
1278
|
+
* `undefined` when there is no standalone header for `path`.
|
|
1279
|
+
*
|
|
1280
|
+
* The START must be a fully parsed header for exactly `path`, confirmed
|
|
1281
|
+
* real. The END is the first bracket-led line after it that is neither one
|
|
1282
|
+
* of our own sub-tables nor proven to be string content — a header the path
|
|
1283
|
+
* scanner cannot read, or one whose probe breaks the document, still ends
|
|
1284
|
+
* the body. Misjudging a line can only make our region smaller.
|
|
1285
|
+
*/
|
|
1286
|
+
function locateTable(doc, path) {
|
|
1287
|
+
const { lines } = doc;
|
|
1288
|
+
let start = -1;
|
|
1289
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1290
|
+
const hp = headerPathOf(lines[i]);
|
|
1291
|
+
if (hp && samePath(hp, path) && probeHeader(doc, i) === "real") {
|
|
1292
|
+
start = i;
|
|
1293
|
+
break;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (start === -1) return void 0;
|
|
1297
|
+
const last = lines.length - 1;
|
|
1298
|
+
const limit = lines[last] === "" ? last : lines.length;
|
|
1299
|
+
let end = limit;
|
|
1300
|
+
for (let i = start + 1; i < limit; i++) {
|
|
1301
|
+
if (!isBracketLed(lines[i])) continue;
|
|
1302
|
+
const verdict = probeHeader(doc, i);
|
|
1303
|
+
if (verdict === "text") continue;
|
|
1304
|
+
const hp = headerPathOf(lines[i]);
|
|
1305
|
+
if (hp && isSubPath(hp, path) && verdict === "real") continue;
|
|
1306
|
+
end = i;
|
|
1307
|
+
break;
|
|
1308
|
+
}
|
|
1309
|
+
while (end - 1 > start && /^[ \t]*(#.*)?$/.test(lines[end - 1])) end--;
|
|
1310
|
+
return {
|
|
1311
|
+
start,
|
|
1312
|
+
end
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
function requireEditable(text, path) {
|
|
1316
|
+
const parsed = parseDocument(text);
|
|
1317
|
+
if (!parsed.ok) throw new UnsupportedConfigShapeError(parsed.detail);
|
|
1318
|
+
return {
|
|
1319
|
+
doc: splitLines(text),
|
|
1320
|
+
exists: getAtPath(parsed.root, path) !== void 0
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
function notStandalone(path) {
|
|
1324
|
+
return new UnsupportedConfigShapeError(`${path.join(".")} exists but not as a standalone [${path.join(".")}] table (inline table or dotted key) — refusing to restructure it`);
|
|
1325
|
+
}
|
|
1326
|
+
const tomlAdapter = {
|
|
1327
|
+
empty: "",
|
|
1328
|
+
backupSuffix: TOML_BACKUP_SUFFIX,
|
|
1329
|
+
readAt(text, path) {
|
|
1330
|
+
if (stripBom(text).trim() === "") return { kind: "absent" };
|
|
1331
|
+
const doc = parseDocument(text);
|
|
1332
|
+
if (!doc.ok) return {
|
|
1333
|
+
kind: "unsupported",
|
|
1334
|
+
detail: doc.detail
|
|
1335
|
+
};
|
|
1336
|
+
const value = getAtPath(doc.root, path);
|
|
1337
|
+
return value === void 0 ? { kind: "absent" } : {
|
|
1338
|
+
kind: "value",
|
|
1339
|
+
value
|
|
1340
|
+
};
|
|
1341
|
+
},
|
|
1342
|
+
setAt(text, path, value) {
|
|
1343
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new UnsupportedConfigShapeError(`a TOML table body must be an object, got ${value === null ? "null" : Array.isArray(value) ? "array" : typeof value}`);
|
|
1344
|
+
const body = value;
|
|
1345
|
+
const bom = text.startsWith(BOM) ? BOM : "";
|
|
1346
|
+
const inner = stripBom(text);
|
|
1347
|
+
if (inner.trim() === "") return bom + renderTable(path, body, inner.includes("\r\n") ? "\r\n" : "\n");
|
|
1348
|
+
const { doc, exists } = requireEditable(inner, path);
|
|
1349
|
+
const table = locateTable(doc, path);
|
|
1350
|
+
if (!table) {
|
|
1351
|
+
if (exists) throw notStandalone(path);
|
|
1352
|
+
const { eol } = doc;
|
|
1353
|
+
let base = inner.endsWith("\n") ? inner : inner + eol;
|
|
1354
|
+
if (!/\r?\n\r?\n$/.test(base)) base += eol;
|
|
1355
|
+
return bom + base + renderTable(path, body, eol);
|
|
1356
|
+
}
|
|
1357
|
+
const rendered = renderTomlBodyLines(body, path);
|
|
1358
|
+
const atEof = table.end === doc.lines.length;
|
|
1359
|
+
return bom + joinLines([
|
|
1360
|
+
...doc.lines.slice(0, table.start + 1),
|
|
1361
|
+
...rendered,
|
|
1362
|
+
...doc.lines.slice(table.end)
|
|
1363
|
+
], [
|
|
1364
|
+
...doc.eols.slice(0, table.start + 1),
|
|
1365
|
+
...rendered.map((_, i) => atEof && i === rendered.length - 1 ? "" : doc.eol),
|
|
1366
|
+
...doc.eols.slice(table.end)
|
|
1367
|
+
]);
|
|
1368
|
+
},
|
|
1369
|
+
removeAt(text, path) {
|
|
1370
|
+
const bom = text.startsWith(BOM) ? BOM : "";
|
|
1371
|
+
const inner = stripBom(text);
|
|
1372
|
+
if (inner.trim() === "") return text;
|
|
1373
|
+
const { doc, exists } = requireEditable(inner, path);
|
|
1374
|
+
if (!exists) return text;
|
|
1375
|
+
const table = locateTable(doc, path);
|
|
1376
|
+
if (!table) throw notStandalone(path);
|
|
1377
|
+
const lines = [...doc.lines.slice(0, table.start), ...doc.lines.slice(table.end)];
|
|
1378
|
+
const eols = [...doc.eols.slice(0, table.start), ...doc.eols.slice(table.end)];
|
|
1379
|
+
const at = table.start;
|
|
1380
|
+
if (at === 0) {
|
|
1381
|
+
if (lines[0] === "" && lines.length > 1) {
|
|
1382
|
+
lines.splice(0, 1);
|
|
1383
|
+
eols.splice(0, 1);
|
|
1384
|
+
}
|
|
1385
|
+
} else if (lines[at - 1] === "" && (at >= lines.length || lines[at] === "")) {
|
|
1386
|
+
lines.splice(at - 1, 1);
|
|
1387
|
+
eols.splice(at - 1, 1);
|
|
1388
|
+
}
|
|
1389
|
+
return bom + joinLines(lines, eols);
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
function renderTable(path, body, eol) {
|
|
1393
|
+
return [`[${path.map(renderTomlKey).join(".")}]`, ...renderTomlBodyLines(body, path)].join(eol) + eol;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
//#endregion
|
|
1397
|
+
//#region ../mcp-harnesses/dist/node/format/index.js
|
|
1398
|
+
/** Every syntax the installer can write, keyed by the catalog's `configFormat`. */
|
|
1399
|
+
const ADAPTERS = {
|
|
1400
|
+
jsonc: jsoncAdapter,
|
|
1401
|
+
toml: tomlAdapter
|
|
1402
|
+
};
|
|
1403
|
+
/** The format a harness declares, with the catalog's documented default. */
|
|
1404
|
+
function configFormatOf(harness) {
|
|
1405
|
+
return harness.configFormat ?? "jsonc";
|
|
1406
|
+
}
|
|
1407
|
+
/** The adapter for this harness's config syntax. */
|
|
1408
|
+
function adapterFor(harness) {
|
|
1409
|
+
const format = configFormatOf(harness);
|
|
1410
|
+
const adapter = ADAPTERS[format];
|
|
1411
|
+
if (!adapter) throw new Error(`no config adapter registered for configFormat "${format}" (declared by harness "${harness.id}")`);
|
|
1412
|
+
return adapter;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
727
1415
|
//#endregion
|
|
728
1416
|
//#region ../mcp-harnesses/dist/node/detect.js
|
|
729
1417
|
/**
|
|
@@ -737,8 +1425,8 @@ function getAtPath(obj, path) {
|
|
|
737
1425
|
function isServerConfigured(harness, scope, configPath, cwdKey) {
|
|
738
1426
|
const text = readTextOrNull(configPath);
|
|
739
1427
|
if (!text) return false;
|
|
740
|
-
const
|
|
741
|
-
return
|
|
1428
|
+
const read = adapterFor(harness).readAt(text, entryPathFor(harness, scope, cwdKey));
|
|
1429
|
+
return read.kind === "value" && read.value !== null;
|
|
742
1430
|
}
|
|
743
1431
|
function detectScope(harness, env, def) {
|
|
744
1432
|
const configPath = resolveConfigPath(harness, env, def.scope);
|
|
@@ -867,11 +1555,6 @@ function runHarnessCommandSync(argv, opts = {}) {
|
|
|
867
1555
|
|
|
868
1556
|
//#endregion
|
|
869
1557
|
//#region ../mcp-harnesses/dist/node/install.js
|
|
870
|
-
const FORMAT = {
|
|
871
|
-
insertSpaces: true,
|
|
872
|
-
tabSize: 2,
|
|
873
|
-
eol: "\n"
|
|
874
|
-
};
|
|
875
1558
|
/**
|
|
876
1559
|
* Resolve the target path for a scope, or the reason it cannot be resolved.
|
|
877
1560
|
* Shared by install and remove so the two can never disagree about where a
|
|
@@ -901,7 +1584,11 @@ function resolveTarget(harness, env, scope) {
|
|
|
901
1584
|
function currentEntry(harness, env, scope) {
|
|
902
1585
|
const configPath = resolveConfigPath(harness, env, scope);
|
|
903
1586
|
if (!configPath) return { present: false };
|
|
904
|
-
const
|
|
1587
|
+
const adapter = adapterFor(harness);
|
|
1588
|
+
const entry = readServerEntry(harness, scope, configPath, env.cwd, (t) => {
|
|
1589
|
+
const read = adapter.readAt(t, []);
|
|
1590
|
+
return read.kind === "value" ? read.value : void 0;
|
|
1591
|
+
});
|
|
905
1592
|
if (entry === void 0 || entry === null) return { present: false };
|
|
906
1593
|
const e = entry;
|
|
907
1594
|
if (typeof e.url === "string") return {
|
|
@@ -917,9 +1604,19 @@ function currentEntry(harness, env, scope) {
|
|
|
917
1604
|
}
|
|
918
1605
|
return { present: true };
|
|
919
1606
|
}
|
|
920
|
-
/**
|
|
1607
|
+
/**
|
|
1608
|
+
* Structural equality, key order ignored. A client that writes its keys in a
|
|
1609
|
+
* different order than we do (Antigravity sorts them; older versions of our
|
|
1610
|
+
* own writer put `type` before `url`) must still read as already configured,
|
|
1611
|
+
* or every install becomes a perpetual rewrite.
|
|
1612
|
+
*/
|
|
921
1613
|
function sameValue(a, b) {
|
|
922
|
-
return
|
|
1614
|
+
return canonical(a) === canonical(b);
|
|
1615
|
+
}
|
|
1616
|
+
function canonical(value) {
|
|
1617
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
1618
|
+
if (value !== null && typeof value === "object") return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([x], [y]) => x < y ? -1 : x > y ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}`;
|
|
1619
|
+
return JSON.stringify(value) ?? "undefined";
|
|
923
1620
|
}
|
|
924
1621
|
/** Merge (or preview) our MCP into a harness config. */
|
|
925
1622
|
function installHarnessSync(harness, mcpUrl, opts = {}) {
|
|
@@ -993,11 +1690,19 @@ function installHarnessSync(harness, mcpUrl, opts = {}) {
|
|
|
993
1690
|
const { path } = target;
|
|
994
1691
|
const entryValue = buildServerEntry(harness, mcpUrl, scope)[SERVER_NAME];
|
|
995
1692
|
const modPath = entryPathFor(harness, scope, env.cwd);
|
|
1693
|
+
const adapter = adapterFor(harness);
|
|
996
1694
|
const existing = readTextOrNull(path);
|
|
997
|
-
const baseText = existing && existing.trim() ? existing :
|
|
998
|
-
const current =
|
|
999
|
-
|
|
1000
|
-
const
|
|
1695
|
+
const baseText = existing && existing.trim() ? existing : adapter.empty;
|
|
1696
|
+
const current = adapter.readAt(baseText, modPath);
|
|
1697
|
+
if (current.kind === "unsupported") return unsupportedShape(path, dryRun, scope, current.detail);
|
|
1698
|
+
const alreadyConfigured = current.kind === "value" && sameValue(current.value, entryValue);
|
|
1699
|
+
let nextText;
|
|
1700
|
+
try {
|
|
1701
|
+
nextText = adapter.setAt(baseText, modPath, entryValue);
|
|
1702
|
+
} catch (err) {
|
|
1703
|
+
if (err instanceof UnsupportedConfigShapeError) return unsupportedShape(path, dryRun, scope, err.detail);
|
|
1704
|
+
throw err;
|
|
1705
|
+
}
|
|
1001
1706
|
if (alreadyConfigured) return {
|
|
1002
1707
|
ok: true,
|
|
1003
1708
|
wrote: false,
|
|
@@ -1007,6 +1712,7 @@ function installHarnessSync(harness, mcpUrl, opts = {}) {
|
|
|
1007
1712
|
scope,
|
|
1008
1713
|
preview: nextText
|
|
1009
1714
|
};
|
|
1715
|
+
const backupPath = backupPathFor(path, adapter.backupSuffix);
|
|
1010
1716
|
if (dryRun) return {
|
|
1011
1717
|
ok: true,
|
|
1012
1718
|
wrote: false,
|
|
@@ -1014,11 +1720,17 @@ function installHarnessSync(harness, mcpUrl, opts = {}) {
|
|
|
1014
1720
|
alreadyConfigured: false,
|
|
1015
1721
|
dryRun: true,
|
|
1016
1722
|
scope,
|
|
1017
|
-
preview: nextText
|
|
1723
|
+
preview: nextText,
|
|
1724
|
+
...backupPath ? { backupPath } : {}
|
|
1018
1725
|
};
|
|
1019
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
1020
1726
|
const finalText = nextText.endsWith("\n") ? nextText : `${nextText}\n`;
|
|
1021
|
-
|
|
1727
|
+
try {
|
|
1728
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1729
|
+
if (backupPath) backupOnce(path, backupPath);
|
|
1730
|
+
writeConfigAtomic(path, finalText);
|
|
1731
|
+
} catch (err) {
|
|
1732
|
+
return writeFailed(path, scope, err);
|
|
1733
|
+
}
|
|
1022
1734
|
return {
|
|
1023
1735
|
ok: true,
|
|
1024
1736
|
wrote: true,
|
|
@@ -1026,9 +1738,82 @@ function installHarnessSync(harness, mcpUrl, opts = {}) {
|
|
|
1026
1738
|
alreadyConfigured: false,
|
|
1027
1739
|
dryRun: false,
|
|
1028
1740
|
scope,
|
|
1029
|
-
preview: finalText
|
|
1741
|
+
preview: finalText,
|
|
1742
|
+
...backupPath ? { backupPath } : {}
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Where the one-shot backup of an existing `path` lives, for an adapter that
|
|
1747
|
+
* asks for one — `undefined` when the format does not, or when there is no
|
|
1748
|
+
* original to protect yet.
|
|
1749
|
+
*/
|
|
1750
|
+
function backupPathFor(path, suffix) {
|
|
1751
|
+
if (!suffix || !existsSync(path)) return void 0;
|
|
1752
|
+
return `${path}${suffix}`;
|
|
1753
|
+
}
|
|
1754
|
+
/** Copy the original once. An existing backup is never overwritten. */
|
|
1755
|
+
function backupOnce(path, backupPath) {
|
|
1756
|
+
if (existsSync(backupPath)) return;
|
|
1757
|
+
copyFileSync(path, backupPath);
|
|
1758
|
+
}
|
|
1759
|
+
/** The refusal every format shares: the file is there, and we will not touch it. */
|
|
1760
|
+
function unsupportedShape(path, dryRun, scope, detail) {
|
|
1761
|
+
return {
|
|
1762
|
+
ok: false,
|
|
1763
|
+
wrote: false,
|
|
1764
|
+
path,
|
|
1765
|
+
alreadyConfigured: false,
|
|
1766
|
+
dryRun,
|
|
1767
|
+
scope,
|
|
1768
|
+
reason: "unsupported-config-shape",
|
|
1769
|
+
detail
|
|
1030
1770
|
};
|
|
1031
1771
|
}
|
|
1772
|
+
/** The filesystem said no: report it per client instead of aborting the run. */
|
|
1773
|
+
function writeFailed(path, scope, err) {
|
|
1774
|
+
return {
|
|
1775
|
+
ok: false,
|
|
1776
|
+
wrote: false,
|
|
1777
|
+
path,
|
|
1778
|
+
alreadyConfigured: false,
|
|
1779
|
+
dryRun: false,
|
|
1780
|
+
scope,
|
|
1781
|
+
reason: "write-failed",
|
|
1782
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Replace `path`'s contents atomically: the new text goes to a sibling temp
|
|
1787
|
+
* file that is then renamed over the target, so an interrupted write (ENOSPC,
|
|
1788
|
+
* a kill, sleep) leaves the ORIGINAL file intact rather than a truncated one,
|
|
1789
|
+
* and a concurrent reader sees either the old document or the new one. The
|
|
1790
|
+
* original mode is preserved (a `0600` config stays `0600`).
|
|
1791
|
+
*
|
|
1792
|
+
* This matters more than it did: a harness config can be the file that also
|
|
1793
|
+
* holds a user's model providers, profiles and sandbox policy (review F-005).
|
|
1794
|
+
*/
|
|
1795
|
+
function writeConfigAtomic(path, text) {
|
|
1796
|
+
let target = path;
|
|
1797
|
+
let mode;
|
|
1798
|
+
try {
|
|
1799
|
+
target = realpathSync(path);
|
|
1800
|
+
mode = statSync(target).mode & 511;
|
|
1801
|
+
} catch {}
|
|
1802
|
+
const tmp = join(dirname(target), `.${basename(target)}.${process.pid}.tmp`);
|
|
1803
|
+
writeFileSync(tmp, text, {
|
|
1804
|
+
encoding: "utf8",
|
|
1805
|
+
...mode !== void 0 ? { mode } : {}
|
|
1806
|
+
});
|
|
1807
|
+
try {
|
|
1808
|
+
if (mode !== void 0) chmodSync(tmp, mode);
|
|
1809
|
+
renameSync(tmp, target);
|
|
1810
|
+
} catch (err) {
|
|
1811
|
+
try {
|
|
1812
|
+
unlinkSync(tmp);
|
|
1813
|
+
} catch {}
|
|
1814
|
+
throw err;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1032
1817
|
|
|
1033
1818
|
//#endregion
|
|
1034
1819
|
//#region src/mcp/url.ts
|
|
@@ -1258,6 +2043,8 @@ function failureText(o) {
|
|
|
1258
2043
|
case "not-a-repo": return "project scope needs a git repository (run from inside one)";
|
|
1259
2044
|
case "scope-collision": return `${r.scope} scope resolves to the same file as ${r.collidesWith ?? "another"} scope here — refusing rather than overwriting it`;
|
|
1260
2045
|
case "url-mismatch": return "already configured with a different URL" + (r.currentUrl ? ` (${r.currentUrl})` : "") + "; remove it first, then re-run";
|
|
2046
|
+
case "write-failed": return `its config could not be written` + (r.detail ? ` (${r.detail})` : "") + `; check the file's permissions and re-run`;
|
|
2047
|
+
case "unsupported-config-shape": return `its config could not be edited safely` + (r.detail ? ` (${r.detail})` : "") + `; fix the file or add the entry by hand`;
|
|
1261
2048
|
default:
|
|
1262
2049
|
if (r.commandError) return `\`${r.command}\` (${r.commandError})`;
|
|
1263
2050
|
return "no config location on this platform";
|
|
@@ -1268,14 +2055,15 @@ function outcomeLine(o, dryRun) {
|
|
|
1268
2055
|
const r = o.result;
|
|
1269
2056
|
const note = o.fallbackFrom ? ` [${o.fallbackFrom} scope unsupported — used ${r.scope}]` : "";
|
|
1270
2057
|
const where = r.path ? ` → ${r.path}` : "";
|
|
2058
|
+
const backup = r.backupPath ? dryRun ? ` [original would be backed up to ${r.backupPath}]` : ` [original backed up to ${r.backupPath}]` : "";
|
|
1271
2059
|
if (!r.ok) return `${o.label}: failed — ${failureText(o)}`;
|
|
1272
2060
|
if (r.alreadyConfigured) return `${o.label}: already set up (${r.scope})${where}${note}`;
|
|
1273
2061
|
if (r.command) {
|
|
1274
2062
|
if (r.executed) return `${o.label}: installed (${r.scope}) via \`${r.command}\`${note}`;
|
|
1275
2063
|
return `${o.label} (${r.scope}): run \`${r.command}\`${note}`;
|
|
1276
2064
|
}
|
|
1277
|
-
if (dryRun) return `${o.label}: would update (${r.scope})${where} (dry run — no changes)${note}`;
|
|
1278
|
-
if (r.wrote) return `${o.label}: installed (${r.scope})${where}${note}`;
|
|
2065
|
+
if (dryRun) return `${o.label}: would update (${r.scope})${where} (dry run — no changes)${backup}${note}`;
|
|
2066
|
+
if (r.wrote) return `${o.label}: installed (${r.scope})${where}${backup}${note}`;
|
|
1279
2067
|
return `${o.label}: no change (${r.scope})${where}${note}`;
|
|
1280
2068
|
}
|
|
1281
2069
|
/** Render a report as a plain multi-line summary (used by the CLI + tests). */
|
package/dist/cli.js
CHANGED
|
@@ -106,8 +106,8 @@ const mcpAddCommand = buildCommand({
|
|
|
106
106
|
docs: {
|
|
107
107
|
brief: "Add the Levr MCP server to installed AI clients",
|
|
108
108
|
fullDescription: `Detect MCP-capable clients on this machine (Claude Desktop,
|
|
109
|
-
Claude Code, Cursor, Windsurf, Zed
|
|
110
|
-
one's config. The entry is credential-free — the client opens a browser to
|
|
109
|
+
Claude Code, Cursor, Windsurf, Zed, Gemini CLI, Codex CLI, Grok Build,
|
|
110
|
+
Antigravity) and write the Levr MCP server into each one's config. The entry is credential-free — the client opens a browser to
|
|
111
111
|
authorize with Levr the first time it connects.
|
|
112
112
|
|
|
113
113
|
--scope decides where the entry lands:
|
|
@@ -115,8 +115,8 @@ authorize with Levr the first time it connects.
|
|
|
115
115
|
project this repository, shared with everyone who checks it out
|
|
116
116
|
local this repository, only you (Claude Code only)
|
|
117
117
|
|
|
118
|
-
Not every client supports every scope — Claude Desktop
|
|
119
|
-
user-only. A client you name with --client fails if it cannot honor the
|
|
118
|
+
Not every client supports every scope — Claude Desktop, Windsurf, Codex CLI
|
|
119
|
+
and Antigravity are user-only. A client you name with --client fails if it cannot honor the
|
|
120
120
|
scope you asked for; one picked up by --all or interactively falls back to
|
|
121
121
|
the scope it does support, and the report says so.
|
|
122
122
|
|
|
@@ -175,7 +175,7 @@ Examples:
|
|
|
175
175
|
aliases: { y: "yes" }
|
|
176
176
|
},
|
|
177
177
|
loader: async () => {
|
|
178
|
-
const { mcpAddHandler } = await import("./addHandler-
|
|
178
|
+
const { mcpAddHandler } = await import("./addHandler-BlZQkDtP.js");
|
|
179
179
|
return mcpAddHandler;
|
|
180
180
|
}
|
|
181
181
|
});
|
|
@@ -213,7 +213,7 @@ Examples:
|
|
|
213
213
|
aliases: { d: "device-code" }
|
|
214
214
|
},
|
|
215
215
|
loader: async () => {
|
|
216
|
-
const { loginHandler } = await import("./loginHandler-
|
|
216
|
+
const { loginHandler } = await import("./loginHandler-B7ggggGI.js");
|
|
217
217
|
return loginHandler;
|
|
218
218
|
}
|
|
219
219
|
});
|
|
@@ -252,7 +252,7 @@ Examples:
|
|
|
252
252
|
},
|
|
253
253
|
parameters: {},
|
|
254
254
|
loader: async () => {
|
|
255
|
-
const { statusHandler } = await import("./statusHandler-
|
|
255
|
+
const { statusHandler } = await import("./statusHandler-D5n1x-lz.js");
|
|
256
256
|
return statusHandler;
|
|
257
257
|
}
|
|
258
258
|
});
|
|
@@ -364,7 +364,7 @@ Examples:
|
|
|
364
364
|
}
|
|
365
365
|
},
|
|
366
366
|
loader: async () => {
|
|
367
|
-
const { pushHandler } = await import("./pushHandler-
|
|
367
|
+
const { pushHandler } = await import("./pushHandler-C7s-Ki18.js");
|
|
368
368
|
return pushHandler;
|
|
369
369
|
}
|
|
370
370
|
});
|
|
@@ -484,7 +484,7 @@ Examples:
|
|
|
484
484
|
}
|
|
485
485
|
},
|
|
486
486
|
loader: async () => {
|
|
487
|
-
const { importHandler } = await import("./importHandler-
|
|
487
|
+
const { importHandler } = await import("./importHandler-C3w-uUQ_.js");
|
|
488
488
|
return importHandler;
|
|
489
489
|
}
|
|
490
490
|
});
|
|
@@ -505,7 +505,7 @@ Examples:
|
|
|
505
505
|
},
|
|
506
506
|
parameters: {},
|
|
507
507
|
loader: async () => {
|
|
508
|
-
const { listHandler } = await import("./listHandler-
|
|
508
|
+
const { listHandler } = await import("./listHandler-CKsZ1_8V.js");
|
|
509
509
|
return listHandler;
|
|
510
510
|
}
|
|
511
511
|
});
|
|
@@ -538,7 +538,7 @@ Examples:
|
|
|
538
538
|
flags: {}
|
|
539
539
|
},
|
|
540
540
|
loader: async () => {
|
|
541
|
-
const { selectHandler } = await import("./selectHandler-
|
|
541
|
+
const { selectHandler } = await import("./selectHandler-CdQ4t9Zd.js");
|
|
542
542
|
return selectHandler;
|
|
543
543
|
}
|
|
544
544
|
});
|
|
@@ -562,7 +562,7 @@ Examples:
|
|
|
562
562
|
|
|
563
563
|
//#endregion
|
|
564
564
|
//#region package.json
|
|
565
|
-
var version = "0.9.
|
|
565
|
+
var version = "0.9.13";
|
|
566
566
|
|
|
567
567
|
//#endregion
|
|
568
568
|
//#region src/app.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getApiUrl, getTeamId } from "./env-CHeKHu5S.js";
|
|
2
|
-
import { authGetProfileV1, configureClient, teamFindAllV1, testCaseImportCommitV1, testCaseImportPreviewV1 } from "./sdk-client-
|
|
2
|
+
import { authGetProfileV1, configureClient, teamFindAllV1, testCaseImportCommitV1, testCaseImportPreviewV1 } from "./sdk-client-QtfUKoae.js";
|
|
3
3
|
import "./workspace-store-DDOxnut1.js";
|
|
4
|
-
import { resolveWorkspace } from "./resolve-workspace-
|
|
4
|
+
import { resolveWorkspace } from "./resolve-workspace-febNMGo2.js";
|
|
5
5
|
import "./token-refresh-Cu5RpkLJ.js";
|
|
6
6
|
import { resolveToken } from "./resolve-token-DbQsmn03.js";
|
|
7
7
|
import chalk from "chalk";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./env-CHeKHu5S.js";
|
|
2
|
-
import { authGetSitesV1, configureClient } from "./sdk-client-
|
|
2
|
+
import { authGetSitesV1, configureClient } from "./sdk-client-QtfUKoae.js";
|
|
3
3
|
import { loadWorkspace } from "./workspace-store-DDOxnut1.js";
|
|
4
4
|
import "./token-refresh-Cu5RpkLJ.js";
|
|
5
5
|
import { resolveToken } from "./resolve-token-DbQsmn03.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CLI_CLIENT_ID, getApiUrl, getAuthUrl, setSessionApiUrl, writeCredentials } from "./env-CHeKHu5S.js";
|
|
2
|
-
import { configureClient } from "./sdk-client-
|
|
2
|
+
import { configureClient } from "./sdk-client-QtfUKoae.js";
|
|
3
3
|
import "./workspace-store-DDOxnut1.js";
|
|
4
|
-
import { autoSelectWorkspace } from "./resolve-workspace-
|
|
4
|
+
import { autoSelectWorkspace } from "./resolve-workspace-febNMGo2.js";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { createHash, randomBytes } from "node:crypto";
|
|
7
7
|
import { createServer } from "node:http";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getApiUrl, getAutomationSourceIdOverride, getSourceOverride, getTeamId } from "./env-CHeKHu5S.js";
|
|
2
|
-
import { client, configureClient, uploadAutomationIngest, uploadImport } from "./sdk-client-
|
|
2
|
+
import { client, configureClient, uploadAutomationIngest, uploadImport } from "./sdk-client-QtfUKoae.js";
|
|
3
3
|
import "./workspace-store-DDOxnut1.js";
|
|
4
|
-
import { resolveWorkspace } from "./resolve-workspace-
|
|
4
|
+
import { resolveWorkspace } from "./resolve-workspace-febNMGo2.js";
|
|
5
5
|
import "./token-refresh-Cu5RpkLJ.js";
|
|
6
6
|
import { resolveToken } from "./resolve-token-DbQsmn03.js";
|
|
7
7
|
import { readFileSync, statSync } from "node:fs";
|
|
@@ -12880,8 +12880,10 @@ const zFirstRunStatusDto = z.object({
|
|
|
12880
12880
|
state: z.enum([
|
|
12881
12881
|
"welcome",
|
|
12882
12882
|
"reminder",
|
|
12883
|
+
"land",
|
|
12883
12884
|
"hidden"
|
|
12884
12885
|
]),
|
|
12886
|
+
landingPath: z.enum(["/inbox", "/dashboard"]),
|
|
12885
12887
|
mcpUrl: z.string(),
|
|
12886
12888
|
cliCommand: z.string(),
|
|
12887
12889
|
connected: z.array(z.object({
|
|
@@ -12902,6 +12904,7 @@ const zFirstRunGetStatusV1Data = z.object({ url: z.literal("/v1/first-run/mcp")
|
|
|
12902
12904
|
const zFirstRunMarkSeenV1Data = z.object({ url: z.literal("/v1/first-run/mcp/seen") });
|
|
12903
12905
|
const zFirstRunDeclineV1Data = z.object({ url: z.literal("/v1/first-run/mcp/decline") });
|
|
12904
12906
|
const zFirstRunHideReminderV1Data = z.object({ url: z.literal("/v1/first-run/mcp/hide-reminder") });
|
|
12907
|
+
const zFirstRunMarkLandedV1Data = z.object({ url: z.literal("/v1/first-run/mcp/landed") });
|
|
12905
12908
|
|
|
12906
12909
|
//#endregion
|
|
12907
12910
|
//#region ../sdk/dist/gen/folder/zod.js
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./env-CHeKHu5S.js";
|
|
2
|
-
import { authGetSitesV1, configureClient } from "./sdk-client-
|
|
2
|
+
import { authGetSitesV1, configureClient } from "./sdk-client-QtfUKoae.js";
|
|
3
3
|
import { saveWorkspace } from "./workspace-store-DDOxnut1.js";
|
|
4
4
|
import "./token-refresh-Cu5RpkLJ.js";
|
|
5
5
|
import { resolveToken } from "./resolve-token-DbQsmn03.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getApiUrl, getPatToken, readCredentials } from "./env-CHeKHu5S.js";
|
|
2
|
-
import { authGetProfileV1, configureClient } from "./sdk-client-
|
|
2
|
+
import { authGetProfileV1, configureClient } from "./sdk-client-QtfUKoae.js";
|
|
3
3
|
import { isTokenExpired } from "./token-refresh-Cu5RpkLJ.js";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@levr-one/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.13",
|
|
4
4
|
"description": "The command-line interface for Levr",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"jsonc-parser": "^3.3.1",
|
|
32
32
|
"open": "^10.1.0",
|
|
33
33
|
"ora": "^9.0.0",
|
|
34
|
+
"smol-toml": "^1.8.0",
|
|
34
35
|
"zod": "^3.25.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|