deepseek-web-retrieval 0.7.0 → 0.9.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/CHANGELOG.md +20 -0
- package/README.md +100 -101
- package/dist/cli.js +526 -175
- package/docs/agents/claude-code.md +279 -0
- package/docs/agents/codex.md +9 -1
- package/docs/agents/opencode-v2.md +13 -3
- package/docs/agents/pi.md +8 -1
- package/docs/configuration.md +116 -0
- package/docs/design/installer-design.md +20 -17
- package/docs/hosts.jsonc +5 -1
- package/docs/integration.md +9 -6
- package/docs/research/control-matrix.md +34 -9
- package/docs/research/mcp-compatibility.md +2 -2
- package/docs/research/ori-question-sourcing.md +12 -1
- package/package.json +3 -2
- package/src/core/auth.ts +7 -0
- package/src/core/client.ts +89 -25
- package/src/core/config-summary.ts +1 -0
- package/src/core/constants.ts +17 -2
- package/src/core/descriptions.ts +10 -1
- package/src/core/env.ts +12 -7
- package/src/core/format.ts +25 -4
- package/src/core/options.ts +47 -0
- package/src/core/search.ts +97 -14
- package/src/core/types.ts +17 -0
- package/src/installer/apply.ts +4 -4
- package/src/installer/cli.ts +2 -2
- package/src/installer/config-command.ts +4 -3
- package/src/installer/hosts/claude-code.ts +311 -0
- package/src/installer/hosts/codex.ts +3 -14
- package/src/installer/hosts/index.ts +5 -2
- package/src/installer/hosts/opencode.ts +1 -1
- package/src/installer/hosts/pi.ts +38 -18
- package/src/installer/hosts/shared.ts +23 -0
- package/src/installer/ownership.ts +2 -2
- package/src/installer/paths.ts +2 -2
- package/src/installer/{state.ts → record.ts} +16 -16
- package/src/installer/types.ts +2 -2
- package/src/installer/uninstall.ts +8 -5
- package/src/mcp/serve.ts +1 -2
- package/src/mcp/server.ts +16 -19
- package/src/opencode/plugin.ts +15 -10
- package/src/pi/extension.ts +23 -17
- package/src/mcp/auth.ts +0 -11
package/dist/cli.js
CHANGED
|
@@ -16,10 +16,12 @@ var __export = (target, all) => {
|
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
18
|
// src/core/constants.ts
|
|
19
|
-
var
|
|
19
|
+
var DEFAULT_API_BASE_URL = "https://api.deepseek.com", SEARCH_ENDPOINT_PATH = "/v1/tools/search", FETCH_ENDPOINT_PATH = "/v1/tools/fetch", SEARCH_ENDPOINT, FETCH_ENDPOINT, DEFAULT_SEARCH_COUNT = 10, DEFAULT_MAX_RESULTS = 10, DEFAULT_MIN_RERANK = 0, DEFAULT_MIN_AUTHORITY = 0, DEFAULT_MAX_SUMMARY_CHARS = 1200, DEFAULT_FETCH_MAX_CHARS = 16000, MAX_FETCH_CHARS = 1e6, MAX_SEARCH_COUNT = 50, DEFAULT_TIMEOUT_MS = 30000, MIN_TIMEOUT_MS = 1000, MAX_TIMEOUT_MS = 120000, MAX_QUERY_CHARS = 128, MAX_ORI_QUESTION_CHARS = 128, MAX_URL_CHARS = 8192, MAX_ERROR_BODY_CHARS = 2000, MAX_RESPONSE_BYTES, FETCH_TRUNCATION_MARKER = `
|
|
20
20
|
|
|
21
|
-
[content truncated]`, DEFAULT_API_KEY_SOURCE = "auto", DEFAULT_MAX_RETRIES = 2, MAX_RETRIES = 5, DEFAULT_RETRY_BASE_DELAY_MS = 250, MAX_RETRY_DELAY_MS = 1e4, API_KEY_ENV = "DEEPSEEK_API_KEY", ENABLE_SEARCH_ENV = "DEEPSEEK_ENABLE_SEARCH", ENABLE_FETCH_ENV = "DEEPSEEK_ENABLE_FETCH", SEARCH_LOCALIZATION_ENV = "DEEPSEEK_SEARCH_LOCALIZATION";
|
|
21
|
+
[content truncated]`, DEFAULT_API_KEY_SOURCE = "auto", DEFAULT_MAX_RETRIES = 2, MAX_RETRIES = 5, DEFAULT_RETRY_BASE_DELAY_MS = 250, MAX_RETRY_DELAY_MS = 1e4, API_KEY_ENV = "DEEPSEEK_API_KEY", BASE_URL_ENV = "DEEPSEEK_BASE_URL", ENABLE_SEARCH_ENV = "DEEPSEEK_ENABLE_SEARCH", ENABLE_FETCH_ENV = "DEEPSEEK_ENABLE_FETCH", SEARCH_LOCALIZATION_ENV = "DEEPSEEK_SEARCH_LOCALIZATION";
|
|
22
22
|
var init_constants = __esm(() => {
|
|
23
|
+
SEARCH_ENDPOINT = `${DEFAULT_API_BASE_URL}${SEARCH_ENDPOINT_PATH}`;
|
|
24
|
+
FETCH_ENDPOINT = `${DEFAULT_API_BASE_URL}${FETCH_ENDPOINT_PATH}`;
|
|
23
25
|
MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
24
26
|
});
|
|
25
27
|
|
|
@@ -138,9 +140,15 @@ function resolvePluginOptions(input) {
|
|
|
138
140
|
search: resolveSearchOptions(input),
|
|
139
141
|
fetch: resolveFetchOptions(input),
|
|
140
142
|
auth: resolveAuthOptions(input),
|
|
141
|
-
retry: resolveRetryOptions(input)
|
|
143
|
+
retry: resolveRetryOptions(input),
|
|
144
|
+
baseUrl: resolveBaseUrl(input?.baseUrl)
|
|
142
145
|
};
|
|
143
146
|
}
|
|
147
|
+
function bothDisabledPrecheck(config, reason) {
|
|
148
|
+
if (config.search.enabled || config.fetch.enabled)
|
|
149
|
+
return;
|
|
150
|
+
return `Both tools are disabled (${reason}); at least one must stay enabled.`;
|
|
151
|
+
}
|
|
144
152
|
function resolveSearchOptions(input) {
|
|
145
153
|
const count = clampInt(input?.count, DEFAULT_SEARCH_COUNT, 1, 50);
|
|
146
154
|
return {
|
|
@@ -180,6 +188,28 @@ function resolveRetryOptions(input) {
|
|
|
180
188
|
baseDelayMs: clampInt(input?.retryBaseDelayMs, DEFAULT_RETRY_BASE_DELAY_MS, 0, MAX_RETRY_DELAY_MS)
|
|
181
189
|
};
|
|
182
190
|
}
|
|
191
|
+
function resolveBaseUrl(value) {
|
|
192
|
+
if (typeof value !== "string")
|
|
193
|
+
return DEFAULT_API_BASE_URL;
|
|
194
|
+
const trimmed = value.trim();
|
|
195
|
+
if (!trimmed)
|
|
196
|
+
return DEFAULT_API_BASE_URL;
|
|
197
|
+
let parsed;
|
|
198
|
+
try {
|
|
199
|
+
parsed = new URL(trimmed);
|
|
200
|
+
} catch {
|
|
201
|
+
return DEFAULT_API_BASE_URL;
|
|
202
|
+
}
|
|
203
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
204
|
+
return DEFAULT_API_BASE_URL;
|
|
205
|
+
return trimmed.replace(/\/+$/, "");
|
|
206
|
+
}
|
|
207
|
+
function withPerCallCount(options, count) {
|
|
208
|
+
if (count === undefined || !Number.isFinite(count))
|
|
209
|
+
return options;
|
|
210
|
+
const clamped = Math.min(MAX_SEARCH_COUNT, Math.max(1, Math.trunc(count)));
|
|
211
|
+
return { ...options, count: clamped, maxResults: clamped };
|
|
212
|
+
}
|
|
183
213
|
function optionalText(value) {
|
|
184
214
|
if (typeof value !== "string")
|
|
185
215
|
return;
|
|
@@ -256,7 +286,8 @@ var init_config_summary = __esm(() => {
|
|
|
256
286
|
{ key: "fetchTimeoutMs", read: (o) => o.fetch.timeoutMs },
|
|
257
287
|
{ key: "fetchBlockPrivateHosts", read: (o) => o.fetch.blockPrivateHosts },
|
|
258
288
|
{ key: "maxRetries", read: (o) => o.retry.maxRetries },
|
|
259
|
-
{ key: "retryBaseDelayMs", read: (o) => o.retry.baseDelayMs }
|
|
289
|
+
{ key: "retryBaseDelayMs", read: (o) => o.retry.baseDelayMs },
|
|
290
|
+
{ key: "baseUrl", read: (o) => o.baseUrl }
|
|
260
291
|
];
|
|
261
292
|
CONFIG_KEYS = VOCABULARY.map((entry) => entry.key);
|
|
262
293
|
});
|
|
@@ -3415,55 +3446,6 @@ var init_formats = __esm(() => {
|
|
|
3415
3446
|
init_toml();
|
|
3416
3447
|
});
|
|
3417
3448
|
|
|
3418
|
-
// src/installer/revert.ts
|
|
3419
|
-
import { existsSync as existsSync4, readFileSync as readFileSync7, rmdirSync, unlinkSync } from "node:fs";
|
|
3420
|
-
import { dirname as dirname2 } from "node:path";
|
|
3421
|
-
function pruneEmptyDir(dir) {
|
|
3422
|
-
try {
|
|
3423
|
-
rmdirSync(dir);
|
|
3424
|
-
} catch {}
|
|
3425
|
-
}
|
|
3426
|
-
function revertRecordedFile(file, host, opts) {
|
|
3427
|
-
const lines = [];
|
|
3428
|
-
if (!existsSync4(file.file)) {
|
|
3429
|
-
lines.push(`already absent: ${file.file}${opts.label}`);
|
|
3430
|
-
return lines;
|
|
3431
|
-
}
|
|
3432
|
-
const codec = codecFor(file.format, host);
|
|
3433
|
-
const text = readFileSync7(file.file, "utf8");
|
|
3434
|
-
if (file.format === "rules") {
|
|
3435
|
-
const edit2 = { file: file.file, format: "rules", block: file.blockApplied ?? "" };
|
|
3436
|
-
if (!codec.verify(text, edit2)) {
|
|
3437
|
-
lines.push(`no rules block${opts.label} in ${file.file}; left untouched`);
|
|
3438
|
-
return lines;
|
|
3439
|
-
}
|
|
3440
|
-
const updated = codec.revert(text, file).text;
|
|
3441
|
-
if (opts.whole && codec.isEffectivelyEmpty(updated)) {
|
|
3442
|
-
unlinkSync(file.file);
|
|
3443
|
-
pruneEmptyDir(dirname2(file.file));
|
|
3444
|
-
lines.push(`deleted ${file.file} (installer-created, now empty)`);
|
|
3445
|
-
} else {
|
|
3446
|
-
writeConfig(file.file, updated);
|
|
3447
|
-
lines.push(`removed rules block${opts.label} from ${file.file}`);
|
|
3448
|
-
}
|
|
3449
|
-
return lines;
|
|
3450
|
-
}
|
|
3451
|
-
const reverted = codec.revert(text, file);
|
|
3452
|
-
if (opts.whole && file.created && codec.isEffectivelyEmpty(reverted.text)) {
|
|
3453
|
-
unlinkSync(file.file);
|
|
3454
|
-
pruneEmptyDir(dirname2(file.file));
|
|
3455
|
-
lines.push(`deleted ${file.file} (installer-created, now empty)`);
|
|
3456
|
-
} else {
|
|
3457
|
-
writeConfig(file.file, reverted.text);
|
|
3458
|
-
lines.push(`reverted artifacts${opts.label} in ${file.file}`);
|
|
3459
|
-
}
|
|
3460
|
-
lines.push(...reverted.notes.map((note) => ` note: ${note}`));
|
|
3461
|
-
return lines;
|
|
3462
|
-
}
|
|
3463
|
-
var init_revert = __esm(() => {
|
|
3464
|
-
init_formats();
|
|
3465
|
-
});
|
|
3466
|
-
|
|
3467
3449
|
// src/installer/paths.ts
|
|
3468
3450
|
import { join as pathJoin } from "node:path";
|
|
3469
3451
|
function join2(...parts) {
|
|
@@ -3473,35 +3455,35 @@ function join2(...parts) {
|
|
|
3473
3455
|
function configHome(roots) {
|
|
3474
3456
|
return roots.configHome ?? join2(roots.home, ".config");
|
|
3475
3457
|
}
|
|
3476
|
-
function
|
|
3458
|
+
function recordDir(roots, scope) {
|
|
3477
3459
|
return scope === "global" ? join2(configHome(roots), "deepseek-web-retrieval") : join2(roots.cwd, ".deepseek-web-retrieval");
|
|
3478
3460
|
}
|
|
3479
3461
|
var init_paths = () => {};
|
|
3480
3462
|
|
|
3481
|
-
// src/installer/
|
|
3482
|
-
import { existsSync as
|
|
3463
|
+
// src/installer/record.ts
|
|
3464
|
+
import { existsSync as existsSync4, lstatSync as lstatSync2, readdirSync, readFileSync as readFileSync7, rmSync as rmSync2 } from "node:fs";
|
|
3483
3465
|
import { isAbsolute, join as join3 } from "node:path";
|
|
3484
3466
|
import { z } from "zod/v4";
|
|
3485
|
-
function
|
|
3486
|
-
return join3(
|
|
3467
|
+
function recordPathFor(input) {
|
|
3468
|
+
return join3(recordDir(input, input.scope), `install.${input.host}.json`);
|
|
3487
3469
|
}
|
|
3488
3470
|
function platformOf(roots) {
|
|
3489
3471
|
return roots.platform ?? process.platform;
|
|
3490
3472
|
}
|
|
3491
|
-
function
|
|
3473
|
+
function legacyGlobalRecordDir(roots) {
|
|
3492
3474
|
return join3(roots.home, ".config", "deepseek-web-retrieval");
|
|
3493
3475
|
}
|
|
3494
3476
|
function recordReadPath(input, platform = platformOf(input)) {
|
|
3495
|
-
const canonical =
|
|
3496
|
-
if (platform !== "win32" || input.scope !== "global" ||
|
|
3477
|
+
const canonical = recordPathFor(input);
|
|
3478
|
+
if (platform !== "win32" || input.scope !== "global" || existsSync4(canonical))
|
|
3497
3479
|
return canonical;
|
|
3498
|
-
const legacy = join3(
|
|
3499
|
-
return
|
|
3480
|
+
const legacy = join3(legacyGlobalRecordDir(input), `install.${input.host}.json`);
|
|
3481
|
+
return existsSync4(legacy) ? legacy : canonical;
|
|
3500
3482
|
}
|
|
3501
3483
|
function readRecordFile(file, host, scope) {
|
|
3502
3484
|
let text;
|
|
3503
3485
|
try {
|
|
3504
|
-
text =
|
|
3486
|
+
text = readFileSync7(file, "utf8");
|
|
3505
3487
|
} catch (error) {
|
|
3506
3488
|
if (error.code === "ENOENT") {
|
|
3507
3489
|
try {
|
|
@@ -3539,7 +3521,7 @@ function claimedByOtherScope(input, files) {
|
|
|
3539
3521
|
return files.filter((file) => record.files.some((candidate) => candidate.file === file));
|
|
3540
3522
|
}
|
|
3541
3523
|
function writeRecord(input, record) {
|
|
3542
|
-
const file =
|
|
3524
|
+
const file = recordPathFor(input);
|
|
3543
3525
|
atomicWrite(file, `${JSON.stringify(record, null, 2)}
|
|
3544
3526
|
`, 384);
|
|
3545
3527
|
}
|
|
@@ -3559,7 +3541,7 @@ function appendedByPrevious(record, path) {
|
|
|
3559
3541
|
}
|
|
3560
3542
|
function deleteRecord(input) {
|
|
3561
3543
|
const file = recordReadPath(input);
|
|
3562
|
-
if (
|
|
3544
|
+
if (existsSync4(file))
|
|
3563
3545
|
rmSync2(file);
|
|
3564
3546
|
}
|
|
3565
3547
|
function readRecordsFromDir(dir, scope) {
|
|
@@ -3591,18 +3573,18 @@ function hasRecordFiles(dir) {
|
|
|
3591
3573
|
function readingLegacyGlobalRecords(roots) {
|
|
3592
3574
|
if (platformOf(roots) !== "win32")
|
|
3593
3575
|
return false;
|
|
3594
|
-
if (hasRecordFiles(
|
|
3576
|
+
if (hasRecordFiles(recordDir(roots, "global")))
|
|
3595
3577
|
return false;
|
|
3596
|
-
return hasRecordFiles(
|
|
3578
|
+
return hasRecordFiles(legacyGlobalRecordDir(roots));
|
|
3597
3579
|
}
|
|
3598
3580
|
function listRecords(roots) {
|
|
3599
|
-
const global = readRecordsFromDir(
|
|
3600
|
-
const project = readRecordsFromDir(
|
|
3601
|
-
const legacy = readingLegacyGlobalRecords(roots) ? readRecordsFromDir(
|
|
3581
|
+
const global = readRecordsFromDir(recordDir(roots, "global"), "global");
|
|
3582
|
+
const project = readRecordsFromDir(recordDir(roots, "project"), "project");
|
|
3583
|
+
const legacy = readingLegacyGlobalRecords(roots) ? readRecordsFromDir(legacyGlobalRecordDir(roots), "global") : [];
|
|
3602
3584
|
return [...global, ...legacy, ...project];
|
|
3603
3585
|
}
|
|
3604
3586
|
var keyPath, tier, operation, recordSchema, InstallRecordError;
|
|
3605
|
-
var
|
|
3587
|
+
var init_record = __esm(() => {
|
|
3606
3588
|
init_paths();
|
|
3607
3589
|
init_persistence();
|
|
3608
3590
|
keyPath = z.array(z.string().min(1)).min(1);
|
|
@@ -3619,7 +3601,7 @@ var init_state = __esm(() => {
|
|
|
3619
3601
|
recordSchema = z.object({
|
|
3620
3602
|
version: z.literal(1),
|
|
3621
3603
|
installerVersion: z.string().optional(),
|
|
3622
|
-
host: z.enum(["opencode", "pi", "codex"]),
|
|
3604
|
+
host: z.enum(["opencode", "pi", "codex", "claude-code"]),
|
|
3623
3605
|
scope: z.enum(["global", "project"]),
|
|
3624
3606
|
tiers: z.object({ search: tier, fetch: tier }),
|
|
3625
3607
|
createdAt: z.string().refine((value) => Number.isFinite(Date.parse(value))),
|
|
@@ -3637,7 +3619,7 @@ var init_state = __esm(() => {
|
|
|
3637
3619
|
file;
|
|
3638
3620
|
kind;
|
|
3639
3621
|
constructor(file, kind, detail) {
|
|
3640
|
-
super(`${kind === "invalid" ? "Invalid install record" : "Cannot read install record"} at ${file} (${detail}). ` + "Restore a valid record from
|
|
3622
|
+
super(`${kind === "invalid" ? "Invalid install record" : "Cannot read install record"} at ${file} (${detail}). ` + "Restore a valid install record from your own copy or repair its permissions before retrying; configuration was not changed. Do not delete the install record to bypass this check.");
|
|
3641
3623
|
this.file = file;
|
|
3642
3624
|
this.kind = kind;
|
|
3643
3625
|
this.name = "InstallRecordError";
|
|
@@ -3645,6 +3627,55 @@ var init_state = __esm(() => {
|
|
|
3645
3627
|
};
|
|
3646
3628
|
});
|
|
3647
3629
|
|
|
3630
|
+
// src/installer/revert.ts
|
|
3631
|
+
import { existsSync as existsSync5, readFileSync as readFileSync8, rmdirSync, unlinkSync } from "node:fs";
|
|
3632
|
+
import { dirname as dirname2 } from "node:path";
|
|
3633
|
+
function pruneEmptyDir(dir) {
|
|
3634
|
+
try {
|
|
3635
|
+
rmdirSync(dir);
|
|
3636
|
+
} catch {}
|
|
3637
|
+
}
|
|
3638
|
+
function revertRecordedFile(file, host, opts) {
|
|
3639
|
+
const lines = [];
|
|
3640
|
+
if (!existsSync5(file.file)) {
|
|
3641
|
+
lines.push(`already absent: ${file.file}${opts.label}`);
|
|
3642
|
+
return lines;
|
|
3643
|
+
}
|
|
3644
|
+
const codec = codecFor(file.format, host);
|
|
3645
|
+
const text = readFileSync8(file.file, "utf8");
|
|
3646
|
+
if (file.format === "rules") {
|
|
3647
|
+
const edit2 = { file: file.file, format: "rules", block: file.blockApplied ?? "" };
|
|
3648
|
+
if (!codec.verify(text, edit2)) {
|
|
3649
|
+
lines.push(`no rules block${opts.label} in ${file.file}; left untouched`);
|
|
3650
|
+
return lines;
|
|
3651
|
+
}
|
|
3652
|
+
const updated = codec.revert(text, file).text;
|
|
3653
|
+
if (opts.whole && codec.isEffectivelyEmpty(updated)) {
|
|
3654
|
+
unlinkSync(file.file);
|
|
3655
|
+
pruneEmptyDir(dirname2(file.file));
|
|
3656
|
+
lines.push(`deleted ${file.file} (installer-created, now empty)`);
|
|
3657
|
+
} else {
|
|
3658
|
+
writeConfig(file.file, updated);
|
|
3659
|
+
lines.push(`removed rules block${opts.label} from ${file.file}`);
|
|
3660
|
+
}
|
|
3661
|
+
return lines;
|
|
3662
|
+
}
|
|
3663
|
+
const reverted = codec.revert(text, file);
|
|
3664
|
+
if (opts.whole && file.created && codec.isEffectivelyEmpty(reverted.text)) {
|
|
3665
|
+
unlinkSync(file.file);
|
|
3666
|
+
pruneEmptyDir(dirname2(file.file));
|
|
3667
|
+
lines.push(`deleted ${file.file} (installer-created, now empty)`);
|
|
3668
|
+
} else {
|
|
3669
|
+
writeConfig(file.file, reverted.text);
|
|
3670
|
+
lines.push(`reverted artifacts${opts.label} in ${file.file}`);
|
|
3671
|
+
}
|
|
3672
|
+
lines.push(...reverted.notes.map((note) => ` note: ${note}`));
|
|
3673
|
+
return lines;
|
|
3674
|
+
}
|
|
3675
|
+
var init_revert = __esm(() => {
|
|
3676
|
+
init_formats();
|
|
3677
|
+
});
|
|
3678
|
+
|
|
3648
3679
|
// src/installer/ownership.ts
|
|
3649
3680
|
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3650
3681
|
import { basename as basename2, dirname as dirname3, join as join4, resolve } from "node:path";
|
|
@@ -3672,7 +3703,7 @@ function canonicalPath(file) {
|
|
|
3672
3703
|
return join4(canonicalPath(dirname3(absolute)), basename2(absolute));
|
|
3673
3704
|
}
|
|
3674
3705
|
function ownerOf(input) {
|
|
3675
|
-
return { recordPath: canonicalPath(
|
|
3706
|
+
return { recordPath: canonicalPath(recordPathFor(input)), scope: input.scope, cwd: canonicalPath(input.cwd) };
|
|
3676
3707
|
}
|
|
3677
3708
|
function claimPaths(input, files) {
|
|
3678
3709
|
return [
|
|
@@ -3748,8 +3779,8 @@ function releaseFileOwnership(input, files) {
|
|
|
3748
3779
|
releaseClaim(input, claim);
|
|
3749
3780
|
}
|
|
3750
3781
|
var init_ownership = __esm(() => {
|
|
3782
|
+
init_record();
|
|
3751
3783
|
init_revert();
|
|
3752
|
-
init_state();
|
|
3753
3784
|
});
|
|
3754
3785
|
|
|
3755
3786
|
// src/installer/operation-lock.ts
|
|
@@ -3808,7 +3839,7 @@ function applyPlan(plan, input) {
|
|
|
3808
3839
|
if (plan.errors.length > 0) {
|
|
3809
3840
|
throw new Error(`refusing to apply a plan with errors: ${plan.errors.map((error) => error.message).join("; ")}`);
|
|
3810
3841
|
}
|
|
3811
|
-
return withOperationLock([
|
|
3842
|
+
return withOperationLock([recordPathFor(input)], () => withOperationLock([...plan.edits, ...plan.reverts ?? []].map((file) => file.file), () => applyLocked(plan, input)));
|
|
3812
3843
|
}
|
|
3813
3844
|
function applyLocked(plan, input) {
|
|
3814
3845
|
const touched = [...plan.edits, ...plan.reverts ?? []];
|
|
@@ -3934,7 +3965,7 @@ function rollbackApply(input, previous, snapshots) {
|
|
|
3934
3965
|
writeRecord(input, previous);
|
|
3935
3966
|
else
|
|
3936
3967
|
deleteRecord(input);
|
|
3937
|
-
if (previous === undefined ? existsSync7(
|
|
3968
|
+
if (previous === undefined ? existsSync7(recordPathFor(input)) : !deepEqual(readRecord(input), previous)) {
|
|
3938
3969
|
throw new Error("restored install record differs from its previous state");
|
|
3939
3970
|
}
|
|
3940
3971
|
} catch (restoreError) {
|
|
@@ -4033,8 +4064,8 @@ var init_apply = __esm(() => {
|
|
|
4033
4064
|
init_value();
|
|
4034
4065
|
init_operation_lock();
|
|
4035
4066
|
init_ownership();
|
|
4067
|
+
init_record();
|
|
4036
4068
|
init_revert();
|
|
4037
|
-
init_state();
|
|
4038
4069
|
ApplyError = class ApplyError extends Error {
|
|
4039
4070
|
status;
|
|
4040
4071
|
constructor(status, message, cause) {
|
|
@@ -4050,6 +4081,12 @@ function apiKeyFromEnv(env = process.env) {
|
|
|
4050
4081
|
const key = env[API_KEY_ENV]?.trim();
|
|
4051
4082
|
return key ? key : undefined;
|
|
4052
4083
|
}
|
|
4084
|
+
function requireApiKey(env) {
|
|
4085
|
+
const apiKey = apiKeyFromEnv(env);
|
|
4086
|
+
if (!apiKey)
|
|
4087
|
+
throw new Error(MISSING_API_KEY_MESSAGE);
|
|
4088
|
+
return apiKey;
|
|
4089
|
+
}
|
|
4053
4090
|
var MISSING_API_KEY_MESSAGE;
|
|
4054
4091
|
var init_auth = __esm(() => {
|
|
4055
4092
|
init_constants();
|
|
@@ -4113,10 +4150,22 @@ function formatApiError(operation2, status, body) {
|
|
|
4113
4150
|
function formatTimeoutError(operation2, timeoutMs) {
|
|
4114
4151
|
return `${operation2} timed out after ${timeoutMs} ms`;
|
|
4115
4152
|
}
|
|
4116
|
-
function
|
|
4117
|
-
if (
|
|
4118
|
-
return
|
|
4119
|
-
|
|
4153
|
+
function searchHeader(context) {
|
|
4154
|
+
if (!context)
|
|
4155
|
+
return;
|
|
4156
|
+
if (context.effectiveQuery === context.inputQuery && context.notes.length === 0)
|
|
4157
|
+
return;
|
|
4158
|
+
const header = `Searched for: ${context.effectiveQuery}`;
|
|
4159
|
+
return context.notes.length > 0 ? `${header} — ${context.notes.join("; ")}` : header;
|
|
4160
|
+
}
|
|
4161
|
+
function formatSearchResults(results, context) {
|
|
4162
|
+
const header = searchHeader(context);
|
|
4163
|
+
if (results.length === 0) {
|
|
4164
|
+
return header ? `${header}
|
|
4165
|
+
|
|
4166
|
+
No results found.` : "No results found.";
|
|
4167
|
+
}
|
|
4168
|
+
const body = results.map((result, index) => {
|
|
4120
4169
|
const title = result.title?.trim() || result.url;
|
|
4121
4170
|
const marker = formatQualityMarker(result);
|
|
4122
4171
|
const lines = [`${index + 1}. [${escapeBrackets(title)}](${result.url})${marker ? ` ${marker}` : ""}`];
|
|
@@ -4132,6 +4181,9 @@ function formatSearchResults(results) {
|
|
|
4132
4181
|
}).join(`
|
|
4133
4182
|
|
|
4134
4183
|
`);
|
|
4184
|
+
return header ? `${header}
|
|
4185
|
+
|
|
4186
|
+
${body}` : body;
|
|
4135
4187
|
}
|
|
4136
4188
|
function formatQualityMarker(result) {
|
|
4137
4189
|
const parts = [];
|
|
@@ -4212,17 +4264,62 @@ var init_url = __esm(() => {
|
|
|
4212
4264
|
});
|
|
4213
4265
|
|
|
4214
4266
|
// src/core/search.ts
|
|
4215
|
-
function
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
const
|
|
4220
|
-
|
|
4221
|
-
parts.push(include.map((domain) => `site:${domain}`).join(" OR "));
|
|
4267
|
+
function charCount(value) {
|
|
4268
|
+
return Array.from(value).length;
|
|
4269
|
+
}
|
|
4270
|
+
function operatorParts(options) {
|
|
4271
|
+
const blocked = normalizeDomains(options.blockedDomains).map((domain) => `-site:${domain}`);
|
|
4272
|
+
const include = normalizeDomains(options.includeDomains).map((domain) => `site:${domain}`);
|
|
4222
4273
|
const language = options.language?.trim().replace(/^lang:/i, "");
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4274
|
+
return {
|
|
4275
|
+
blocked,
|
|
4276
|
+
include: include.length ? include.join(" OR ") : undefined,
|
|
4277
|
+
language: language ? `lang:${language}` : undefined
|
|
4278
|
+
};
|
|
4279
|
+
}
|
|
4280
|
+
function assemble(body, parts, withInclude, withLang) {
|
|
4281
|
+
const segments = [body, ...parts.blocked];
|
|
4282
|
+
if (withInclude && parts.include)
|
|
4283
|
+
segments.push(parts.include);
|
|
4284
|
+
if (withLang && parts.language)
|
|
4285
|
+
segments.push(parts.language);
|
|
4286
|
+
return segments.filter((segment) => segment.length > 0).join(" ");
|
|
4287
|
+
}
|
|
4288
|
+
function bodyBudget(parts, withInclude, withLang, maxChars) {
|
|
4289
|
+
const fixed = [
|
|
4290
|
+
...parts.blocked,
|
|
4291
|
+
...withInclude && parts.include ? [parts.include] : [],
|
|
4292
|
+
...withLang && parts.language ? [parts.language] : []
|
|
4293
|
+
].join(" ");
|
|
4294
|
+
return maxChars - (fixed ? charCount(fixed) + 1 : 0);
|
|
4295
|
+
}
|
|
4296
|
+
function composeSearchQuery(query, options, maxChars = MAX_QUERY_CHARS) {
|
|
4297
|
+
const parts = operatorParts(options);
|
|
4298
|
+
const body = query.trim();
|
|
4299
|
+
const notes = [];
|
|
4300
|
+
const attempt = (withInclude, withLang) => {
|
|
4301
|
+
const budget = bodyBudget(parts, withInclude, withLang, maxChars);
|
|
4302
|
+
if (charCount(body) <= budget)
|
|
4303
|
+
return assemble(body, parts, withInclude, withLang);
|
|
4304
|
+
if (budget >= MIN_BODY_CHARS) {
|
|
4305
|
+
notes.push(`query truncated to ${budget} characters (query length limit)`);
|
|
4306
|
+
return assemble(truncate(body, budget), parts, withInclude, withLang);
|
|
4307
|
+
}
|
|
4308
|
+
return;
|
|
4309
|
+
};
|
|
4310
|
+
let assembled = attempt(true, true);
|
|
4311
|
+
if (assembled === undefined && parts.language) {
|
|
4312
|
+
notes.push("language filter dropped (query length limit)");
|
|
4313
|
+
assembled = attempt(true, false);
|
|
4314
|
+
}
|
|
4315
|
+
if (assembled === undefined && parts.include) {
|
|
4316
|
+
notes.push("includeDomains filter dropped (query length limit)");
|
|
4317
|
+
assembled = attempt(false, false);
|
|
4318
|
+
}
|
|
4319
|
+
if (assembled === undefined) {
|
|
4320
|
+
throw new Error(`DeepSeek search query cannot fit ${maxChars} characters: the blockedDomains -site: ` + `operators alone occupy ${charCount(parts.blocked.join(" "))} characters`);
|
|
4321
|
+
}
|
|
4322
|
+
return { query: assembled, notes };
|
|
4226
4323
|
}
|
|
4227
4324
|
function parsePublished(value) {
|
|
4228
4325
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -4257,7 +4354,9 @@ function processResults(raw, options) {
|
|
|
4257
4354
|
continue;
|
|
4258
4355
|
}
|
|
4259
4356
|
seen.add(url);
|
|
4260
|
-
const
|
|
4357
|
+
const rawContent = optionalText2(item.summary) ?? optionalText2(item.snippet) ?? "";
|
|
4358
|
+
const cutContent = truncate(rawContent, options.maxSummaryChars);
|
|
4359
|
+
const content = cutContent && charCount(rawContent) > options.maxSummaryChars ? `${cutContent}…` : cutContent;
|
|
4261
4360
|
const published = parsePublished(item.date);
|
|
4262
4361
|
const title = optionalText2(item.title);
|
|
4263
4362
|
const rerankScore = typeof rerank === "number" && Number.isFinite(rerank) ? rerank : undefined;
|
|
@@ -4281,7 +4380,9 @@ function optionalText2(value) {
|
|
|
4281
4380
|
const trimmed = value.trim();
|
|
4282
4381
|
return trimmed ? trimmed : undefined;
|
|
4283
4382
|
}
|
|
4383
|
+
var MIN_BODY_CHARS = 8;
|
|
4284
4384
|
var init_search = __esm(() => {
|
|
4385
|
+
init_constants();
|
|
4285
4386
|
init_domains();
|
|
4286
4387
|
init_format2();
|
|
4287
4388
|
init_url();
|
|
@@ -4487,25 +4588,41 @@ var init_transport = __esm(() => {
|
|
|
4487
4588
|
function effectiveTimeout(configuredMs, perCallMs) {
|
|
4488
4589
|
return perCallMs === undefined ? configuredMs : Math.min(configuredMs, perCallMs);
|
|
4489
4590
|
}
|
|
4591
|
+
function effectiveMaxChars(configured, perCall) {
|
|
4592
|
+
if (perCall === undefined || !Number.isFinite(perCall))
|
|
4593
|
+
return configured;
|
|
4594
|
+
return Math.min(MAX_FETCH_CHARS, Math.max(0, Math.trunc(perCall)));
|
|
4595
|
+
}
|
|
4596
|
+
function apiUrl(baseUrl, path) {
|
|
4597
|
+
return `${baseUrl ?? DEFAULT_API_BASE_URL}${path}`;
|
|
4598
|
+
}
|
|
4490
4599
|
function oriQuestionValue(oriQuestion) {
|
|
4491
4600
|
const trimmed = oriQuestion?.trim();
|
|
4492
4601
|
return trimmed ? truncate(trimmed, MAX_ORI_QUESTION_CHARS) : undefined;
|
|
4493
4602
|
}
|
|
4603
|
+
function requestContext(config, apiKey, signal, perCall) {
|
|
4604
|
+
return {
|
|
4605
|
+
signal,
|
|
4606
|
+
auth: { apiKey },
|
|
4607
|
+
retry: config.retry,
|
|
4608
|
+
baseUrl: config.baseUrl,
|
|
4609
|
+
...perCall?.timeoutMs === undefined ? {} : { timeoutMs: perCall.timeoutMs },
|
|
4610
|
+
...perCall?.maxChars === undefined ? {} : { maxChars: perCall.maxChars }
|
|
4611
|
+
};
|
|
4612
|
+
}
|
|
4494
4613
|
async function searchWeb(query, options, request) {
|
|
4495
4614
|
const trimmed = query.trim();
|
|
4496
4615
|
if (!trimmed)
|
|
4497
4616
|
throw new Error("DeepSeek search query must not be empty");
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
const builtQuery = buildQuery(trimmed, options);
|
|
4502
|
-
const builtQueryLength = Array.from(builtQuery).length;
|
|
4503
|
-
if (builtQueryLength > MAX_QUERY_CHARS) {
|
|
4504
|
-
throw new Error(`DeepSeek search query must be ${MAX_QUERY_CHARS} characters or fewer after applying ` + `blockedDomains, includeDomains, and language ` + `(built query is ${builtQueryLength} characters)`);
|
|
4617
|
+
const trimmedLength = Array.from(trimmed).length;
|
|
4618
|
+
if (trimmedLength > MAX_QUERY_CHARS) {
|
|
4619
|
+
throw new Error(`DeepSeek search query must be ${MAX_QUERY_CHARS} characters or fewer ` + `(got ${trimmedLength}: ${JSON.stringify(truncate(trimmed, 64))})`);
|
|
4505
4620
|
}
|
|
4621
|
+
const composed = composeSearchQuery(trimmed, options);
|
|
4622
|
+
const builtQuery = composed.query;
|
|
4506
4623
|
const timeout = effectiveTimeout(options.timeoutMs, request.timeoutMs);
|
|
4507
4624
|
const oriQuestion = oriQuestionValue(request.oriQuestion);
|
|
4508
|
-
const payload = await postJson(
|
|
4625
|
+
const payload = await postJson(apiUrl(request.baseUrl, SEARCH_ENDPOINT_PATH), {
|
|
4509
4626
|
query: builtQuery,
|
|
4510
4627
|
count: options.count,
|
|
4511
4628
|
need_content: false,
|
|
@@ -4515,13 +4632,19 @@ async function searchWeb(query, options, request) {
|
|
|
4515
4632
|
if (!Array.isArray(payload.results)) {
|
|
4516
4633
|
throw new Error("DeepSeek Search returned a response without a results array");
|
|
4517
4634
|
}
|
|
4518
|
-
|
|
4635
|
+
const results = processResults(payload.results, options);
|
|
4636
|
+
return {
|
|
4637
|
+
results,
|
|
4638
|
+
inputQuery: trimmed,
|
|
4639
|
+
effectiveQuery: builtQuery,
|
|
4640
|
+
notes: composed.notes
|
|
4641
|
+
};
|
|
4519
4642
|
}
|
|
4520
4643
|
async function fetchPage(url, options, request) {
|
|
4521
4644
|
const target = parseFetchUrl(url.href);
|
|
4522
4645
|
assertFetchHostPolicy(target, options);
|
|
4523
4646
|
const timeout = effectiveTimeout(options.timeoutMs, request.timeoutMs);
|
|
4524
|
-
const payload = await postJson(
|
|
4647
|
+
const payload = await postJson(apiUrl(request.baseUrl, FETCH_ENDPOINT_PATH), { url: target.href }, "DeepSeek Fetch", {
|
|
4525
4648
|
...request,
|
|
4526
4649
|
timeoutMs: timeout
|
|
4527
4650
|
});
|
|
@@ -4536,7 +4659,7 @@ async function fetchDocument(url, options, request) {
|
|
|
4536
4659
|
if (typeof url !== "string" && !(url instanceof URL))
|
|
4537
4660
|
throw new Error('"url" must be a string or URL');
|
|
4538
4661
|
const page = await fetchPage(url instanceof URL ? url : parseFetchUrl(url), options, request);
|
|
4539
|
-
const formatted = formatFetchedContent(page.title, page.content, options.maxChars);
|
|
4662
|
+
const formatted = formatFetchedContent(page.title, page.content, effectiveMaxChars(options.maxChars, request.maxChars));
|
|
4540
4663
|
return {
|
|
4541
4664
|
url: page.url,
|
|
4542
4665
|
title: page.title,
|
|
@@ -4591,6 +4714,7 @@ function envOptionsReport(env = process.env) {
|
|
|
4591
4714
|
putFalseOnly(options, "fetchEnabled", env, warnings);
|
|
4592
4715
|
putNumber(options, "maxRetries", env, warnings);
|
|
4593
4716
|
putNumber(options, "retryBaseDelayMs", env, warnings);
|
|
4717
|
+
putText(options, "baseUrl", env);
|
|
4594
4718
|
const known = new Set(OPTION_ENV_VARS);
|
|
4595
4719
|
for (const key of Object.keys(env)) {
|
|
4596
4720
|
if (!key.startsWith("DEEPSEEK_") || key === API_KEY_ENV || known.has(key))
|
|
@@ -4656,7 +4780,7 @@ function isFalseValue(value) {
|
|
|
4656
4780
|
const trimmed = value?.trim().toLowerCase();
|
|
4657
4781
|
return trimmed === "false" || trimmed === "0" || trimmed === "no" || trimmed === "off";
|
|
4658
4782
|
}
|
|
4659
|
-
var NUMBER_ENV, TEXT_ENV, DOMAINS_ENV, FALSE_ONLY_ENV, OPTION_ENV_VARS;
|
|
4783
|
+
var NUMBER_ENV, TEXT_ENV, DOMAINS_ENV, FALSE_ONLY_ENV, OPTION_ENV_BY_KEY, OPTION_ENV_VARS;
|
|
4660
4784
|
var init_env = __esm(() => {
|
|
4661
4785
|
init_constants();
|
|
4662
4786
|
init_domains();
|
|
@@ -4675,7 +4799,8 @@ var init_env = __esm(() => {
|
|
|
4675
4799
|
};
|
|
4676
4800
|
TEXT_ENV = {
|
|
4677
4801
|
language: "DEEPSEEK_SEARCH_LANGUAGE",
|
|
4678
|
-
searchLocalization: SEARCH_LOCALIZATION_ENV
|
|
4802
|
+
searchLocalization: SEARCH_LOCALIZATION_ENV,
|
|
4803
|
+
baseUrl: BASE_URL_ENV
|
|
4679
4804
|
};
|
|
4680
4805
|
DOMAINS_ENV = {
|
|
4681
4806
|
blockedDomains: "DEEPSEEK_SEARCH_BLOCKED_DOMAINS",
|
|
@@ -4689,12 +4814,13 @@ var init_env = __esm(() => {
|
|
|
4689
4814
|
searchEnabled: ENABLE_SEARCH_ENV,
|
|
4690
4815
|
fetchEnabled: ENABLE_FETCH_ENV
|
|
4691
4816
|
};
|
|
4692
|
-
|
|
4693
|
-
...
|
|
4694
|
-
...
|
|
4695
|
-
...
|
|
4696
|
-
...
|
|
4697
|
-
|
|
4817
|
+
OPTION_ENV_BY_KEY = {
|
|
4818
|
+
...NUMBER_ENV,
|
|
4819
|
+
...TEXT_ENV,
|
|
4820
|
+
...DOMAINS_ENV,
|
|
4821
|
+
...FALSE_ONLY_ENV
|
|
4822
|
+
};
|
|
4823
|
+
OPTION_ENV_VARS = Object.values(OPTION_ENV_BY_KEY);
|
|
4698
4824
|
});
|
|
4699
4825
|
|
|
4700
4826
|
// src/installer/config-command.ts
|
|
@@ -4724,15 +4850,16 @@ async function runConfigCommand(query, io, deps = {}) {
|
|
|
4724
4850
|
const client = deps.client ?? createRetrievalClient();
|
|
4725
4851
|
const startedAt = Date.now();
|
|
4726
4852
|
try {
|
|
4727
|
-
const
|
|
4853
|
+
const outcome = await client.searchWeb(query, resolved.search, {
|
|
4728
4854
|
signal: new AbortController().signal,
|
|
4729
4855
|
auth: { apiKey },
|
|
4730
|
-
retry: resolved.retry
|
|
4856
|
+
retry: resolved.retry,
|
|
4857
|
+
baseUrl: resolved.baseUrl
|
|
4731
4858
|
});
|
|
4732
4859
|
const elapsedMs = Date.now() - startedAt;
|
|
4733
|
-
io.out(`search "${query.trim()}" — ${results.length} ${results.length === 1 ? "result" : "results"} in ${elapsedMs} ms`);
|
|
4860
|
+
io.out(`search "${query.trim()}" — ${outcome.results.length} ${outcome.results.length === 1 ? "result" : "results"} in ${elapsedMs} ms`);
|
|
4734
4861
|
io.out("");
|
|
4735
|
-
for (const line of formatSearchResults(results).split(`
|
|
4862
|
+
for (const line of formatSearchResults(outcome.results, outcome).split(`
|
|
4736
4863
|
`))
|
|
4737
4864
|
io.out(line);
|
|
4738
4865
|
return 0;
|
|
@@ -4845,6 +4972,7 @@ var init_rulesContent = __esm(() => {
|
|
|
4845
4972
|
});
|
|
4846
4973
|
|
|
4847
4974
|
// src/installer/hosts/shared.ts
|
|
4975
|
+
import { spawnSync } from "node:child_process";
|
|
4848
4976
|
function planHeader(input, paths, loaded, dialect) {
|
|
4849
4977
|
const notes = [];
|
|
4850
4978
|
const errors = [];
|
|
@@ -4865,20 +4993,233 @@ function planHeader(input, paths, loaded, dialect) {
|
|
|
4865
4993
|
notes.push(competitorSteeringNote(paths.rules, steering));
|
|
4866
4994
|
return { notes, errors, abort: false };
|
|
4867
4995
|
}
|
|
4996
|
+
function probeHostVersion(command, parse4, err) {
|
|
4997
|
+
const result = spawnSync(command, ["--version"], { timeout: 5000, encoding: "utf8", env: process.env, shell: true });
|
|
4998
|
+
const version = result.status === 0 ? parse4(result.stdout ?? "") : undefined;
|
|
4999
|
+
if (version === undefined) {
|
|
5000
|
+
err(`note: ${command} was not found on PATH or printed no version; the version-drift check was skipped`);
|
|
5001
|
+
}
|
|
5002
|
+
return version;
|
|
5003
|
+
}
|
|
4868
5004
|
var init_shared = __esm(() => {
|
|
4869
5005
|
init_detect();
|
|
4870
5006
|
init_formats();
|
|
4871
5007
|
});
|
|
4872
5008
|
|
|
5009
|
+
// src/installer/hosts/claude-code.ts
|
|
5010
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
5011
|
+
function valueKind(value) {
|
|
5012
|
+
return Array.isArray(value) ? "an array" : `a ${typeof value}`;
|
|
5013
|
+
}
|
|
5014
|
+
function probeClaudeCodeVersion(err) {
|
|
5015
|
+
return probeHostVersion("claude", parseClaudeCodeVersion, err);
|
|
5016
|
+
}
|
|
5017
|
+
function parseClaudeCodeVersion(output) {
|
|
5018
|
+
return [...output.matchAll(/\b\d+\.\d+\.\d+\b/g)].map((match) => match[0]).at(-1);
|
|
5019
|
+
}
|
|
5020
|
+
function claudeCodePaths(input) {
|
|
5021
|
+
return input.scope === "global" ? {
|
|
5022
|
+
config: join2(input.home, ".claude.json"),
|
|
5023
|
+
settings: join2(input.home, ".claude", "settings.json"),
|
|
5024
|
+
rules: join2(input.home, ".claude", "CLAUDE.md")
|
|
5025
|
+
} : {
|
|
5026
|
+
config: join2(input.cwd, ".mcp.json"),
|
|
5027
|
+
settings: join2(input.cwd, ".claude", "settings.json"),
|
|
5028
|
+
rules: join2(input.cwd, "CLAUDE.md")
|
|
5029
|
+
};
|
|
5030
|
+
}
|
|
5031
|
+
function managedSettingsPath(input) {
|
|
5032
|
+
const platform = input.platform ?? process.platform;
|
|
5033
|
+
if (platform === "darwin")
|
|
5034
|
+
return "/Library/Application Support/ClaudeCode/managed-settings.json";
|
|
5035
|
+
if (platform === "win32")
|
|
5036
|
+
return "C:\\Program Files\\ClaudeCode\\managed-settings.json";
|
|
5037
|
+
return "/etc/claude-code/managed-settings.json";
|
|
5038
|
+
}
|
|
5039
|
+
function planClaudeCode(input, _previous) {
|
|
5040
|
+
const paths = claudeCodePaths(input);
|
|
5041
|
+
const loaded = codecFor("json", input.host).load(paths.config);
|
|
5042
|
+
const tiers = { search: input.search, fetch: input.fetch };
|
|
5043
|
+
const header = planHeader(input, paths, loaded, "JSON");
|
|
5044
|
+
if (header.abort) {
|
|
5045
|
+
return { host: "claude-code", scope: input.scope, tiers, edits: [], notes: header.notes, errors: header.errors };
|
|
5046
|
+
}
|
|
5047
|
+
const notes = [...header.notes];
|
|
5048
|
+
const errors = [...header.errors];
|
|
5049
|
+
const edits = [];
|
|
5050
|
+
if (input.hostVersion !== undefined && input.hostVersion !== CLAUDE_CODE_TESTED_VERSION) {
|
|
5051
|
+
notes.push(`Claude Code ${input.hostVersion} is installed, but this installer's Claude Code facts are verified against ` + `${CLAUDE_CODE_TESTED_VERSION} (the docs/hosts.jsonc lock). Deny-rule and tool-visibility behavior may have ` + "drifted between the two — re-verify the tiers if routing misbehaves.");
|
|
5052
|
+
}
|
|
5053
|
+
const managed = managedSettingsPath(input);
|
|
5054
|
+
if (existsSync9(managed)) {
|
|
5055
|
+
notes.push(`${managed} exists: the managed policy layer outranks every settings file this plan writes, so a deny or allow ` + "there overrides these changes. Check with your administrator if routing does not take effect.");
|
|
5056
|
+
}
|
|
5057
|
+
const servers = loaded.value.mcpServers;
|
|
5058
|
+
if (servers !== undefined && !isPlainObject(servers)) {
|
|
5059
|
+
errors.push({
|
|
5060
|
+
message: `${paths.config}: mcpServers is ${valueKind(servers)}, not an object; fix it before installing.`
|
|
5061
|
+
});
|
|
5062
|
+
}
|
|
5063
|
+
const existingEntry = isPlainObject(servers) ? servers.deepseek : undefined;
|
|
5064
|
+
const entry = {
|
|
5065
|
+
command: "npx",
|
|
5066
|
+
args: ["-y", input.packageRef],
|
|
5067
|
+
env: { [API_KEY_ENV]: `\${${API_KEY_ENV}}` }
|
|
5068
|
+
};
|
|
5069
|
+
const ops = [{ op: "set", path: ["mcpServers", "deepseek"], value: entry, previous: existingEntry }];
|
|
5070
|
+
edits.push({
|
|
5071
|
+
file: paths.config,
|
|
5072
|
+
format: "json",
|
|
5073
|
+
...input.scope === "global" ? { shared: true } : {},
|
|
5074
|
+
ops
|
|
5075
|
+
});
|
|
5076
|
+
if (isPlainObject(servers)) {
|
|
5077
|
+
if ("deepseek" in servers)
|
|
5078
|
+
notes.push(replacingExistingEntryNote("mcpServers.deepseek"));
|
|
5079
|
+
}
|
|
5080
|
+
notes.push(`\`${API_KEY_ENV}\` is written as "\${${API_KEY_ENV}}" in the server's env block, so the key expands from the ` + "environment that launches Claude Code and stays out of the file.");
|
|
5081
|
+
if (input.scope === "project") {
|
|
5082
|
+
notes.push("Claude Code asks once per project whether to use servers from .mcp.json; approve the deepseek server when " + "prompted (or via the /mcp menu), or its tools will not load in that project.");
|
|
5083
|
+
} else {
|
|
5084
|
+
notes.push(`User-scope servers live in the mcpServers key of ${paths.config} — Claude Code's own user-level store, where ` + "`claude mcp add --scope user` writes too; every other key in the file is left untouched.");
|
|
5085
|
+
}
|
|
5086
|
+
const forced = input.search === "forced" || input.fetch === "forced";
|
|
5087
|
+
const scanned = {};
|
|
5088
|
+
if (isPlainObject(servers))
|
|
5089
|
+
Object.assign(scanned, servers);
|
|
5090
|
+
const otherFile = input.scope === "project" ? join2(input.home, ".claude.json") : join2(input.cwd, ".mcp.json");
|
|
5091
|
+
if (forced && existsSync9(otherFile)) {
|
|
5092
|
+
let otherLoaded;
|
|
5093
|
+
try {
|
|
5094
|
+
otherLoaded = codecFor("json", input.host).load(otherFile);
|
|
5095
|
+
} catch (error) {
|
|
5096
|
+
errors.push({
|
|
5097
|
+
message: `${otherFile} exists but cannot be read (${error instanceof Error ? error.message : String(error)}); the forced-tier competitor scan requires it. Fix the file or its permissions, or choose a lower tier.`
|
|
5098
|
+
});
|
|
5099
|
+
}
|
|
5100
|
+
if (otherLoaded?.parseError) {
|
|
5101
|
+
errors.push({ message: `${otherFile} exists but does not parse as JSON: ${otherLoaded.parseError}` });
|
|
5102
|
+
} else if (otherLoaded !== undefined && otherLoaded.value.mcpServers !== undefined && !isPlainObject(otherLoaded.value.mcpServers)) {
|
|
5103
|
+
errors.push({
|
|
5104
|
+
message: `${otherFile}: mcpServers is ${valueKind(otherLoaded.value.mcpServers)}, not an object; the forced-tier competitor scan cannot read it. Fix the file or choose a lower tier.`
|
|
5105
|
+
});
|
|
5106
|
+
} else if (otherLoaded !== undefined && isPlainObject(otherLoaded.value.mcpServers)) {
|
|
5107
|
+
const otherServers = otherLoaded.value.mcpServers;
|
|
5108
|
+
Object.assign(scanned, otherServers);
|
|
5109
|
+
if ("deepseek" in otherServers) {
|
|
5110
|
+
notes.push(`A deepseek server is also registered in ${otherFile}; Claude Code resolves the name local first, then ` + "project, then user scope, so consider uninstalling the other scope to keep one registration.");
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
const denyEntries = [];
|
|
5115
|
+
const { competitors, bystanders } = splitCompetitors(scanned);
|
|
5116
|
+
if (forced && competitors.length > 0) {
|
|
5117
|
+
const rules = competitors.map((name) => `mcp__${name}`);
|
|
5118
|
+
denyEntries.push(...rules);
|
|
5119
|
+
notes.push(`Forced tier: web-capable MCP servers are denied by server-level rules (${rules.join(", ")}), so DeepSeek ` + "routing is exclusive while the entries stay." + (input.scope === "global" ? " These denies apply in every project on this machine and match by server name, so a same-named server in another project is denied too." : ""));
|
|
5120
|
+
}
|
|
5121
|
+
const stayRegistered = forced ? bystanders : [...competitors, ...bystanders];
|
|
5122
|
+
if (stayRegistered.length > 0)
|
|
5123
|
+
notes.push(othersStayRegisteredNote(stayRegistered));
|
|
5124
|
+
if (input.search === "forced")
|
|
5125
|
+
denyEntries.push(BUILTIN_TOOL_IDS.search);
|
|
5126
|
+
if (input.fetch === "forced")
|
|
5127
|
+
denyEntries.push(BUILTIN_TOOL_IDS.fetch);
|
|
5128
|
+
if (input.search === "off")
|
|
5129
|
+
denyEntries.push(MCP_TOOL_IDS.search);
|
|
5130
|
+
if (input.fetch === "off")
|
|
5131
|
+
denyEntries.push(MCP_TOOL_IDS.fetch);
|
|
5132
|
+
if (denyEntries.length > 0) {
|
|
5133
|
+
const settingsLoaded = codecFor("json", input.host).load(paths.settings);
|
|
5134
|
+
if (settingsLoaded.parseError) {
|
|
5135
|
+
errors.push({ message: `${paths.settings} exists but does not parse as JSON: ${settingsLoaded.parseError}` });
|
|
5136
|
+
} else {
|
|
5137
|
+
const permissions = settingsLoaded.value.permissions;
|
|
5138
|
+
const deny = isPlainObject(permissions) ? permissions.deny : undefined;
|
|
5139
|
+
if (permissions !== undefined && !isPlainObject(permissions)) {
|
|
5140
|
+
errors.push({
|
|
5141
|
+
message: `${paths.settings}: the permissions key is ${valueKind(permissions)}, not an object; fix it before installing.`
|
|
5142
|
+
});
|
|
5143
|
+
} else if (deny !== undefined && !Array.isArray(deny)) {
|
|
5144
|
+
errors.push({
|
|
5145
|
+
message: `${paths.settings}: permissions.deny is ${valueKind(deny)}, not an array; fix it before installing.`
|
|
5146
|
+
});
|
|
5147
|
+
} else {
|
|
5148
|
+
const existingCount = Array.isArray(deny) ? deny.length : 0;
|
|
5149
|
+
const existingDeny = Array.isArray(deny) ? deny.filter((item) => typeof item === "string") : [];
|
|
5150
|
+
const denyOps = [
|
|
5151
|
+
{
|
|
5152
|
+
op: "append-array",
|
|
5153
|
+
path: ["permissions", "deny"],
|
|
5154
|
+
entries: denyEntries,
|
|
5155
|
+
existingCount
|
|
5156
|
+
}
|
|
5157
|
+
];
|
|
5158
|
+
edits.push({
|
|
5159
|
+
file: paths.settings,
|
|
5160
|
+
format: "json",
|
|
5161
|
+
...input.scope === "global" ? { shared: true } : {},
|
|
5162
|
+
ops: denyOps
|
|
5163
|
+
});
|
|
5164
|
+
if (existingCount > 0) {
|
|
5165
|
+
notes.push(`permissions.deny already holds ${existingCount} ${existingCount === 1 ? "entry" : "entries"}; ` + "the plan appends only its own and never removes yours.");
|
|
5166
|
+
}
|
|
5167
|
+
const preexisting = denyEntries.filter((name) => existingDeny.includes(name));
|
|
5168
|
+
if (preexisting.length > 0) {
|
|
5169
|
+
notes.push(`${preexisting.join(", ")} ${preexisting.length === 1 ? "is" : "are"} already present in permissions.deny — present entries are not duplicated.`);
|
|
5170
|
+
}
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5174
|
+
if (errors.length > 0) {
|
|
5175
|
+
return { host: "claude-code", scope: input.scope, tiers, edits: [], notes, errors };
|
|
5176
|
+
}
|
|
5177
|
+
const forcedIds = [
|
|
5178
|
+
...input.search === "forced" ? [BUILTIN_TOOL_IDS.search] : [],
|
|
5179
|
+
...input.fetch === "forced" ? [BUILTIN_TOOL_IDS.fetch] : []
|
|
5180
|
+
];
|
|
5181
|
+
if (forcedIds.length > 0) {
|
|
5182
|
+
notes.push(`Forced is exclusion: bare deny rules (${forcedIds.join(", ")}) in permissions.deny remove the built-in tool ` + "from the model's context entirely — deny outranks allow at every settings level and cannot be carved out, so " + `the exclusion also applies to every other workflow that relied on the tool ${input.scope === "global" ? "in every project on this machine" : "in this project"}. ` + FORCED_COST_NOTE);
|
|
5183
|
+
}
|
|
5184
|
+
const offIds = [
|
|
5185
|
+
...input.search === "off" ? [MCP_TOOL_IDS.search] : [],
|
|
5186
|
+
...input.fetch === "off" ? [MCP_TOOL_IDS.fetch] : []
|
|
5187
|
+
];
|
|
5188
|
+
if (offIds.length > 0) {
|
|
5189
|
+
notes.push(`Off capabilities are written as their full tool ids (${offIds.join(", ")}) in permissions.deny: the tool ` + "leaves the model's context while the server keeps running; delete the entry to re-enable it.");
|
|
5190
|
+
}
|
|
5191
|
+
if (input.search === "optional" || input.fetch === "optional") {
|
|
5192
|
+
notes.push("Claude Code ships built-in WebSearch and WebFetch tools; under the optional tier they stay available " + "alongside the DeepSeek tools.");
|
|
5193
|
+
}
|
|
5194
|
+
if (input.search === "preferred" || input.fetch === "preferred") {
|
|
5195
|
+
if (input.scope === "project" && !existsSync9(paths.rules) && existsSync9(join2(input.cwd, "AGENTS.md"))) {
|
|
5196
|
+
notes.push(`This plan creates ${paths.rules} where none exists, and ${join2(input.cwd, "AGENTS.md")} is present: Claude ` + "Code reads AGENTS.md only when no CLAUDE.md exists above the cwd, so the new CLAUDE.md takes precedence — " + "merge anything you rely on from AGENTS.md before applying.");
|
|
5197
|
+
}
|
|
5198
|
+
edits.push({
|
|
5199
|
+
file: paths.rules,
|
|
5200
|
+
format: "rules",
|
|
5201
|
+
block: buildRulesBlock(input.host, rulesLines(MCP_TOOL_IDS, input.search, input.fetch))
|
|
5202
|
+
});
|
|
5203
|
+
notes.push(input.scope === "global" ? `Rules block upserted in ${paths.rules}; Claude Code loads the user CLAUDE.md plus every CLAUDE.md from the cwd up the directory tree, concatenating the levels.` : `Rules block upserted in ${paths.rules}; Claude Code loads every CLAUDE.md from the cwd up the directory tree, concatenating the levels.`);
|
|
5204
|
+
}
|
|
5205
|
+
return { host: "claude-code", scope: input.scope, tiers, edits, notes, errors };
|
|
5206
|
+
}
|
|
5207
|
+
var CLAUDE_CODE_TESTED_VERSION = "2.1.303", BUILTIN_TOOL_IDS;
|
|
5208
|
+
var init_claude_code = __esm(() => {
|
|
5209
|
+
init_constants();
|
|
5210
|
+
init_detect();
|
|
5211
|
+
init_formats();
|
|
5212
|
+
init_rules();
|
|
5213
|
+
init_value();
|
|
5214
|
+
init_paths();
|
|
5215
|
+
init_rulesContent();
|
|
5216
|
+
init_shared();
|
|
5217
|
+
BUILTIN_TOOL_IDS = { search: "WebSearch", fetch: "WebFetch" };
|
|
5218
|
+
});
|
|
5219
|
+
|
|
4873
5220
|
// src/installer/hosts/codex.ts
|
|
4874
|
-
import { spawnSync } from "node:child_process";
|
|
4875
5221
|
function probeCodexVersion(err) {
|
|
4876
|
-
|
|
4877
|
-
const version = result.status === 0 ? parseCodexVersion(result.stdout ?? "") : undefined;
|
|
4878
|
-
if (version === undefined) {
|
|
4879
|
-
err("note: codex was not found on PATH or printed no version; the version-drift check was skipped");
|
|
4880
|
-
}
|
|
4881
|
-
return version;
|
|
5222
|
+
return probeHostVersion("codex", parseCodexVersion, err);
|
|
4882
5223
|
}
|
|
4883
5224
|
function parseCodexVersion(output) {
|
|
4884
5225
|
return /\bcodex(?:-cli)?\s+(\d+\.\d+\.\d+)\b/.exec(output)?.[1];
|
|
@@ -5081,8 +5422,8 @@ var init_opencode = __esm(() => {
|
|
|
5081
5422
|
init_value();
|
|
5082
5423
|
init_ownership();
|
|
5083
5424
|
init_paths();
|
|
5425
|
+
init_record();
|
|
5084
5426
|
init_rulesContent();
|
|
5085
|
-
init_state();
|
|
5086
5427
|
init_shared();
|
|
5087
5428
|
OPENCODE_TOOL_IDS = { search: "websearch (provider deepseek)", fetch: "deepseek_fetch" };
|
|
5088
5429
|
});
|
|
@@ -5163,7 +5504,12 @@ function planPiExtension(input, previous) {
|
|
|
5163
5504
|
previous: typeof namespace.enableFetch === "boolean" ? namespace.enableFetch : undefined
|
|
5164
5505
|
});
|
|
5165
5506
|
}
|
|
5166
|
-
edits.push({
|
|
5507
|
+
edits.push({
|
|
5508
|
+
file: paths.config,
|
|
5509
|
+
format: "json",
|
|
5510
|
+
...input.scope === "global" ? { shared: true } : {},
|
|
5511
|
+
ops
|
|
5512
|
+
});
|
|
5167
5513
|
notes.push("Pi resolves the packages source at session start; the first launch after apply fetches the package from npm " + "(pi update --extensions refreshes it later).");
|
|
5168
5514
|
notes.push(`The extension reads ${API_KEY_ENV} from the Pi process environment; no env forwarding is needed. ` + "Set it in the shell that launches Pi.");
|
|
5169
5515
|
if (input.search === "off" || input.fetch === "off") {
|
|
@@ -5182,10 +5528,10 @@ function planPiExtension(input, previous) {
|
|
|
5182
5528
|
format: "rules",
|
|
5183
5529
|
block: buildRulesBlock(input.host, rulesLines(RAW_MCP_TOOL_IDS, input.search, input.fetch))
|
|
5184
5530
|
});
|
|
5185
|
-
notes.push(
|
|
5531
|
+
notes.push(rulesUpsertNote(paths.rules));
|
|
5186
5532
|
}
|
|
5187
5533
|
if (forced) {
|
|
5188
|
-
notes.push(
|
|
5534
|
+
notes.push(FORCED_NO_BUILTIN_NOTE);
|
|
5189
5535
|
}
|
|
5190
5536
|
return { host: "pi", scope: input.scope, tiers, edits, notes, errors };
|
|
5191
5537
|
}
|
|
@@ -5227,8 +5573,14 @@ function planMcpCompetitorDisables(input, notes, errors) {
|
|
|
5227
5573
|
const edits = [];
|
|
5228
5574
|
if (competitors.length > 0) {
|
|
5229
5575
|
const ops = planCompetitorDisables(servers, competitors, notes, errors);
|
|
5230
|
-
if (ops.length > 0)
|
|
5231
|
-
edits.push({
|
|
5576
|
+
if (ops.length > 0) {
|
|
5577
|
+
edits.push({
|
|
5578
|
+
file: mcpFile,
|
|
5579
|
+
format: "json",
|
|
5580
|
+
...input.scope === "global" ? { shared: true } : {},
|
|
5581
|
+
ops
|
|
5582
|
+
});
|
|
5583
|
+
}
|
|
5232
5584
|
}
|
|
5233
5585
|
if (bystanders.length > 0)
|
|
5234
5586
|
notes.push(othersStayRegisteredNote(bystanders));
|
|
@@ -5260,7 +5612,12 @@ function planPiMcp(input) {
|
|
|
5260
5612
|
const servers = loaded.value.mcpServers;
|
|
5261
5613
|
const existingEntry = isPlainObject(servers) ? servers.deepseek : undefined;
|
|
5262
5614
|
const ops = [{ op: "set", path: ["mcpServers", "deepseek"], value: entry, previous: existingEntry }];
|
|
5263
|
-
edits.push({
|
|
5615
|
+
edits.push({
|
|
5616
|
+
file: paths.config,
|
|
5617
|
+
format: "json",
|
|
5618
|
+
...input.scope === "global" ? { shared: true } : {},
|
|
5619
|
+
ops
|
|
5620
|
+
});
|
|
5264
5621
|
if (isPlainObject(servers)) {
|
|
5265
5622
|
if (servers.deepseek !== undefined)
|
|
5266
5623
|
notes.push(replacingExistingEntryNote("mcpServers.deepseek"));
|
|
@@ -5281,16 +5638,17 @@ function planPiMcp(input) {
|
|
|
5281
5638
|
format: "rules",
|
|
5282
5639
|
block: buildRulesBlock(input.host, rulesLines(MCP_TOOL_IDS, input.search, input.fetch))
|
|
5283
5640
|
});
|
|
5284
|
-
notes.push(
|
|
5641
|
+
notes.push(rulesUpsertNote(paths.rules));
|
|
5285
5642
|
}
|
|
5286
5643
|
if (input.search === "forced" || input.fetch === "forced") {
|
|
5287
|
-
notes.push(
|
|
5644
|
+
notes.push(FORCED_NO_BUILTIN_NOTE);
|
|
5288
5645
|
}
|
|
5289
5646
|
return { host: "pi", scope: input.scope, tiers, edits, notes, errors };
|
|
5290
5647
|
}
|
|
5291
5648
|
function collectSources(value) {
|
|
5292
5649
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
5293
5650
|
}
|
|
5651
|
+
var rulesUpsertNote = (rules) => `Rules block upserted in ${rules}; Pi loads AGENTS.md context files from the working directory and its parents.`, FORCED_NO_BUILTIN_NOTE;
|
|
5294
5652
|
var init_pi = __esm(() => {
|
|
5295
5653
|
init_constants();
|
|
5296
5654
|
init_settings();
|
|
@@ -5300,9 +5658,10 @@ var init_pi = __esm(() => {
|
|
|
5300
5658
|
init_value();
|
|
5301
5659
|
init_ownership();
|
|
5302
5660
|
init_paths();
|
|
5661
|
+
init_record();
|
|
5303
5662
|
init_rulesContent();
|
|
5304
|
-
init_state();
|
|
5305
5663
|
init_shared();
|
|
5664
|
+
FORCED_NO_BUILTIN_NOTE = "Forced tier: Pi 1.3.0 ships no built-in web tool, so the registered tools are the only web path (de-facto). " + FORCED_COST_NOTE;
|
|
5306
5665
|
});
|
|
5307
5666
|
|
|
5308
5667
|
// src/installer/hosts/index.ts
|
|
@@ -5389,7 +5748,7 @@ function withSecretGuard(plan) {
|
|
|
5389
5748
|
reverts: undefined,
|
|
5390
5749
|
errors: [
|
|
5391
5750
|
{
|
|
5392
|
-
message: `${edit2.file}: the existing value at ${op.path.join(".")} contains a credential-looking key ` + `(${offender}). The installer refuses to snapshot secrets into its
|
|
5751
|
+
message: `${edit2.file}: the existing value at ${op.path.join(".")} contains a credential-looking key ` + `(${offender}). The installer refuses to snapshot secrets into its install record. Move the key to ` + `environment indirection ("\${${API_KEY_ENV}}" expansion or env_vars) and re-run.`
|
|
5393
5752
|
}
|
|
5394
5753
|
]
|
|
5395
5754
|
};
|
|
@@ -5446,17 +5805,20 @@ var init_hosts = __esm(() => {
|
|
|
5446
5805
|
init_constants();
|
|
5447
5806
|
init_value();
|
|
5448
5807
|
init_ownership();
|
|
5449
|
-
|
|
5808
|
+
init_record();
|
|
5809
|
+
init_claude_code();
|
|
5450
5810
|
init_codex();
|
|
5451
5811
|
init_opencode();
|
|
5452
5812
|
init_pi();
|
|
5453
5813
|
HOST_PLANNERS = {
|
|
5454
5814
|
opencode: planOpenCode,
|
|
5455
5815
|
pi: planPi,
|
|
5456
|
-
codex: planCodex
|
|
5816
|
+
codex: planCodex,
|
|
5817
|
+
"claude-code": planClaudeCode
|
|
5457
5818
|
};
|
|
5458
5819
|
HOST_VERSION_PROBES = {
|
|
5459
|
-
codex: probeCodexVersion
|
|
5820
|
+
codex: probeCodexVersion,
|
|
5821
|
+
"claude-code": probeClaudeCodeVersion
|
|
5460
5822
|
};
|
|
5461
5823
|
});
|
|
5462
5824
|
|
|
@@ -5521,7 +5883,7 @@ function preview(value) {
|
|
|
5521
5883
|
|
|
5522
5884
|
// src/installer/uninstall.ts
|
|
5523
5885
|
function uninstall(input) {
|
|
5524
|
-
return withOperationLock([
|
|
5886
|
+
return withOperationLock([recordPathFor(input)], () => {
|
|
5525
5887
|
const record = readRecord(input);
|
|
5526
5888
|
if (record === undefined) {
|
|
5527
5889
|
throw new Error(`No ${input.host} install is recorded for the ${input.scope} scope.`);
|
|
@@ -5547,9 +5909,9 @@ function uninstallLocked(input, record) {
|
|
|
5547
5909
|
for (const file of [...record.files].reverse()) {
|
|
5548
5910
|
lines.push(...revertRecordedFile(file, record.host, { label: "", whole: true }));
|
|
5549
5911
|
}
|
|
5550
|
-
deleteRecord(input);
|
|
5551
5912
|
releaseFileOwnership(input, record.files);
|
|
5552
|
-
|
|
5913
|
+
deleteRecord(input);
|
|
5914
|
+
pruneEmptyDir(recordDir(input, input.scope));
|
|
5553
5915
|
lines.push(`removed install record (${input.host}, ${input.scope})`);
|
|
5554
5916
|
return lines;
|
|
5555
5917
|
}
|
|
@@ -5558,8 +5920,8 @@ var init_uninstall = __esm(() => {
|
|
|
5558
5920
|
init_operation_lock();
|
|
5559
5921
|
init_ownership();
|
|
5560
5922
|
init_paths();
|
|
5923
|
+
init_record();
|
|
5561
5924
|
init_revert();
|
|
5562
|
-
init_state();
|
|
5563
5925
|
});
|
|
5564
5926
|
|
|
5565
5927
|
// src/installer/cli.ts
|
|
@@ -5659,7 +6021,7 @@ ${USAGE}`);
|
|
|
5659
6021
|
return 0;
|
|
5660
6022
|
}
|
|
5661
6023
|
if (readingLegacyGlobalRecords({ ...roots, configHome: configHome2 })) {
|
|
5662
|
-
out(`Global install records are being read from the legacy ${
|
|
6024
|
+
out(`Global install records are being read from the legacy ${legacyGlobalRecordDir(roots)} ` + "(written before the Windows config home moved to %APPDATA%).");
|
|
5663
6025
|
}
|
|
5664
6026
|
for (const record of records) {
|
|
5665
6027
|
const by = record.installerVersion ? ` by v${record.installerVersion}` : "";
|
|
@@ -5793,7 +6155,7 @@ var init_cli = __esm(() => {
|
|
|
5793
6155
|
init_config_command();
|
|
5794
6156
|
init_hosts();
|
|
5795
6157
|
init_ownership();
|
|
5796
|
-
|
|
6158
|
+
init_record();
|
|
5797
6159
|
init_uninstall();
|
|
5798
6160
|
HOSTS = Object.keys(HOST_PLANNERS);
|
|
5799
6161
|
HOST_CHOICES = `<${HOSTS.join("|")}>`;
|
|
@@ -5833,17 +6195,6 @@ re-running.`;
|
|
|
5833
6195
|
PATH_CHOICES = ["extension", "mcp"];
|
|
5834
6196
|
});
|
|
5835
6197
|
|
|
5836
|
-
// src/mcp/auth.ts
|
|
5837
|
-
function requireApiKey(env) {
|
|
5838
|
-
const apiKey = apiKeyFromEnv(env);
|
|
5839
|
-
if (!apiKey)
|
|
5840
|
-
throw new Error(MISSING_API_KEY_MESSAGE);
|
|
5841
|
-
return apiKey;
|
|
5842
|
-
}
|
|
5843
|
-
var init_auth2 = __esm(() => {
|
|
5844
|
-
init_auth();
|
|
5845
|
-
});
|
|
5846
|
-
|
|
5847
6198
|
// src/mcp/client.ts
|
|
5848
6199
|
import { CLIENT_INFO_META_KEY } from "@modelcontextprotocol/server";
|
|
5849
6200
|
function clientNameOf(server, ctx) {
|
|
@@ -5871,7 +6222,7 @@ var init_package = __esm(() => {
|
|
|
5871
6222
|
package_default = {
|
|
5872
6223
|
$schema: "https://json.schemastore.org/package.json",
|
|
5873
6224
|
name: "deepseek-web-retrieval",
|
|
5874
|
-
version: "0.
|
|
6225
|
+
version: "0.9.0",
|
|
5875
6226
|
description: "Token-efficient DeepSeek Search and Fetch for coding agents, starting with OpenCode v2.",
|
|
5876
6227
|
type: "module",
|
|
5877
6228
|
license: "MIT",
|
|
@@ -5927,7 +6278,8 @@ var init_package = __esm(() => {
|
|
|
5927
6278
|
prepublishOnly: "bun run check:release",
|
|
5928
6279
|
test: "bun test",
|
|
5929
6280
|
"test:package": "node scripts/test-package.mjs",
|
|
5930
|
-
"test:e2e:keyless": "bun run test:e2e pi pi-extension installer reinit upgrade pkg capability-off",
|
|
6281
|
+
"test:e2e:keyless": "bun run test:e2e pi pi-extension pi-mock opencode-mock installer reinit upgrade pkg capability-off",
|
|
6282
|
+
"test:e2e:billed": "bun run test:e2e pi-billed opencode codex claude-code preferred",
|
|
5931
6283
|
"check:release": "bun run check && bun run test:package && bun run test:e2e:keyless",
|
|
5932
6284
|
"test:e2e": "bun run test/e2e/run.ts",
|
|
5933
6285
|
"docs:matrix": "bun run scripts/render-matrix.ts",
|
|
@@ -5974,11 +6326,13 @@ var init_package = __esm(() => {
|
|
|
5974
6326
|
});
|
|
5975
6327
|
|
|
5976
6328
|
// src/core/descriptions.ts
|
|
5977
|
-
var FETCH_SERVICE_LIMITS = "DeepSeek does not render JavaScript and cannot reach private networks; PDFs, images, and other binary files are unsupported.", FETCH_OUTPUT_TRUNCATED = "Output is truncated to the configured size.", SEARCH_RANKED_FILTERED = "Results are ranked by relevance and source authority, deduplicated by URL, and capped at the configured maximum.", SEARCH_EMPTY_IS_NORMAL = "An empty result list is normal, not an error.", SEARCH_QUERY_OPERATORS = "Use site:/-site: operators in the query to include or exclude domains, and after:/before: with a year or date (e.g. after:2025-06-01) to filter by publish time; the full query must stay within 128 characters.", SEARCH_TOOL_DESCRIPTION, FETCH_TOOL_DESCRIPTION, SEARCH_QUERY_PARAMETER = "Search query, 128 characters or fewer including operators (site:, -site:, quoted phrases, lang:, after:, before:).", SEARCH_ORI_QUESTION_PARAMETER, FETCH_URL_PARAMETER = "Absolute http:// or https:// URL to fetch. Credentials are rejected.";
|
|
6329
|
+
var FETCH_SERVICE_LIMITS = "DeepSeek does not render JavaScript and cannot reach private networks; PDFs, images, and other binary files are unsupported.", FETCH_OUTPUT_TRUNCATED = "Output is truncated to the configured size; the optional maxChars parameter raises or lowers that size for one call.", SEARCH_RANKED_FILTERED = "Results are ranked by relevance and source authority, deduplicated by URL, and capped at the configured maximum.", SEARCH_EMPTY_IS_NORMAL = "An empty result list is normal, not an error.", SEARCH_QUERY_OPERATORS = "Use site:/-site: operators in the query to include or exclude domains, and after:/before: with a year or date (e.g. after:2025-06-01) to filter by publish time; the full query must stay within 128 characters.", SEARCH_TOOL_DESCRIPTION, FETCH_TOOL_DESCRIPTION, SEARCH_QUERY_PARAMETER = "Search query, 128 characters or fewer including operators (site:, -site:, quoted phrases, lang:, after:, before:).", SEARCH_ORI_QUESTION_PARAMETER, FETCH_URL_PARAMETER = "Absolute http:// or https:// URL to fetch. Credentials are rejected.", FETCH_MAX_CHARS_PARAMETER, SEARCH_COUNT_PARAMETER;
|
|
5978
6330
|
var init_descriptions = __esm(() => {
|
|
5979
6331
|
SEARCH_TOOL_DESCRIPTION = "Search the web with DeepSeek's in-house search engine and return a short, filtered result " + "list (title, URL, summary). " + `${SEARCH_RANKED_FILTERED} ` + `${SEARCH_EMPTY_IS_NORMAL} ` + `${SEARCH_QUERY_OPERATORS} ` + "To read a full page, pass a result URL to the deepseek_fetch tool.";
|
|
5980
6332
|
FETCH_TOOL_DESCRIPTION = "Fetch one web page as clean Markdown through DeepSeek's Fetch API. " + `${FETCH_SERVICE_LIMITS} ` + `${FETCH_OUTPUT_TRUNCATED}`;
|
|
5981
6333
|
SEARCH_ORI_QUESTION_PARAMETER = "The user's original question or task that triggered this search, in the user's own words rather than a " + "reformulated query. Optional; 1-128 characters, and longer values are truncated.";
|
|
6334
|
+
FETCH_MAX_CHARS_PARAMETER = "Optional per-call character budget for this fetch, 0-1,000,000, title included. Overrides the configured cap " + "for this call; a [content truncated] marker is appended when the output is cut.";
|
|
6335
|
+
SEARCH_COUNT_PARAMETER = "Optional number of results for this call, 1-50. Overrides the configured count and result cap for this call; " + "billing is per request, not per result.";
|
|
5982
6336
|
});
|
|
5983
6337
|
|
|
5984
6338
|
// src/mcp/server.ts
|
|
@@ -5992,15 +6346,15 @@ function createDeepSeekServer(config, env = process.env, client = retrievalClien
|
|
|
5992
6346
|
description: SEARCH_TOOL_DESCRIPTION,
|
|
5993
6347
|
inputSchema: SEARCH_INPUT,
|
|
5994
6348
|
annotations: { readOnlyHint: true }
|
|
5995
|
-
}, async ({ query, original_question }, ctx) => {
|
|
6349
|
+
}, async ({ query, original_question, count }, ctx) => {
|
|
5996
6350
|
try {
|
|
5997
|
-
const request = requestContext(config, env, ctx.mcpReq.signal);
|
|
5998
|
-
const
|
|
6351
|
+
const request = requestContext(config, requireApiKey(env), ctx.mcpReq.signal);
|
|
6352
|
+
const outcome = await client.searchWeb(query, withPerCallCount(config.search, count), {
|
|
5999
6353
|
...request,
|
|
6000
6354
|
oriQuestion: original_question
|
|
6001
6355
|
});
|
|
6002
|
-
return callResult(server, ctx, formatSearchResults(results), {
|
|
6003
|
-
results: results.map(toStructuredResult)
|
|
6356
|
+
return callResult(server, ctx, formatSearchResults(outcome.results, outcome), {
|
|
6357
|
+
results: outcome.results.map(toStructuredResult)
|
|
6004
6358
|
});
|
|
6005
6359
|
} catch (error) {
|
|
6006
6360
|
return toErrorResult(error);
|
|
@@ -6013,9 +6367,9 @@ function createDeepSeekServer(config, env = process.env, client = retrievalClien
|
|
|
6013
6367
|
description: FETCH_TOOL_DESCRIPTION,
|
|
6014
6368
|
inputSchema: FETCH_INPUT,
|
|
6015
6369
|
annotations: { readOnlyHint: true }
|
|
6016
|
-
}, async ({ url }, ctx) => {
|
|
6370
|
+
}, async ({ url, maxChars }, ctx) => {
|
|
6017
6371
|
try {
|
|
6018
|
-
const request = requestContext(config, env, ctx.mcpReq.signal);
|
|
6372
|
+
const request = requestContext(config, requireApiKey(env), ctx.mcpReq.signal, { maxChars });
|
|
6019
6373
|
const page = await client.fetchDocument(url, config.fetch, request);
|
|
6020
6374
|
return callResult(server, ctx, page.content, {
|
|
6021
6375
|
url: page.url,
|
|
@@ -6030,18 +6384,13 @@ function createDeepSeekServer(config, env = process.env, client = retrievalClien
|
|
|
6030
6384
|
}
|
|
6031
6385
|
return server;
|
|
6032
6386
|
}
|
|
6033
|
-
function requestContext(config, env, signal) {
|
|
6034
|
-
return { signal, auth: { apiKey: requireApiKey(env) }, retry: config.retry };
|
|
6035
|
-
}
|
|
6036
6387
|
function callResult(server, ctx, text, structured) {
|
|
6037
6388
|
if (omitStructuredContent(server, ctx))
|
|
6038
6389
|
return { content: [{ type: "text", text }] };
|
|
6039
6390
|
return { content: [{ type: "text", text }], structuredContent: structured };
|
|
6040
6391
|
}
|
|
6041
6392
|
function servePrecheck(config) {
|
|
6042
|
-
|
|
6043
|
-
return;
|
|
6044
|
-
return `Both tools are disabled (${ENABLE_SEARCH_ENV} and ${ENABLE_FETCH_ENV} are false); ` + "at least one must stay enabled.";
|
|
6393
|
+
return bothDisabledPrecheck(config, `${ENABLE_SEARCH_ENV} and ${ENABLE_FETCH_ENV} are false`);
|
|
6045
6394
|
}
|
|
6046
6395
|
function toErrorResult(error) {
|
|
6047
6396
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -6050,18 +6399,21 @@ function toErrorResult(error) {
|
|
|
6050
6399
|
var MCP_SERVER_NAME = "deepseek-web-retrieval", SEARCH_INPUT, FETCH_INPUT;
|
|
6051
6400
|
var init_server = __esm(() => {
|
|
6052
6401
|
init_package();
|
|
6402
|
+
init_auth();
|
|
6053
6403
|
init_client();
|
|
6054
6404
|
init_constants();
|
|
6055
6405
|
init_descriptions();
|
|
6056
6406
|
init_format2();
|
|
6057
|
-
|
|
6407
|
+
init_options();
|
|
6058
6408
|
init_client2();
|
|
6059
6409
|
SEARCH_INPUT = z2.object({
|
|
6060
6410
|
query: z2.string().min(1).describe(SEARCH_QUERY_PARAMETER),
|
|
6061
|
-
original_question: z2.string().optional().describe(SEARCH_ORI_QUESTION_PARAMETER)
|
|
6411
|
+
original_question: z2.string().optional().describe(SEARCH_ORI_QUESTION_PARAMETER),
|
|
6412
|
+
count: z2.number().int().min(1).max(MAX_SEARCH_COUNT).optional().describe(SEARCH_COUNT_PARAMETER)
|
|
6062
6413
|
});
|
|
6063
6414
|
FETCH_INPUT = z2.object({
|
|
6064
|
-
url: z2.string().min(1).describe(FETCH_URL_PARAMETER)
|
|
6415
|
+
url: z2.string().min(1).describe(FETCH_URL_PARAMETER),
|
|
6416
|
+
maxChars: z2.number().int().min(0).max(MAX_FETCH_CHARS).optional().describe(FETCH_MAX_CHARS_PARAMETER)
|
|
6065
6417
|
});
|
|
6066
6418
|
});
|
|
6067
6419
|
|
|
@@ -6109,7 +6461,6 @@ var init_serve = __esm(() => {
|
|
|
6109
6461
|
init_config_file();
|
|
6110
6462
|
init_config_summary();
|
|
6111
6463
|
init_env();
|
|
6112
|
-
init_auth2();
|
|
6113
6464
|
init_client2();
|
|
6114
6465
|
init_server();
|
|
6115
6466
|
});
|