skillwiki 0.10.108 → 0.10.110

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.
Files changed (30) hide show
  1. package/dist/{chunk-JCDAEZVD.js → chunk-5MV5SSRM.js} +1 -1
  2. package/dist/{chunk-Z4NCF6K6.js → chunk-6X6PPQNI.js} +481 -179
  3. package/dist/{chunk-DTTKCCQ4.js → chunk-KX5Y3GSX.js} +2 -2
  4. package/dist/{chunk-QU2IXZRN.js → chunk-NA3TZ4JN.js} +2 -2
  5. package/dist/{chunk-PGD3J2GE.js → chunk-QU2CKXM4.js} +49 -37
  6. package/dist/{chunk-GJC2CI3W.js → chunk-R2DJ7IJ5.js} +103 -0
  7. package/dist/{chunk-HMHMCPCE.js → chunk-WPJHDRTP.js} +1 -1
  8. package/dist/cli.js +135 -101
  9. package/dist/{index-projection-ESBJOBS7.js → index-projection-6XGESSHV.js} +3 -3
  10. package/dist/{managed-write-preflight-AYD3CJYD.js → managed-write-preflight-JPOBVVSU.js} +4 -4
  11. package/dist/skillwiki-mcp.js +7 -7
  12. package/dist/sources-CLOF5VAT.js +9 -0
  13. package/dist/vault-sync/scripts/wiki-snapshot.sh +20 -12
  14. package/package.json +1 -1
  15. package/skills/.claude-plugin/plugin.json +1 -1
  16. package/skills/.codex-plugin/plugin.json +1 -1
  17. package/skills/.cursor-plugin/plugin.json +1 -1
  18. package/skills/package.json +1 -1
  19. package/skills/skills/skillwiki-connect/SKILL.md +8 -0
  20. package/skills/skills/skillwiki-mcp/SKILL.md +8 -0
  21. package/skills/skills/using-skillwiki/SKILL.md +1 -1
  22. package/skills/skills/wiki-init/SKILL.md +12 -11
  23. package/skills/skillwiki-connect/SKILL.md +8 -0
  24. package/skills/skillwiki-mcp/SKILL.md +8 -0
  25. package/skills/using-skillwiki/SKILL.md +1 -1
  26. package/skills/wiki-init/SKILL.md +12 -11
  27. package/templates/launchd/com.karlchow.skillwiki-clip-outbox.plist +22 -0
  28. package/templates/web-clipper/llm-wiki-clippings.json +1 -1
  29. package/templates/web-clipper/readme.txt +13 -13
  30. package/dist/sources-QTQSZMST.js +0 -9
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  renderRootIndex,
8
8
  writeRootIndexProjection
