skillwiki 0.5.4 → 0.5.5
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/cli.js +520 -184
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/cli.ts
|
|
11
11
|
import { readFileSync as readFileSync10 } from "fs";
|
|
12
|
-
import { join as
|
|
12
|
+
import { join as join40 } from "path";
|
|
13
13
|
import { Command as Command2 } from "commander";
|
|
14
14
|
|
|
15
15
|
// ../shared/src/exit-codes.ts
|
|
@@ -3439,10 +3439,9 @@ async function runConfigPath(input) {
|
|
|
3439
3439
|
}
|
|
3440
3440
|
|
|
3441
3441
|
// src/commands/doctor.ts
|
|
3442
|
-
import { existsSync as
|
|
3443
|
-
import { join as
|
|
3444
|
-
import { execSync } from "child_process";
|
|
3445
|
-
import { platform } from "os";
|
|
3442
|
+
import { existsSync as existsSync7, lstatSync, readlinkSync, readdirSync, statSync as statSync2 } from "fs";
|
|
3443
|
+
import { join as join24, resolve as resolve4 } from "path";
|
|
3444
|
+
import { execSync as execSync2 } from "child_process";
|
|
3446
3445
|
|
|
3447
3446
|
// src/utils/auto-update.ts
|
|
3448
3447
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -3526,6 +3525,208 @@ function findPlugin(home, key = PLUGIN_KEY) {
|
|
|
3526
3525
|
return entries[0];
|
|
3527
3526
|
}
|
|
3528
3527
|
|
|
3528
|
+
// src/utils/s3-mount-health.ts
|
|
3529
|
+
import { execSync } from "child_process";
|
|
3530
|
+
import { platform } from "os";
|
|
3531
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, readFileSync as readFile17 } from "fs";
|
|
3532
|
+
import { join as join23 } from "path";
|
|
3533
|
+
var OS = platform();
|
|
3534
|
+
function findRcloneMountPid() {
|
|
3535
|
+
try {
|
|
3536
|
+
const out = execSync("pgrep -f 'rclone.*mount'", {
|
|
3537
|
+
encoding: "utf8",
|
|
3538
|
+
timeout: 2e3,
|
|
3539
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3540
|
+
}).trim();
|
|
3541
|
+
const pids = out.split("\n").filter(Boolean);
|
|
3542
|
+
if (pids.length === 0) return null;
|
|
3543
|
+
return parseInt(pids[0], 10);
|
|
3544
|
+
} catch {
|
|
3545
|
+
try {
|
|
3546
|
+
const out = execSync("ps aux", { encoding: "utf8", timeout: 2e3, stdio: ["pipe", "pipe", "pipe"] });
|
|
3547
|
+
for (const line of out.split("\n")) {
|
|
3548
|
+
if (line.includes("rclone") && line.includes("mount") && !line.includes("grep")) {
|
|
3549
|
+
const parts = line.trim().split(/\s+/);
|
|
3550
|
+
if (parts.length >= 2) return parseInt(parts[1], 10);
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
} catch {
|
|
3554
|
+
}
|
|
3555
|
+
return null;
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
function parseRcloneFlags(pid) {
|
|
3559
|
+
const flags = /* @__PURE__ */ new Map();
|
|
3560
|
+
try {
|
|
3561
|
+
const args = getRcloneArgs(pid);
|
|
3562
|
+
for (let i = 0; i < args.length; i++) {
|
|
3563
|
+
const arg = args[i];
|
|
3564
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
3565
|
+
const eq = arg.indexOf("=");
|
|
3566
|
+
flags.set(arg.slice(0, eq), arg.slice(eq + 1));
|
|
3567
|
+
} else if (arg.startsWith("--")) {
|
|
3568
|
+
const next = args[i + 1];
|
|
3569
|
+
if (next && !next.startsWith("-")) {
|
|
3570
|
+
flags.set(arg, next);
|
|
3571
|
+
i++;
|
|
3572
|
+
} else {
|
|
3573
|
+
flags.set(arg, "");
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
} catch {
|
|
3578
|
+
}
|
|
3579
|
+
return flags;
|
|
3580
|
+
}
|
|
3581
|
+
function getRcloneVersion() {
|
|
3582
|
+
try {
|
|
3583
|
+
const out = execSync("rclone version", {
|
|
3584
|
+
encoding: "utf8",
|
|
3585
|
+
timeout: 3e3,
|
|
3586
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3587
|
+
});
|
|
3588
|
+
const match = out.match(/rclone\s+v(\d+)\.(\d+)\.(\d+)/i);
|
|
3589
|
+
if (!match) return null;
|
|
3590
|
+
return {
|
|
3591
|
+
major: parseInt(match[1], 10),
|
|
3592
|
+
minor: parseInt(match[2], 10),
|
|
3593
|
+
patch: parseInt(match[3], 10),
|
|
3594
|
+
raw: out.split("\n")[0].trim()
|
|
3595
|
+
};
|
|
3596
|
+
} catch {
|
|
3597
|
+
return null;
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
function extractRcloneFs(args) {
|
|
3601
|
+
let foundMount = false;
|
|
3602
|
+
for (const arg of args) {
|
|
3603
|
+
if (arg === "mount") {
|
|
3604
|
+
foundMount = true;
|
|
3605
|
+
continue;
|
|
3606
|
+
}
|
|
3607
|
+
if (foundMount && arg.includes(":") && !arg.startsWith("-") && !arg.startsWith("/")) {
|
|
3608
|
+
return arg;
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3611
|
+
return null;
|
|
3612
|
+
}
|
|
3613
|
+
function getRcloneArgs(pid) {
|
|
3614
|
+
try {
|
|
3615
|
+
if (OS === "linux") {
|
|
3616
|
+
const raw = readFileSync6(`/proc/${pid}/cmdline`);
|
|
3617
|
+
return new TextDecoder().decode(raw).split("\0").filter(Boolean);
|
|
3618
|
+
} else {
|
|
3619
|
+
const out = execSync(`ps -o args= -p ${pid}`, {
|
|
3620
|
+
encoding: "utf8",
|
|
3621
|
+
timeout: 2e3,
|
|
3622
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3623
|
+
}).trim();
|
|
3624
|
+
return out.split(/\s+/);
|
|
3625
|
+
}
|
|
3626
|
+
} catch {
|
|
3627
|
+
return [];
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
function queryRcloneRC(rcAddr, fs) {
|
|
3631
|
+
try {
|
|
3632
|
+
const payload = JSON.stringify({ fs });
|
|
3633
|
+
const out = execSync(
|
|
3634
|
+
`curl -s --max-time 3 -X POST "http://${rcAddr}/vfs/stats" -H "Content-Type: application/json" -d '${payload}' 2>/dev/null`,
|
|
3635
|
+
{ encoding: "utf8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
3636
|
+
);
|
|
3637
|
+
if (!out.trim()) return null;
|
|
3638
|
+
const data = JSON.parse(out);
|
|
3639
|
+
if (data.status && data.status >= 400) {
|
|
3640
|
+
return { error: data.error || `RC error (status ${data.status})`, erroredFiles: 0, uploadsInProgress: 0, uploadsQueued: 0, outOfSpace: false, bytesUsed: 0, files: 0, totalSize: "unknown" };
|
|
3641
|
+
}
|
|
3642
|
+
const dc = data.diskCache || {};
|
|
3643
|
+
return {
|
|
3644
|
+
erroredFiles: dc.erroredFiles ?? 0,
|
|
3645
|
+
uploadsInProgress: dc.uploadsInProgress ?? 0,
|
|
3646
|
+
uploadsQueued: dc.uploadsQueued ?? 0,
|
|
3647
|
+
outOfSpace: dc.outOfSpace ?? false,
|
|
3648
|
+
bytesUsed: dc.bytesUsed ?? 0,
|
|
3649
|
+
files: dc.files ?? 0,
|
|
3650
|
+
totalSize: data.totalSize || "unknown"
|
|
3651
|
+
};
|
|
3652
|
+
} catch {
|
|
3653
|
+
return { error: "RC endpoint unreachable", erroredFiles: 0, uploadsInProgress: 0, uploadsQueued: 0, outOfSpace: false, bytesUsed: 0, files: 0, totalSize: "unknown" };
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
function detectFuseMount(vaultPath) {
|
|
3657
|
+
try {
|
|
3658
|
+
if (OS === "linux") {
|
|
3659
|
+
const mounts = readFileSync6("/proc/mounts", "utf8");
|
|
3660
|
+
let best = null;
|
|
3661
|
+
for (const line of mounts.split("\n")) {
|
|
3662
|
+
const parts = line.split(" ");
|
|
3663
|
+
if (parts.length < 3) continue;
|
|
3664
|
+
const point = parts[1];
|
|
3665
|
+
const fs = parts[2];
|
|
3666
|
+
if (vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
|
|
3667
|
+
best = { point, fs };
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
if (best && best.fs.includes("fuse")) return { mountPoint: best.point, fsType: best.fs };
|
|
3671
|
+
} else if (OS === "darwin") {
|
|
3672
|
+
const out = execSync("mount", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
3673
|
+
let best = null;
|
|
3674
|
+
for (const line of out.split("\n")) {
|
|
3675
|
+
const match = line.match(/^(\S+) on (\S+) \((.*?)\)/);
|
|
3676
|
+
if (!match) continue;
|
|
3677
|
+
const point = match[2];
|
|
3678
|
+
const opts = match[3];
|
|
3679
|
+
if (opts.includes("fuse") && vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
|
|
3680
|
+
best = { point, fsType: `fuse.${match[1].split(":")[0] || "unknown"}` };
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
if (best) return best;
|
|
3684
|
+
}
|
|
3685
|
+
} catch {
|
|
3686
|
+
}
|
|
3687
|
+
return null;
|
|
3688
|
+
}
|
|
3689
|
+
function writeTest(dir) {
|
|
3690
|
+
const testFile = join23(dir, `.doctor-write-test-${process.pid}.tmp`);
|
|
3691
|
+
const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
|
|
3692
|
+
const start = Date.now();
|
|
3693
|
+
try {
|
|
3694
|
+
writeFileSync4(testFile, payload, "utf8");
|
|
3695
|
+
} catch (e) {
|
|
3696
|
+
return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
|
|
3697
|
+
}
|
|
3698
|
+
const writeMs = Date.now() - start;
|
|
3699
|
+
const readStart = Date.now();
|
|
3700
|
+
try {
|
|
3701
|
+
const back = readFile17(testFile, "utf8");
|
|
3702
|
+
const readMs = Date.now() - readStart;
|
|
3703
|
+
if (back !== payload) {
|
|
3704
|
+
try {
|
|
3705
|
+
unlinkSync3(testFile);
|
|
3706
|
+
} catch {
|
|
3707
|
+
}
|
|
3708
|
+
return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
|
|
3709
|
+
}
|
|
3710
|
+
} catch (e) {
|
|
3711
|
+
try {
|
|
3712
|
+
unlinkSync3(testFile);
|
|
3713
|
+
} catch {
|
|
3714
|
+
}
|
|
3715
|
+
return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
|
|
3716
|
+
}
|
|
3717
|
+
try {
|
|
3718
|
+
unlinkSync3(testFile);
|
|
3719
|
+
} catch {
|
|
3720
|
+
}
|
|
3721
|
+
return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
|
|
3722
|
+
}
|
|
3723
|
+
var FLAG_THRESHOLDS = {
|
|
3724
|
+
"--vfs-write-back": { min: 15, unit: "s", label: "VFS write-back window" },
|
|
3725
|
+
"--vfs-write-wait": { min: 10, unit: "s", label: "VFS write-wait" },
|
|
3726
|
+
"--vfs-cache-max-age": { min: 24, unit: "h", label: "VFS cache max age" }
|
|
3727
|
+
};
|
|
3728
|
+
var MIN_RCLONE_VERSION = { major: 1, minor: 65, patch: 0 };
|
|
3729
|
+
|
|
3529
3730
|
// src/commands/doctor.ts
|
|
3530
3731
|
function check(status, id, label, detail) {
|
|
3531
3732
|
return { id, label, status, detail };
|
|
@@ -3544,7 +3745,7 @@ function detectCliChannels(argv, home) {
|
|
|
3544
3745
|
channels.push({ name: "dev", path: devPath, isDevLink: true });
|
|
3545
3746
|
}
|
|
3546
3747
|
try {
|
|
3547
|
-
const whichOut =
|
|
3748
|
+
const whichOut = execSync2("which skillwiki 2>/dev/null", { encoding: "utf8" }).trim();
|
|
3548
3749
|
if (whichOut) {
|
|
3549
3750
|
const isDev = isDevSymlink(whichOut);
|
|
3550
3751
|
if (!channels.some((c) => c.path === resolve4(whichOut))) {
|
|
@@ -3555,13 +3756,13 @@ function detectCliChannels(argv, home) {
|
|
|
3555
3756
|
}
|
|
3556
3757
|
const plugin = findPlugin(home);
|
|
3557
3758
|
if (plugin) {
|
|
3558
|
-
const pluginBin =
|
|
3559
|
-
if (
|
|
3759
|
+
const pluginBin = join24(plugin.installPath, "bin", "skillwiki");
|
|
3760
|
+
if (existsSync7(pluginBin)) {
|
|
3560
3761
|
channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
|
|
3561
3762
|
}
|
|
3562
3763
|
}
|
|
3563
|
-
const installBin =
|
|
3564
|
-
if (
|
|
3764
|
+
const installBin = join24(home, ".claude", "skills", "bin", "skillwiki");
|
|
3765
|
+
if (existsSync7(installBin)) {
|
|
3565
3766
|
channels.push({ name: "install", path: installBin, isDevLink: false });
|
|
3566
3767
|
}
|
|
3567
3768
|
return channels;
|
|
@@ -3613,7 +3814,7 @@ function checkCliChannels(argv, home) {
|
|
|
3613
3814
|
}
|
|
3614
3815
|
async function checkConfigFile(home) {
|
|
3615
3816
|
const cfgPath = configPath(home);
|
|
3616
|
-
if (!
|
|
3817
|
+
if (!existsSync7(cfgPath)) {
|
|
3617
3818
|
return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
|
|
3618
3819
|
}
|
|
3619
3820
|
try {
|
|
@@ -3628,7 +3829,7 @@ function checkWikiPathExists(resolvedPath) {
|
|
|
3628
3829
|
if (resolvedPath === void 0) {
|
|
3629
3830
|
return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3630
3831
|
}
|
|
3631
|
-
if (
|
|
3832
|
+
if (existsSync7(resolvedPath) && statSync2(resolvedPath).isDirectory()) {
|
|
3632
3833
|
return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
|
|
3633
3834
|
}
|
|
3634
3835
|
return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
|
|
@@ -3637,13 +3838,13 @@ function checkVaultStructure(resolvedPath) {
|
|
|
3637
3838
|
if (resolvedPath === void 0) {
|
|
3638
3839
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3639
3840
|
}
|
|
3640
|
-
if (!
|
|
3841
|
+
if (!existsSync7(resolvedPath)) {
|
|
3641
3842
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
|
|
3642
3843
|
}
|
|
3643
3844
|
const missing = [];
|
|
3644
|
-
if (!
|
|
3845
|
+
if (!existsSync7(join24(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
|
|
3645
3846
|
for (const dir of ["raw", "entities", "concepts", "meta"]) {
|
|
3646
|
-
if (!
|
|
3847
|
+
if (!existsSync7(join24(resolvedPath, dir))) missing.push(dir + "/");
|
|
3647
3848
|
}
|
|
3648
3849
|
if (missing.length === 0) {
|
|
3649
3850
|
return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
|
|
@@ -3651,8 +3852,8 @@ function checkVaultStructure(resolvedPath) {
|
|
|
3651
3852
|
return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
|
|
3652
3853
|
}
|
|
3653
3854
|
function checkSkillsInstalled(home, cwd) {
|
|
3654
|
-
const srcDir = cwd ?
|
|
3655
|
-
if (srcDir &&
|
|
3855
|
+
const srcDir = cwd ? join24(cwd, "packages", "skills") : void 0;
|
|
3856
|
+
if (srcDir && existsSync7(srcDir)) {
|
|
3656
3857
|
const found = findSkillMd(srcDir);
|
|
3657
3858
|
if (found.length > 0) {
|
|
3658
3859
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
|
|
@@ -3665,8 +3866,8 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
3665
3866
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
|
|
3666
3867
|
}
|
|
3667
3868
|
}
|
|
3668
|
-
const skillsDir =
|
|
3669
|
-
if (
|
|
3869
|
+
const skillsDir = join24(home, ".claude", "skills");
|
|
3870
|
+
if (existsSync7(skillsDir)) {
|
|
3670
3871
|
const found = findSkillMd(skillsDir);
|
|
3671
3872
|
if (found.length > 0) {
|
|
3672
3873
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
|
|
@@ -3676,10 +3877,10 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
3676
3877
|
}
|
|
3677
3878
|
function checkDuplicateSkills(home) {
|
|
3678
3879
|
const plugin = findPlugin(home);
|
|
3679
|
-
const skillsDir =
|
|
3880
|
+
const skillsDir = join24(home, ".claude", "skills");
|
|
3680
3881
|
const agentSkillDirs = [
|
|
3681
|
-
{ label: "~/.codex/skills/", path:
|
|
3682
|
-
{ label: "~/.agents/skills/", path:
|
|
3882
|
+
{ label: "~/.codex/skills/", path: join24(home, ".codex", "skills") },
|
|
3883
|
+
{ label: "~/.agents/skills/", path: join24(home, ".agents", "skills") }
|
|
3683
3884
|
];
|
|
3684
3885
|
if (!plugin) {
|
|
3685
3886
|
return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
|
|
@@ -3756,8 +3957,8 @@ async function checkProfiles(home) {
|
|
|
3756
3957
|
}
|
|
3757
3958
|
async function checkProjectLocalOverride(cwd) {
|
|
3758
3959
|
const dir = cwd ?? process.cwd();
|
|
3759
|
-
const envPath =
|
|
3760
|
-
if (
|
|
3960
|
+
const envPath = join24(dir, ".skillwiki", ".env");
|
|
3961
|
+
if (existsSync7(envPath)) {
|
|
3761
3962
|
return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
|
|
3762
3963
|
}
|
|
3763
3964
|
return check("pass", "project_local", "Project-local config", "None");
|
|
@@ -3766,17 +3967,17 @@ function checkVaultGitRemote(resolvedPath) {
|
|
|
3766
3967
|
if (resolvedPath === void 0) {
|
|
3767
3968
|
return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3768
3969
|
}
|
|
3769
|
-
if (!
|
|
3970
|
+
if (!existsSync7(join24(resolvedPath, ".git"))) {
|
|
3770
3971
|
return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
|
|
3771
3972
|
}
|
|
3772
3973
|
try {
|
|
3773
|
-
const remote =
|
|
3974
|
+
const remote = execSync2("git remote", { cwd: resolvedPath, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3774
3975
|
if (!remote) {
|
|
3775
3976
|
return check("warn", "vault_git_remote", "Vault git remote", "No remote configured \u2014 push/pull unavailable");
|
|
3776
3977
|
}
|
|
3777
3978
|
let branch = "(no commits yet)";
|
|
3778
3979
|
try {
|
|
3779
|
-
branch =
|
|
3980
|
+
branch = execSync2("git rev-parse --abbrev-ref HEAD", { cwd: resolvedPath, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3780
3981
|
} catch {
|
|
3781
3982
|
}
|
|
3782
3983
|
return check("pass", "vault_git_remote", "Vault git remote", `Remote: ${remote.split("\n")[0]}, branch: ${branch}`);
|
|
@@ -3789,9 +3990,9 @@ function checkObsidianTemplates(resolvedPath) {
|
|
|
3789
3990
|
return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3790
3991
|
}
|
|
3791
3992
|
const missing = [];
|
|
3792
|
-
if (!
|
|
3793
|
-
if (!
|
|
3794
|
-
if (!
|
|
3993
|
+
if (!existsSync7(join24(resolvedPath, "_Templates"))) missing.push("_Templates/");
|
|
3994
|
+
if (!existsSync7(join24(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
|
|
3995
|
+
if (!existsSync7(join24(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
|
|
3795
3996
|
if (missing.length === 0) {
|
|
3796
3997
|
return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
|
|
3797
3998
|
}
|
|
@@ -3801,8 +4002,8 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
3801
4002
|
if (resolvedPath === void 0) {
|
|
3802
4003
|
return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3803
4004
|
}
|
|
3804
|
-
const rawDir =
|
|
3805
|
-
if (!
|
|
4005
|
+
const rawDir = join24(resolvedPath, "raw");
|
|
4006
|
+
if (!existsSync7(rawDir)) {
|
|
3806
4007
|
return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
|
|
3807
4008
|
}
|
|
3808
4009
|
const found = [];
|
|
@@ -3817,7 +4018,7 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
3817
4018
|
if (entry.name === ".DS_Store") {
|
|
3818
4019
|
found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
|
|
3819
4020
|
} else if (entry.isDirectory()) {
|
|
3820
|
-
walk2(
|
|
4021
|
+
walk2(join24(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
|
|
3821
4022
|
}
|
|
3822
4023
|
}
|
|
3823
4024
|
})(rawDir, "");
|
|
@@ -3830,12 +4031,12 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
3830
4031
|
if (resolvedPath === void 0) {
|
|
3831
4032
|
return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3832
4033
|
}
|
|
3833
|
-
if (!
|
|
4034
|
+
if (!existsSync7(join24(resolvedPath, ".git"))) {
|
|
3834
4035
|
return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
|
|
3835
4036
|
}
|
|
3836
4037
|
let timestamp;
|
|
3837
4038
|
try {
|
|
3838
|
-
const out =
|
|
4039
|
+
const out = execSync2("git log -1 --format=%ct origin/HEAD", {
|
|
3839
4040
|
cwd: resolvedPath,
|
|
3840
4041
|
encoding: "utf8",
|
|
3841
4042
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3843,7 +4044,7 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
3843
4044
|
timestamp = parseInt(out, 10);
|
|
3844
4045
|
} catch {
|
|
3845
4046
|
try {
|
|
3846
|
-
const out =
|
|
4047
|
+
const out = execSync2("git log -1 --format=%ct HEAD", {
|
|
3847
4048
|
cwd: resolvedPath,
|
|
3848
4049
|
encoding: "utf8",
|
|
3849
4050
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3862,56 +4063,23 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
3862
4063
|
}
|
|
3863
4064
|
return check("pass", "sync_last_push", "Vault sync recency", `Last push: ${dateStr} (${daysSince2} day(s) ago)`);
|
|
3864
4065
|
}
|
|
3865
|
-
function detectFuseMount(vaultPath) {
|
|
3866
|
-
const os = platform();
|
|
3867
|
-
try {
|
|
3868
|
-
if (os === "linux") {
|
|
3869
|
-
const mounts = readFileSync6("/proc/mounts", "utf8");
|
|
3870
|
-
let best = null;
|
|
3871
|
-
for (const line of mounts.split("\n")) {
|
|
3872
|
-
const parts = line.split(" ");
|
|
3873
|
-
if (parts.length < 3) continue;
|
|
3874
|
-
const point = parts[1];
|
|
3875
|
-
const fs = parts[2];
|
|
3876
|
-
if (vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
|
|
3877
|
-
best = { point, fs };
|
|
3878
|
-
}
|
|
3879
|
-
}
|
|
3880
|
-
if (best && best.fs.includes("fuse")) return best.point;
|
|
3881
|
-
} else if (os === "darwin") {
|
|
3882
|
-
const out = execSync("mount", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
3883
|
-
let best = null;
|
|
3884
|
-
for (const line of out.split("\n")) {
|
|
3885
|
-
const match = line.match(/^(\S+) on (\S+) \((.*?)\)/);
|
|
3886
|
-
if (!match) continue;
|
|
3887
|
-
const point = match[2];
|
|
3888
|
-
const opts = match[3];
|
|
3889
|
-
if (opts.includes("fuse") && vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
|
|
3890
|
-
best = { point };
|
|
3891
|
-
}
|
|
3892
|
-
}
|
|
3893
|
-
if (best) return best.point;
|
|
3894
|
-
}
|
|
3895
|
-
} catch {
|
|
3896
|
-
}
|
|
3897
|
-
return null;
|
|
3898
|
-
}
|
|
3899
4066
|
function checkS3MountPerf(resolvedPath) {
|
|
3900
4067
|
if (resolvedPath === void 0) {
|
|
3901
4068
|
return check("pass", "s3_mount_perf", "S3 mount performance", "No vault path \u2014 check skipped");
|
|
3902
4069
|
}
|
|
3903
|
-
const
|
|
3904
|
-
if (!
|
|
4070
|
+
const fuse = detectFuseMount(resolvedPath);
|
|
4071
|
+
if (!fuse) {
|
|
3905
4072
|
return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
|
|
3906
4073
|
}
|
|
3907
|
-
const
|
|
3908
|
-
|
|
4074
|
+
const mountPoint = fuse.mountPoint;
|
|
4075
|
+
const conceptsDir = join24(resolvedPath, "concepts");
|
|
4076
|
+
if (!existsSync7(conceptsDir)) {
|
|
3909
4077
|
return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
|
|
3910
4078
|
}
|
|
3911
4079
|
const start = Date.now();
|
|
3912
4080
|
let timedOut = false;
|
|
3913
4081
|
try {
|
|
3914
|
-
|
|
4082
|
+
execSync2(`rg -l "." "${conceptsDir}"`, {
|
|
3915
4083
|
timeout: 5e3,
|
|
3916
4084
|
encoding: "utf8",
|
|
3917
4085
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3944,6 +4112,170 @@ function checkS3MountPerf(resolvedPath) {
|
|
|
3944
4112
|
`S3 FUSE mount, cache warm (rg scan: ${elapsed.toFixed(3)}s)`
|
|
3945
4113
|
);
|
|
3946
4114
|
}
|
|
4115
|
+
function checkRcloneFlagAudit(resolvedPath) {
|
|
4116
|
+
if (!resolvedPath) {
|
|
4117
|
+
return check("pass", "rclone_flags", "rclone VFS flags", "No vault path \u2014 check skipped");
|
|
4118
|
+
}
|
|
4119
|
+
const fuse = detectFuseMount(resolvedPath);
|
|
4120
|
+
if (!fuse) {
|
|
4121
|
+
return check("pass", "rclone_flags", "rclone VFS flags", "local disk \u2014 check skipped");
|
|
4122
|
+
}
|
|
4123
|
+
const pid = findRcloneMountPid();
|
|
4124
|
+
if (pid === null) {
|
|
4125
|
+
return check("warn", "rclone_flags", "rclone VFS flags", `S3 FUSE mount (${fuse.mountPoint}) but no rclone process found \u2014 cannot audit flags`);
|
|
4126
|
+
}
|
|
4127
|
+
const flags = parseRcloneFlags(pid);
|
|
4128
|
+
if (flags.size === 0) {
|
|
4129
|
+
return check("warn", "rclone_flags", "rclone VFS flags", `rclone PID ${pid} found but could not parse flags`);
|
|
4130
|
+
}
|
|
4131
|
+
const warnings = [];
|
|
4132
|
+
for (const [flag, threshold] of Object.entries(FLAG_THRESHOLDS)) {
|
|
4133
|
+
const raw = flags.get(flag);
|
|
4134
|
+
if (raw === void 0) {
|
|
4135
|
+
warnings.push(`${flag} not set (default may be unsafe)`);
|
|
4136
|
+
continue;
|
|
4137
|
+
}
|
|
4138
|
+
const value = parseFloat(raw);
|
|
4139
|
+
if (isNaN(value)) continue;
|
|
4140
|
+
let inSeconds = value;
|
|
4141
|
+
if (raw.endsWith("h")) inSeconds = value * 3600;
|
|
4142
|
+
else if (raw.endsWith("m")) inSeconds = value * 60;
|
|
4143
|
+
const thresholdSec = threshold.unit === "h" ? threshold.min * 3600 : threshold.unit === "m" ? threshold.min * 60 : threshold.min;
|
|
4144
|
+
if (inSeconds < thresholdSec) {
|
|
4145
|
+
warnings.push(`${flag}=${raw} (recommended \u2265${threshold.min}${threshold.unit})`);
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
const cacheMode = flags.get("--vfs-cache-mode");
|
|
4149
|
+
if (!cacheMode) {
|
|
4150
|
+
warnings.push("--vfs-cache-mode not set (recommended: full)");
|
|
4151
|
+
} else if (cacheMode !== "full") {
|
|
4152
|
+
warnings.push(`--vfs-cache-mode=${cacheMode} (recommended: full)`);
|
|
4153
|
+
}
|
|
4154
|
+
if (!flags.has("--log-file")) {
|
|
4155
|
+
warnings.push("--log-file not set \u2014 no rclone error log configured");
|
|
4156
|
+
}
|
|
4157
|
+
if (warnings.length > 0) {
|
|
4158
|
+
return check("warn", "rclone_flags", "rclone VFS flags", warnings.join("; "));
|
|
4159
|
+
}
|
|
4160
|
+
return check("pass", "rclone_flags", "rclone VFS flags", `PID ${pid}: all critical flags at safe values`);
|
|
4161
|
+
}
|
|
4162
|
+
function checkRcloneVersion(resolvedPath) {
|
|
4163
|
+
if (!resolvedPath) {
|
|
4164
|
+
return check("pass", "rclone_version", "rclone version", "No vault path \u2014 check skipped");
|
|
4165
|
+
}
|
|
4166
|
+
const fuse = detectFuseMount(resolvedPath);
|
|
4167
|
+
if (!fuse) {
|
|
4168
|
+
return check("pass", "rclone_version", "rclone version", "local disk \u2014 check skipped");
|
|
4169
|
+
}
|
|
4170
|
+
const ver = getRcloneVersion();
|
|
4171
|
+
if (!ver) {
|
|
4172
|
+
return check("warn", "rclone_version", "rclone version", "rclone not found on PATH \u2014 cannot verify version");
|
|
4173
|
+
}
|
|
4174
|
+
const min = MIN_RCLONE_VERSION;
|
|
4175
|
+
const tooOld = ver.major < min.major || ver.major === min.major && ver.minor < min.minor || ver.major === min.major && ver.minor === min.minor && ver.patch < min.patch;
|
|
4176
|
+
if (tooOld) {
|
|
4177
|
+
return check(
|
|
4178
|
+
"warn",
|
|
4179
|
+
"rclone_version",
|
|
4180
|
+
"rclone version",
|
|
4181
|
+
`${ver.raw} \u2014 upgrade to \u2265v${min.major}.${min.minor}.${min.patch} for --vfs-write-wait support (current version may silently ignore this flag)`
|
|
4182
|
+
);
|
|
4183
|
+
}
|
|
4184
|
+
return check("pass", "rclone_version", "rclone version", ver.raw);
|
|
4185
|
+
}
|
|
4186
|
+
function checkWriteTest(resolvedPath) {
|
|
4187
|
+
if (!resolvedPath) {
|
|
4188
|
+
return check("pass", "s3_write_test", "S3 write test", "No vault path \u2014 check skipped");
|
|
4189
|
+
}
|
|
4190
|
+
const fuse = detectFuseMount(resolvedPath);
|
|
4191
|
+
if (!fuse) {
|
|
4192
|
+
return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
|
|
4193
|
+
}
|
|
4194
|
+
const conceptsDir = join24(resolvedPath, "concepts");
|
|
4195
|
+
if (!existsSync7(conceptsDir)) {
|
|
4196
|
+
return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
|
|
4197
|
+
}
|
|
4198
|
+
const result = writeTest(conceptsDir);
|
|
4199
|
+
if (result.success) {
|
|
4200
|
+
const totalMs = result.writeMs + result.readMs;
|
|
4201
|
+
if (totalMs > 3e3) {
|
|
4202
|
+
return check(
|
|
4203
|
+
"warn",
|
|
4204
|
+
"s3_write_test",
|
|
4205
|
+
"S3 write test",
|
|
4206
|
+
`write+read ${totalMs}ms (write ${result.writeMs}ms, read ${result.readMs}ms, ${result.size}B) \u2014 S3 mount is slow`
|
|
4207
|
+
);
|
|
4208
|
+
}
|
|
4209
|
+
return check(
|
|
4210
|
+
"pass",
|
|
4211
|
+
"s3_write_test",
|
|
4212
|
+
"S3 write test",
|
|
4213
|
+
`write+read ${totalMs}ms (write ${result.writeMs}ms, read ${result.readMs}ms)`
|
|
4214
|
+
);
|
|
4215
|
+
}
|
|
4216
|
+
return check(
|
|
4217
|
+
"warn",
|
|
4218
|
+
"s3_write_test",
|
|
4219
|
+
"S3 write test",
|
|
4220
|
+
`${result.error} \u2014 S3 mount may have a stale FUSE handle or write-back failure`
|
|
4221
|
+
);
|
|
4222
|
+
}
|
|
4223
|
+
function checkVfsCacheHealth(resolvedPath) {
|
|
4224
|
+
if (!resolvedPath) {
|
|
4225
|
+
return check("pass", "vfs_cache_health", "VFS cache health", "No vault path \u2014 check skipped");
|
|
4226
|
+
}
|
|
4227
|
+
const fuse = detectFuseMount(resolvedPath);
|
|
4228
|
+
if (!fuse) {
|
|
4229
|
+
return check("pass", "vfs_cache_health", "VFS cache health", "local disk \u2014 check skipped");
|
|
4230
|
+
}
|
|
4231
|
+
const pid = findRcloneMountPid();
|
|
4232
|
+
if (pid === null) {
|
|
4233
|
+
return check("warn", "vfs_cache_health", "VFS cache health", "no rclone process found \u2014 cannot query VFS stats");
|
|
4234
|
+
}
|
|
4235
|
+
const flags = parseRcloneFlags(pid);
|
|
4236
|
+
const rcAddr = flags.get("--rc-addr") || "127.0.0.1:5572";
|
|
4237
|
+
if (!flags.has("--rc")) {
|
|
4238
|
+
return check(
|
|
4239
|
+
"info",
|
|
4240
|
+
"vfs_cache_health",
|
|
4241
|
+
"VFS cache health",
|
|
4242
|
+
`rclone RC not enabled \u2014 add --rc --rc-addr ${rcAddr} to enable cache health monitoring`
|
|
4243
|
+
);
|
|
4244
|
+
}
|
|
4245
|
+
const args = getRcloneArgs(pid);
|
|
4246
|
+
const fs = extractRcloneFs(args) || "unknown:";
|
|
4247
|
+
const stats = queryRcloneRC(rcAddr, fs || "unknown:");
|
|
4248
|
+
if (!stats) {
|
|
4249
|
+
return check(
|
|
4250
|
+
"warn",
|
|
4251
|
+
"vfs_cache_health",
|
|
4252
|
+
"VFS cache health",
|
|
4253
|
+
`RC endpoint ${rcAddr} unreachable \u2014 is rclone --rc enabled?`
|
|
4254
|
+
);
|
|
4255
|
+
}
|
|
4256
|
+
if (stats.error) {
|
|
4257
|
+
return check("warn", "vfs_cache_health", "VFS cache health", stats.error);
|
|
4258
|
+
}
|
|
4259
|
+
const issues = [];
|
|
4260
|
+
if (stats.uploadsInProgress > 0) issues.push(`${stats.uploadsInProgress} upload(s) in progress`);
|
|
4261
|
+
if (stats.uploadsQueued > 10) issues.push(`${stats.uploadsQueued} upload(s) queued (backlog)`);
|
|
4262
|
+
if (stats.erroredFiles > 0) issues.push(`${stats.erroredFiles} errored file(s)`);
|
|
4263
|
+
if (stats.outOfSpace) issues.push("cache disk full");
|
|
4264
|
+
if (issues.length > 0) {
|
|
4265
|
+
return check(
|
|
4266
|
+
"warn",
|
|
4267
|
+
"vfs_cache_health",
|
|
4268
|
+
"VFS cache health",
|
|
4269
|
+
`${stats.files} files, ${stats.bytesUsed} bytes \u2014 ${issues.join("; ")}`
|
|
4270
|
+
);
|
|
4271
|
+
}
|
|
4272
|
+
return check(
|
|
4273
|
+
"pass",
|
|
4274
|
+
"vfs_cache_health",
|
|
4275
|
+
"VFS cache health",
|
|
4276
|
+
`${stats.files} files, ${(stats.bytesUsed / 1024 / 1024).toFixed(1)}MB \u2014 clean (0 errored, 0 pending)`
|
|
4277
|
+
);
|
|
4278
|
+
}
|
|
3947
4279
|
function findSkillMd(dir) {
|
|
3948
4280
|
const results = [];
|
|
3949
4281
|
let entries;
|
|
@@ -3954,9 +4286,9 @@ function findSkillMd(dir) {
|
|
|
3954
4286
|
}
|
|
3955
4287
|
for (const entry of entries) {
|
|
3956
4288
|
if (entry.isFile() && entry.name === "SKILL.md") {
|
|
3957
|
-
results.push(
|
|
4289
|
+
results.push(join24(dir, entry.name));
|
|
3958
4290
|
} else if (entry.isDirectory()) {
|
|
3959
|
-
results.push(...findSkillMd(
|
|
4291
|
+
results.push(...findSkillMd(join24(dir, entry.name)));
|
|
3960
4292
|
}
|
|
3961
4293
|
}
|
|
3962
4294
|
return results;
|
|
@@ -3970,7 +4302,7 @@ function findSkillNames(dir) {
|
|
|
3970
4302
|
return results;
|
|
3971
4303
|
}
|
|
3972
4304
|
for (const entry of entries) {
|
|
3973
|
-
if (entry.isDirectory() &&
|
|
4305
|
+
if (entry.isDirectory() && existsSync7(join24(dir, entry.name, "SKILL.md"))) {
|
|
3974
4306
|
results.push(entry.name);
|
|
3975
4307
|
}
|
|
3976
4308
|
}
|
|
@@ -3997,6 +4329,10 @@ async function runDoctor(input) {
|
|
|
3997
4329
|
checks.push(checkSyncLastPush(resolvedPath));
|
|
3998
4330
|
checks.push(checkDotStoreClean(resolvedPath));
|
|
3999
4331
|
checks.push(checkS3MountPerf(resolvedPath));
|
|
4332
|
+
checks.push(checkRcloneFlagAudit(resolvedPath));
|
|
4333
|
+
checks.push(checkRcloneVersion(resolvedPath));
|
|
4334
|
+
checks.push(checkWriteTest(resolvedPath));
|
|
4335
|
+
checks.push(checkVfsCacheHealth(resolvedPath));
|
|
4000
4336
|
checks.push(checkSkillsInstalled(input.home, input.cwd));
|
|
4001
4337
|
checks.push(checkDuplicateSkills(input.home));
|
|
4002
4338
|
checks.push(checkNpmUpdate(input.home, input.currentVersion));
|
|
@@ -4024,8 +4360,8 @@ async function runDoctor(input) {
|
|
|
4024
4360
|
}
|
|
4025
4361
|
|
|
4026
4362
|
// src/commands/archive.ts
|
|
4027
|
-
import { rename as rename5, mkdir as mkdir8, readFile as
|
|
4028
|
-
import { join as
|
|
4363
|
+
import { rename as rename5, mkdir as mkdir8, readFile as readFile18, writeFile as writeFile9 } from "fs/promises";
|
|
4364
|
+
import { join as join25, dirname as dirname9 } from "path";
|
|
4029
4365
|
async function runArchive(input) {
|
|
4030
4366
|
const scan = await scanVault(input.vault);
|
|
4031
4367
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
@@ -4041,13 +4377,13 @@ async function runArchive(input) {
|
|
|
4041
4377
|
}
|
|
4042
4378
|
if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
|
|
4043
4379
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
4044
|
-
const archivePath =
|
|
4045
|
-
await mkdir8(dirname9(
|
|
4380
|
+
const archivePath = join25("_archive", relPath).replace(/\\/g, "/");
|
|
4381
|
+
await mkdir8(dirname9(join25(input.vault, archivePath)), { recursive: true });
|
|
4046
4382
|
let indexUpdated = false;
|
|
4047
4383
|
if (!isRaw) {
|
|
4048
|
-
const indexPath =
|
|
4384
|
+
const indexPath = join25(input.vault, "index.md");
|
|
4049
4385
|
try {
|
|
4050
|
-
const idx = await
|
|
4386
|
+
const idx = await readFile18(indexPath, "utf8");
|
|
4051
4387
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
4052
4388
|
const originalLines = idx.split("\n");
|
|
4053
4389
|
const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
|
|
@@ -4059,7 +4395,7 @@ async function runArchive(input) {
|
|
|
4059
4395
|
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
4060
4396
|
}
|
|
4061
4397
|
}
|
|
4062
|
-
await rename5(
|
|
4398
|
+
await rename5(join25(input.vault, relPath), join25(input.vault, archivePath));
|
|
4063
4399
|
appendLastOp(input.vault, {
|
|
4064
4400
|
operation: "archive",
|
|
4065
4401
|
summary: `moved ${relPath} to ${archivePath}`,
|
|
@@ -4432,16 +4768,16 @@ ${newBody}`;
|
|
|
4432
4768
|
}
|
|
4433
4769
|
|
|
4434
4770
|
// src/commands/update.ts
|
|
4435
|
-
import { execSync as
|
|
4771
|
+
import { execSync as execSync3 } from "child_process";
|
|
4436
4772
|
import { readFileSync as readFileSync7 } from "fs";
|
|
4437
|
-
import { join as
|
|
4773
|
+
import { join as join26 } from "path";
|
|
4438
4774
|
function resolveGlobalSkillsRoot() {
|
|
4439
4775
|
try {
|
|
4440
|
-
const globalRoot =
|
|
4776
|
+
const globalRoot = execSync3("npm root -g", {
|
|
4441
4777
|
encoding: "utf8",
|
|
4442
4778
|
timeout: 5e3
|
|
4443
4779
|
}).trim();
|
|
4444
|
-
return
|
|
4780
|
+
return join26(globalRoot, "skillwiki", "skills");
|
|
4445
4781
|
} catch {
|
|
4446
4782
|
return null;
|
|
4447
4783
|
}
|
|
@@ -4467,10 +4803,10 @@ async function runUpdate(input) {
|
|
|
4467
4803
|
);
|
|
4468
4804
|
const currentVersion = pkg2.version;
|
|
4469
4805
|
const tag = input.distTag ?? "latest";
|
|
4470
|
-
const target =
|
|
4806
|
+
const target = join26(input.home, ".claude", "skills");
|
|
4471
4807
|
let latest;
|
|
4472
4808
|
try {
|
|
4473
|
-
latest =
|
|
4809
|
+
latest = execSync3(`npm view skillwiki@${tag} version`, {
|
|
4474
4810
|
encoding: "utf8",
|
|
4475
4811
|
timeout: 15e3
|
|
4476
4812
|
}).trim();
|
|
@@ -4500,7 +4836,7 @@ async function runUpdate(input) {
|
|
|
4500
4836
|
};
|
|
4501
4837
|
}
|
|
4502
4838
|
try {
|
|
4503
|
-
|
|
4839
|
+
execSync3(`npm install -g skillwiki@${tag}`, {
|
|
4504
4840
|
stdio: "pipe",
|
|
4505
4841
|
timeout: 6e4
|
|
4506
4842
|
});
|
|
@@ -4536,17 +4872,17 @@ async function runUpdate(input) {
|
|
|
4536
4872
|
}
|
|
4537
4873
|
|
|
4538
4874
|
// src/commands/self-update.ts
|
|
4539
|
-
import { execSync as
|
|
4540
|
-
import { existsSync as
|
|
4541
|
-
import { join as
|
|
4875
|
+
import { execSync as execSync4 } from "child_process";
|
|
4876
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
4877
|
+
import { join as join27 } from "path";
|
|
4542
4878
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
4543
4879
|
async function runSelfUpdate(input) {
|
|
4544
4880
|
const currentVersion = JSON.parse(
|
|
4545
4881
|
readFileSync8(new URL("../../package.json", import.meta.url), "utf8")
|
|
4546
4882
|
).version;
|
|
4547
4883
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
4548
|
-
const localPkgPath =
|
|
4549
|
-
const hasLocalSource =
|
|
4884
|
+
const localPkgPath = join27(sourceRoot, "packages", "cli", "package.json");
|
|
4885
|
+
const hasLocalSource = existsSync8(localPkgPath);
|
|
4550
4886
|
if (input.check) {
|
|
4551
4887
|
let availableVersion = null;
|
|
4552
4888
|
let source;
|
|
@@ -4560,7 +4896,7 @@ async function runSelfUpdate(input) {
|
|
|
4560
4896
|
} else {
|
|
4561
4897
|
source = "npm";
|
|
4562
4898
|
try {
|
|
4563
|
-
availableVersion =
|
|
4899
|
+
availableVersion = execSync4("npm view skillwiki@latest version", {
|
|
4564
4900
|
encoding: "utf8",
|
|
4565
4901
|
timeout: 15e3
|
|
4566
4902
|
}).trim();
|
|
@@ -4586,7 +4922,7 @@ async function runSelfUpdate(input) {
|
|
|
4586
4922
|
}
|
|
4587
4923
|
if (hasLocalSource) {
|
|
4588
4924
|
try {
|
|
4589
|
-
|
|
4925
|
+
execSync4("npm run build -w packages/cli", {
|
|
4590
4926
|
cwd: sourceRoot,
|
|
4591
4927
|
stdio: "pipe",
|
|
4592
4928
|
timeout: 6e4
|
|
@@ -4598,7 +4934,7 @@ async function runSelfUpdate(input) {
|
|
|
4598
4934
|
};
|
|
4599
4935
|
}
|
|
4600
4936
|
try {
|
|
4601
|
-
|
|
4937
|
+
execSync4("npm link ./packages/cli", {
|
|
4602
4938
|
cwd: sourceRoot,
|
|
4603
4939
|
stdio: "pipe",
|
|
4604
4940
|
timeout: 3e4
|
|
@@ -4630,7 +4966,7 @@ async function runSelfUpdate(input) {
|
|
|
4630
4966
|
}
|
|
4631
4967
|
let latestVersion;
|
|
4632
4968
|
try {
|
|
4633
|
-
latestVersion =
|
|
4969
|
+
latestVersion = execSync4("npm view skillwiki@latest version", {
|
|
4634
4970
|
encoding: "utf8",
|
|
4635
4971
|
timeout: 15e3
|
|
4636
4972
|
}).trim();
|
|
@@ -4653,7 +4989,7 @@ async function runSelfUpdate(input) {
|
|
|
4653
4989
|
};
|
|
4654
4990
|
}
|
|
4655
4991
|
try {
|
|
4656
|
-
|
|
4992
|
+
execSync4("npm install -g skillwiki@latest", {
|
|
4657
4993
|
stdio: "pipe",
|
|
4658
4994
|
timeout: 6e4
|
|
4659
4995
|
});
|
|
@@ -4677,10 +5013,10 @@ async function runSelfUpdate(input) {
|
|
|
4677
5013
|
}
|
|
4678
5014
|
|
|
4679
5015
|
// src/commands/transcripts.ts
|
|
4680
|
-
import { readdir as readdir5, stat as stat6, readFile as
|
|
4681
|
-
import { join as
|
|
5016
|
+
import { readdir as readdir5, stat as stat6, readFile as readFile19 } from "fs/promises";
|
|
5017
|
+
import { join as join28 } from "path";
|
|
4682
5018
|
async function runTranscripts(input) {
|
|
4683
|
-
const dir =
|
|
5019
|
+
const dir = join28(input.vault, "raw", "transcripts");
|
|
4684
5020
|
let entries;
|
|
4685
5021
|
try {
|
|
4686
5022
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
@@ -4690,8 +5026,8 @@ async function runTranscripts(input) {
|
|
|
4690
5026
|
const transcripts = [];
|
|
4691
5027
|
for (const entry of entries) {
|
|
4692
5028
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4693
|
-
const filePath =
|
|
4694
|
-
const content = await
|
|
5029
|
+
const filePath = join28(dir, entry.name);
|
|
5030
|
+
const content = await readFile19(filePath, "utf8");
|
|
4695
5031
|
const fm = extractFrontmatter(content);
|
|
4696
5032
|
if (!fm.ok) continue;
|
|
4697
5033
|
const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
|
|
@@ -4708,12 +5044,12 @@ async function runTranscripts(input) {
|
|
|
4708
5044
|
}
|
|
4709
5045
|
|
|
4710
5046
|
// src/commands/project-index.ts
|
|
4711
|
-
import { readdir as readdir6, readFile as
|
|
4712
|
-
import { join as
|
|
5047
|
+
import { readdir as readdir6, readFile as readFile20, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
|
|
5048
|
+
import { join as join29, dirname as dirname10 } from "path";
|
|
4713
5049
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
4714
5050
|
async function runProjectIndex(input) {
|
|
4715
5051
|
const slug = input.slug;
|
|
4716
|
-
const projectDir =
|
|
5052
|
+
const projectDir = join29(input.vault, "projects", slug);
|
|
4717
5053
|
try {
|
|
4718
5054
|
await readdir6(projectDir);
|
|
4719
5055
|
} catch {
|
|
@@ -4724,15 +5060,15 @@ async function runProjectIndex(input) {
|
|
|
4724
5060
|
}
|
|
4725
5061
|
const wikilinkPattern = `[[${slug}]]`;
|
|
4726
5062
|
const entries = [];
|
|
4727
|
-
const compoundDir =
|
|
5063
|
+
const compoundDir = join29(input.vault, "projects", slug, "compound");
|
|
4728
5064
|
try {
|
|
4729
5065
|
const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
|
|
4730
5066
|
for (const entry of compoundFiles) {
|
|
4731
5067
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4732
|
-
const filePath =
|
|
5068
|
+
const filePath = join29(compoundDir, entry.name);
|
|
4733
5069
|
let text;
|
|
4734
5070
|
try {
|
|
4735
|
-
text = await
|
|
5071
|
+
text = await readFile20(filePath, "utf8");
|
|
4736
5072
|
} catch {
|
|
4737
5073
|
continue;
|
|
4738
5074
|
}
|
|
@@ -4749,16 +5085,16 @@ async function runProjectIndex(input) {
|
|
|
4749
5085
|
for (const dir of LAYER2_DIRS) {
|
|
4750
5086
|
let files;
|
|
4751
5087
|
try {
|
|
4752
|
-
files = await readdir6(
|
|
5088
|
+
files = await readdir6(join29(input.vault, dir), { withFileTypes: true });
|
|
4753
5089
|
} catch {
|
|
4754
5090
|
continue;
|
|
4755
5091
|
}
|
|
4756
5092
|
for (const entry of files) {
|
|
4757
5093
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4758
|
-
const filePath =
|
|
5094
|
+
const filePath = join29(input.vault, dir, entry.name);
|
|
4759
5095
|
let text;
|
|
4760
5096
|
try {
|
|
4761
|
-
text = await
|
|
5097
|
+
text = await readFile20(filePath, "utf8");
|
|
4762
5098
|
} catch {
|
|
4763
5099
|
continue;
|
|
4764
5100
|
}
|
|
@@ -4779,11 +5115,11 @@ async function runProjectIndex(input) {
|
|
|
4779
5115
|
const tb = typeOrder[b.type] ?? 99;
|
|
4780
5116
|
return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
|
|
4781
5117
|
});
|
|
4782
|
-
const indexPath =
|
|
5118
|
+
const indexPath = join29(projectDir, "knowledge.md");
|
|
4783
5119
|
let existing = false;
|
|
4784
5120
|
let stale = false;
|
|
4785
5121
|
try {
|
|
4786
|
-
const existingText = await
|
|
5122
|
+
const existingText = await readFile20(indexPath, "utf8");
|
|
4787
5123
|
existing = true;
|
|
4788
5124
|
const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
|
|
4789
5125
|
const existingPages = new Set(existingEntries.map((l) => {
|
|
@@ -4853,9 +5189,9 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
|
|
|
4853
5189
|
|
|
4854
5190
|
// src/commands/compound.ts
|
|
4855
5191
|
import { writeFile as writeFile11, mkdir as mkdir10, readdir as readdir7, unlink as unlink3 } from "fs/promises";
|
|
4856
|
-
import { join as
|
|
4857
|
-
import { existsSync as
|
|
4858
|
-
import { readFile as
|
|
5192
|
+
import { join as join30 } from "path";
|
|
5193
|
+
import { existsSync as existsSync9 } from "fs";
|
|
5194
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
4859
5195
|
var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
|
|
4860
5196
|
var FIELD_RE = {
|
|
4861
5197
|
improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
|
|
@@ -4953,17 +5289,17 @@ function extractRetroFields(date, cycleName, block) {
|
|
|
4953
5289
|
};
|
|
4954
5290
|
}
|
|
4955
5291
|
async function runCompound(input) {
|
|
4956
|
-
const logPath =
|
|
5292
|
+
const logPath = join30(input.vault, "log.md");
|
|
4957
5293
|
let logText;
|
|
4958
5294
|
try {
|
|
4959
|
-
logText = await
|
|
5295
|
+
logText = await readFile21(logPath, "utf8");
|
|
4960
5296
|
} catch {
|
|
4961
5297
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
4962
5298
|
}
|
|
4963
5299
|
const entries = parseRetroEntries(logText);
|
|
4964
5300
|
const promoted = [];
|
|
4965
5301
|
const skipped = [];
|
|
4966
|
-
const compoundDir =
|
|
5302
|
+
const compoundDir = join30(input.vault, "projects", input.project, "compound");
|
|
4967
5303
|
for (const entry of entries) {
|
|
4968
5304
|
const generalizeValue = entry.generalize.trim();
|
|
4969
5305
|
if (!/^yes/i.test(generalizeValue)) {
|
|
@@ -4971,8 +5307,8 @@ async function runCompound(input) {
|
|
|
4971
5307
|
continue;
|
|
4972
5308
|
}
|
|
4973
5309
|
const slug = slugify(entry.cycleName);
|
|
4974
|
-
const compoundPath =
|
|
4975
|
-
if (
|
|
5310
|
+
const compoundPath = join30(compoundDir, `${slug}.md`);
|
|
5311
|
+
if (existsSync9(compoundPath)) {
|
|
4976
5312
|
skipped.push(entry.date);
|
|
4977
5313
|
continue;
|
|
4978
5314
|
}
|
|
@@ -5010,7 +5346,7 @@ async function runCompound(input) {
|
|
|
5010
5346
|
].join("\n");
|
|
5011
5347
|
const content = frontmatter + "\n" + body;
|
|
5012
5348
|
if (!input.dryRun) {
|
|
5013
|
-
if (!
|
|
5349
|
+
if (!existsSync9(compoundDir)) {
|
|
5014
5350
|
await mkdir10(compoundDir, { recursive: true });
|
|
5015
5351
|
}
|
|
5016
5352
|
await writeFile11(compoundPath, content, "utf8");
|
|
@@ -5032,16 +5368,16 @@ async function runCompound(input) {
|
|
|
5032
5368
|
};
|
|
5033
5369
|
}
|
|
5034
5370
|
async function runCompoundDelete(input) {
|
|
5035
|
-
const projectDir =
|
|
5036
|
-
if (!
|
|
5371
|
+
const projectDir = join30(input.vault, "projects", input.project);
|
|
5372
|
+
if (!existsSync9(projectDir)) {
|
|
5037
5373
|
return {
|
|
5038
5374
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
5039
5375
|
result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
|
|
5040
5376
|
};
|
|
5041
5377
|
}
|
|
5042
5378
|
const entryName = input.entry.replace(/\.md$/, "");
|
|
5043
|
-
const compoundPath =
|
|
5044
|
-
if (!
|
|
5379
|
+
const compoundPath = join30(projectDir, "compound", `${entryName}.md`);
|
|
5380
|
+
if (!existsSync9(compoundPath)) {
|
|
5045
5381
|
return {
|
|
5046
5382
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
5047
5383
|
result: err("FILE_NOT_FOUND", { path: compoundPath })
|
|
@@ -5074,8 +5410,8 @@ knowledge.md regenerated`
|
|
|
5074
5410
|
};
|
|
5075
5411
|
}
|
|
5076
5412
|
async function runCompoundList(input) {
|
|
5077
|
-
const compoundDir =
|
|
5078
|
-
if (!
|
|
5413
|
+
const compoundDir = join30(input.vault, "projects", input.project, "compound");
|
|
5414
|
+
if (!existsSync9(compoundDir)) {
|
|
5079
5415
|
return {
|
|
5080
5416
|
exitCode: ExitCode.OK,
|
|
5081
5417
|
result: ok({
|
|
@@ -5105,10 +5441,10 @@ could not read compound directory`
|
|
|
5105
5441
|
const entries = [];
|
|
5106
5442
|
for (const dirent of dirents) {
|
|
5107
5443
|
if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
|
|
5108
|
-
const filePath =
|
|
5444
|
+
const filePath = join30(compoundDir, dirent.name);
|
|
5109
5445
|
let text;
|
|
5110
5446
|
try {
|
|
5111
|
-
text = await
|
|
5447
|
+
text = await readFile21(filePath, "utf8");
|
|
5112
5448
|
} catch {
|
|
5113
5449
|
continue;
|
|
5114
5450
|
}
|
|
@@ -5138,8 +5474,8 @@ no compound entries found`;
|
|
|
5138
5474
|
|
|
5139
5475
|
// src/commands/observe.ts
|
|
5140
5476
|
import { mkdir as mkdir11, writeFile as writeFile12 } from "fs/promises";
|
|
5141
|
-
import { existsSync as
|
|
5142
|
-
import { join as
|
|
5477
|
+
import { existsSync as existsSync10, statSync as statSync3 } from "fs";
|
|
5478
|
+
import { join as join31 } from "path";
|
|
5143
5479
|
import { createHash as createHash4 } from "crypto";
|
|
5144
5480
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
5145
5481
|
function slugify2(text) {
|
|
@@ -5162,13 +5498,13 @@ async function runObserve(input) {
|
|
|
5162
5498
|
result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
|
|
5163
5499
|
};
|
|
5164
5500
|
}
|
|
5165
|
-
if (!
|
|
5501
|
+
if (!existsSync10(input.vault) || !statSync3(input.vault).isDirectory()) {
|
|
5166
5502
|
return {
|
|
5167
5503
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5168
5504
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
5169
5505
|
};
|
|
5170
5506
|
}
|
|
5171
|
-
const transcriptsDir =
|
|
5507
|
+
const transcriptsDir = join31(input.vault, "raw", "transcripts");
|
|
5172
5508
|
try {
|
|
5173
5509
|
await mkdir11(transcriptsDir, { recursive: true });
|
|
5174
5510
|
} catch {
|
|
@@ -5180,7 +5516,7 @@ async function runObserve(input) {
|
|
|
5180
5516
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5181
5517
|
const slug = slugify2(input.text);
|
|
5182
5518
|
const fileName = `${today}-observation-${slug}.md`;
|
|
5183
|
-
const filePath =
|
|
5519
|
+
const filePath = join31(transcriptsDir, fileName);
|
|
5184
5520
|
const body = `
|
|
5185
5521
|
${input.text.trim()}
|
|
5186
5522
|
`;
|
|
@@ -5220,8 +5556,8 @@ ${input.text.trim()}
|
|
|
5220
5556
|
}
|
|
5221
5557
|
|
|
5222
5558
|
// src/commands/ingest.ts
|
|
5223
|
-
import { readFile as
|
|
5224
|
-
import { join as
|
|
5559
|
+
import { readFile as readFile22, writeFile as writeFile13, mkdir as mkdir12 } from "fs/promises";
|
|
5560
|
+
import { join as join32 } from "path";
|
|
5225
5561
|
import { createHash as createHash5 } from "crypto";
|
|
5226
5562
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
5227
5563
|
var TYPE_DIR = {
|
|
@@ -5380,7 +5716,7 @@ async function runIngest(input) {
|
|
|
5380
5716
|
sourceContent = fetchResult.data.body;
|
|
5381
5717
|
} else {
|
|
5382
5718
|
try {
|
|
5383
|
-
sourceContent = await
|
|
5719
|
+
sourceContent = await readFile22(input.source, "utf8");
|
|
5384
5720
|
} catch {
|
|
5385
5721
|
return {
|
|
5386
5722
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -5395,8 +5731,8 @@ async function runIngest(input) {
|
|
|
5395
5731
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
5396
5732
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
5397
5733
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
5398
|
-
const rawAbsPath =
|
|
5399
|
-
const typedAbsPath =
|
|
5734
|
+
const rawAbsPath = join32(input.vault, rawRelPath);
|
|
5735
|
+
const typedAbsPath = join32(input.vault, typedRelPath);
|
|
5400
5736
|
const rawContent = buildRawContent(sourceUrl, today, sha256, sourceContent);
|
|
5401
5737
|
const typedContent = buildTypedContent(
|
|
5402
5738
|
input.title,
|
|
@@ -5459,7 +5795,7 @@ async function runIngest(input) {
|
|
|
5459
5795
|
};
|
|
5460
5796
|
}
|
|
5461
5797
|
try {
|
|
5462
|
-
await mkdir12(
|
|
5798
|
+
await mkdir12(join32(input.vault, "raw", "articles"), { recursive: true });
|
|
5463
5799
|
await writeFile13(rawAbsPath, rawContent, "utf8");
|
|
5464
5800
|
} catch (e) {
|
|
5465
5801
|
return {
|
|
@@ -5468,7 +5804,7 @@ async function runIngest(input) {
|
|
|
5468
5804
|
};
|
|
5469
5805
|
}
|
|
5470
5806
|
try {
|
|
5471
|
-
await mkdir12(
|
|
5807
|
+
await mkdir12(join32(input.vault, typedDir), { recursive: true });
|
|
5472
5808
|
await writeFile13(typedAbsPath, typedContent, "utf8");
|
|
5473
5809
|
} catch (e) {
|
|
5474
5810
|
return {
|
|
@@ -5647,11 +5983,11 @@ ${body}`;
|
|
|
5647
5983
|
}
|
|
5648
5984
|
|
|
5649
5985
|
// src/commands/sync.ts
|
|
5650
|
-
import { existsSync as
|
|
5651
|
-
import { join as
|
|
5986
|
+
import { existsSync as existsSync11 } from "fs";
|
|
5987
|
+
import { join as join33 } from "path";
|
|
5652
5988
|
function runSyncStatus(input) {
|
|
5653
5989
|
const vault = input.vault;
|
|
5654
|
-
if (!
|
|
5990
|
+
if (!existsSync11(join33(vault, ".git"))) {
|
|
5655
5991
|
return {
|
|
5656
5992
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5657
5993
|
result: ok({
|
|
@@ -5720,7 +6056,7 @@ function runSyncStatus(input) {
|
|
|
5720
6056
|
}
|
|
5721
6057
|
async function runSyncPush(input) {
|
|
5722
6058
|
const vault = input.vault;
|
|
5723
|
-
if (!
|
|
6059
|
+
if (!existsSync11(join33(vault, ".git"))) {
|
|
5724
6060
|
return {
|
|
5725
6061
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5726
6062
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -5805,7 +6141,7 @@ async function runSyncPush(input) {
|
|
|
5805
6141
|
}
|
|
5806
6142
|
async function runSyncPull(input) {
|
|
5807
6143
|
const vault = input.vault;
|
|
5808
|
-
if (!
|
|
6144
|
+
if (!existsSync11(join33(vault, ".git"))) {
|
|
5809
6145
|
return {
|
|
5810
6146
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5811
6147
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -5880,8 +6216,8 @@ async function runSyncPull(input) {
|
|
|
5880
6216
|
}
|
|
5881
6217
|
|
|
5882
6218
|
// src/commands/backup.ts
|
|
5883
|
-
import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as readFileSync9, mkdirSync as mkdirSync3, writeFileSync as
|
|
5884
|
-
import { join as
|
|
6219
|
+
import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as readFileSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
6220
|
+
import { join as join34, relative as relative3, dirname as dirname11 } from "path";
|
|
5885
6221
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
5886
6222
|
|
|
5887
6223
|
// src/utils/s3-client.ts
|
|
@@ -5905,7 +6241,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
|
|
|
5905
6241
|
function* walkMarkdown(dir, base) {
|
|
5906
6242
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
5907
6243
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
5908
|
-
const full =
|
|
6244
|
+
const full = join34(dir, entry.name);
|
|
5909
6245
|
if (entry.isDirectory()) {
|
|
5910
6246
|
yield* walkMarkdown(full, base);
|
|
5911
6247
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -5928,7 +6264,7 @@ async function runBackupSync(input) {
|
|
|
5928
6264
|
let failed = 0;
|
|
5929
6265
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
5930
6266
|
for (const relPath of files) {
|
|
5931
|
-
const absPath =
|
|
6267
|
+
const absPath = join34(input.vault, relPath);
|
|
5932
6268
|
const localStat = statSync4(absPath);
|
|
5933
6269
|
let needsUpload = true;
|
|
5934
6270
|
try {
|
|
@@ -6004,7 +6340,7 @@ async function runBackupRestore(input) {
|
|
|
6004
6340
|
const objects = list.Contents ?? [];
|
|
6005
6341
|
for (const obj of objects) {
|
|
6006
6342
|
if (!obj.Key) continue;
|
|
6007
|
-
const localPath =
|
|
6343
|
+
const localPath = join34(target, obj.Key);
|
|
6008
6344
|
try {
|
|
6009
6345
|
const localStat = statSync4(localPath);
|
|
6010
6346
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
@@ -6018,7 +6354,7 @@ async function runBackupRestore(input) {
|
|
|
6018
6354
|
const body = await resp.Body?.transformToByteArray();
|
|
6019
6355
|
if (body) {
|
|
6020
6356
|
mkdirSync3(dirname11(localPath), { recursive: true });
|
|
6021
|
-
|
|
6357
|
+
writeFileSync5(localPath, Buffer.from(body));
|
|
6022
6358
|
downloaded++;
|
|
6023
6359
|
}
|
|
6024
6360
|
} catch {
|
|
@@ -6050,11 +6386,11 @@ async function runBackupRestore(input) {
|
|
|
6050
6386
|
}
|
|
6051
6387
|
|
|
6052
6388
|
// src/commands/status.ts
|
|
6053
|
-
import { existsSync as
|
|
6054
|
-
import { readFile as
|
|
6055
|
-
import { join as
|
|
6389
|
+
import { existsSync as existsSync12, statSync as statSync5 } from "fs";
|
|
6390
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
6391
|
+
import { join as join35 } from "path";
|
|
6056
6392
|
async function runStatus(input) {
|
|
6057
|
-
if (!
|
|
6393
|
+
if (!existsSync12(input.vault)) {
|
|
6058
6394
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
6059
6395
|
}
|
|
6060
6396
|
const scan = await scanVault(input.vault);
|
|
@@ -6079,7 +6415,7 @@ async function runStatus(input) {
|
|
|
6079
6415
|
const compound = scan.data.compound.length;
|
|
6080
6416
|
let schemaVersion = "v1";
|
|
6081
6417
|
try {
|
|
6082
|
-
const schemaContent = await
|
|
6418
|
+
const schemaContent = await readFile23(join35(input.vault, "SCHEMA.md"), "utf8");
|
|
6083
6419
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
6084
6420
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
6085
6421
|
} catch {
|
|
@@ -6140,7 +6476,7 @@ async function runStatus(input) {
|
|
|
6140
6476
|
|
|
6141
6477
|
// src/commands/seed.ts
|
|
6142
6478
|
import { mkdir as mkdir13, writeFile as writeFile14, stat as stat7 } from "fs/promises";
|
|
6143
|
-
import { join as
|
|
6479
|
+
import { join as join36 } from "path";
|
|
6144
6480
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6145
6481
|
var EXAMPLE_PAGES = {
|
|
6146
6482
|
"entities/example-project.md": `---
|
|
@@ -6209,29 +6545,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
6209
6545
|
`;
|
|
6210
6546
|
async function runSeed(input) {
|
|
6211
6547
|
try {
|
|
6212
|
-
await stat7(
|
|
6548
|
+
await stat7(join36(input.vault, "SCHEMA.md"));
|
|
6213
6549
|
} catch {
|
|
6214
6550
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
6215
6551
|
}
|
|
6216
6552
|
const created = [];
|
|
6217
6553
|
const skipped = [];
|
|
6218
6554
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
6219
|
-
const absPath =
|
|
6555
|
+
const absPath = join36(input.vault, relPath);
|
|
6220
6556
|
try {
|
|
6221
6557
|
await stat7(absPath);
|
|
6222
6558
|
skipped.push(relPath);
|
|
6223
6559
|
} catch {
|
|
6224
|
-
await mkdir13(
|
|
6560
|
+
await mkdir13(join36(absPath, ".."), { recursive: true });
|
|
6225
6561
|
await writeFile14(absPath, content, "utf8");
|
|
6226
6562
|
created.push(relPath);
|
|
6227
6563
|
}
|
|
6228
6564
|
}
|
|
6229
|
-
const rawPath =
|
|
6565
|
+
const rawPath = join36(input.vault, "raw", "articles", "example-source.md");
|
|
6230
6566
|
try {
|
|
6231
6567
|
await stat7(rawPath);
|
|
6232
6568
|
skipped.push("raw/articles/example-source.md");
|
|
6233
6569
|
} catch {
|
|
6234
|
-
await mkdir13(
|
|
6570
|
+
await mkdir13(join36(rawPath, ".."), { recursive: true });
|
|
6235
6571
|
await writeFile14(rawPath, EXAMPLE_RAW, "utf8");
|
|
6236
6572
|
created.push("raw/articles/example-source.md");
|
|
6237
6573
|
}
|
|
@@ -6254,9 +6590,9 @@ async function runSeed(input) {
|
|
|
6254
6590
|
}
|
|
6255
6591
|
|
|
6256
6592
|
// src/commands/canvas.ts
|
|
6257
|
-
import { readFile as
|
|
6258
|
-
import { existsSync as
|
|
6259
|
-
import { join as
|
|
6593
|
+
import { readFile as readFile24, writeFile as writeFile15 } from "fs/promises";
|
|
6594
|
+
import { existsSync as existsSync13 } from "fs";
|
|
6595
|
+
import { join as join37 } from "path";
|
|
6260
6596
|
var NODE_WIDTH = 240;
|
|
6261
6597
|
var NODE_HEIGHT = 60;
|
|
6262
6598
|
var COLUMN_SPACING = 400;
|
|
@@ -6334,8 +6670,8 @@ function buildCanvasEdges(adjacency) {
|
|
|
6334
6670
|
return edges;
|
|
6335
6671
|
}
|
|
6336
6672
|
async function runCanvasGenerate(input) {
|
|
6337
|
-
const graphPath = input.graphPath ??
|
|
6338
|
-
if (!
|
|
6673
|
+
const graphPath = input.graphPath ?? join37(input.vault, ".skillwiki", "graph.json");
|
|
6674
|
+
if (!existsSync13(graphPath)) {
|
|
6339
6675
|
return {
|
|
6340
6676
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
6341
6677
|
result: err("FILE_NOT_FOUND", {
|
|
@@ -6346,7 +6682,7 @@ async function runCanvasGenerate(input) {
|
|
|
6346
6682
|
}
|
|
6347
6683
|
let raw;
|
|
6348
6684
|
try {
|
|
6349
|
-
raw = await
|
|
6685
|
+
raw = await readFile24(graphPath, "utf8");
|
|
6350
6686
|
} catch (e) {
|
|
6351
6687
|
return {
|
|
6352
6688
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -6372,7 +6708,7 @@ async function runCanvasGenerate(input) {
|
|
|
6372
6708
|
const nodes = buildCanvasNodes(paths);
|
|
6373
6709
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
6374
6710
|
const canvas = { nodes, edges };
|
|
6375
|
-
const outPath =
|
|
6711
|
+
const outPath = join37(input.vault, "vault-graph.canvas");
|
|
6376
6712
|
try {
|
|
6377
6713
|
await writeFile15(outPath, JSON.stringify(canvas, null, 2));
|
|
6378
6714
|
} catch (e) {
|
|
@@ -6394,8 +6730,8 @@ written: ${outPath}`
|
|
|
6394
6730
|
}
|
|
6395
6731
|
|
|
6396
6732
|
// src/commands/query.ts
|
|
6397
|
-
import { readFile as
|
|
6398
|
-
import { join as
|
|
6733
|
+
import { readFile as readFile25, stat as stat8 } from "fs/promises";
|
|
6734
|
+
import { join as join38 } from "path";
|
|
6399
6735
|
var W_KEYWORD = 2;
|
|
6400
6736
|
var W_SOURCE_OVERLAP = 4;
|
|
6401
6737
|
var W_WIKILINK = 3;
|
|
@@ -6516,7 +6852,7 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
6516
6852
|
return score;
|
|
6517
6853
|
}
|
|
6518
6854
|
async function loadOrBuildGraph(vault) {
|
|
6519
|
-
const graphPath =
|
|
6855
|
+
const graphPath = join38(vault, ".skillwiki", "graph.json");
|
|
6520
6856
|
let needsBuild = false;
|
|
6521
6857
|
try {
|
|
6522
6858
|
const fileStat = await stat8(graphPath);
|
|
@@ -6530,7 +6866,7 @@ async function loadOrBuildGraph(vault) {
|
|
|
6530
6866
|
if (buildResult.exitCode !== 0) return null;
|
|
6531
6867
|
}
|
|
6532
6868
|
try {
|
|
6533
|
-
const raw = await
|
|
6869
|
+
const raw = await readFile25(graphPath, "utf8");
|
|
6534
6870
|
return JSON.parse(raw);
|
|
6535
6871
|
} catch {
|
|
6536
6872
|
return null;
|
|
@@ -6538,14 +6874,14 @@ async function loadOrBuildGraph(vault) {
|
|
|
6538
6874
|
}
|
|
6539
6875
|
|
|
6540
6876
|
// src/utils/auto-commit.ts
|
|
6541
|
-
import { existsSync as
|
|
6542
|
-
import { join as
|
|
6877
|
+
import { existsSync as existsSync14 } from "fs";
|
|
6878
|
+
import { join as join39 } from "path";
|
|
6543
6879
|
async function postCommit(vault, exitCode) {
|
|
6544
6880
|
if (exitCode !== 0) return;
|
|
6545
6881
|
const home = process.env.HOME ?? "";
|
|
6546
6882
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
6547
6883
|
if (dotenv["AUTO_COMMIT"] === "false") return;
|
|
6548
|
-
if (!
|
|
6884
|
+
if (!existsSync14(join39(vault, ".git"))) return;
|
|
6549
6885
|
const lastOps = readLastOp(vault);
|
|
6550
6886
|
if (lastOps.length === 0) return;
|
|
6551
6887
|
const porcelain = git(vault, ["status", "--porcelain"]);
|
|
@@ -6596,7 +6932,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
6596
6932
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
6597
6933
|
});
|
|
6598
6934
|
program.command("graph").description("graph subcommands").command("build <vault>").option("--out <path>", "graph output path (default: <vault>/.skillwiki/graph.json)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
6599
|
-
const out = opts.out ??
|
|
6935
|
+
const out = opts.out ?? join40(vault, ".skillwiki", "graph.json");
|
|
6600
6936
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
6601
6937
|
});
|
|
6602
6938
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|