skillwiki 0.10.109 → 0.10.111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-JCDAEZVD.js → chunk-5IBLXGLY.js} +5 -3
- package/dist/chunk-6OQLBNKR.js +250 -0
- package/dist/chunk-GQWCTJTU.js +55 -0
- package/dist/{chunk-FYAKMULW.js → chunk-HBJLFPQP.js} +91 -198
- package/dist/chunk-I2J3IJB7.js +54 -0
- package/dist/{chunk-HMHMCPCE.js → chunk-I6IRJCUJ.js} +12 -57
- package/dist/{chunk-QU2IXZRN.js → chunk-JQV5KNPT.js} +8 -6
- package/dist/chunk-KWEYA3OE.js +60 -0
- package/dist/{chunk-DTTKCCQ4.js → chunk-MRPJ7X7W.js} +5 -3
- package/dist/{chunk-LDEZQXH6.js → chunk-OJJBZDPR.js} +306 -116
- package/dist/chunk-OSL4J7GF.js +32 -0
- package/dist/chunk-PYPPG7RF.js +126 -0
- package/dist/chunk-QESNTTVY.js +50 -0
- package/dist/chunk-TDBUJUYH.js +109 -0
- package/dist/{chunk-GJC2CI3W.js → chunk-UIEQIJXP.js} +108 -336
- package/dist/chunk-X5GL2FCU.js +344 -0
- package/dist/chunk-ZRGZBAVS.js +98 -0
- package/dist/cli.js +352 -297
- package/dist/{index-projection-ESBJOBS7.js → index-projection-2K672CBO.js} +5 -3
- package/dist/journal-QU5IDXQF.js +13 -0
- package/dist/{managed-write-preflight-MAIGWFQJ.js → managed-write-preflight-GRSHQYKC.js} +7 -4
- package/dist/migrate-PIB4QA54.js +21 -0
- package/dist/ready-S7F53Q2D.js +9 -0
- package/dist/runtime-GLMI5OCT.js +15 -0
- package/dist/skillwiki-mcp.js +12 -7
- package/dist/sources-D2MGP246.js +10 -0
- package/dist/upload-HTE62TOV.js +14 -0
- package/dist/watcher-MHG46J3R.js +616 -0
- package/docs/clip-outbox.md +119 -0
- package/package.json +5 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/.cursor-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
- package/skills/skills/using-skillwiki/SKILL.md +1 -1
- package/skills/skills/wiki-init/SKILL.md +12 -11
- package/skills/using-skillwiki/SKILL.md +1 -1
- package/skills/wiki-init/SKILL.md +12 -11
- package/templates/launchd/com.karlchow.skillwiki-clip-outbox.plist +21 -0
- package/templates/web-clipper/llm-wiki-clippings.json +1 -1
- package/templates/web-clipper/readme.txt +13 -13
- package/dist/sources-QTQSZMST.js +0 -9
|
@@ -419,6 +419,106 @@ function parseVaultSyncKeyValue(key, value) {
|
|
|
419
419
|
const first = parsed.error.issues[0];
|
|
420
420
|
return { ok: false, message: first?.message ?? "invalid value" };
|
|
421
421
|
}
|
|
422
|
+
var clipBool = z.enum(["true", "false"]);
|
|
423
|
+
var clipIntString = (min, max) => z.string().regex(/^[0-9]+$/, "must be a decimal integer").refine((s) => {
|
|
424
|
+
const n = Number(s);
|
|
425
|
+
return Number.isInteger(n) && n >= min && n <= max;
|
|
426
|
+
}, { message: `must be an integer ${min}\u2013${max}` });
|
|
427
|
+
var clipHttpsUrl = z.string().min(1).refine((s) => {
|
|
428
|
+
try {
|
|
429
|
+
const u = new URL(s);
|
|
430
|
+
return u.protocol === "https:" && u.username === "" && u.password === "";
|
|
431
|
+
} catch {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
}, { message: "must be an https URL without userinfo" });
|
|
435
|
+
var clipCredentialRef = z.string().min(1).max(128).regex(
|
|
436
|
+
/^[a-zA-Z0-9_.-]+$/,
|
|
437
|
+
"must be a credential reference name, never a secret value"
|
|
438
|
+
);
|
|
439
|
+
var CLIP_IMAGE_TYPES = ["png", "jpeg", "gif", "webp"];
|
|
440
|
+
var CLIP_ALLOWED_PREFIXES = "raw/articles,raw/assets";
|
|
441
|
+
var CLIP_OUTBOX_DEFAULTS = {
|
|
442
|
+
enabled: "false",
|
|
443
|
+
vault_id: "central",
|
|
444
|
+
allowed_prefixes: CLIP_ALLOWED_PREFIXES,
|
|
445
|
+
destination: "seaweed-wiki:cloud/wiki",
|
|
446
|
+
mode: "event-watch",
|
|
447
|
+
poll_fallback_enabled: "true",
|
|
448
|
+
reconcile_seconds: "3600",
|
|
449
|
+
debounce_ms: "500",
|
|
450
|
+
stable_hash_ms: "500",
|
|
451
|
+
max_article_bytes: "2097152",
|
|
452
|
+
max_images: "64",
|
|
453
|
+
max_image_bytes: "20971520",
|
|
454
|
+
max_bundle_bytes: "104857600",
|
|
455
|
+
max_pixels: "40000000",
|
|
456
|
+
download_concurrency: "4",
|
|
457
|
+
image_types: CLIP_IMAGE_TYPES.join(","),
|
|
458
|
+
max_redirects: "5",
|
|
459
|
+
request_timeout_ms: "30000",
|
|
460
|
+
localize_timeout_ms: "120000",
|
|
461
|
+
watcher_label: "com.karlchow.skillwiki-clip-outbox"
|
|
462
|
+
};
|
|
463
|
+
var ClipOutboxConfigSchema = z.object({
|
|
464
|
+
"clip_outbox.enabled": clipBool,
|
|
465
|
+
"clip_outbox.root": absolutePath,
|
|
466
|
+
"clip_outbox.state_root": absolutePath,
|
|
467
|
+
"clip_outbox.mirror_root": absolutePath,
|
|
468
|
+
"clip_outbox.d8_note_root": absolutePath,
|
|
469
|
+
"clip_outbox.d8_note_absent": clipBool,
|
|
470
|
+
"clip_outbox.vault_id": z.literal("central"),
|
|
471
|
+
"clip_outbox.allowed_prefixes": z.string().refine((s) => {
|
|
472
|
+
const parts = s.split(",").map((p) => p.trim()).filter(Boolean).sort();
|
|
473
|
+
return parts.join(",") === "raw/articles,raw/assets";
|
|
474
|
+
}, { message: "must be exactly raw/articles,raw/assets" }),
|
|
475
|
+
"clip_outbox.destination": z.literal("seaweed-wiki:cloud/wiki"),
|
|
476
|
+
"clip_outbox.mode": z.literal("event-watch"),
|
|
477
|
+
"clip_outbox.poll_fallback_enabled": clipBool,
|
|
478
|
+
"clip_outbox.reconcile_seconds": clipIntString(1, 86400),
|
|
479
|
+
"clip_outbox.debounce_ms": clipIntString(100, 5e3),
|
|
480
|
+
"clip_outbox.stable_hash_ms": clipIntString(100, 5e3),
|
|
481
|
+
"clip_outbox.max_article_bytes": clipIntString(1, 2097152),
|
|
482
|
+
"clip_outbox.max_images": clipIntString(0, 64),
|
|
483
|
+
"clip_outbox.max_image_bytes": clipIntString(1, 20971520),
|
|
484
|
+
"clip_outbox.max_bundle_bytes": clipIntString(1, 104857600),
|
|
485
|
+
"clip_outbox.max_pixels": clipIntString(1, 4e7),
|
|
486
|
+
"clip_outbox.download_concurrency": clipIntString(1, 4),
|
|
487
|
+
"clip_outbox.image_types": z.string().refine((s) => {
|
|
488
|
+
const parts = s.split(",").map((p) => p.trim()).filter(Boolean);
|
|
489
|
+
if (parts.length === 0) return false;
|
|
490
|
+
const allowed = new Set(CLIP_IMAGE_TYPES);
|
|
491
|
+
const seen = /* @__PURE__ */ new Set();
|
|
492
|
+
for (const p of parts) {
|
|
493
|
+
if (!allowed.has(p) || seen.has(p)) return false;
|
|
494
|
+
seen.add(p);
|
|
495
|
+
}
|
|
496
|
+
return true;
|
|
497
|
+
}, { message: "must be a unique subset of png,jpeg,gif,webp" }),
|
|
498
|
+
"clip_outbox.max_redirects": clipIntString(0, 5),
|
|
499
|
+
"clip_outbox.request_timeout_ms": clipIntString(1, 3e4),
|
|
500
|
+
"clip_outbox.localize_timeout_ms": clipIntString(1, 12e4),
|
|
501
|
+
"clip_outbox.ready_url": clipHttpsUrl,
|
|
502
|
+
"clip_outbox.s3_credential_ref": clipCredentialRef,
|
|
503
|
+
"clip_outbox.ready_credential_ref": clipCredentialRef,
|
|
504
|
+
"clip_outbox.watcher_label": z.literal("com.karlchow.skillwiki-clip-outbox")
|
|
505
|
+
}).strict();
|
|
506
|
+
var CLIP_OUTBOX_KEYS = Object.keys(ClipOutboxConfigSchema.shape);
|
|
507
|
+
function isClipOutboxKey(key) {
|
|
508
|
+
return CLIP_OUTBOX_KEYS.includes(key);
|
|
509
|
+
}
|
|
510
|
+
function parseClipOutboxKeyValue(key, value) {
|
|
511
|
+
if (!isClipOutboxKey(key)) {
|
|
512
|
+
return { ok: false, message: `unknown clip_outbox key: ${key}` };
|
|
513
|
+
}
|
|
514
|
+
const fieldSchema = ClipOutboxConfigSchema.shape[key];
|
|
515
|
+
const parsed = fieldSchema.safeParse(value);
|
|
516
|
+
if (parsed.success) {
|
|
517
|
+
return { ok: true };
|
|
518
|
+
}
|
|
519
|
+
const first = parsed.error.issues[0];
|
|
520
|
+
return { ok: false, message: first?.message ?? "invalid value" };
|
|
521
|
+
}
|
|
422
522
|
function detectSchema(fm) {
|
|
423
523
|
const COMPOUND_TYPES = /* @__PURE__ */ new Set(["lesson", "pattern", "antipattern", "gotcha"]);
|
|
424
524
|
if (typeof fm.type === "string" && COMPOUND_TYPES.has(fm.type) && "project" in fm) return { schema: "compound" };
|
|
@@ -455,6 +555,9 @@ function inRange(ip, baseStr, prefix) {
|
|
|
455
555
|
const mask = prefix === 0 ? 0 : ~0 << 32 - prefix >>> 0;
|
|
456
556
|
return (ipN & mask) === (baseN & mask);
|
|
457
557
|
}
|
|
558
|
+
function ipv4InCidr(ip, base, prefix) {
|
|
559
|
+
return inRange(ip, base, prefix);
|
|
560
|
+
}
|
|
458
561
|
function isBlockedHost(host) {
|
|
459
562
|
const lower = host.toLowerCase();
|
|
460
563
|
if (METADATA_HOSTS.has(lower)) return true;
|
|
@@ -498,330 +601,6 @@ function systemdPropertyFor(semantic) {
|
|
|
498
601
|
return SYSTEMD_PROPERTY_CATALOG[semantic];
|
|
499
602
|
}
|
|
500
603
|
|
|
501
|
-
// src/parsers/frontmatter.ts
|
|
502
|
-
import yaml from "js-yaml";
|
|
503
|
-
var FM_OPEN = /^---\r?\n/;
|
|
504
|
-
function splitFrontmatter(text) {
|
|
505
|
-
if (!FM_OPEN.test(text)) return ok({ rawFrontmatter: "", body: text, bodyStart: 0 });
|
|
506
|
-
const afterOpen = text.replace(FM_OPEN, "");
|
|
507
|
-
const closeIdx = afterOpen.search(/\r?\n---\r?\n/);
|
|
508
|
-
if (closeIdx === -1) return err("MISSING_CLOSING_DELIMITER");
|
|
509
|
-
const rawFrontmatter = afterOpen.slice(0, closeIdx);
|
|
510
|
-
const closeMatch = afterOpen.slice(closeIdx).match(/\r?\n---\r?\n/);
|
|
511
|
-
const bodyStart = text.length - (afterOpen.length - closeIdx - closeMatch[0].length);
|
|
512
|
-
const body = text.slice(bodyStart);
|
|
513
|
-
return ok({ rawFrontmatter, body, bodyStart });
|
|
514
|
-
}
|
|
515
|
-
function extractFrontmatter(text) {
|
|
516
|
-
const split = splitFrontmatter(text);
|
|
517
|
-
if (!split.ok) return split;
|
|
518
|
-
if (!split.data.rawFrontmatter) return ok({});
|
|
519
|
-
try {
|
|
520
|
-
const parsed = yaml.load(split.data.rawFrontmatter, { schema: yaml.JSON_SCHEMA });
|
|
521
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return ok({});
|
|
522
|
-
return ok(parsed);
|
|
523
|
-
} catch (e) {
|
|
524
|
-
return err("INVALID_FRONTMATTER", { message: getErrorMessage(e) });
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// src/utils/sensitive-content.ts
|
|
529
|
-
import { createHash } from "crypto";
|
|
530
|
-
var REDACTED_RE = /\[REDACTED:[^\]]+\]/i;
|
|
531
|
-
var SYNTHETIC_RE = /^(?:<[^>]+>|\$\{[^}]+\}|REPLACE_WITH_[A-Z0-9_]+|YOUR_[A-Z0-9_]+|EXAMPLE_[A-Z0-9_]+)$/i;
|
|
532
|
-
var MATCHERS = [
|
|
533
|
-
{
|
|
534
|
-
kind: "private_key",
|
|
535
|
-
re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g
|
|
536
|
-
},
|
|
537
|
-
{
|
|
538
|
-
kind: "authorization_header",
|
|
539
|
-
re: /\bAuthorization["']?\s*:\s*["']?(Bearer\s+[A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
540
|
-
valueGroup: 1
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
kind: "cookie",
|
|
544
|
-
re: /\b(?:Cookie|Set-Cookie)\s*:\s*([^\n]{20,})/gi,
|
|
545
|
-
valueGroup: 1
|
|
546
|
-
},
|
|
547
|
-
{
|
|
548
|
-
kind: "jwt",
|
|
549
|
-
re: /\b([A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{12,}\.[A-Za-z0-9_-]{12,})\b/g,
|
|
550
|
-
valueGroup: 1
|
|
551
|
-
},
|
|
552
|
-
{
|
|
553
|
-
kind: "provider_key",
|
|
554
|
-
re: /\b(sk-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{20,}|gh[pousr]_[A-Za-z0-9_=-]{20,})\b/g,
|
|
555
|
-
valueGroup: 1
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
kind: "access_key",
|
|
559
|
-
re: /\b((?:AKIA|ASIA)[A-Z0-9]{16})\b/g,
|
|
560
|
-
valueGroup: 1
|
|
561
|
-
},
|
|
562
|
-
{
|
|
563
|
-
kind: "access_key",
|
|
564
|
-
re: /\b(?:access[-_ ]?key|credential)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
565
|
-
valueGroup: 1
|
|
566
|
-
},
|
|
567
|
-
{
|
|
568
|
-
kind: "api_key",
|
|
569
|
-
re: /\b(?:api[-_ ]?key)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
570
|
-
valueGroup: 1
|
|
571
|
-
},
|
|
572
|
-
{
|
|
573
|
-
kind: "password",
|
|
574
|
-
re: /\b(?:password|passwd)["']?\s*[:=]\s*[`"']?([^\s`"']{8,})[`"']?/gi,
|
|
575
|
-
valueGroup: 1
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
kind: "secret",
|
|
579
|
-
re: /\b(?:secret|client[-_ ]?secret)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{16,})["']?/gi,
|
|
580
|
-
valueGroup: 1
|
|
581
|
-
},
|
|
582
|
-
{
|
|
583
|
-
kind: "token",
|
|
584
|
-
re: /\b(?:token|session)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{16,})["']?/gi,
|
|
585
|
-
valueGroup: 1
|
|
586
|
-
}
|
|
587
|
-
];
|
|
588
|
-
function fingerprint(value) {
|
|
589
|
-
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
590
|
-
}
|
|
591
|
-
function lineFor(text, offset) {
|
|
592
|
-
return text.slice(0, offset).split(/\r?\n/).length;
|
|
593
|
-
}
|
|
594
|
-
function redactMarker(kind, value) {
|
|
595
|
-
return `[REDACTED:${kind}:${fingerprint(value)}]`;
|
|
596
|
-
}
|
|
597
|
-
function isSyntheticPlaceholder(value) {
|
|
598
|
-
return REDACTED_RE.test(value) || SYNTHETIC_RE.test(value.trim());
|
|
599
|
-
}
|
|
600
|
-
function isInsideRedactedMarker(text, start, end) {
|
|
601
|
-
for (const marker of text.matchAll(new RegExp(REDACTED_RE.source, "gi"))) {
|
|
602
|
-
const markerStart = marker.index ?? 0;
|
|
603
|
-
const markerEnd = markerStart + marker[0].length;
|
|
604
|
-
if (start >= markerStart && end <= markerEnd) return true;
|
|
605
|
-
}
|
|
606
|
-
return false;
|
|
607
|
-
}
|
|
608
|
-
function isNonSecretTokenCapture(value) {
|
|
609
|
-
if (value.startsWith("//")) return true;
|
|
610
|
-
if (value.includes("/")) return true;
|
|
611
|
-
if (/^[a-z0-9.]+(?:-[a-z0-9.]+)+$/.test(value) && !/^[0-9a-f]+(?:-[0-9a-f]+)+$/.test(value)) return true;
|
|
612
|
-
return false;
|
|
613
|
-
}
|
|
614
|
-
var PASSWORD_PROSE_SENTINELS = /* @__PURE__ */ new Set([
|
|
615
|
-
"generated",
|
|
616
|
-
"configured",
|
|
617
|
-
"required",
|
|
618
|
-
"optional",
|
|
619
|
-
"provided",
|
|
620
|
-
"disabled",
|
|
621
|
-
"enabled",
|
|
622
|
-
"undefined",
|
|
623
|
-
"unspecified",
|
|
624
|
-
"unknown"
|
|
625
|
-
]);
|
|
626
|
-
function isNonSecretPasswordCapture(value) {
|
|
627
|
-
return PASSWORD_PROSE_SENTINELS.has(value.toLowerCase());
|
|
628
|
-
}
|
|
629
|
-
function collectMatches(text) {
|
|
630
|
-
const matches = [];
|
|
631
|
-
for (const matcher of MATCHERS) {
|
|
632
|
-
matcher.re.lastIndex = 0;
|
|
633
|
-
for (const m of text.matchAll(matcher.re)) {
|
|
634
|
-
const whole = m[0];
|
|
635
|
-
const start = m.index ?? 0;
|
|
636
|
-
if (REDACTED_RE.test(whole) || isInsideRedactedMarker(text, start, start + whole.length)) continue;
|
|
637
|
-
const value = matcher.valueGroup ? m[matcher.valueGroup] : whole;
|
|
638
|
-
if (isSyntheticPlaceholder(value)) continue;
|
|
639
|
-
if (matcher.kind === "token" && isNonSecretTokenCapture(value)) continue;
|
|
640
|
-
if (matcher.kind === "password" && isNonSecretPasswordCapture(value)) continue;
|
|
641
|
-
const valueOffset = whole.lastIndexOf(value);
|
|
642
|
-
const valueStart = start + Math.max(0, valueOffset);
|
|
643
|
-
matches.push({
|
|
644
|
-
start,
|
|
645
|
-
end: start + whole.length,
|
|
646
|
-
valueStart,
|
|
647
|
-
valueEnd: valueStart + value.length,
|
|
648
|
-
kind: matcher.kind
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
return matches.sort((a, b) => {
|
|
653
|
-
if (a.valueStart !== b.valueStart) return a.valueStart - b.valueStart;
|
|
654
|
-
return b.valueEnd - b.valueStart - (a.valueEnd - a.valueStart);
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
function collapseOverlaps(matches) {
|
|
658
|
-
const kept = [];
|
|
659
|
-
for (const match of matches) {
|
|
660
|
-
const overlaps = kept.some((k) => match.valueStart < k.valueEnd && match.valueEnd > k.valueStart);
|
|
661
|
-
if (!overlaps) kept.push(match);
|
|
662
|
-
}
|
|
663
|
-
return kept;
|
|
664
|
-
}
|
|
665
|
-
function scanSensitiveContent(text, opts = {}) {
|
|
666
|
-
return collapseOverlaps(collectMatches(text)).map((match) => {
|
|
667
|
-
const value = text.slice(match.valueStart, match.valueEnd);
|
|
668
|
-
const marker = redactMarker(match.kind, value);
|
|
669
|
-
const rawPreview = text.slice(Math.max(0, match.start - 24), Math.min(text.length, match.end + 24));
|
|
670
|
-
const preview = rawPreview.replace(value, marker);
|
|
671
|
-
return {
|
|
672
|
-
file: opts.file,
|
|
673
|
-
line: lineFor(text, match.valueStart),
|
|
674
|
-
kind: match.kind,
|
|
675
|
-
preview,
|
|
676
|
-
fingerprint: fingerprint(value)
|
|
677
|
-
};
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
function redactSensitiveContent(text, opts = {}) {
|
|
681
|
-
const matches = collapseOverlaps(collectMatches(text));
|
|
682
|
-
if (matches.length === 0) return { text, changed: false, findings: [] };
|
|
683
|
-
let out = "";
|
|
684
|
-
let cursor = 0;
|
|
685
|
-
for (const match of matches) {
|
|
686
|
-
const value = text.slice(match.valueStart, match.valueEnd);
|
|
687
|
-
out += text.slice(cursor, match.valueStart);
|
|
688
|
-
out += redactMarker(match.kind, value);
|
|
689
|
-
cursor = match.valueEnd;
|
|
690
|
-
}
|
|
691
|
-
out += text.slice(cursor);
|
|
692
|
-
return {
|
|
693
|
-
text: out,
|
|
694
|
-
changed: out !== text,
|
|
695
|
-
findings: scanSensitiveContent(text, opts)
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// src/utils/vault.ts
|
|
700
|
-
import { existsSync, readFileSync } from "fs";
|
|
701
|
-
import { readFile, readdir, stat } from "fs/promises";
|
|
702
|
-
import { join, relative, sep } from "path";
|
|
703
|
-
import { execFileSync } from "child_process";
|
|
704
|
-
var TYPED_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
705
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
706
|
-
var DEFAULT_IO_CONCURRENCY = 1;
|
|
707
|
-
function vaultIoConcurrency() {
|
|
708
|
-
const raw = Number.parseInt(process.env.SKILLWIKI_VAULT_IO_CONCURRENCY ?? "", 10);
|
|
709
|
-
return Number.isFinite(raw) && raw > 0 ? Math.min(raw, 64) : DEFAULT_IO_CONCURRENCY;
|
|
710
|
-
}
|
|
711
|
-
function decodeProcMountPath(value) {
|
|
712
|
-
return value.replace(/\\040/g, " ");
|
|
713
|
-
}
|
|
714
|
-
function isRcloneFuseVaultFromMounts(root, mounts) {
|
|
715
|
-
return mounts.split(/\r?\n/).some((line) => {
|
|
716
|
-
const parts = line.split(" ");
|
|
717
|
-
if (parts.length < 3) return false;
|
|
718
|
-
const mountPoint = decodeProcMountPath(parts[1]);
|
|
719
|
-
const fsType = parts[2];
|
|
720
|
-
return fsType === "fuse.rclone" && (root === mountPoint || root.startsWith(`${mountPoint}/`));
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
function resolveReadOnlyVaultRootWithMounts(root, mounts) {
|
|
724
|
-
if (/^(1|true|yes)$/i.test(process.env.SKILLWIKI_DISABLE_VAULT_READ_MIRROR ?? "")) {
|
|
725
|
-
return { root, mirrored: false };
|
|
726
|
-
}
|
|
727
|
-
const explicitMirror = process.env.SKILLWIKI_VAULT_READ_MIRROR;
|
|
728
|
-
if (explicitMirror && existsSync(join(explicitMirror, "SCHEMA.md"))) {
|
|
729
|
-
return { root: explicitMirror, mirrored: explicitMirror !== root };
|
|
730
|
-
}
|
|
731
|
-
const siblingMirror = `${root}-git`;
|
|
732
|
-
if (isRcloneFuseVaultFromMounts(root, mounts) && existsSync(join(siblingMirror, "SCHEMA.md"))) {
|
|
733
|
-
return { root: siblingMirror, mirrored: true };
|
|
734
|
-
}
|
|
735
|
-
return { root, mirrored: false };
|
|
736
|
-
}
|
|
737
|
-
function resolveReadOnlyVaultRoot(root) {
|
|
738
|
-
let mounts = "";
|
|
739
|
-
try {
|
|
740
|
-
mounts = readFileSync("/proc/mounts", "utf8");
|
|
741
|
-
} catch {
|
|
742
|
-
}
|
|
743
|
-
return resolveReadOnlyVaultRootWithMounts(root, mounts);
|
|
744
|
-
}
|
|
745
|
-
async function mapWithConcurrency(items, limit, mapper) {
|
|
746
|
-
const out = new Array(items.length);
|
|
747
|
-
let next = 0;
|
|
748
|
-
const workers = Array.from({ length: Math.min(Math.max(1, limit), items.length) }, async () => {
|
|
749
|
-
for (; ; ) {
|
|
750
|
-
const index = next++;
|
|
751
|
-
if (index >= items.length) return;
|
|
752
|
-
out[index] = await mapper(items[index], index);
|
|
753
|
-
}
|
|
754
|
-
});
|
|
755
|
-
await Promise.all(workers);
|
|
756
|
-
return out;
|
|
757
|
-
}
|
|
758
|
-
function filterGitIgnoredRelativePaths(root, relPaths) {
|
|
759
|
-
if (relPaths.length === 0 || !existsSync(join(root, ".git"))) {
|
|
760
|
-
return /* @__PURE__ */ new Set();
|
|
761
|
-
}
|
|
762
|
-
try {
|
|
763
|
-
const input = relPaths.join("\0") + "\0";
|
|
764
|
-
const stdout = execFileSync("git", ["check-ignore", "-z", "--stdin"], {
|
|
765
|
-
cwd: root,
|
|
766
|
-
input,
|
|
767
|
-
encoding: "utf8",
|
|
768
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
769
|
-
});
|
|
770
|
-
const ignored = stdout.split("\0").filter(Boolean);
|
|
771
|
-
return new Set(ignored);
|
|
772
|
-
} catch (error) {
|
|
773
|
-
if (error && typeof error === "object" && error.status === 1) {
|
|
774
|
-
return /* @__PURE__ */ new Set();
|
|
775
|
-
}
|
|
776
|
-
return /* @__PURE__ */ new Set();
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
async function scanVault(root) {
|
|
780
|
-
try {
|
|
781
|
-
await stat(join(root, "SCHEMA.md"));
|
|
782
|
-
} catch {
|
|
783
|
-
return err("VAULT_PATH_INVALID", { root, reason: "SCHEMA.md missing" });
|
|
784
|
-
}
|
|
785
|
-
const all = await walk(root);
|
|
786
|
-
const rawRels = all.map((p) => ({ absPath: p, relPath: relative(root, p).split(sep).join("/") }));
|
|
787
|
-
const ignored = filterGitIgnoredRelativePaths(root, rawRels.map((p) => p.relPath));
|
|
788
|
-
const rels = ignored.size > 0 ? rawRels.filter((p) => !ignored.has(p.relPath)) : rawRels;
|
|
789
|
-
return ok({
|
|
790
|
-
root,
|
|
791
|
-
allMarkdown: rels,
|
|
792
|
-
typedKnowledge: rels.filter((p) => TYPED_DIRS.some((d) => p.relPath.startsWith(d + "/"))),
|
|
793
|
-
raw: rels.filter((p) => p.relPath.startsWith("raw/")),
|
|
794
|
-
workItems: rels.filter((p) => /^projects\/[^/]+\/work\/[^/]+\/(spec|plan|log)\.md$/.test(p.relPath)),
|
|
795
|
-
compound: rels.filter((p) => /^projects\/[^/]+\/compound\//.test(p.relPath))
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
async function walk(dir) {
|
|
799
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
800
|
-
const out = [];
|
|
801
|
-
const subdirs = [];
|
|
802
|
-
for (const e of entries) {
|
|
803
|
-
const p = join(dir, e.name);
|
|
804
|
-
if (e.isDirectory()) {
|
|
805
|
-
if (SKIP_DIRS.has(e.name)) continue;
|
|
806
|
-
subdirs.push(p);
|
|
807
|
-
} else if (e.isFile() && e.name.endsWith(".md")) out.push(p);
|
|
808
|
-
}
|
|
809
|
-
const nested = await mapWithConcurrency(subdirs, Math.min(8, vaultIoConcurrency()), walk);
|
|
810
|
-
for (const files of nested) out.push(...files);
|
|
811
|
-
return out;
|
|
812
|
-
}
|
|
813
|
-
async function readPage(p) {
|
|
814
|
-
return readFile(p.absPath, "utf8");
|
|
815
|
-
}
|
|
816
|
-
async function readPageCached(p, cache) {
|
|
817
|
-
if (!cache) return readPage(p);
|
|
818
|
-
const existing = cache.get(p.absPath);
|
|
819
|
-
if (existing) return existing;
|
|
820
|
-
const pending = readPage(p);
|
|
821
|
-
cache.set(p.absPath, pending);
|
|
822
|
-
return pending;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
604
|
export {
|
|
826
605
|
ExitCode,
|
|
827
606
|
ok,
|
|
@@ -834,20 +613,13 @@ export {
|
|
|
834
613
|
FleetManifestSchema,
|
|
835
614
|
isVaultSyncKey,
|
|
836
615
|
parseVaultSyncKeyValue,
|
|
616
|
+
CLIP_OUTBOX_DEFAULTS,
|
|
617
|
+
isClipOutboxKey,
|
|
618
|
+
parseClipOutboxKeyValue,
|
|
837
619
|
detectSchema,
|
|
620
|
+
ipv4InCidr,
|
|
838
621
|
isBlockedHost,
|
|
839
622
|
getErrorMessage,
|
|
840
623
|
resolveSessionKind,
|
|
841
|
-
systemdPropertyFor
|
|
842
|
-
splitFrontmatter,
|
|
843
|
-
extractFrontmatter,
|
|
844
|
-
scanSensitiveContent,
|
|
845
|
-
redactSensitiveContent,
|
|
846
|
-
vaultIoConcurrency,
|
|
847
|
-
resolveReadOnlyVaultRoot,
|
|
848
|
-
mapWithConcurrency,
|
|
849
|
-
filterGitIgnoredRelativePaths,
|
|
850
|
-
scanVault,
|
|
851
|
-
readPage,
|
|
852
|
-
readPageCached
|
|
624
|
+
systemdPropertyFor
|
|
853
625
|
};
|