9
- } from "./chunk-DTTKCCQ4.js";
9
+ } from "./chunk-KX5Y3GSX.js";
10
10
  import {
11
11
  CONFIG_KEYS,
12
12
  VAULT_HYGIENE_GENERATED_COMMIT_PATHS,
@@ -31,6 +31,7 @@ import {
31
31
  releaseOwnedSyncLock,
32
32
  resolveConfiguredSnapshotWorktree,
33
33
  resolveRuntimePath,
34
+ resolveSkillwikiHome,
34
35
  resolveVaultSyncPullHelper,
35
36
  resolveWikiS3Remote,
36
37
  runLint,
@@ -40,11 +41,11 @@ import {
40
41
  snapshotterAliasForLocalHost,
41
42
  toUndirectedWeighted,
42
43
  writeDotenv
43
- } from "./chunk-PGD3J2GE.js";
44
+ } from "./chunk-QU2CKXM4.js";
44
45
  import {
45
46
  atomicWriteText,
46
47
  prepareTypedPage
47
- } from "./chunk-HMHMCPCE.js";
48
+ } from "./chunk-WPJHDRTP.js";
48
49
  import {
49
50
  applySourceCompileClaim,
50
51
  applySourceCompilePublished,
@@ -57,12 +58,13 @@ import {
57
58
  planSourceCompileRelease,
58
59
  planSourceReview,
59
60
  runSourcesPending
60
- } from "./chunk-QU2IXZRN.js";
61
+ } from "./chunk-NA3TZ4JN.js";
61
62
  import {
62
63
  eventPathFor,
63
64
  writeLogEvent
64
- } from "./chunk-JCDAEZVD.js";
65
+ } from "./chunk-5MV5SSRM.js";
65
66
  import {
67
+ CLIP_OUTBOX_DEFAULTS,
66
68
  CompoundSchema,
67
69
  ExitCode,
68
70
  MetaSchema,
@@ -72,8 +74,10 @@ import {
72
74
  detectSchema,
73
75
  err,
74
76
  extractFrontmatter,
77
+ isClipOutboxKey,
75
78
  isVaultSyncKey,
76
79
  ok,
80
+ parseClipOutboxKeyValue,
77
81
  parseVaultSyncKeyValue,
78
82
  readPage,
79
83
  redactSensitiveContent,
@@ -82,7 +86,7 @@ import {
82
86
  scanVault,
83
87
  splitFrontmatter,
84
88
  systemdPropertyFor
85
- } from "./chunk-GJC2CI3W.js";
89
+ } from "./chunk-R2DJ7IJ5.js";
86
90
 
87
91
  // src/commands/log-append.ts
88
92
  import { readFile, stat } from "fs/promises";
@@ -636,7 +640,7 @@ function computeAdamicAdar(adj) {
636
640
  import { resolve as resolvePath } from "path";
637
641
  async function guardProtectedVaultWrite(input) {
638
642
  const env = input.env ?? process.env;
639
- const home = input.home ?? process.env.HOME ?? "";
643
+ const home = resolveSkillwikiHome(input.home, env);
640
644
  const cwd = input.cwd ?? process.cwd();
641
645
  const liveVaultPath = await resolveLiveVaultPath({ env, home, cwd });
642
646
  const load = await loadFleetManifestAndHost({
@@ -1016,14 +1020,14 @@ import { readFile as readFile5 } from "fs/promises";
1016
1020
  import { existsSync as existsSync2 } from "fs";
1017
1021
  import { join as join5 } from "path";
1018
1022
  function validateKey(key) {
1019
- return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key) || isVaultSyncKey(key);
1023
+ return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key) || isVaultSyncKey(key) || isClipOutboxKey(key);
1020
1024
  }
1021
1025
  function displayConfigValue(key, value) {
1022
1026
  if (key === "SKILLWIKI_MCP_TOKEN") return value.length > 0 ? "TOKEN_SET" : "";
1023
1027
  return value;
1024
1028
  }
1025
1029
  function configPath(home) {
1026
- return join5(home, ".skillwiki", ".env");
1030
+ return join5(resolveSkillwikiHome(home), ".skillwiki", ".env");
1027
1031
  }
1028
1032
  async function runConfigGet(input) {
1029
1033
  if (!validateKey(input.key)) {
@@ -1043,6 +1047,12 @@ async function runConfigSet(input) {
1043
1047
  return { exitCode: ExitCode.INVALID_CONFIG_VALUE, result: err("INVALID_CONFIG_VALUE", { key: input.key, value: input.value, message: parsed.message }) };
1044
1048
  }
1045
1049
  }
1050
+ if (isClipOutboxKey(input.key)) {
1051
+ const parsed = parseClipOutboxKeyValue(input.key, input.value);
1052
+ if (!parsed.ok) {
1053
+ return { exitCode: ExitCode.INVALID_CONFIG_VALUE, result: err("INVALID_CONFIG_VALUE", { key: input.key, value: input.value, message: parsed.message }) };
1054
+ }
1055
+ }
1046
1056
  const filePath = configPath(input.home);
1047
1057
  try {
1048
1058
  let originalContent;
@@ -1104,7 +1114,7 @@ function mcpAuthFromEnv(env) {
1104
1114
  }
1105
1115
  async function resolveMcpAuthEnv(input) {
1106
1116
  const processAuth = mcpAuthFromEnv(input.env ?? process.env);
1107
- const dotenv = await parseDotenvFile(join6(input.home, ".skillwiki", ".env"));
1117
+ const dotenv = await parseDotenvFile(join6(resolveSkillwikiHome(input.home, input.env), ".skillwiki", ".env"));
1108
1118
  const fileAuth = mcpAuthFromEnv(dotenv);
1109
1119
  return {
1110
1120
  url: processAuth.url ?? fileAuth.url,
@@ -1129,7 +1139,7 @@ function isMcpOnlyLeaf(input) {
1129
1139
  }
1130
1140
 
1131
1141
  // src/doctor/runner.ts
1132
- import { existsSync as existsSync18 } from "fs";
1142
+ import { existsSync as existsSync20 } from "fs";
1133
1143
 
1134
1144
  // src/doctor/probes/environment.ts
1135
1145
  import { existsSync as existsSync4, lstatSync, readlinkSync } from "fs";
@@ -1398,7 +1408,7 @@ async function checkConfigFile(home) {
1398
1408
  try {
1399
1409
  const map = await parseDotenvFile(cfgPath);
1400
1410
  const keys = Object.keys(map);
1401
- return check("pass", "config_file", "Config file exists", `Found with keys: ${keys.length > 0 ? keys.join(", ") : "(none set)"}`);
1411
+ return check("pass", "config_file", "Config file exists", `Found ${cfgPath} with keys: ${keys.length > 0 ? keys.join(", ") : "(none set)"}`);
1402
1412
  } catch (e) {
1403
1413
  return check("warn", "config_file", "Config file exists", `Failed to parse ${cfgPath}: ${String(e)}`);
1404
1414
  }
@@ -2101,7 +2111,9 @@ import { execSync as execSync4 } from "child_process";
2101
2111
  // src/utils/s3-mount-health.ts
2102
2112
  import { execSync as execSync3 } from "child_process";
2103
2113
  import { platform as platform2 } from "os";
2104
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, readFileSync as readFile6 } from "fs";
2114
+ import { readFileSync as readFileSync6 } from "fs";
2115
+ import { readFile as readFile6, writeFile as writeFile2, unlink } from "fs/promises";
2116
+ import { randomUUID } from "crypto";
2105
2117
  import { join as join13 } from "path";
2106
2118
  var OS = platform2();
2107
2119
  function findRcloneMountPid() {
@@ -2259,36 +2271,45 @@ function detectFuseMount(vaultPath) {
2259
2271
  }
2260
2272
  return null;
2261
2273
  }
2262
- function writeTest(dir) {
2263
- const testFile = join13(dir, `.doctor-write-test-${process.pid}.tmp`);
2264
- const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
2274
+ async function writeTest(dir, options = {}) {
2275
+ const testFile = join13(dir, options.fileName ?? `.doctor-write-test-${process.pid}.tmp`);
2276
+ const io = options.io ?? {
2277
+ write: (path, payload2) => writeFile2(path, payload2, "utf8"),
2278
+ read: (path) => readFile6(path, "utf8"),
2279
+ remove: (path) => unlink(path)
2280
+ };
2281
+ const payload = `skillwiki write test \u2014 ${(/* @__PURE__ */ new Date()).toISOString()} \u2014 ${randomUUID()}`;
2265
2282
  const start = Date.now();
2266
2283
  try {
2267
- writeFileSync2(testFile, payload, "utf8");
2284
+ await io.write(testFile, payload);
2268
2285
  } catch (e) {
2286
+ try {
2287
+ await io.remove(testFile);
2288
+ } catch {
2289
+ }
2269
2290
  return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
2270
2291
  }
2271
2292
  const writeMs = Date.now() - start;
2272
2293
  const readStart = Date.now();
2273
2294
  try {
2274
- const back = readFile6(testFile, "utf8");
2295
+ const back = await io.read(testFile);
2275
2296
  const readMs = Date.now() - readStart;
2276
2297
  if (back !== payload) {
2277
2298
  try {
2278
- unlinkSync2(testFile);
2299
+ await io.remove(testFile);
2279
2300
  } catch {
2280
2301
  }
2281
2302
  return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
2282
2303
  }
2283
2304
  } catch (e) {
2284
2305
  try {
2285
- unlinkSync2(testFile);
2306
+ await io.remove(testFile);
2286
2307
  } catch {
2287
2308
  }
2288
2309
  return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
2289
2310
  }
2290
2311
  try {
2291
- unlinkSync2(testFile);
2312
+ await io.remove(testFile);
2292
2313
  } catch {
2293
2314
  }
2294
2315
  return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
@@ -2522,7 +2543,7 @@ function checkRcloneVersion(resolvedPath, vaultSyncInstalled, pushEnabled = true
2522
2543
  }
2523
2544
  return check("pass", "rclone_version", "rclone version", ver.raw);
2524
2545
  }
2525
- function checkWriteTest(resolvedPath) {
2546
+ async function checkWriteTest(resolvedPath) {
2526
2547
  if (!resolvedPath) {
2527
2548
  return check("pass", "s3_write_test", "S3 write test", "No vault path \u2014 check skipped");
2528
2549
  }
@@ -2534,7 +2555,7 @@ function checkWriteTest(resolvedPath) {
2534
2555
  if (!existsSync10(conceptsDir)) {
2535
2556
  return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
2536
2557
  }
2537
- const result = writeTest(conceptsDir);
2558
+ const result = await writeTest(conceptsDir);
2538
2559
  if (result.success) {
2539
2560
  const totalMs = result.writeMs + result.readMs;
2540
2561
  if (totalMs > 3e3) {
@@ -2617,13 +2638,13 @@ function checkVfsCacheHealth(resolvedPath) {
2617
2638
  }
2618
2639
  var s3MountHealthProbe = {
2619
2640
  id: "s3_mount_health",
2620
- run(ctx) {
2641
+ async run(ctx) {
2621
2642
  return [
2622
2643
  checkS3MountPerf(ctx.resolvedPath),
2623
2644
  checkS3MountFreshness(ctx.resolvedPath),
2624
2645
  checkRcloneFlagAudit(ctx.resolvedPath),
2625
2646
  checkRcloneVersion(ctx.resolvedPath, ctx.vsConfig.installed, ctx.vsConfig.pushEnabled !== false, ctx.mcpOnlyLeaf),
2626
- checkWriteTest(ctx.resolvedPath),
2647
+ await checkWriteTest(ctx.resolvedPath),
2627
2648
  checkVfsCacheHealth(ctx.resolvedPath)
2628
2649
  ];
2629
2650
  }
@@ -4255,6 +4276,46 @@ var dsStoreNoiseProbe = {
4255
4276
  // src/doctor/probes/mcp-handshake.ts
4256
4277
  import { existsSync as existsSync17 } from "fs";
4257
4278
  import { join as join21 } from "path";
4279
+
4280
+ // src/utils/mcp-client.ts
4281
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4282
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4283
+ var MCP_PROBE_TIMEOUT_MS = 1e4;
4284
+ async function withMcpClient(input, probe) {
4285
+ const controller = new AbortController();
4286
+ const client = new Client({ name: input.clientName, version: input.version });
4287
+ const fetchFn = input.fetchFn ?? globalThis.fetch;
4288
+ const transport = new StreamableHTTPClientTransport(new URL(input.url), {
4289
+ requestInit: { headers: { Authorization: `Bearer ${input.token}` } },
4290
+ fetch: (url, init) => fetchFn(url, {
4291
+ ...init,
4292
+ signal: init?.signal ? AbortSignal.any([init.signal, controller.signal]) : controller.signal
4293
+ })
4294
+ });
4295
+ let timer;
4296
+ const deadline = new Promise((_resolve, reject) => {
4297
+ timer = setTimeout(() => {
4298
+ const error = new Error(`MCP probe timed out after ${MCP_PROBE_TIMEOUT_MS}ms`);
4299
+ reject(error);
4300
+ controller.abort(error);
4301
+ }, MCP_PROBE_TIMEOUT_MS);
4302
+ });
4303
+ try {
4304
+ const work = async () => {
4305
+ await client.connect(transport, { signal: controller.signal, timeout: MCP_PROBE_TIMEOUT_MS });
4306
+ return probe(client, controller.signal);
4307
+ };
4308
+ return await Promise.race([work(), deadline]);
4309
+ } catch (error) {
4310
+ throw new Error(redactMcpSecret(error instanceof Error ? error.message : String(error), input.token));
4311
+ } finally {
4312
+ clearTimeout(timer);
4313
+ controller.abort();
4314
+ await client.close();
4315
+ }
4316
+ }
4317
+
4318
+ // src/doctor/probes/mcp-handshake.ts
4258
4319
  var DEFAULT_MCP_URL = "https://wiki.karldigi.dev/mcp";
4259
4320
  function mcpEnv(ctx) {
4260
4321
  return ctx.input.env ?? process.env;
@@ -4322,54 +4383,6 @@ function redactSecret(detail, secret) {
4322
4383
  function handshakeRow(status, detail, secret) {
4323
4384
  return check(status, HANDSHAKE_ID, HANDSHAKE_LABEL, redactSecret(detail, secret));
4324
4385
  }
4325
- function parseMcpBody(text) {
4326
- const trimmed = text.trim();
4327
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
4328
- return JSON.parse(trimmed);
4329
- }
4330
- for (const line of trimmed.split(/\r?\n/)) {
4331
- const prefix = line.startsWith("data:") ? line.slice(5).trim() : "";
4332
- if (prefix.startsWith("{")) return JSON.parse(prefix);
4333
- }
4334
- throw new Error("non-JSON MCP body");
4335
- }
4336
- function jsonRpcResult(body) {
4337
- if (typeof body !== "object" || body === null) {
4338
- throw new Error("invalid JSON-RPC body");
4339
- }
4340
- const rec = body;
4341
- if (rec.error) {
4342
- throw new Error(typeof rec.error.message === "string" ? rec.error.message : "JSON-RPC error");
4343
- }
4344
- if (typeof rec.result !== "object" || rec.result === null) {
4345
- throw new Error("missing JSON-RPC result");
4346
- }
4347
- return rec.result;
4348
- }
4349
- async function mcpJsonRpc(fetchFn, url, token, method, id, params, sessionId) {
4350
- const headers = {
4351
- Authorization: `Bearer ${token}`,
4352
- "Content-Type": "application/json",
4353
- Accept: "application/json, text/event-stream"
4354
- };
4355
- if (sessionId) headers["mcp-session-id"] = sessionId;
4356
- const res = await fetchFn(url, {
4357
- method: "POST",
4358
- headers,
4359
- body: JSON.stringify({ jsonrpc: "2.0", id, method, params })
4360
- });
4361
- if (!res.ok) {
4362
- throw new Error(`HTTP ${res.status}`);
4363
- }
4364
- const nextSession = res.headers.get("mcp-session-id") ?? sessionId;
4365
- const result = jsonRpcResult(parseMcpBody(await res.text()));
4366
- return { result, sessionId: nextSession ?? void 0 };
4367
- }
4368
- function toolNames(result) {
4369
- const tools = result.tools;
4370
- if (!Array.isArray(tools)) return [];
4371
- return tools.map((t) => typeof t === "object" && t !== null && typeof t.name === "string" ? t.name : "").filter((name) => name.length > 0);
4372
- }
4373
4386
  async function checkMcpHandshake(ctx) {
4374
4387
  if (!ctx.input.checkMcp) {
4375
4388
  return handshakeRow("pass", "not requested \u2014 check skipped");
@@ -4384,33 +4397,16 @@ async function checkMcpHandshake(ctx) {
4384
4397
  const fetchFn = ctx.input.mcpFetch ?? globalThis.fetch;
4385
4398
  const shipped = ctx.input.currentVersion;
4386
4399
  try {
4387
- const init = await mcpJsonRpc(
4388
- fetchFn,
4389
- url,
4390
- token,
4391
- "initialize",
4392
- 1,
4393
- {
4394
- protocolVersion: "2025-11-25",
4395
- capabilities: {},
4396
- clientInfo: { name: "skillwiki-doctor", version: shipped }
4397
- }
4400
+ const { advertised, tools } = await withMcpClient(
4401
+ { fetchFn, url, token, clientName: "skillwiki-doctor", version: shipped },
4402
+ async (client, signal) => ({
4403
+ advertised: client.getServerVersion()?.version ?? "",
4404
+ tools: (await client.listTools({}, { signal })).tools.map((tool) => tool.name)
4405
+ })
4398
4406
  );
4399
- const serverInfo = init.result.serverInfo;
4400
- const advertised = typeof serverInfo === "object" && serverInfo !== null && typeof serverInfo.version === "string" ? serverInfo.version : "";
4401
4407
  if (!advertised) {
4402
4408
  return handshakeRow("error", "handshake failed \u2014 missing serverInfo.version", token);
4403
4409
  }
4404
- const listed = await mcpJsonRpc(
4405
- fetchFn,
4406
- url,
4407
- token,
4408
- "tools/list",
4409
- 2,
4410
- {},
4411
- init.sessionId
4412
- );
4413
- const tools = toolNames(listed.result);
4414
4410
  const frozen = Boolean(ctx.resolvedPath && existsSync17(join21(ctx.resolvedPath, ".WIKI_GIT_FROZEN")));
4415
4411
  if (frozen && !tools.includes(WORKITEM_TOOL)) {
4416
4412
  return handshakeRow(
@@ -4446,6 +4442,307 @@ var mcpHandshakeProbe = {
4446
4442
  }
4447
4443
  };
4448
4444
 
4445
+ // src/doctor/probes/clip-outbox.ts
4446
+ import { existsSync as existsSync19 } from "fs";
4447
+ import { execSync as execSync7 } from "child_process";
4448
+ import { platform as platform6 } from "os";
4449
+ import { join as join23 } from "path";
4450
+
4451
+ // src/utils/clip-outbox-config.ts
4452
+ import { realpathSync, statSync as statSync3, existsSync as existsSync18, accessSync, constants } from "fs";
4453
+ import { homedir } from "os";
4454
+ import { isAbsolute, join as join22, resolve as resolve3, sep as sep2 } from "path";
4455
+ var CLIP_NOT_CONFIGURED = "clip channel not configured";
4456
+ var PRODUCTION_WATCHER_LABEL = CLIP_OUTBOX_DEFAULTS.watcher_label;
4457
+ var RETIRED_WATCHER_LABELS = [
4458
+ "com.karlchow.wiki-dev-outbox",
4459
+ "com.karlchow.wiki-dev-sse",
4460
+ "com.karlchow.wiki-dev-hourly",
4461
+ "com.karlchow.skillwiki-note-outbox"
4462
+ ];
4463
+ function expandUserPath(raw, home) {
4464
+ if (raw === "~") return home;
4465
+ if (raw.startsWith("~/")) return resolve3(home, raw.slice(2));
4466
+ return raw;
4467
+ }
4468
+ function canonicalPath(raw, home) {
4469
+ const expanded = expandUserPath(raw.trim(), home);
4470
+ if (expanded.includes("\0")) return { ok: false, message: "path contains NUL" };
4471
+ if (expanded.includes("://") || expanded.includes("@")) {
4472
+ return { ok: false, message: "path must not include URL credentials" };
4473
+ }
4474
+ if (!isAbsolute(expanded)) return { ok: false, message: "must be an absolute path" };
4475
+ try {
4476
+ const real = existsSync18(expanded) ? realpathSync(expanded) : resolve3(expanded);
4477
+ if (real.split(sep2).some((seg) => seg === "..")) {
4478
+ return { ok: false, message: "path must not contain traversal" };
4479
+ }
4480
+ return { ok: true, path: real };
4481
+ } catch (error) {
4482
+ return { ok: false, message: error instanceof Error ? error.message : "invalid path" };
4483
+ }
4484
+ }
4485
+ function nestedOrEqual(a, b) {
4486
+ if (a === b) return true;
4487
+ const prefixA = a.endsWith(sep2) ? a : a + sep2;
4488
+ const prefixB = b.endsWith(sep2) ? b : b + sep2;
4489
+ return prefixA.startsWith(prefixB) || prefixB.startsWith(prefixA);
4490
+ }
4491
+ function intField(value, fallback) {
4492
+ return Number(value && value.length > 0 ? value : fallback);
4493
+ }
4494
+ function requireWritableDir(path, label) {
4495
+ try {
4496
+ const st = statSync3(path);
4497
+ if (!st.isDirectory()) return `${label} is not a directory`;
4498
+ accessSync(path, constants.W_OK);
4499
+ return void 0;
4500
+ } catch {
4501
+ return `${label} is missing or not writable`;
4502
+ }
4503
+ }
4504
+ function resolveClipOutboxConfig(map, home) {
4505
+ const present = Object.keys(map).filter(isClipOutboxKey);
4506
+ for (const key of present) {
4507
+ const parsed = parseClipOutboxKeyValue(key, map[key] ?? "");
4508
+ if (!parsed.ok) return { ok: false, message: `${key}: ${parsed.message}` };
4509
+ }
4510
+ const enabled = (map["clip_outbox.enabled"] ?? CLIP_OUTBOX_DEFAULTS.enabled) === "true";
4511
+ if (!enabled) {
4512
+ return { ok: true, enabled: false };
4513
+ }
4514
+ const required = [
4515
+ "clip_outbox.root",
4516
+ "clip_outbox.state_root",
4517
+ "clip_outbox.mirror_root",
4518
+ "clip_outbox.ready_url",
4519
+ "clip_outbox.s3_credential_ref",
4520
+ "clip_outbox.ready_credential_ref"
4521
+ ];
4522
+ for (const key of required) {
4523
+ if (!map[key]) return { ok: false, message: `${key} is required when clip_outbox.enabled=true` };
4524
+ }
4525
+ const d8Absent = map["clip_outbox.d8_note_absent"] === "true";
4526
+ const d8RootRaw = map["clip_outbox.d8_note_root"];
4527
+ if (d8Absent && d8RootRaw) {
4528
+ return { ok: false, message: "clip_outbox.d8_note_root and clip_outbox.d8_note_absent cannot both be set" };
4529
+ }
4530
+ if (!d8Absent && !d8RootRaw) {
4531
+ return { ok: false, message: "clip_outbox.d8_note_root or clip_outbox.d8_note_absent=true is required when enabled" };
4532
+ }
4533
+ const root = canonicalPath(map["clip_outbox.root"], home);
4534
+ const stateRoot = canonicalPath(map["clip_outbox.state_root"], home);
4535
+ const mirrorRoot = canonicalPath(map["clip_outbox.mirror_root"], home);
4536
+ if (!root.ok) return { ok: false, message: `clip_outbox.root: ${root.message}` };
4537
+ if (!stateRoot.ok) return { ok: false, message: `clip_outbox.state_root: ${stateRoot.message}` };
4538
+ if (!mirrorRoot.ok) return { ok: false, message: `clip_outbox.mirror_root: ${mirrorRoot.message}` };
4539
+ let d8NoteRoot = null;
4540
+ if (d8RootRaw) {
4541
+ const d8 = canonicalPath(d8RootRaw, home);
4542
+ if (!d8.ok) return { ok: false, message: `clip_outbox.d8_note_root: ${d8.message}` };
4543
+ d8NoteRoot = d8.path;
4544
+ }
4545
+ if (nestedOrEqual(root.path, mirrorRoot.path)) {
4546
+ return { ok: false, message: "clip_outbox.root must be outside the frozen mirror" };
4547
+ }
4548
+ if (nestedOrEqual(root.path, stateRoot.path) || nestedOrEqual(stateRoot.path, mirrorRoot.path)) {
4549
+ return { ok: false, message: "clip_outbox.state_root must be disjoint from outbox and mirror" };
4550
+ }
4551
+ if (d8NoteRoot && (nestedOrEqual(root.path, d8NoteRoot) || nestedOrEqual(stateRoot.path, d8NoteRoot))) {
4552
+ return { ok: false, message: "clip outbox must not equal or nest the D8 note outbox" };
4553
+ }
4554
+ const rootErr = requireWritableDir(root.path, "clip_outbox.root");
4555
+ if (rootErr) return { ok: false, message: rootErr };
4556
+ const stateErr = requireWritableDir(stateRoot.path, "clip_outbox.state_root");
4557
+ if (stateErr) {
4558
+ if (existsSync18(stateRoot.path)) return { ok: false, message: stateErr };
4559
+ const parentErr = requireWritableDir(resolve3(stateRoot.path, ".."), "clip_outbox.state_root parent");
4560
+ if (parentErr) return { ok: false, message: stateErr };
4561
+ }
4562
+ if (!existsSync18(mirrorRoot.path)) {
4563
+ return { ok: false, message: "clip_outbox.mirror_root does not exist" };
4564
+ }
4565
+ const vaultId = map["clip_outbox.vault_id"] ?? CLIP_OUTBOX_DEFAULTS.vault_id;
4566
+ if (vaultId !== "central") return { ok: false, message: "clip_outbox.vault_id must be central" };
4567
+ const destination = map["clip_outbox.destination"] ?? CLIP_OUTBOX_DEFAULTS.destination;
4568
+ if (destination !== CLIP_OUTBOX_DEFAULTS.destination) {
4569
+ return { ok: false, message: "clip_outbox.destination must be seaweed-wiki:cloud/wiki" };
4570
+ }
4571
+ const mode = map["clip_outbox.mode"] ?? CLIP_OUTBOX_DEFAULTS.mode;
4572
+ if (mode !== "event-watch") return { ok: false, message: "clip_outbox.mode must be event-watch" };
4573
+ const watcherLabel = map["clip_outbox.watcher_label"] ?? CLIP_OUTBOX_DEFAULTS.watcher_label;
4574
+ if (watcherLabel !== PRODUCTION_WATCHER_LABEL) {
4575
+ return { ok: false, message: "clip_outbox.watcher_label must be the production clip watcher" };
4576
+ }
4577
+ const imageTypes = (map["clip_outbox.image_types"] ?? CLIP_OUTBOX_DEFAULTS.image_types).split(",").map((p) => p.trim()).filter(Boolean);
4578
+ return {
4579
+ ok: true,
4580
+ enabled: true,
4581
+ config: {
4582
+ enabled: true,
4583
+ root: root.path,
4584
+ stateRoot: stateRoot.path,
4585
+ mirrorRoot: mirrorRoot.path,
4586
+ d8NoteRoot,
4587
+ d8NoteAbsent: d8Absent,
4588
+ vaultId: "central",
4589
+ allowedPrefixes: ["raw/articles", "raw/assets"],
4590
+ destination: "seaweed-wiki:cloud/wiki",
4591
+ mode: "event-watch",
4592
+ pollFallbackEnabled: (map["clip_outbox.poll_fallback_enabled"] ?? CLIP_OUTBOX_DEFAULTS.poll_fallback_enabled) === "true",
4593
+ reconcileSeconds: intField(map["clip_outbox.reconcile_seconds"], CLIP_OUTBOX_DEFAULTS.reconcile_seconds),
4594
+ debounceMs: intField(map["clip_outbox.debounce_ms"], CLIP_OUTBOX_DEFAULTS.debounce_ms),
4595
+ stableHashMs: intField(map["clip_outbox.stable_hash_ms"], CLIP_OUTBOX_DEFAULTS.stable_hash_ms),
4596
+ maxArticleBytes: intField(map["clip_outbox.max_article_bytes"], CLIP_OUTBOX_DEFAULTS.max_article_bytes),
4597
+ maxImages: intField(map["clip_outbox.max_images"], CLIP_OUTBOX_DEFAULTS.max_images),
4598
+ maxImageBytes: intField(map["clip_outbox.max_image_bytes"], CLIP_OUTBOX_DEFAULTS.max_image_bytes),
4599
+ maxBundleBytes: intField(map["clip_outbox.max_bundle_bytes"], CLIP_OUTBOX_DEFAULTS.max_bundle_bytes),
4600
+ maxPixels: intField(map["clip_outbox.max_pixels"], CLIP_OUTBOX_DEFAULTS.max_pixels),
4601
+ downloadConcurrency: intField(map["clip_outbox.download_concurrency"], CLIP_OUTBOX_DEFAULTS.download_concurrency),
4602
+ imageTypes,
4603
+ maxRedirects: intField(map["clip_outbox.max_redirects"], CLIP_OUTBOX_DEFAULTS.max_redirects),
4604
+ requestTimeoutMs: intField(map["clip_outbox.request_timeout_ms"], CLIP_OUTBOX_DEFAULTS.request_timeout_ms),
4605
+ localizeTimeoutMs: intField(map["clip_outbox.localize_timeout_ms"], CLIP_OUTBOX_DEFAULTS.localize_timeout_ms),
4606
+ readyUrl: map["clip_outbox.ready_url"],
4607
+ s3CredentialRef: map["clip_outbox.s3_credential_ref"],
4608
+ readyCredentialRef: map["clip_outbox.ready_credential_ref"],
4609
+ watcherLabel: PRODUCTION_WATCHER_LABEL
4610
+ }
4611
+ };
4612
+ }
4613
+ function clipOutboxConfigPath(home) {
4614
+ return join22(resolveSkillwikiHome(home), ".skillwiki", ".env");
4615
+ }
4616
+ async function loadClipOutboxConfig(home) {
4617
+ const map = await parseDotenvFile(clipOutboxConfigPath(home));
4618
+ return resolveClipOutboxConfig(map, home || homedir());
4619
+ }
4620
+
4621
+ // src/doctor/probes/clip-outbox.ts
4622
+ var CLIP_CHECK_IDS = [
4623
+ "clip_config",
4624
+ "clip_frozen_mirror",
4625
+ "clip_push_isolation",
4626
+ "clip_outbox_root",
4627
+ "clip_watcher",
4628
+ "clip_storage_credentials",
4629
+ "clip_ready_channel",
4630
+ "clip_mirror_listener"
4631
+ ];
4632
+ function notConfigured() {
4633
+ return CLIP_CHECK_IDS.map(
4634
+ (id) => check("info", id, clipLabel(id), CLIP_NOT_CONFIGURED)
4635
+ );
4636
+ }
4637
+ function clipLabel(id) {
4638
+ switch (id) {
4639
+ case "clip_config":
4640
+ return "Clip outbox configuration";
4641
+ case "clip_frozen_mirror":
4642
+ return "Clip frozen mirror";
4643
+ case "clip_push_isolation":
4644
+ return "Clip push isolation";
4645
+ case "clip_outbox_root":
4646
+ return "Clip outbox root";
4647
+ case "clip_watcher":
4648
+ return "Clip outbox watcher";
4649
+ case "clip_storage_credentials":
4650
+ return "Clip storage credentials";
4651
+ case "clip_ready_channel":
4652
+ return "Clip ready channel";
4653
+ case "clip_mirror_listener":
4654
+ return "Clip mirror listener";
4655
+ default:
4656
+ return id;
4657
+ }
4658
+ }
4659
+ function launchdLoaded(label) {
4660
+ if (platform6() !== "darwin") return false;
4661
+ try {
4662
+ execSync7(`launchctl list ${label}`, {
4663
+ encoding: "utf8",
4664
+ timeout: 2e3,
4665
+ stdio: ["pipe", "ignore", "ignore"]
4666
+ });
4667
+ return true;
4668
+ } catch {
4669
+ return false;
4670
+ }
4671
+ }
4672
+ function watcherStatus(env, label) {
4673
+ const fixture = env.CLIP_WATCHER_FIXTURE;
4674
+ if (fixture === "loaded-enabled") return { loaded: true, enabled: true, kind: "production" };
4675
+ if (fixture === "stopped") return { loaded: false, enabled: false, kind: "production" };
4676
+ if (fixture === "retired-wiki-dev") return { loaded: true, enabled: true, kind: "retired" };
4677
+ if (fixture === "missing") return { loaded: false, enabled: false, kind: "unknown" };
4678
+ if (RETIRED_WATCHER_LABELS.includes(label)) {
4679
+ return { loaded: false, enabled: false, kind: "retired" };
4680
+ }
4681
+ if (!launchdLoaded(label)) {
4682
+ return { loaded: false, enabled: false, kind: "unknown" };
4683
+ }
4684
+ const plist = join23(process.env.HOME ?? "", "Library", "LaunchAgents", `${label}.plist`);
4685
+ return { loaded: true, enabled: existsSync19(plist), kind: "production" };
4686
+ }
4687
+ function pushLoaded(env) {
4688
+ if (env.CLIP_PUSH_FIXTURE === "loaded") return true;
4689
+ if (env.CLIP_PUSH_FIXTURE === "unloaded") return false;
4690
+ return launchdLoaded("com.karlchow.wiki-push");
4691
+ }
4692
+ function credentialPresent(env, ref) {
4693
+ if (env.CLIP_CREDENTIAL_FIXTURE === "present") return true;
4694
+ if (env.CLIP_CREDENTIAL_FIXTURE === "absent") return false;
4695
+ const value = env[ref];
4696
+ return typeof value === "string" && value.length > 0;
4697
+ }
4698
+ function listenerOk(env) {
4699
+ if (env.CLIP_LISTENER_FIXTURE === "ok") return true;
4700
+ return false;
4701
+ }
4702
+ function conditionalCreateEvidence(env) {
4703
+ return env.CLIP_CONDITIONAL_CREATE_FIXTURE === "ok";
4704
+ }
4705
+ var clipOutboxProbe = {
4706
+ id: "clip_outbox",
4707
+ async run(ctx) {
4708
+ if (ctx.mcpOnlyLeaf) {
4709
+ return CLIP_CHECK_IDS.map(
4710
+ (id) => check("info", id, clipLabel(id), MCP_ONLY_LEAF_SKIP)
4711
+ );
4712
+ }
4713
+ const env = ctx.input.env ?? process.env;
4714
+ const resolved = await loadClipOutboxConfig(ctx.input.home);
4715
+ if (!resolved.ok) {
4716
+ return [
4717
+ check("error", "clip_config", clipLabel("clip_config"), resolved.message),
4718
+ ...CLIP_CHECK_IDS.slice(1).map((id) => check("error", id, clipLabel(id), "clip configuration invalid"))
4719
+ ];
4720
+ }
4721
+ if (!resolved.enabled) {
4722
+ return notConfigured();
4723
+ }
4724
+ const cfg = resolved.config;
4725
+ const frozen = existsSync19(join23(cfg.mirrorRoot, ".WIKI_GIT_FROZEN"));
4726
+ const pushOn = ctx.vsConfig.pushEnabled !== false;
4727
+ const pushIsLoaded = pushLoaded(env);
4728
+ const watch = watcherStatus(env, cfg.watcherLabel);
4729
+ const s3Creds = credentialPresent(env, cfg.s3CredentialRef);
4730
+ const readyCreds = credentialPresent(env, cfg.readyCredentialRef);
4731
+ const condCreate = conditionalCreateEvidence(env);
4732
+ const listener = listenerOk(env);
4733
+ return [
4734
+ check("pass", "clip_config", clipLabel("clip_config"), "clip_outbox.enabled=true; schema valid"),
4735
+ frozen ? check("pass", "clip_frozen_mirror", clipLabel("clip_frozen_mirror"), "frozen marker present") : check("error", "clip_frozen_mirror", clipLabel("clip_frozen_mirror"), "frozen marker missing; do not unfreeze"),
4736
+ !pushOn && !pushIsLoaded ? check("pass", "clip_push_isolation", clipLabel("clip_push_isolation"), "wiki-push disabled and unloaded") : check("error", "clip_push_isolation", clipLabel("clip_push_isolation"), "wiki-push must stay disabled and unloaded"),
4737
+ check("pass", "clip_outbox_root", clipLabel("clip_outbox_root"), "outbox and state roots are canonical and disjoint"),
4738
+ watch.kind === "retired" ? check("error", "clip_watcher", clipLabel("clip_watcher"), "retired wiki-dev or D8 job does not satisfy the clip watcher") : watch.loaded && watch.enabled && watch.kind === "production" ? check("pass", "clip_watcher", clipLabel("clip_watcher"), `loaded and enabled: ${PRODUCTION_WATCHER_LABEL}`) : check("error", "clip_watcher", clipLabel("clip_watcher"), "production clip watcher not loaded and enabled"),
4739
+ s3Creds && readyCreds && condCreate ? check("pass", "clip_storage_credentials", clipLabel("clip_storage_credentials"), "credential references present; conditional-create evidence available") : check("error", "clip_storage_credentials", clipLabel("clip_storage_credentials"), "missing credential reference or conditional-create evidence"),
4740
+ readyCreds ? check("pass", "clip_ready_channel", clipLabel("clip_ready_channel"), "ready URL and credential reference present") : check("error", "clip_ready_channel", clipLabel("clip_ready_channel"), "ready credential missing"),
4741
+ listener ? check("pass", "clip_mirror_listener", clipLabel("clip_mirror_listener"), "inbound production SSE listener configured") : check("error", "clip_mirror_listener", clipLabel("clip_mirror_listener"), "inbound SSE listener disabled or missing")
4742
+ ];
4743
+ }
4744
+ };
4745
+
4449
4746
  // src/doctor/probes/index.ts
4450
4747
  var DOCTOR_PROBES = [
4451
4748
  environmentProbe,
@@ -4460,7 +4757,8 @@ var DOCTOR_PROBES = [
4460
4757
  fuseStalenessProbe,
4461
4758
  activationMarkerProbe,
4462
4759
  dsStoreNoiseProbe,
4463
- mcpHandshakeProbe
4760
+ mcpHandshakeProbe,
4761
+ clipOutboxProbe
4464
4762
  ];
4465
4763
 
4466
4764
  // src/doctor/runner.ts
@@ -4471,7 +4769,7 @@ function resolveSnapshotGitWorktree(home) {
4471
4769
  const configured = resolveConfiguredSnapshotWorktree(home);
4472
4770
  if (configured) return configured;
4473
4771
  const defaultPath = "/root/wiki-git";
4474
- return existsSync18(defaultPath) ? defaultPath : void 0;
4772
+ return existsSync20(defaultPath) ? defaultPath : void 0;
4475
4773
  }
4476
4774
  var DoctorRunner = class {
4477
4775
  probes;
@@ -4482,6 +4780,7 @@ var DoctorRunner = class {
4482
4780
  return this.probes;
4483
4781
  }
4484
4782
  async run(input) {
4783
+ input = { ...input, home: resolveSkillwikiHome(input.home, input.env) };
4485
4784
  const devSourceRun = isDevSourceRun(input.argv);
4486
4785
  const vsConfig = readVaultSyncConfig(input.home);
4487
4786
  const baseEnv = input.env ?? process.env;
@@ -4581,8 +4880,8 @@ function readCliPackageJson(baseUrl = import.meta.url) {
4581
4880
 
4582
4881
  // src/utils/vault-write-gates.ts
4583
4882
  import { execFileSync } from "child_process";
4584
- import { existsSync as existsSync19, readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync3 } from "fs";
4585
- import { join as join22, relative as relative2 } from "path";
4883
+ import { existsSync as existsSync21, readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
4884
+ import { join as join24, relative as relative2 } from "path";
4586
4885
  var DEFAULT_DIRTY_VOLUME_THRESHOLD = 50;
4587
4886
  var DEFAULT_CAPTURE_BUDGET = 20;
4588
4887
  var DEFAULT_NO_DECISION_STREAK = 3;
@@ -4638,7 +4937,7 @@ function isEventLedgerPath(rel) {
4638
4937
  return n === "meta/log-events" || n.startsWith("meta/log-events/");
4639
4938
  }
4640
4939
  function countEventLedgerFiles(vault) {
4641
- return listFilesRecursive(join22(vault, "meta", "log-events")).length;
4940
+ return listFilesRecursive(join24(vault, "meta", "log-events")).length;
4642
4941
  }
4643
4942
  function measureDirtyVolume(vault) {
4644
4943
  const empty = (extra = {}) => ({
@@ -4654,7 +4953,7 @@ function measureDirtyVolume(vault) {
4654
4953
  is_git_repo: false,
4655
4954
  ...extra
4656
4955
  });
4657
- if (!existsSync19(vault) || !statSync3(vault).isDirectory()) {
4956
+ if (!existsSync21(vault) || !statSync4(vault).isDirectory()) {
4658
4957
  return empty();
4659
4958
  }
4660
4959
  const gitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
@@ -4698,8 +4997,8 @@ function measureDirtyVolume(vault) {
4698
4997
  if (!rel) continue;
4699
4998
  if (code === "??") {
4700
4999
  untracked += 1;
4701
- const abs = join22(vault, rel);
4702
- if (existsSync19(abs) && statSync3(abs).isDirectory()) {
5000
+ const abs = join24(vault, rel);
5001
+ if (existsSync21(abs) && statSync4(abs).isDirectory()) {
4703
5002
  const files = listFilesRecursive(abs);
4704
5003
  expanded += files.length;
4705
5004
  addBucket(rel, files.length);
@@ -4742,9 +5041,9 @@ function listFilesRecursive(dir) {
4742
5041
  }
4743
5042
  for (const name of entries) {
4744
5043
  if (name === ".git") continue;
4745
- const p = join22(dir, name);
5044
+ const p = join24(dir, name);
4746
5045
  try {
4747
- const st = statSync3(p);
5046
+ const st = statSync4(p);
4748
5047
  if (st.isDirectory()) out.push(...listFilesRecursive(p));
4749
5048
  else if (st.isFile()) out.push(p);
4750
5049
  } catch {
@@ -4837,7 +5136,7 @@ var CAPTURE_HYGIENE_CONTRACT = `After each productive cycle (or when daily captu
4837
5136
  function listProjectDayCaptures(vault, project, day) {
4838
5137
  const found = [];
4839
5138
  const slug = project.replace(/^\[\[/, "").replace(/\]\]$/, "").trim();
4840
- if (!slug || !existsSync19(vault)) return found;
5139
+ if (!slug || !existsSync21(vault)) return found;
4841
5140
  const consider = (abs, rel) => {
4842
5141
  if (!rel.endsWith(".md")) return;
4843
5142
  const base = rel.split(/[/\\]/).pop() ?? "";
@@ -4865,7 +5164,7 @@ function listProjectDayCaptures(vault, project, day) {
4865
5164
  }
4866
5165
  };
4867
5166
  const walk = (dir, relBase) => {
4868
- if (!existsSync19(dir)) return;
5167
+ if (!existsSync21(dir)) return;
4869
5168
  let entries;
4870
5169
  try {
4871
5170
  entries = readdirSync7(dir);
@@ -4873,27 +5172,27 @@ function listProjectDayCaptures(vault, project, day) {
4873
5172
  return;
4874
5173
  }
4875
5174
  for (const name of entries) {
4876
- const abs = join22(dir, name);
5175
+ const abs = join24(dir, name);
4877
5176
  const rel = relBase ? `${relBase}/${name}` : name;
4878
5177
  try {
4879
- const st = statSync3(abs);
5178
+ const st = statSync4(abs);
4880
5179
  if (st.isDirectory()) walk(abs, rel);
4881
5180
  else if (st.isFile()) consider(abs, rel);
4882
5181
  } catch {
4883
5182
  }
4884
5183
  }
4885
5184
  };
4886
- walk(join22(vault, "raw", "transcripts"), "raw/transcripts");
4887
- walk(join22(vault, "projects", slug, "raw", "transcripts"), `projects/${slug}/raw/transcripts`);
4888
- walk(join22(vault, "projects", slug, "requirements"), `projects/${slug}/requirements`);
4889
- const workRoot = join22(vault, "projects", slug, "work");
4890
- if (existsSync19(workRoot)) {
5185
+ walk(join24(vault, "raw", "transcripts"), "raw/transcripts");
5186
+ walk(join24(vault, "projects", slug, "raw", "transcripts"), `projects/${slug}/raw/transcripts`);
5187
+ walk(join24(vault, "projects", slug, "requirements"), `projects/${slug}/requirements`);
5188
+ const workRoot = join24(vault, "projects", slug, "work");
5189
+ if (existsSync21(workRoot)) {
4891
5190
  try {
4892
5191
  for (const name of readdirSync7(workRoot)) {
4893
5192
  if (!name.startsWith(day)) continue;
4894
5193
  if (!/investigate|pilot-q|research|cycle|dev-loop/.test(name)) continue;
4895
- const abs = join22(workRoot, name);
4896
- if (statSync3(abs).isDirectory()) {
5194
+ const abs = join24(workRoot, name);
5195
+ if (statSync4(abs).isDirectory()) {
4897
5196
  for (const f of listFilesRecursive(abs)) {
4898
5197
  const rel = relative2(vault, f).replace(/\\/g, "/");
4899
5198
  if (rel.endsWith(".md")) found.push(rel);
@@ -4937,7 +5236,7 @@ function evaluateCaptureBudget(input) {
4937
5236
  return { allowed: true, reason: "under_budget", report };
4938
5237
  }
4939
5238
  function runWritePreflight(input) {
4940
- if (!input.vault || !existsSync19(input.vault)) {
5239
+ if (!input.vault || !existsSync21(input.vault)) {
4941
5240
  return err(GateError.VAULT_PATH_INVALID, { path: input.vault });
4942
5241
  }
4943
5242
  const want = new Set(input.checks ?? ["all"]);
@@ -4996,9 +5295,9 @@ function runWritePreflight(input) {
4996
5295
  }
4997
5296
 
4998
5297
  // src/commands/observe.ts
4999
- import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
5000
- import { existsSync as existsSync20, statSync as statSync4 } from "fs";
5001
- import { join as join23 } from "path";
5298
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
5299
+ import { existsSync as existsSync22, statSync as statSync5 } from "fs";
5300
+ import { join as join25 } from "path";
5002
5301
  import { createHash as createHash2 } from "crypto";
5003
5302
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
5004
5303
  function slugify(text) {
@@ -5021,7 +5320,7 @@ async function runObserve(input) {
5021
5320
  result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
5022
5321
  };
5023
5322
  }
5024
- if (!existsSync20(input.vault) || !statSync4(input.vault).isDirectory()) {
5323
+ if (!existsSync22(input.vault) || !statSync5(input.vault).isDirectory()) {
5025
5324
  return {
5026
5325
  exitCode: ExitCode.VAULT_PATH_INVALID,
5027
5326
  result: err("VAULT_PATH_INVALID", { path: input.vault })
@@ -5047,7 +5346,7 @@ async function runObserve(input) {
5047
5346
  };
5048
5347
  }
5049
5348
  }
5050
- const transcriptsDir = join23(input.vault, "raw", "transcripts");
5349
+ const transcriptsDir = join25(input.vault, "raw", "transcripts");
5051
5350
  try {
5052
5351
  await mkdir3(transcriptsDir, { recursive: true });
5053
5352
  } catch {
@@ -5059,7 +5358,7 @@ async function runObserve(input) {
5059
5358
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5060
5359
  const slug = slugify(input.text);
5061
5360
  const fileName = `${today}-observation-${slug}.md`;
5062
- const filePath = join23(transcriptsDir, fileName);
5361
+ const filePath = join25(transcriptsDir, fileName);
5063
5362
  const body = `
5064
5363
  ${input.text.trim()}
5065
5364
  `;
@@ -5077,7 +5376,7 @@ ${input.text.trim()}
5077
5376
  frontmatterLines.push("---");
5078
5377
  const content = frontmatterLines.join("\n") + body;
5079
5378
  try {
5080
- await writeFile2(filePath, content, "utf8");
5379
+ await writeFile3(filePath, content, "utf8");
5081
5380
  } catch (e) {
5082
5381
  return {
5083
5382
  exitCode: ExitCode.WRITE_FAILED,
@@ -5100,8 +5399,8 @@ ${input.text.trim()}
5100
5399
 
5101
5400
  // src/commands/memory.ts
5102
5401
  import { createHash as createHash3 } from "crypto";
5103
- import { mkdir as mkdir4, readFile as readFile7, readdir as readdir2, stat as stat2, writeFile as writeFile3 } from "fs/promises";
5104
- import { basename as basename2, extname, join as join24, relative as relative3, sep as sep2 } from "path";
5402
+ import { mkdir as mkdir4, readFile as readFile7, readdir as readdir2, stat as stat2, writeFile as writeFile4 } from "fs/promises";
5403
+ import { basename as basename2, extname, join as join26, relative as relative3, sep as sep3 } from "path";
5105
5404
 
5106
5405
  // src/utils/memory-authority.ts
5107
5406
  var TIER_RANK = {
@@ -5220,9 +5519,9 @@ async function runMemoryIndex(input) {
5220
5519
  }
5221
5520
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
5222
5521
  const relCachePath = memoryCacheRelPath(input.project);
5223
- const absCachePath = join24(input.vault, relCachePath);
5224
- await mkdir4(join24(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
5225
- await writeFile3(absCachePath, `${JSON.stringify({
5522
+ const absCachePath = join26(input.vault, relCachePath);
5523
+ await mkdir4(join26(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
5524
+ await writeFile4(absCachePath, `${JSON.stringify({
5226
5525
  generated_at: generatedAt,
5227
5526
  project: input.project,
5228
5527
  topics: state.topics,
@@ -5413,7 +5712,7 @@ async function buildMemoryIndexState(pages, project) {
5413
5712
  }
5414
5713
  async function checkMemoryIndex(vault, project, current) {
5415
5714
  const relCachePath = memoryCacheRelPath(project);
5416
- const cacheText = await readIfExists(join24(vault, relCachePath));
5715
+ const cacheText = await readIfExists(join26(vault, relCachePath));
5417
5716
  if (!cacheText) {
5418
5717
  return {
5419
5718
  ok: true,
@@ -5822,7 +6121,7 @@ async function walkImportFiles(dir, out) {
5822
6121
  const entries = await readdir2(dir, { withFileTypes: true });
5823
6122
  for (const entry of entries) {
5824
6123
  if (entry.name === ".git" || entry.name === "node_modules") continue;
5825
- const path = join24(dir, entry.name);
6124
+ const path = join26(dir, entry.name);
5826
6125
  if (entry.isDirectory()) {
5827
6126
  await walkImportFiles(path, out);
5828
6127
  } else if (entry.isFile() && isImportCandidate(path)) {
@@ -5872,7 +6171,7 @@ async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
5872
6171
  const redacted = redactSensitiveContent(extracted, { file });
5873
6172
  const privacy = redacted.findings.length > 0 ? "sensitive" : "local";
5874
6173
  const sourceSlug = slugify2(basename2(file, extname(file)));
5875
- const relSource = relative3(sourceRoot, file).split(sep2).join("/");
6174
+ const relSource = relative3(sourceRoot, file).split(sep3).join("/");
5876
6175
  const entry = {
5877
6176
  ...baseEntry,
5878
6177
  status: "ready",
@@ -5889,9 +6188,9 @@ async function writeImportCapture(vault, entry, today) {
5889
6188
  const content = hiddenString(entry, "__content");
5890
6189
  const project = hiddenString(entry, "__project");
5891
6190
  const relPath = await availableImportPath(vault, entry.proposed_path);
5892
- const absPath = join24(vault, relPath);
5893
- await mkdir4(join24(vault, "raw", "transcripts"), { recursive: true });
5894
- await writeFile3(absPath, renderImportCapture(entry, content, project, today), "utf8");
6191
+ const absPath = join26(vault, relPath);
6192
+ await mkdir4(join26(vault, "raw", "transcripts"), { recursive: true });
6193
+ await writeFile4(absPath, renderImportCapture(entry, content, project, today), "utf8");
5895
6194
  const validation = await runValidate({ file: absPath });
5896
6195
  return {
5897
6196
  relPath,
@@ -5906,7 +6205,7 @@ async function availableImportPath(vault, proposed) {
5906
6205
  const stem = proposed.slice(0, -ext.length);
5907
6206
  let candidate = proposed;
5908
6207
  let i = 2;
5909
- while (await readIfExists(join24(vault, candidate))) {
6208
+ while (await readIfExists(join26(vault, candidate))) {
5910
6209
  candidate = `${stem}-${i}${ext}`;
5911
6210
  i++;
5912
6211
  }
@@ -5944,7 +6243,7 @@ function hiddenString(entry, key) {
5944
6243
  return entry[key] ?? "";
5945
6244
  }
5946
6245
  function classifyImportSource(file) {
5947
- const rel = file.split(sep2).join("/");
6246
+ const rel = file.split(sep3).join("/");
5948
6247
  const name = basename2(file);
5949
6248
  if (rel.includes("/.codex/memories/")) return "codex-memory";
5950
6249
  if (rel.includes("/.codex/rules/")) return "codex-rule";
@@ -6091,10 +6390,10 @@ function memoryCacheRelPath(project) {
6091
6390
  }
6092
6391
  async function readMemoryCache(vault, project) {
6093
6392
  if (project) {
6094
- const projectCache = await readIfExists(join24(vault, memoryCacheRelPath(project)));
6393
+ const projectCache = await readIfExists(join26(vault, memoryCacheRelPath(project)));
6095
6394
  if (projectCache) return projectCache;
6096
6395
  }
6097
- return readIfExists(join24(vault, ".skillwiki", "memory-topics.json"));
6396
+ return readIfExists(join26(vault, ".skillwiki", "memory-topics.json"));
6098
6397
  }
6099
6398
  function dedupePages(pages) {
6100
6399
  const seen = /* @__PURE__ */ new Set();
@@ -6186,7 +6485,7 @@ function slugify2(value) {
6186
6485
 
6187
6486
  // src/commands/query.ts
6188
6487
  import { readFile as readFile9, stat as stat4 } from "fs/promises";
6189
- import { join as join26 } from "path";
6488
+ import { join as join28 } from "path";
6190
6489
 
6191
6490
  // src/utils/rrf.ts
6192
6491
  var RRF_K = 60;
@@ -6202,8 +6501,8 @@ function fuseRankings(lists, k = RRF_K) {
6202
6501
 
6203
6502
  // src/utils/vector-index.ts
6204
6503
  import { mkdir as mkdir5, readFile as readFile8, stat as stat3 } from "fs/promises";
6205
- import { join as join25 } from "path";
6206
- import { existsSync as existsSync21 } from "fs";
6504
+ import { join as join27 } from "path";
6505
+ import { existsSync as existsSync23 } from "fs";
6207
6506
  var VECTOR_INDEX_REL = ".skillwiki/vectors/index.json";
6208
6507
  var VECTOR_INDEX_SCHEMA = "skillwiki-tfidf-index/v1";
6209
6508
  function tokenize(text) {
@@ -6217,7 +6516,7 @@ function extractPageTokens(text) {
6217
6516
  return tokenize(`${title} ${body}`);
6218
6517
  }
6219
6518
  function vectorIndexPath(vault) {
6220
- return join25(vault, ...VECTOR_INDEX_REL.split("/"));
6519
+ return join27(vault, ...VECTOR_INDEX_REL.split("/"));
6221
6520
  }
6222
6521
  function termFreq(tokens) {
6223
6522
  const tf = /* @__PURE__ */ new Map();
@@ -6272,7 +6571,7 @@ async function buildVectorIndex(vault, now = (/* @__PURE__ */ new Date()).toISOS
6272
6571
  docs
6273
6572
  };
6274
6573
  const dest = vectorIndexPath(vault);
6275
- await mkdir5(join25(vault, ".skillwiki", "vectors"), { recursive: true });
6574
+ await mkdir5(join27(vault, ".skillwiki", "vectors"), { recursive: true });
6276
6575
  await atomicWriteText(dest, `${JSON.stringify(index)}
6277
6576
  `);
6278
6577
  return ok(index);
@@ -6315,7 +6614,7 @@ async function reindexPageInVectorIndex(vault, pageRelPath, now = (/* @__PURE__
6315
6614
  if (!normalizedPage.endsWith(".md")) {
6316
6615
  return err("USAGE", { message: `Page must be a .md file: ${pageRelPath}` });
6317
6616
  }
6318
- const absPath = join25(vault, ...normalizedPage.split("/"));
6617
+ const absPath = join27(vault, ...normalizedPage.split("/"));
6319
6618
  let text;
6320
6619
  try {
6321
6620
  text = await readFile8(absPath, "utf8");
@@ -6400,7 +6699,7 @@ async function reindexPageInVectorIndex(vault, pageRelPath, now = (/* @__PURE__
6400
6699
  }
6401
6700
  }
6402
6701
  const dest = vectorIndexPath(vault);
6403
- await mkdir5(join25(vault, ".skillwiki", "vectors"), { recursive: true });
6702
+ await mkdir5(join27(vault, ".skillwiki", "vectors"), { recursive: true });
6404
6703
  await atomicWriteText(dest, `${JSON.stringify(index)}
6405
6704
  `);
6406
6705
  return ok({
@@ -6418,8 +6717,8 @@ async function pruneVectorIndex(vault, opts) {
6418
6717
  const orphans = [];
6419
6718
  for (const docKey of Object.keys(index.docs)) {
6420
6719
  const normalizedKey = docKey.split(/\\|\//).join("/");
6421
- const absPath = join25(vault, ...normalizedKey.split("/"));
6422
- if (!existsSync21(absPath)) {
6720
+ const absPath = join27(vault, ...normalizedKey.split("/"));
6721
+ if (!existsSync23(absPath)) {
6423
6722
  orphans.push(normalizedKey);
6424
6723
  }
6425
6724
  }
@@ -6465,7 +6764,7 @@ async function pruneVectorIndex(vault, opts) {
6465
6764
  index.docs[docKey] = toTfIdf(termFreq(tokens), index.df, newPageCount);
6466
6765
  } else {
6467
6766
  try {
6468
- const text = await readFile8(join25(vault, ...docKey.split("/")), "utf8");
6767
+ const text = await readFile8(join27(vault, ...docKey.split("/")), "utf8");
6469
6768
  const tokens = extractPageTokens(text);
6470
6769
  index.docs[docKey] = toTfIdf(termFreq(tokens), index.df, newPageCount);
6471
6770
  } catch {
@@ -6475,7 +6774,7 @@ async function pruneVectorIndex(vault, opts) {
6475
6774
  }
6476
6775
  if (!opts?.dryRun) {
6477
6776
  const dest = vectorIndexPath(vault);
6478
- await mkdir5(join25(vault, ".skillwiki", "vectors"), { recursive: true });
6777
+ await mkdir5(join27(vault, ".skillwiki", "vectors"), { recursive: true });
6479
6778
  await atomicWriteText(dest, `${JSON.stringify(index)}
6480
6779
  `);
6481
6780
  }
@@ -6606,7 +6905,7 @@ async function runQuery(input) {
6606
6905
  }
6607
6906
  let pendingSources;
6608
6907
  if (input.includePending) {
6609
- const { runSourcesPending: runSourcesPending2 } = await import("./sources-QTQSZMST.js");
6908
+ const { runSourcesPending: runSourcesPending2 } = await import("./sources-CLOF5VAT.js");
6610
6909
  const pending = await runSourcesPending2({
6611
6910
  vault: input.vault,
6612
6911
  match: input.text,
@@ -6776,7 +7075,7 @@ function computeKeywordScore(terms, title, tags, body, relPath) {
6776
7075
  return score;
6777
7076
  }
6778
7077
  async function loadOrBuildGraph(vault) {
6779
- const graphPath = join26(vault, ".skillwiki", "graph.json");
7078
+ const graphPath = join28(vault, ".skillwiki", "graph.json");
6780
7079
  let needsBuild = false;
6781
7080
  try {
6782
7081
  const fileStat = await stat4(graphPath);
@@ -6865,27 +7164,27 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
6865
7164
  import { z } from "zod";
6866
7165
 
6867
7166
  // src/mcp/vault-resolve.ts
6868
- import { join as join27, resolve as resolve4 } from "path";
7167
+ import { join as join29, resolve as resolve5 } from "path";
6869
7168
 
6870
7169
  // src/mcp/allowlist.ts
6871
- import { resolve as resolve3, sep as sep3 } from "path";
6872
- import { realpathSync } from "fs";
7170
+ import { resolve as resolve4, sep as sep4 } from "path";
7171
+ import { realpathSync as realpathSync2 } from "fs";
6873
7172
  function parseVaultAllowlist(envValue) {
6874
7173
  if (envValue === void 0 || envValue.trim() === "") return null;
6875
- return envValue.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((p) => resolve3(p));
7174
+ return envValue.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((p) => resolve4(p));
6876
7175
  }
6877
7176
  function vaultAllowedByList(vaultPath, allowlist) {
6878
7177
  if (!allowlist || allowlist.length === 0) return true;
6879
7178
  let canonical = vaultPath;
6880
7179
  try {
6881
- canonical = realpathSync(vaultPath);
7180
+ canonical = realpathSync2(vaultPath);
6882
7181
  } catch {
6883
- canonical = resolve3(vaultPath);
7182
+ canonical = resolve4(vaultPath);
6884
7183
  }
6885
- const resolved = resolve3(canonical);
7184
+ const resolved = resolve4(canonical);
6886
7185
  return allowlist.some((root) => {
6887
- const r = resolve3(root);
6888
- return resolved === r || resolved.startsWith(r + sep3);
7186
+ const r = resolve4(root);
7187
+ return resolved === r || resolved.startsWith(r + sep4);
6889
7188
  });
6890
7189
  }
6891
7190
  function getVaultAllowlistFromEnv() {
@@ -6894,11 +7193,11 @@ function getVaultAllowlistFromEnv() {
6894
7193
 
6895
7194
  // src/mcp/vault-resolve.ts
6896
7195
  async function resolveMcpVault(input) {
6897
- const home = input.home ?? process.env.HOME ?? "";
7196
+ const home = resolveSkillwikiHome(input.home);
6898
7197
  let vaultPath;
6899
7198
  let source = "resolved";
6900
7199
  if (input.vault !== void 0 && input.vault.length > 0) {
6901
- vaultPath = resolve4(input.vault);
7200
+ vaultPath = resolve5(input.vault);
6902
7201
  source = "flag";
6903
7202
  } else {
6904
7203
  const r = await resolveRuntimePath({
@@ -6910,7 +7209,7 @@ async function resolveMcpVault(input) {
6910
7209
  cwd: input.cwd ?? process.cwd()
6911
7210
  });
6912
7211
  if (!r.ok) return r;
6913
- vaultPath = resolve4(r.data.path);
7212
+ vaultPath = resolve5(r.data.path);
6914
7213
  source = r.data.source;
6915
7214
  }
6916
7215
  const scan = await scanVault(vaultPath);
@@ -6927,7 +7226,7 @@ async function resolveMcpVault(input) {
6927
7226
  return ok({ vault: vaultPath, source });
6928
7227
  }
6929
7228
  function defaultGraphOut(vault) {
6930
- return join27(vault, ".skillwiki", "graph.json");
7229
+ return join29(vault, ".skillwiki", "graph.json");
6931
7230
  }
6932
7231
 
6933
7232
  // src/mcp/result-format.ts
@@ -6943,8 +7242,7 @@ function formatToolResult(payload) {
6943
7242
 
6944
7243
  // src/mcp/audit-log.ts
6945
7244
  import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
6946
- import { homedir } from "os";
6947
- import { join as join28 } from "path";
7245
+ import { join as join30 } from "path";
6948
7246
  function auditEnabled() {
6949
7247
  const v = process.env.SKILLWIKI_MCP_AUDIT;
6950
7248
  if (v === "0" || v === "false") return false;
@@ -6956,7 +7254,7 @@ function auditSink() {
6956
7254
  function auditFilePath() {
6957
7255
  const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
6958
7256
  if (custom && custom.length > 0) return custom;
6959
- return join28(homedir(), ".skillwiki", "mcp-audit.jsonl");
7257
+ return join30(resolveSkillwikiHome(), ".skillwiki", "mcp-audit.jsonl");
6960
7258
  }
6961
7259
  function auditMcpToolCall(entry) {
6962
7260
  if (!auditEnabled()) return;
@@ -6966,7 +7264,7 @@ function auditMcpToolCall(entry) {
6966
7264
  return;
6967
7265
  }
6968
7266
  const path = auditFilePath();
6969
- mkdirSync2(join28(path, ".."), { recursive: true });
7267
+ mkdirSync2(join30(path, ".."), { recursive: true });
6970
7268
  appendFileSync(path, line, "utf8");
6971
7269
  }
6972
7270
  async function runMcpToolHandler(tool, input, fn) {
@@ -7059,7 +7357,7 @@ function registerMcpTools(server) {
7059
7357
  await resolveMcpVault({ vault, wiki });
7060
7358
  const pkg = readCliPackageJson();
7061
7359
  const r = await runDoctor({
7062
- home: process.env.HOME ?? "",
7360
+ home: resolveSkillwikiHome(),
7063
7361
  envValue: process.env.WIKI_PATH,
7064
7362
  argv: process.argv,
7065
7363
  currentVersion: pkg.version,
@@ -7132,7 +7430,7 @@ function registerMcpTools(server) {
7132
7430
  })
7133
7431
  },
7134
7432
  async ({ key }) => runMcpToolHandler("skillwiki.config_get", {}, async () => {
7135
- const r = await runConfigGet({ key, home: process.env.HOME ?? "" });
7433
+ const r = await runConfigGet({ key, home: resolveSkillwikiHome() });
7136
7434
  return formatToolResult(r);
7137
7435
  })
7138
7436
  );
@@ -7241,7 +7539,7 @@ function registerMcpMutatingTools(server) {
7241
7539
 
7242
7540
  // src/mcp/resources.ts
7243
7541
  import { readFile as readFile11 } from "fs/promises";
7244
- import { join as join30 } from "path";
7542
+ import { join as join32 } from "path";
7245
7543
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
7246
7544
 
7247
7545
  // src/mcp/lint-bucket.ts
@@ -7371,8 +7669,8 @@ async function fetchQueryPreview(input) {
7371
7669
 
7372
7670
  // src/mcp/graph-html.ts
7373
7671
  import { readFile as readFile10 } from "fs/promises";
7374
- import { join as join29 } from "path";
7375
- import { existsSync as existsSync22 } from "fs";
7672
+ import { join as join31 } from "path";
7673
+ import { existsSync as existsSync24 } from "fs";
7376
7674
  var TYPE_COLORS = {
7377
7675
  entities: "#e74c3c",
7378
7676
  concepts: "#27ae60",
@@ -7440,9 +7738,9 @@ ${nodeSvg}
7440
7738
  return { html, node_count: nodes.length, edge_count: edges.length, truncated };
7441
7739
  }
7442
7740
  async function fetchGraphHtmlReport(input) {
7443
- const graphPath = input.graphPath ?? join29(input.vault, ".skillwiki", "graph.json");
7741
+ const graphPath = input.graphPath ?? join31(input.vault, ".skillwiki", "graph.json");
7444
7742
  const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
7445
- if (!existsSync22(graphPath)) {
7743
+ if (!existsSync24(graphPath)) {
7446
7744
  return {
7447
7745
  exitCode: ExitCode.FILE_NOT_FOUND,
7448
7746
  result: err("GRAPH_MISSING", { path: graphPath, hint: "Run skillwiki.graph_build first." })
@@ -7514,7 +7812,7 @@ async function fetchStaleSummary(input) {
7514
7812
 
7515
7813
  // src/mcp/resources.ts
7516
7814
  async function readVaultFile(vault, rel) {
7517
- return readFile11(join30(vault, rel), "utf8");
7815
+ return readFile11(join32(vault, rel), "utf8");
7518
7816
  }
7519
7817
  async function tailLines(text, lines) {
7520
7818
  const parts = text.split(/\r?\n/);
@@ -7600,7 +7898,7 @@ function registerMcpResources(server) {
7600
7898
  if (!v.ok) {
7601
7899
  return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
7602
7900
  }
7603
- const path = join30(v.data.vault, ".skillwiki", "graph.json");
7901
+ const path = join32(v.data.vault, ".skillwiki", "graph.json");
7604
7902
  try {
7605
7903
  const raw = await readFile11(path, "utf8");
7606
7904
  const graph = JSON.parse(raw);
@@ -7955,6 +8253,7 @@ export {
7955
8253
  MCP_TOKEN_ENV,
7956
8254
  MCP_HOST_ID_ENV,
7957
8255
  nonEmptyEnvValue,
8256
+ mcpAuthFromEnv,
7958
8257
  mergeMcpAuthIntoEnv,
7959
8258
  redactMcpSecret,
7960
8259
  configPath,
@@ -7972,7 +8271,10 @@ export {
7972
8271
  readSatelliteLatestRunFromText,
7973
8272
  readSatelliteLatestRunAt,
7974
8273
  evaluateSatelliteRunHealthState,
8274
+ withMcpClient,
7975
8275
  DEFAULT_MCP_URL,
8276
+ CLIP_NOT_CONFIGURED,
8277
+ loadClipOutboxConfig,
7976
8278
  runDoctor,
7977
8279
  readCliPackageJson,
7978
8280
  DEFAULT_DIRTY_VOLUME_THRESHOLD,