claude-nomad 0.63.2 → 0.64.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -0
- package/README.md +43 -29
- package/dist/nomad.mjs +869 -306
- package/package.json +1 -1
package/dist/nomad.mjs
CHANGED
|
@@ -42,11 +42,21 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
42
42
|
));
|
|
43
43
|
|
|
44
44
|
// src/config.never-sync.ts
|
|
45
|
+
function stripTrailingDotsAndWhitespace(name) {
|
|
46
|
+
let end = name.length;
|
|
47
|
+
while (end > 0 && (name[end - 1] === "." || /\s/.test(name[end - 1]))) end -= 1;
|
|
48
|
+
return name.slice(0, end);
|
|
49
|
+
}
|
|
45
50
|
function isSecretFileName(name) {
|
|
46
|
-
|
|
51
|
+
const stripped = stripTrailingDotsAndWhitespace(name);
|
|
52
|
+
return SECRET_FILE_PATTERNS.some((re) => re.test(stripped));
|
|
47
53
|
}
|
|
48
54
|
function isDeniedName(blockSet, name) {
|
|
49
|
-
|
|
55
|
+
const stripped = stripTrailingDotsAndWhitespace(name);
|
|
56
|
+
return blockSet.has(name) || blockSet.has(name.toLowerCase()) || blockSet.has(stripped) || blockSet.has(stripped.toLowerCase()) || isSecretFileName(name);
|
|
57
|
+
}
|
|
58
|
+
function isClaudeExtraName(name) {
|
|
59
|
+
return stripTrailingDotsAndWhitespace(name).toLowerCase() === ".claude";
|
|
50
60
|
}
|
|
51
61
|
var NEVER_SYNC, CLAUDE_EXTRA_NEVER_SYNC, SECRET_FILE_PATTERNS;
|
|
52
62
|
var init_config_never_sync = __esm({
|
|
@@ -302,14 +312,67 @@ function assertSafeLogical(logical) {
|
|
|
302
312
|
);
|
|
303
313
|
}
|
|
304
314
|
}
|
|
315
|
+
function validateSharedDirEntry(entry) {
|
|
316
|
+
if (typeof entry !== "string") {
|
|
317
|
+
return { reason: "not-a-string", message: "not a string" };
|
|
318
|
+
}
|
|
319
|
+
if (!SAFE_SEGMENT.test(entry) || entry === "." || entry === "..") {
|
|
320
|
+
return {
|
|
321
|
+
reason: "not-a-segment",
|
|
322
|
+
message: 'not a single path segment (contains a path separator, an unsupported character, "." or "..")'
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const addressed = stripTrailingDots(entry);
|
|
326
|
+
const named = classifyDeniedName(addressed);
|
|
327
|
+
if (named !== null) {
|
|
328
|
+
return addressed === entry ? named : {
|
|
329
|
+
reason: named.reason,
|
|
330
|
+
message: `a trailing-dot name shadowing ${named.message}`
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (entry.endsWith(".")) {
|
|
334
|
+
return {
|
|
335
|
+
reason: "win32-alias",
|
|
336
|
+
message: 'a trailing-dot name (git for Windows refuses to create or check out a path ending in ".")'
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
function stripTrailingDots(name) {
|
|
342
|
+
let end = name.length;
|
|
343
|
+
while (end > 0 && name[end - 1] === ".") end -= 1;
|
|
344
|
+
return name.slice(0, end);
|
|
345
|
+
}
|
|
346
|
+
function classifyDeniedName(name) {
|
|
347
|
+
const folded = name.toLowerCase();
|
|
348
|
+
if (NEVER_SYNC.has(name) || NEVER_SYNC.has(folded)) {
|
|
349
|
+
return { reason: "never-sync", message: "a never-sync name" };
|
|
350
|
+
}
|
|
351
|
+
if (RESERVED_SHARED.has(name) || RESERVED_SHARED_FOLDED.has(folded)) {
|
|
352
|
+
return { reason: "reserved", message: "a reserved shared/ name" };
|
|
353
|
+
}
|
|
354
|
+
const dot = folded.indexOf(".");
|
|
355
|
+
const stem = dot === -1 ? folded : folded.slice(0, dot);
|
|
356
|
+
if (WIN32_DEVICE_NAMES.has(stem)) {
|
|
357
|
+
return { reason: "reserved", message: "a reserved Windows device name" };
|
|
358
|
+
}
|
|
359
|
+
if (isSecretFileName(name)) {
|
|
360
|
+
return {
|
|
361
|
+
reason: "secret-shaped",
|
|
362
|
+
message: "a credential-shaped filename (.env, id_rsa, credentials, *.pem, *.key and similar)"
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
function mayJoinRefusedEntry(entry, reason, remediable) {
|
|
368
|
+
if (UNJOINABLE_REASONS.has(reason)) return false;
|
|
369
|
+
if (entry.endsWith(".")) return true;
|
|
370
|
+
return remediable.has(reason);
|
|
371
|
+
}
|
|
305
372
|
function isValidSharedDir(entry) {
|
|
306
|
-
|
|
307
|
-
if (!SAFE_SEGMENT.test(entry) || entry === "." || entry === "..") return false;
|
|
308
|
-
if (NEVER_SYNC.has(entry)) return false;
|
|
309
|
-
if (RESERVED_SHARED.has(entry)) return false;
|
|
310
|
-
return true;
|
|
373
|
+
return validateSharedDirEntry(entry) === null;
|
|
311
374
|
}
|
|
312
|
-
var SAFE_LOGICAL, SAFE_SEGMENT, RESERVED_SHARED;
|
|
375
|
+
var SAFE_LOGICAL, SAFE_SEGMENT, RESERVED_SHARED, RESERVED_SHARED_FOLDED, WIN32_DEVICE_NAMES, UNJOINABLE_REASONS;
|
|
313
376
|
var init_config_sharedDirs_guard = __esm({
|
|
314
377
|
"src/config.sharedDirs.guard.ts"() {
|
|
315
378
|
"use strict";
|
|
@@ -331,6 +394,19 @@ var init_config_sharedDirs_guard = __esm({
|
|
|
331
394
|
"extras",
|
|
332
395
|
"projects"
|
|
333
396
|
]);
|
|
397
|
+
RESERVED_SHARED_FOLDED = new Set([...RESERVED_SHARED].map((name) => name.toLowerCase()));
|
|
398
|
+
WIN32_DEVICE_NAMES = /* @__PURE__ */ new Set([
|
|
399
|
+
"con",
|
|
400
|
+
"prn",
|
|
401
|
+
"aux",
|
|
402
|
+
"nul",
|
|
403
|
+
...Array.from({ length: 9 }, (_unused, i) => `com${i + 1}`),
|
|
404
|
+
...Array.from({ length: 9 }, (_unused, i) => `lpt${i + 1}`)
|
|
405
|
+
]);
|
|
406
|
+
UNJOINABLE_REASONS = /* @__PURE__ */ new Set([
|
|
407
|
+
"not-a-string",
|
|
408
|
+
"not-a-segment"
|
|
409
|
+
]);
|
|
334
410
|
}
|
|
335
411
|
});
|
|
336
412
|
|
|
@@ -421,7 +497,6 @@ var init_settings_keys = __esm({
|
|
|
421
497
|
"includeGitInstructions",
|
|
422
498
|
"inputNeededNotifEnabled",
|
|
423
499
|
"language",
|
|
424
|
-
"leftArrowOpensAgents",
|
|
425
500
|
"managedMcpServers",
|
|
426
501
|
"minimumVersion",
|
|
427
502
|
"model",
|
|
@@ -539,19 +614,31 @@ function crashDir() {
|
|
|
539
614
|
function manifestPath() {
|
|
540
615
|
return join(home(), ".cache", "claude-nomad", `push-manifest-${encodeURIComponent(HOST)}.json`);
|
|
541
616
|
}
|
|
542
|
-
function
|
|
617
|
+
function sharedBaselinePath() {
|
|
618
|
+
return join(home(), ".cache", "claude-nomad", `shared-baseline-${encodeURIComponent(HOST)}.json`);
|
|
619
|
+
}
|
|
620
|
+
function allSharedLinks(map, opts = {}) {
|
|
621
|
+
const emit = opts.quiet === true ? () => void 0 : warn;
|
|
622
|
+
const raw = map.sharedDirs;
|
|
623
|
+
if (raw !== void 0 && !Array.isArray(raw)) {
|
|
624
|
+
emit("sharedDirs in path-map.json is not an array; ignoring the whole field");
|
|
625
|
+
return [...SHARED_LINKS];
|
|
626
|
+
}
|
|
543
627
|
const extras = [];
|
|
544
|
-
for (const entry of
|
|
545
|
-
|
|
628
|
+
for (const entry of raw ?? []) {
|
|
629
|
+
const rejection = validateSharedDirEntry(entry);
|
|
630
|
+
if (rejection === null) {
|
|
546
631
|
extras.push(entry);
|
|
547
632
|
} else {
|
|
548
|
-
|
|
549
|
-
`sharedDirs entry ${JSON.stringify(entry)} is invalid (path separator, reserved name, or NEVER_SYNC); skipping`
|
|
550
|
-
);
|
|
633
|
+
emit(`sharedDirs entry ${JSON.stringify(entry)} rejected: ${rejection.message}; skipping`);
|
|
551
634
|
}
|
|
552
635
|
}
|
|
553
636
|
return [...SHARED_LINKS, ...extras];
|
|
554
637
|
}
|
|
638
|
+
function sharedDirEntries(map) {
|
|
639
|
+
const raw = map.sharedDirs;
|
|
640
|
+
return Array.isArray(raw) ? raw : [];
|
|
641
|
+
}
|
|
555
642
|
var SETTINGS_SCHEMA_URL, NPM_REGISTRY_LATEST_URL, GITLEAKS_PINNED_VERSION, GITLEAKS_SCAN_TIMEOUT_MS, HOST, SHARED_LINKS, GSD_PREFIX, GSD_DROPPED_NAMES, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, PUSH_ALLOWED_STATIC;
|
|
556
643
|
var init_config = __esm({
|
|
557
644
|
"src/config.ts"() {
|
|
@@ -1420,6 +1507,7 @@ var init_push_leak_verdict = __esm({
|
|
|
1420
1507
|
// src/commands.adopt.ts
|
|
1421
1508
|
init_config();
|
|
1422
1509
|
init_config_sharedDirs_guard();
|
|
1510
|
+
init_exit_codes();
|
|
1423
1511
|
import { cpSync as cpSync4, existsSync as existsSync5, lstatSync as lstatSync5, rmSync as rmSync4 } from "node:fs";
|
|
1424
1512
|
import { join as join6 } from "node:path";
|
|
1425
1513
|
|
|
@@ -1890,7 +1978,7 @@ function copyExtrasFileSkipDiverged(src, dst) {
|
|
|
1890
1978
|
copyExtras(src, dst);
|
|
1891
1979
|
}
|
|
1892
1980
|
function extrasDenySet(dirname14) {
|
|
1893
|
-
return dirname14
|
|
1981
|
+
return isClaudeExtraName(dirname14) ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
1894
1982
|
}
|
|
1895
1983
|
function copyExtrasFiltered(src, dst, blockSet) {
|
|
1896
1984
|
rmSync2(dst, { recursive: true, force: true });
|
|
@@ -2131,7 +2219,7 @@ function readMapIfPresent(repoHome2) {
|
|
|
2131
2219
|
return existsSync5(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
2132
2220
|
}
|
|
2133
2221
|
function isConfiguredTarget(name, map) {
|
|
2134
|
-
return SHARED_LINKS.includes(name) || (map.
|
|
2222
|
+
return SHARED_LINKS.includes(name) || sharedDirEntries(map).includes(name);
|
|
2135
2223
|
}
|
|
2136
2224
|
function isValidAdoptName(name) {
|
|
2137
2225
|
if (SHARED_LINKS.includes(name)) return true;
|
|
@@ -2153,6 +2241,13 @@ function performAdoptMove(name, linkPath, sharedTarget, repo, backup) {
|
|
|
2153
2241
|
}
|
|
2154
2242
|
function cmdAdopt(name, opts = {}) {
|
|
2155
2243
|
const dryRun = opts.dryRun === true;
|
|
2244
|
+
const rejection = validateSharedDirEntry(name);
|
|
2245
|
+
if (rejection !== null && rejection.reason === "secret-shaped") {
|
|
2246
|
+
throw new NomadFatal(
|
|
2247
|
+
`cannot adopt ${JSON.stringify(name)}: ${rejection.message}. If it is listed in sharedDirs in path-map.json, remove it there too.`,
|
|
2248
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2156
2251
|
if (!isValidAdoptName(name)) {
|
|
2157
2252
|
fail(`invalid name: ${JSON.stringify(name)}`);
|
|
2158
2253
|
process.exit(1);
|
|
@@ -2566,6 +2661,7 @@ function cmdClean(opts, backupBase2 = backupBase()) {
|
|
|
2566
2661
|
|
|
2567
2662
|
// src/commands.eject.ts
|
|
2568
2663
|
init_config();
|
|
2664
|
+
init_config_sharedDirs_guard();
|
|
2569
2665
|
init_utils();
|
|
2570
2666
|
init_utils_fs();
|
|
2571
2667
|
init_utils_json();
|
|
@@ -2703,6 +2799,19 @@ function materializeOneOrDie(name, linkPath, sharedRoot, done) {
|
|
|
2703
2799
|
);
|
|
2704
2800
|
}
|
|
2705
2801
|
}
|
|
2802
|
+
var WIDENED_REASONS = /* @__PURE__ */ new Set([
|
|
2803
|
+
"never-sync",
|
|
2804
|
+
"reserved",
|
|
2805
|
+
"secret-shaped"
|
|
2806
|
+
]);
|
|
2807
|
+
function ejectNames(map) {
|
|
2808
|
+
const alreadyMaterialized = sharedDirEntries(map).filter((entry) => {
|
|
2809
|
+
if (typeof entry !== "string") return false;
|
|
2810
|
+
const rejection = validateSharedDirEntry(entry);
|
|
2811
|
+
return rejection === null || mayJoinRefusedEntry(entry, rejection.reason, WIDENED_REASONS);
|
|
2812
|
+
});
|
|
2813
|
+
return [.../* @__PURE__ */ new Set([...allSharedLinks(map), ...alreadyMaterialized])];
|
|
2814
|
+
}
|
|
2706
2815
|
function defaultEjectRoots() {
|
|
2707
2816
|
return { claudeHome: claudeHome(), repoHome: repoHome() };
|
|
2708
2817
|
}
|
|
@@ -2710,16 +2819,31 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2710
2819
|
const dryRun = opts.dryRun === true;
|
|
2711
2820
|
const { claudeHome: claudeHome2, repoHome: repoHome2 } = roots;
|
|
2712
2821
|
const map = readMapIfPresent2(repoHome2);
|
|
2713
|
-
const names =
|
|
2822
|
+
const names = ejectNames(map);
|
|
2714
2823
|
const classifications = /* @__PURE__ */ new Map();
|
|
2715
2824
|
for (const name of names) {
|
|
2716
2825
|
classifications.set(name, classifyName(join11(claudeHome2, name)));
|
|
2717
2826
|
}
|
|
2827
|
+
const base = new Set(allSharedLinks(map, { quiet: true }));
|
|
2828
|
+
for (const name of names) {
|
|
2829
|
+
if (base.has(name)) continue;
|
|
2830
|
+
if (classifications.get(name) !== "materialize") continue;
|
|
2831
|
+
item(`processing rejected entry already present on this host: ${name}`);
|
|
2832
|
+
}
|
|
2718
2833
|
const dangling = names.filter((n) => classifications.get(n) === "dangling");
|
|
2719
|
-
|
|
2834
|
+
const danglingBase = dangling.filter((n) => base.has(n));
|
|
2835
|
+
const danglingRefused = dangling.filter((n) => !base.has(n));
|
|
2836
|
+
if (danglingBase.length > 0) {
|
|
2837
|
+
fail(
|
|
2838
|
+
`dangling symlink(s): ${danglingBase.join(", ")}. run \`nomad pull\` first to restore the missing target, then re-run \`nomad eject\``
|
|
2839
|
+
);
|
|
2840
|
+
}
|
|
2841
|
+
if (danglingRefused.length > 0) {
|
|
2720
2842
|
fail(
|
|
2721
|
-
`dangling symlink(s): ${
|
|
2843
|
+
`dangling symlink(s) for a refused name nomad cannot restore: ${danglingRefused.join(", ")}. recover the content by hand if you need it, then remove the dead link and re-run \`nomad eject\``
|
|
2722
2844
|
);
|
|
2845
|
+
}
|
|
2846
|
+
if (dangling.length > 0) {
|
|
2723
2847
|
process.exit(1);
|
|
2724
2848
|
}
|
|
2725
2849
|
const sharedRoot = resolveSharedRoot(repoHome2);
|
|
@@ -2743,7 +2867,7 @@ import { join as join45 } from "node:path";
|
|
|
2743
2867
|
init_color();
|
|
2744
2868
|
init_config();
|
|
2745
2869
|
import { existsSync as existsSync11, lstatSync as lstatSync8, readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
|
|
2746
|
-
import { join as join13 } from "node:path";
|
|
2870
|
+
import { basename as basename3, join as join13 } from "node:path";
|
|
2747
2871
|
|
|
2748
2872
|
// src/commands.doctor.format.ts
|
|
2749
2873
|
init_color();
|
|
@@ -2818,6 +2942,61 @@ function readJsonSafe(path, label, section2) {
|
|
|
2818
2942
|
}
|
|
2819
2943
|
}
|
|
2820
2944
|
|
|
2945
|
+
// src/extras-sync.diff.ts
|
|
2946
|
+
init_utils();
|
|
2947
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2948
|
+
import { relative as relative3 } from "node:path";
|
|
2949
|
+
var NAME_STATUS_DIFF_TIMEOUT_MS = 3e3;
|
|
2950
|
+
function parseNameStatus(stdout) {
|
|
2951
|
+
const fields = stdout.split("\0").filter((f) => f.length > 0);
|
|
2952
|
+
const entries = [];
|
|
2953
|
+
for (let i = 0; i + 1 < fields.length; i += 2) {
|
|
2954
|
+
entries.push({ status: fields[i], path: fields[i + 1] });
|
|
2955
|
+
}
|
|
2956
|
+
return entries;
|
|
2957
|
+
}
|
|
2958
|
+
function labelEntry(entry) {
|
|
2959
|
+
if (entry.status === "D") return `${entry.path} (local only)`;
|
|
2960
|
+
if (entry.status === "A") return `${entry.path} (repo only)`;
|
|
2961
|
+
return entry.path;
|
|
2962
|
+
}
|
|
2963
|
+
function parseDiffOutput(stdout) {
|
|
2964
|
+
return parseNameStatus(stdout).map(labelEntry);
|
|
2965
|
+
}
|
|
2966
|
+
function parseModifiedPaths(stdout, a) {
|
|
2967
|
+
return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative3(a, entry.path));
|
|
2968
|
+
}
|
|
2969
|
+
function runNameStatusDiff(a, b, parse) {
|
|
2970
|
+
try {
|
|
2971
|
+
const stdout = execFileSync2(
|
|
2972
|
+
"git",
|
|
2973
|
+
["diff", "--no-index", "--no-renames", "-z", "--name-status", a, b],
|
|
2974
|
+
{
|
|
2975
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2976
|
+
timeout: NAME_STATUS_DIFF_TIMEOUT_MS
|
|
2977
|
+
}
|
|
2978
|
+
).toString();
|
|
2979
|
+
return parse(stdout);
|
|
2980
|
+
} catch (err) {
|
|
2981
|
+
const e = err;
|
|
2982
|
+
if (e.status === 1 && e.stdout !== void 0) {
|
|
2983
|
+
return parse(e.stdout.toString());
|
|
2984
|
+
}
|
|
2985
|
+
if (e.code === "ENOENT") {
|
|
2986
|
+
warn(`git not on PATH; divergence check skipped for ${a}`);
|
|
2987
|
+
return [];
|
|
2988
|
+
}
|
|
2989
|
+
warn(`divergence check failed for ${a}: ${e.message ?? String(err)}`);
|
|
2990
|
+
return [];
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
function listDivergingFiles(a, b) {
|
|
2994
|
+
return runNameStatusDiff(a, b, parseDiffOutput);
|
|
2995
|
+
}
|
|
2996
|
+
function listDivergingModified(a, b) {
|
|
2997
|
+
return runNameStatusDiff(a, b, (stdout) => parseModifiedPaths(stdout, a));
|
|
2998
|
+
}
|
|
2999
|
+
|
|
2821
3000
|
// src/init.classify.ts
|
|
2822
3001
|
init_config();
|
|
2823
3002
|
init_utils_json();
|
|
@@ -2942,6 +3121,25 @@ function reportRepoState(section2) {
|
|
|
2942
3121
|
function repoHasSharedSource(name) {
|
|
2943
3122
|
return existsSync11(join13(repoHome(), "shared", name));
|
|
2944
3123
|
}
|
|
3124
|
+
function win32CopyOkRow(name) {
|
|
3125
|
+
return { line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)`, fail: false };
|
|
3126
|
+
}
|
|
3127
|
+
function divergingBasename(line) {
|
|
3128
|
+
return basename3(line.replace(/ \((?:local|repo) only\)$/, ""));
|
|
3129
|
+
}
|
|
3130
|
+
function classifyWin32Copy(name, p) {
|
|
3131
|
+
const sharedPath = join13(repoHome(), "shared", name);
|
|
3132
|
+
if (!existsSync11(sharedPath)) return win32CopyOkRow(name);
|
|
3133
|
+
const diverging = listDivergingFiles(p, sharedPath).filter(
|
|
3134
|
+
(line) => !isDeniedName(ALWAYS_NEVER_SYNC, divergingBasename(line))
|
|
3135
|
+
);
|
|
3136
|
+
if (diverging.length === 0) return win32CopyOkRow(name);
|
|
3137
|
+
return {
|
|
3138
|
+
line: `${yellow(warnGlyph)} ${name}: ${diverging.length} file(s) diverge from shared/${name}`,
|
|
3139
|
+
fail: false,
|
|
3140
|
+
children: diverging
|
|
3141
|
+
};
|
|
3142
|
+
}
|
|
2945
3143
|
function classifySharedLink(name, p) {
|
|
2946
3144
|
let stat;
|
|
2947
3145
|
try {
|
|
@@ -2958,10 +3156,7 @@ function classifySharedLink(name, p) {
|
|
|
2958
3156
|
}
|
|
2959
3157
|
if (!stat.isSymbolicLink()) {
|
|
2960
3158
|
if (process.platform === "win32") {
|
|
2961
|
-
return
|
|
2962
|
-
line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)`,
|
|
2963
|
-
fail: false
|
|
2964
|
-
};
|
|
3159
|
+
return classifyWin32Copy(name, p);
|
|
2965
3160
|
}
|
|
2966
3161
|
return {
|
|
2967
3162
|
line: `${red(failGlyph)} ${name}: NOT a symlink (blocks sync); run \`nomad adopt ${name}\` to fix`,
|
|
@@ -2993,11 +3188,14 @@ function classifySymlinkTarget(name, p) {
|
|
|
2993
3188
|
}
|
|
2994
3189
|
function reportSharedLinks(section2, map) {
|
|
2995
3190
|
const claude = claudeHome();
|
|
2996
|
-
for (const name of allSharedLinks(map)) {
|
|
3191
|
+
for (const name of allSharedLinks(map, { quiet: true })) {
|
|
2997
3192
|
const p = join13(claude, name);
|
|
2998
|
-
const { line, fail: fail2 } = classifySharedLink(name, p);
|
|
3193
|
+
const { line, fail: fail2, children } = classifySharedLink(name, p);
|
|
2999
3194
|
addItem(section2, line);
|
|
3000
3195
|
if (fail2) process.exitCode = 1;
|
|
3196
|
+
if (children) {
|
|
3197
|
+
for (const child of children) addChildItem(section2, child);
|
|
3198
|
+
}
|
|
3001
3199
|
}
|
|
3002
3200
|
}
|
|
3003
3201
|
function reportDroppedNamesMigration(section2) {
|
|
@@ -3082,8 +3280,9 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
3082
3280
|
// src/commands.doctor.checks.pathmap.ts
|
|
3083
3281
|
init_color();
|
|
3084
3282
|
init_config();
|
|
3085
|
-
|
|
3086
|
-
import {
|
|
3283
|
+
init_config_sharedDirs_guard();
|
|
3284
|
+
import { existsSync as existsSync13, lstatSync as lstatSync9, readdirSync as readdirSync6, realpathSync as realpathSync2 } from "node:fs";
|
|
3285
|
+
import { join as join15, sep as sep4 } from "node:path";
|
|
3087
3286
|
init_utils_json();
|
|
3088
3287
|
function reportMappedProjects(section2, map) {
|
|
3089
3288
|
const mapped = Object.entries(map.projects).filter(([, hosts]) => hosts[HOST]);
|
|
@@ -3123,6 +3322,95 @@ function reportCurrentHostPathsMissing(section2, map) {
|
|
|
3123
3322
|
}
|
|
3124
3323
|
}
|
|
3125
3324
|
}
|
|
3325
|
+
function hasSharedLeftover(entry) {
|
|
3326
|
+
try {
|
|
3327
|
+
return lstatSync9(join15(repoHome(), "shared", entry), { throwIfNoEntry: false }) !== void 0;
|
|
3328
|
+
} catch {
|
|
3329
|
+
return false;
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
var PROBEABLE_REASONS = /* @__PURE__ */ new Set([
|
|
3333
|
+
"never-sync",
|
|
3334
|
+
"secret-shaped"
|
|
3335
|
+
]);
|
|
3336
|
+
function classifyLocalLink(entry) {
|
|
3337
|
+
const linkPath = join15(claudeHome(), entry);
|
|
3338
|
+
let stat;
|
|
3339
|
+
try {
|
|
3340
|
+
stat = lstatSync9(linkPath, { throwIfNoEntry: false });
|
|
3341
|
+
} catch {
|
|
3342
|
+
return "absent";
|
|
3343
|
+
}
|
|
3344
|
+
if (stat?.isSymbolicLink() !== true) return "absent";
|
|
3345
|
+
let target;
|
|
3346
|
+
try {
|
|
3347
|
+
target = realpathSync2(linkPath);
|
|
3348
|
+
} catch {
|
|
3349
|
+
return "dangling";
|
|
3350
|
+
}
|
|
3351
|
+
let root;
|
|
3352
|
+
try {
|
|
3353
|
+
root = realpathSync2(join15(repoHome(), "shared"));
|
|
3354
|
+
} catch {
|
|
3355
|
+
return "foreign";
|
|
3356
|
+
}
|
|
3357
|
+
return target.startsWith(root + sep4) ? "managed" : "foreign";
|
|
3358
|
+
}
|
|
3359
|
+
function reportRejectedSharedDirs(section2, map) {
|
|
3360
|
+
const raw = map.sharedDirs;
|
|
3361
|
+
if (raw !== void 0 && !Array.isArray(raw)) {
|
|
3362
|
+
addItem(
|
|
3363
|
+
section2,
|
|
3364
|
+
`${yellow(warnGlyph)} path-map: sharedDirs is not an array (${typeof raw}); the whole field is ignored`
|
|
3365
|
+
);
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
const entries = Array.isArray(raw) ? raw : [];
|
|
3369
|
+
const probable = [];
|
|
3370
|
+
for (const entry of entries) {
|
|
3371
|
+
const rejection = validateSharedDirEntry(entry);
|
|
3372
|
+
if (rejection === null) continue;
|
|
3373
|
+
if (typeof entry === "string" && mayJoinRefusedEntry(entry, rejection.reason, PROBEABLE_REASONS)) {
|
|
3374
|
+
probable.push(entry);
|
|
3375
|
+
}
|
|
3376
|
+
addItem(
|
|
3377
|
+
section2,
|
|
3378
|
+
`${yellow(warnGlyph)} path-map: sharedDirs entry ${JSON.stringify(entry)} rejected: ${rejection.message}; skipping`
|
|
3379
|
+
);
|
|
3380
|
+
}
|
|
3381
|
+
reportRejectedLeftovers(section2, probable);
|
|
3382
|
+
}
|
|
3383
|
+
function reportRejectedLeftovers(section2, probable) {
|
|
3384
|
+
for (const entry of probable) {
|
|
3385
|
+
const link = classifyLocalLink(entry);
|
|
3386
|
+
if (link === "managed") {
|
|
3387
|
+
addItem(
|
|
3388
|
+
section2,
|
|
3389
|
+
`${yellow(warnGlyph)} path-map: entry ${JSON.stringify(entry)} is a symlink under ~/.claude/ pointing into shared/; copy the content out first (cp -RL), then remove both. nomad will not do it for you`
|
|
3390
|
+
);
|
|
3391
|
+
continue;
|
|
3392
|
+
}
|
|
3393
|
+
if (link === "dangling") {
|
|
3394
|
+
addItem(
|
|
3395
|
+
section2,
|
|
3396
|
+
`${yellow(warnGlyph)} path-map: entry ${JSON.stringify(entry)} is a DANGLING symlink under ~/.claude/ (its target does not resolve); do not delete anything under shared/ yet, since a copy there may be the only one left. nomad cannot restore this name, because the entry is refused: recover the content by hand if you need it, then remove the dead link`
|
|
3397
|
+
);
|
|
3398
|
+
continue;
|
|
3399
|
+
}
|
|
3400
|
+
if (link === "foreign") {
|
|
3401
|
+
addItem(
|
|
3402
|
+
section2,
|
|
3403
|
+
`${yellow(warnGlyph)} path-map: entry ${JSON.stringify(entry)} is a symlink under ~/.claude/ pointing OUTSIDE shared/; nomad does not manage it, so leave it alone and just drop the entry from sharedDirs`
|
|
3404
|
+
);
|
|
3405
|
+
}
|
|
3406
|
+
if (hasSharedLeftover(entry)) {
|
|
3407
|
+
addItem(
|
|
3408
|
+
section2,
|
|
3409
|
+
`${yellow(warnGlyph)} path-map: shared/ entry ${JSON.stringify(entry)} exists in the repo working tree; remove it by hand, nomad will not delete it`
|
|
3410
|
+
);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3126
3414
|
function reportPathCollisions(section2, map) {
|
|
3127
3415
|
const seen = /* @__PURE__ */ new Map();
|
|
3128
3416
|
let collisionCount = 0;
|
|
@@ -3154,6 +3442,7 @@ function reportPathMap(section2) {
|
|
|
3154
3442
|
}
|
|
3155
3443
|
const map = readJsonSafe(mapPath, mapPath, section2);
|
|
3156
3444
|
if (map === null) return;
|
|
3445
|
+
reportRejectedSharedDirs(section2, map);
|
|
3157
3446
|
const shapeError = validatePathMapShape(map);
|
|
3158
3447
|
if (shapeError !== null) {
|
|
3159
3448
|
addItem(section2, `${red(failGlyph)} ${shapeError}`);
|
|
@@ -3179,61 +3468,6 @@ init_color();
|
|
|
3179
3468
|
import { existsSync as existsSync14 } from "node:fs";
|
|
3180
3469
|
import { join as join16 } from "node:path";
|
|
3181
3470
|
init_config();
|
|
3182
|
-
|
|
3183
|
-
// src/extras-sync.diff.ts
|
|
3184
|
-
init_utils();
|
|
3185
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3186
|
-
import { relative as relative3 } from "node:path";
|
|
3187
|
-
function parseNameStatus(stdout) {
|
|
3188
|
-
const fields = stdout.split("\0").filter((f) => f.length > 0);
|
|
3189
|
-
const entries = [];
|
|
3190
|
-
for (let i = 0; i + 1 < fields.length; i += 2) {
|
|
3191
|
-
entries.push({ status: fields[i], path: fields[i + 1] });
|
|
3192
|
-
}
|
|
3193
|
-
return entries;
|
|
3194
|
-
}
|
|
3195
|
-
function labelEntry(entry) {
|
|
3196
|
-
if (entry.status === "D") return `${entry.path} (local only)`;
|
|
3197
|
-
if (entry.status === "A") return `${entry.path} (repo only)`;
|
|
3198
|
-
return entry.path;
|
|
3199
|
-
}
|
|
3200
|
-
function parseDiffOutput(stdout) {
|
|
3201
|
-
return parseNameStatus(stdout).map(labelEntry);
|
|
3202
|
-
}
|
|
3203
|
-
function parseModifiedPaths(stdout, a) {
|
|
3204
|
-
return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative3(a, entry.path));
|
|
3205
|
-
}
|
|
3206
|
-
function runNameStatusDiff(a, b, parse) {
|
|
3207
|
-
try {
|
|
3208
|
-
const stdout = execFileSync2(
|
|
3209
|
-
"git",
|
|
3210
|
-
["diff", "--no-index", "--no-renames", "-z", "--name-status", a, b],
|
|
3211
|
-
{
|
|
3212
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3213
|
-
}
|
|
3214
|
-
).toString();
|
|
3215
|
-
return parse(stdout);
|
|
3216
|
-
} catch (err) {
|
|
3217
|
-
const e = err;
|
|
3218
|
-
if (e.status === 1 && e.stdout !== void 0) {
|
|
3219
|
-
return parse(e.stdout.toString());
|
|
3220
|
-
}
|
|
3221
|
-
if (e.code === "ENOENT") {
|
|
3222
|
-
warn(`git not on PATH; divergence check skipped for ${a}`);
|
|
3223
|
-
return [];
|
|
3224
|
-
}
|
|
3225
|
-
warn(`divergence check failed for ${a}: ${e.message ?? String(err)}`);
|
|
3226
|
-
return [];
|
|
3227
|
-
}
|
|
3228
|
-
}
|
|
3229
|
-
function listDivergingFiles(a, b) {
|
|
3230
|
-
return runNameStatusDiff(a, b, parseDiffOutput);
|
|
3231
|
-
}
|
|
3232
|
-
function listDivergingModified(a, b) {
|
|
3233
|
-
return runNameStatusDiff(a, b, (stdout) => parseModifiedPaths(stdout, a));
|
|
3234
|
-
}
|
|
3235
|
-
|
|
3236
|
-
// src/commands.doctor.checks.skills.ts
|
|
3237
3471
|
function stripSideIndicator(line) {
|
|
3238
3472
|
if (line.endsWith(" (local only)")) return line.slice(0, -" (local only)".length);
|
|
3239
3473
|
if (line.endsWith(" (repo only)")) return line.slice(0, -" (repo only)".length);
|
|
@@ -3241,15 +3475,15 @@ function stripSideIndicator(line) {
|
|
|
3241
3475
|
}
|
|
3242
3476
|
function isGsdDiffLine(line, localBase, sharedBase) {
|
|
3243
3477
|
const bare = stripSideIndicator(line);
|
|
3244
|
-
let
|
|
3478
|
+
let relative11;
|
|
3245
3479
|
if (bare.startsWith(localBase + "/")) {
|
|
3246
|
-
|
|
3480
|
+
relative11 = bare.slice(localBase.length + 1);
|
|
3247
3481
|
} else if (bare.startsWith(sharedBase + "/")) {
|
|
3248
|
-
|
|
3482
|
+
relative11 = bare.slice(sharedBase.length + 1);
|
|
3249
3483
|
} else {
|
|
3250
|
-
|
|
3484
|
+
relative11 = bare;
|
|
3251
3485
|
}
|
|
3252
|
-
return
|
|
3486
|
+
return relative11.split("/")[0].startsWith(GSD_PREFIX);
|
|
3253
3487
|
}
|
|
3254
3488
|
function reportSkillsDivergence(section2) {
|
|
3255
3489
|
const sharedSkills = join16(repoHome(), "shared", "skills");
|
|
@@ -3404,7 +3638,7 @@ function reportGitIdentity(section2) {
|
|
|
3404
3638
|
|
|
3405
3639
|
// src/commands.doctor.checks.backups.ts
|
|
3406
3640
|
init_color();
|
|
3407
|
-
import { existsSync as existsSync18, lstatSync as
|
|
3641
|
+
import { existsSync as existsSync18, lstatSync as lstatSync10, readdirSync as readdirSync8 } from "node:fs";
|
|
3408
3642
|
import { join as join21 } from "node:path";
|
|
3409
3643
|
init_config();
|
|
3410
3644
|
var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
|
|
@@ -3422,7 +3656,7 @@ function dirSizeBytes(dir) {
|
|
|
3422
3656
|
let bytes = 0;
|
|
3423
3657
|
for (const entry of safeReaddir(dir)) {
|
|
3424
3658
|
const full = join21(dir, entry);
|
|
3425
|
-
const st =
|
|
3659
|
+
const st = lstatSync10(full, { throwIfNoEntry: false });
|
|
3426
3660
|
if (!st) continue;
|
|
3427
3661
|
if (st.isSymbolicLink()) continue;
|
|
3428
3662
|
if (st.isDirectory()) bytes += dirSizeBytes(full);
|
|
@@ -3901,8 +4135,8 @@ init_config();
|
|
|
3901
4135
|
|
|
3902
4136
|
// src/remap.ts
|
|
3903
4137
|
init_config_sharedDirs_guard();
|
|
3904
|
-
import { cpSync as cpSync6, existsSync as existsSync21, lstatSync as
|
|
3905
|
-
import { dirname as dirname6, join as join28, relative as relative5, sep as
|
|
4138
|
+
import { cpSync as cpSync6, existsSync as existsSync21, lstatSync as lstatSync11, mkdirSync as mkdirSync7, readdirSync as readdirSync10, rmSync as rmSync11, statSync as statSync5 } from "node:fs";
|
|
4139
|
+
import { dirname as dirname6, join as join28, relative as relative5, sep as sep5 } from "node:path";
|
|
3906
4140
|
init_config();
|
|
3907
4141
|
|
|
3908
4142
|
// src/push-manifest.ts
|
|
@@ -4034,7 +4268,7 @@ function copyFileAtomic(src, dst) {
|
|
|
4034
4268
|
renameAtomicRetry(tmp, dst);
|
|
4035
4269
|
}
|
|
4036
4270
|
function hasDeltaForDir(sel, localDir) {
|
|
4037
|
-
const prefix = `${localDir}${
|
|
4271
|
+
const prefix = `${localDir}${sep5}`;
|
|
4038
4272
|
for (const p of sel.changed) if (p.startsWith(prefix)) return true;
|
|
4039
4273
|
for (const p of sel.deleted) if (p.startsWith(prefix)) return true;
|
|
4040
4274
|
return false;
|
|
@@ -4043,7 +4277,7 @@ function skipForSelection(sel, localDir) {
|
|
|
4043
4277
|
return sel !== void 0 && !hasDeltaForDir(sel, localDir);
|
|
4044
4278
|
}
|
|
4045
4279
|
function applySelective(sel, localDir, repoDst) {
|
|
4046
|
-
const prefix = `${localDir}${
|
|
4280
|
+
const prefix = `${localDir}${sep5}`;
|
|
4047
4281
|
for (const src of sel.changed) {
|
|
4048
4282
|
if (!src.startsWith(prefix)) continue;
|
|
4049
4283
|
copyFileAtomic(src, join28(repoDst, relative5(localDir, src)));
|
|
@@ -4061,7 +4295,7 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
4061
4295
|
filter: (srcPath) => {
|
|
4062
4296
|
const rel = relative5(src, srcPath);
|
|
4063
4297
|
if (rel === "") return true;
|
|
4064
|
-
if (rel.split(
|
|
4298
|
+
if (rel.split(sep5).length > 1) return true;
|
|
4065
4299
|
if (statSync5(srcPath).isDirectory()) return true;
|
|
4066
4300
|
if (srcPath.endsWith(".jsonl")) return true;
|
|
4067
4301
|
item(`skip ${rel}: extension not in allowlist`);
|
|
@@ -4121,9 +4355,9 @@ function countLocalOnly(src, dst) {
|
|
|
4121
4355
|
for (const name of readdirSync10(dst)) {
|
|
4122
4356
|
const dstPath = join28(dst, name);
|
|
4123
4357
|
const srcPath = join28(src, name);
|
|
4124
|
-
if (
|
|
4358
|
+
if (lstatSync11(dstPath).isDirectory()) {
|
|
4125
4359
|
count += countLocalOnly(srcPath, dstPath);
|
|
4126
|
-
} else if (
|
|
4360
|
+
} else if (lstatSync11(srcPath, { throwIfNoEntry: false }) === void 0) {
|
|
4127
4361
|
count++;
|
|
4128
4362
|
}
|
|
4129
4363
|
}
|
|
@@ -4313,7 +4547,7 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
4313
4547
|
|
|
4314
4548
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
4315
4549
|
init_color();
|
|
4316
|
-
import { existsSync as existsSync23, readFileSync as readFileSync8, readdirSync as readdirSync12, realpathSync as
|
|
4550
|
+
import { existsSync as existsSync23, readFileSync as readFileSync8, readdirSync as readdirSync12, realpathSync as realpathSync3 } from "node:fs";
|
|
4317
4551
|
import { dirname as dirname7, extname, join as join30 } from "node:path";
|
|
4318
4552
|
init_config();
|
|
4319
4553
|
function typeFromPackageJson(pkgPath) {
|
|
@@ -4330,7 +4564,7 @@ function effectiveType(hookPath) {
|
|
|
4330
4564
|
if (ext === ".cjs") return "cjs";
|
|
4331
4565
|
let real;
|
|
4332
4566
|
try {
|
|
4333
|
-
real =
|
|
4567
|
+
real = realpathSync3(hookPath);
|
|
4334
4568
|
} catch {
|
|
4335
4569
|
return null;
|
|
4336
4570
|
}
|
|
@@ -4492,7 +4726,7 @@ import { existsSync as existsSync26, readFileSync as readFileSync9 } from "node:
|
|
|
4492
4726
|
import { join as join33 } from "node:path";
|
|
4493
4727
|
|
|
4494
4728
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
4495
|
-
import { closeSync as closeSync3, existsSync as existsSync25, openSync as openSync3, readSync, realpathSync as
|
|
4729
|
+
import { closeSync as closeSync3, existsSync as existsSync25, openSync as openSync3, readSync, realpathSync as realpathSync4 } from "node:fs";
|
|
4496
4730
|
import { dirname as dirname8, join as join32, resolve as resolve2 } from "node:path";
|
|
4497
4731
|
function suppressedRanges(src) {
|
|
4498
4732
|
const ranges = [];
|
|
@@ -4537,7 +4771,7 @@ function specifierIsMissing(specifier, baseDir) {
|
|
|
4537
4771
|
function relativeRequireTargetsBroken(scriptPath) {
|
|
4538
4772
|
let realPath;
|
|
4539
4773
|
try {
|
|
4540
|
-
realPath =
|
|
4774
|
+
realPath = realpathSync4(scriptPath);
|
|
4541
4775
|
} catch {
|
|
4542
4776
|
return false;
|
|
4543
4777
|
}
|
|
@@ -4681,7 +4915,7 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
4681
4915
|
return;
|
|
4682
4916
|
}
|
|
4683
4917
|
const map = readPathMapSafe();
|
|
4684
|
-
const sharedLinkNames = allSharedLinks(map);
|
|
4918
|
+
const sharedLinkNames = allSharedLinks(map, { quiet: true });
|
|
4685
4919
|
let anyWarn = false;
|
|
4686
4920
|
for (const [event, groups] of Object.entries(hooks)) {
|
|
4687
4921
|
if (!Array.isArray(groups)) continue;
|
|
@@ -5031,21 +5265,21 @@ import { createInterface as createInterface2 } from "node:readline/promises";
|
|
|
5031
5265
|
// src/commands.push.recovery.actions.ts
|
|
5032
5266
|
init_config();
|
|
5033
5267
|
import { readFileSync as readFileSync17 } from "node:fs";
|
|
5034
|
-
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as
|
|
5268
|
+
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep9 } from "node:path";
|
|
5035
5269
|
|
|
5036
5270
|
// src/commands.push.recovery.memory.ts
|
|
5037
5271
|
init_config();
|
|
5038
5272
|
init_config_sharedDirs_guard();
|
|
5039
5273
|
import {
|
|
5040
5274
|
cpSync as cpSync7,
|
|
5041
|
-
lstatSync as
|
|
5275
|
+
lstatSync as lstatSync12,
|
|
5042
5276
|
mkdirSync as mkdirSync9,
|
|
5043
5277
|
readFileSync as readFileSync14,
|
|
5044
|
-
realpathSync as
|
|
5278
|
+
realpathSync as realpathSync5,
|
|
5045
5279
|
statSync as statSync6,
|
|
5046
5280
|
writeFileSync as writeFileSync6
|
|
5047
5281
|
} from "node:fs";
|
|
5048
|
-
import { dirname as dirname9, join as join36, sep as
|
|
5282
|
+
import { dirname as dirname9, join as join36, sep as sep6 } from "node:path";
|
|
5049
5283
|
init_push_gitleaks_scan();
|
|
5050
5284
|
|
|
5051
5285
|
// src/rel-path-guard.ts
|
|
@@ -5076,12 +5310,12 @@ function resolveMemoryLocalPath(logical, relPath, map) {
|
|
|
5076
5310
|
if (abs === void 0) return null;
|
|
5077
5311
|
const memoryRoot = join36(claudeHome(), "projects", encodePath(abs), "memory");
|
|
5078
5312
|
const localPath = join36(memoryRoot, ...relPath.split("/"));
|
|
5079
|
-
if (localPath !== memoryRoot && !localPath.startsWith(memoryRoot +
|
|
5313
|
+
if (localPath !== memoryRoot && !localPath.startsWith(memoryRoot + sep6)) return null;
|
|
5080
5314
|
try {
|
|
5081
|
-
if (
|
|
5082
|
-
const realLocal =
|
|
5083
|
-
const realRoot =
|
|
5084
|
-
if (!realLocal.startsWith(realRoot +
|
|
5315
|
+
if (lstatSync12(localPath).isSymbolicLink()) return null;
|
|
5316
|
+
const realLocal = realpathSync5(localPath);
|
|
5317
|
+
const realRoot = realpathSync5(memoryRoot);
|
|
5318
|
+
if (!realLocal.startsWith(realRoot + sep6)) return null;
|
|
5085
5319
|
if (!statSync6(realLocal).isFile()) return null;
|
|
5086
5320
|
} catch {
|
|
5087
5321
|
return null;
|
|
@@ -5139,28 +5373,28 @@ function applyMemoryRedact(f, ts, map, scan = scanFile) {
|
|
|
5139
5373
|
init_config();
|
|
5140
5374
|
import {
|
|
5141
5375
|
cpSync as cpSync8,
|
|
5142
|
-
lstatSync as
|
|
5376
|
+
lstatSync as lstatSync14,
|
|
5143
5377
|
mkdirSync as mkdirSync11,
|
|
5144
5378
|
readFileSync as readFileSync15,
|
|
5145
|
-
realpathSync as
|
|
5379
|
+
realpathSync as realpathSync6,
|
|
5146
5380
|
statSync as statSync7,
|
|
5147
5381
|
writeFileSync as writeFileSync7
|
|
5148
5382
|
} from "node:fs";
|
|
5149
|
-
import { dirname as dirname10, join as join38, sep as
|
|
5383
|
+
import { dirname as dirname10, join as join38, sep as sep7 } from "node:path";
|
|
5150
5384
|
|
|
5151
5385
|
// src/skills-sync.ts
|
|
5152
5386
|
init_config();
|
|
5153
|
-
import { existsSync as existsSync29, lstatSync as
|
|
5387
|
+
import { existsSync as existsSync29, lstatSync as lstatSync13, mkdirSync as mkdirSync10, readdirSync as readdirSync13, rmSync as rmSync13 } from "node:fs";
|
|
5154
5388
|
import { join as join37 } from "node:path";
|
|
5155
5389
|
|
|
5156
5390
|
// src/skills-sync.tracked.ts
|
|
5157
5391
|
init_utils();
|
|
5158
|
-
import { basename as
|
|
5392
|
+
import { basename as basename4 } from "node:path";
|
|
5159
5393
|
function trackedRootSkillsAt(ref, repo) {
|
|
5160
5394
|
try {
|
|
5161
5395
|
const raw = gitCaptureRaw(["ls-tree", "--name-only", "-z", ref, "--", "shared/skills/"], repo);
|
|
5162
5396
|
const entries = raw.split("\0").filter((entry) => entry !== "");
|
|
5163
|
-
return new Set(entries.map((entry) =>
|
|
5397
|
+
return new Set(entries.map((entry) => basename4(entry)));
|
|
5164
5398
|
} catch {
|
|
5165
5399
|
return /* @__PURE__ */ new Set();
|
|
5166
5400
|
}
|
|
@@ -5190,7 +5424,7 @@ function syncSkillsPull(ts, prePostHeads) {
|
|
|
5190
5424
|
if (!existsSync29(sharedSkills)) return;
|
|
5191
5425
|
const localSkills = join37(claudeHome(), "skills");
|
|
5192
5426
|
backupBeforeWrite(localSkills, ts);
|
|
5193
|
-
const dstStat =
|
|
5427
|
+
const dstStat = lstatSync13(localSkills, { throwIfNoEntry: false });
|
|
5194
5428
|
if (dstStat?.isSymbolicLink() === true) {
|
|
5195
5429
|
rmSync13(localSkills, { recursive: true, force: true });
|
|
5196
5430
|
mkdirSync10(localSkills, { recursive: true });
|
|
@@ -5200,7 +5434,7 @@ function syncSkillsPull(ts, prePostHeads) {
|
|
|
5200
5434
|
}
|
|
5201
5435
|
function syncSkillsPush() {
|
|
5202
5436
|
const localSkills = join37(claudeHome(), "skills");
|
|
5203
|
-
const stat =
|
|
5437
|
+
const stat = lstatSync13(localSkills, { throwIfNoEntry: false });
|
|
5204
5438
|
if (stat === void 0) return;
|
|
5205
5439
|
if (stat.isSymbolicLink()) return;
|
|
5206
5440
|
const sharedSkills = join37(repoHome(), "shared", "skills");
|
|
@@ -5229,12 +5463,12 @@ function resolveSkillLocalPath(name, relPath) {
|
|
|
5229
5463
|
const skillsRoot = join38(claudeHome(), "skills");
|
|
5230
5464
|
const skillRoot = join38(skillsRoot, name);
|
|
5231
5465
|
const localPath = join38(skillRoot, ...relPath.split("/"));
|
|
5232
|
-
if (localPath !== skillRoot && !localPath.startsWith(skillRoot +
|
|
5466
|
+
if (localPath !== skillRoot && !localPath.startsWith(skillRoot + sep7)) return null;
|
|
5233
5467
|
try {
|
|
5234
|
-
if (
|
|
5235
|
-
const realLocal =
|
|
5236
|
-
const realRoot =
|
|
5237
|
-
if (!realLocal.startsWith(realRoot +
|
|
5468
|
+
if (lstatSync14(localPath).isSymbolicLink()) return null;
|
|
5469
|
+
const realLocal = realpathSync6(localPath);
|
|
5470
|
+
const realRoot = realpathSync6(skillsRoot);
|
|
5471
|
+
if (!realLocal.startsWith(realRoot + sep7)) return null;
|
|
5238
5472
|
if (!statSync7(realLocal).isFile()) return null;
|
|
5239
5473
|
} catch {
|
|
5240
5474
|
return null;
|
|
@@ -5359,7 +5593,7 @@ function applyDeferredAllows(findings, state, repo) {
|
|
|
5359
5593
|
init_config();
|
|
5360
5594
|
init_config_sharedDirs_guard();
|
|
5361
5595
|
import { cpSync as cpSync9, existsSync as existsSync32, mkdirSync as mkdirSync12 } from "node:fs";
|
|
5362
|
-
import { dirname as dirname12, join as join41, sep as
|
|
5596
|
+
import { dirname as dirname12, join as join41, sep as sep8 } from "node:path";
|
|
5363
5597
|
|
|
5364
5598
|
// src/commands.redact.ts
|
|
5365
5599
|
init_config();
|
|
@@ -5367,17 +5601,17 @@ import { existsSync as existsSync31 } from "node:fs";
|
|
|
5367
5601
|
import { dirname as dirname11, join as join40 } from "node:path";
|
|
5368
5602
|
|
|
5369
5603
|
// src/commands.redact.subtree.ts
|
|
5370
|
-
import { existsSync as existsSync30, lstatSync as
|
|
5604
|
+
import { existsSync as existsSync30, lstatSync as lstatSync15, readFileSync as readFileSync16, readdirSync as readdirSync14, statSync as statSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
5371
5605
|
import { join as join39 } from "node:path";
|
|
5372
5606
|
init_utils_fs();
|
|
5373
5607
|
init_utils();
|
|
5374
5608
|
function collectFiles(dir, out) {
|
|
5375
5609
|
if (!existsSync30(dir)) return;
|
|
5376
|
-
const st =
|
|
5610
|
+
const st = lstatSync15(dir);
|
|
5377
5611
|
if (!st.isDirectory()) return;
|
|
5378
5612
|
for (const entry of readdirSync14(dir)) {
|
|
5379
5613
|
const abs = join39(dir, entry);
|
|
5380
|
-
const lst =
|
|
5614
|
+
const lst = lstatSync15(abs);
|
|
5381
5615
|
if (lst.isSymbolicLink()) continue;
|
|
5382
5616
|
if (lst.isDirectory()) {
|
|
5383
5617
|
collectFiles(abs, out);
|
|
@@ -5585,7 +5819,7 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
5585
5819
|
assertSafeLogical(logical);
|
|
5586
5820
|
const abs = hostMap[HOST];
|
|
5587
5821
|
if (abs === void 0) continue;
|
|
5588
|
-
if (localPath.startsWith(join41(claude, "projects", encodePath(abs)) +
|
|
5822
|
+
if (localPath.startsWith(join41(claude, "projects", encodePath(abs)) + sep8)) {
|
|
5589
5823
|
return join41(repo, "shared", "projects", logical);
|
|
5590
5824
|
}
|
|
5591
5825
|
}
|
|
@@ -5876,8 +6110,8 @@ function sessionSuffix(finding) {
|
|
|
5876
6110
|
const sid = sessionIdFromFinding(finding);
|
|
5877
6111
|
if (sid === null) return "";
|
|
5878
6112
|
const rawBasename = finding.File.replace(/^.*\//, "");
|
|
5879
|
-
const
|
|
5880
|
-
return
|
|
6113
|
+
const basename5 = rawBasename.endsWith(".jsonl") ? rawBasename.slice(0, -".jsonl".length) : rawBasename;
|
|
6114
|
+
return basename5 === sid ? "" : ` (session: ${sid})`;
|
|
5881
6115
|
}
|
|
5882
6116
|
function renderFindingBlock(finding, readLine, siblings = []) {
|
|
5883
6117
|
const lines = [` file: ${finding.File}:${finding.StartLine}${sessionSuffix(finding)}`];
|
|
@@ -5949,7 +6183,7 @@ function makeDefaultReadLine(repo) {
|
|
|
5949
6183
|
try {
|
|
5950
6184
|
const repoRoot = resolve3(repo);
|
|
5951
6185
|
const target = resolve3(repoRoot, file);
|
|
5952
|
-
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot +
|
|
6186
|
+
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep9)) {
|
|
5953
6187
|
return null;
|
|
5954
6188
|
}
|
|
5955
6189
|
let lines = cache.get(target);
|
|
@@ -6843,8 +7077,8 @@ function unstageOne(rel, repo) {
|
|
|
6843
7077
|
|
|
6844
7078
|
// src/commands.pull.ts
|
|
6845
7079
|
init_autostash_guard();
|
|
6846
|
-
import { existsSync as
|
|
6847
|
-
import { join as
|
|
7080
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync14 } from "node:fs";
|
|
7081
|
+
import { join as join58 } from "node:path";
|
|
6848
7082
|
|
|
6849
7083
|
// src/commands.push.sections.ts
|
|
6850
7084
|
init_color();
|
|
@@ -6959,11 +7193,11 @@ init_utils_json();
|
|
|
6959
7193
|
|
|
6960
7194
|
// src/extras-sync.remap.ts
|
|
6961
7195
|
init_config();
|
|
6962
|
-
import { existsSync as existsSync38, mkdirSync as mkdirSync13, readdirSync as readdirSync16, readFileSync as readFileSync19, realpathSync as
|
|
6963
|
-
import { dirname as dirname13, join as join49, sep as
|
|
7196
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync13, readdirSync as readdirSync16, readFileSync as readFileSync19, realpathSync as realpathSync7, rmSync as rmSync16 } from "node:fs";
|
|
7197
|
+
import { dirname as dirname13, join as join49, sep as sep11 } from "node:path";
|
|
6964
7198
|
|
|
6965
7199
|
// src/extras-sync.planning-diff.ts
|
|
6966
|
-
import { join as join48, normalize as normalize2, sep as
|
|
7200
|
+
import { join as join48, normalize as normalize2, sep as sep10 } from "node:path";
|
|
6967
7201
|
init_utils();
|
|
6968
7202
|
function processRecord(fields, i, changed, deleted) {
|
|
6969
7203
|
const status = fields[i];
|
|
@@ -7016,7 +7250,7 @@ function planningDeleteTargets(opts) {
|
|
|
7016
7250
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
7017
7251
|
const prefix = logicalPrefix + ".planning/";
|
|
7018
7252
|
const planningRoot = join48(localRoot, ".planning");
|
|
7019
|
-
const planningRootBoundary = planningRoot +
|
|
7253
|
+
const planningRootBoundary = planningRoot + sep10;
|
|
7020
7254
|
const targets = [];
|
|
7021
7255
|
for (const repoPath of deleted) {
|
|
7022
7256
|
if (!repoPath.startsWith(prefix)) {
|
|
@@ -7058,7 +7292,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
7058
7292
|
}
|
|
7059
7293
|
function pruneEmptyAncestors(target, planningRoot) {
|
|
7060
7294
|
let dir = dirname13(target);
|
|
7061
|
-
while (dir !== planningRoot && dir.startsWith(planningRoot +
|
|
7295
|
+
while (dir !== planningRoot && dir.startsWith(planningRoot + sep11)) {
|
|
7062
7296
|
try {
|
|
7063
7297
|
if (readdirSync16(dir).length > 0) break;
|
|
7064
7298
|
rmSync16(dir, { recursive: true, force: true });
|
|
@@ -7070,13 +7304,13 @@ function pruneEmptyAncestors(target, planningRoot) {
|
|
|
7070
7304
|
}
|
|
7071
7305
|
function tryRealpath(dir) {
|
|
7072
7306
|
try {
|
|
7073
|
-
return
|
|
7307
|
+
return realpathSync7(dir);
|
|
7074
7308
|
} catch {
|
|
7075
7309
|
return void 0;
|
|
7076
7310
|
}
|
|
7077
7311
|
}
|
|
7078
7312
|
function isInsidePlanningRoot(parentReal, rootReal) {
|
|
7079
|
-
return parentReal === rootReal || parentReal.startsWith(rootReal +
|
|
7313
|
+
return parentReal === rootReal || parentReal.startsWith(rootReal + sep11);
|
|
7080
7314
|
}
|
|
7081
7315
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
7082
7316
|
if (existsSync38(repoCounterpart)) return;
|
|
@@ -7103,11 +7337,11 @@ function planningDiffArgs(pre, post, logical) {
|
|
|
7103
7337
|
function deletePairsFor(t, raw) {
|
|
7104
7338
|
return planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot }).map(
|
|
7105
7339
|
(target) => {
|
|
7106
|
-
const relToLocal = target.slice(t.localRoot.length +
|
|
7340
|
+
const relToLocal = target.slice(t.localRoot.length + sep11.length);
|
|
7107
7341
|
return {
|
|
7108
7342
|
target,
|
|
7109
7343
|
relToLocal,
|
|
7110
|
-
repoRel: `shared/extras/${t.logical}/${relToLocal.split(
|
|
7344
|
+
repoRel: `shared/extras/${t.logical}/${relToLocal.split(sep11).join("/")}`
|
|
7111
7345
|
};
|
|
7112
7346
|
}
|
|
7113
7347
|
);
|
|
@@ -7279,10 +7513,204 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
7279
7513
|
return divergedCount;
|
|
7280
7514
|
}
|
|
7281
7515
|
|
|
7282
|
-
// src/
|
|
7516
|
+
// src/links.baseline.ts
|
|
7517
|
+
init_config();
|
|
7518
|
+
import { lstatSync as lstatSync16, readdirSync as readdirSync17 } from "node:fs";
|
|
7519
|
+
import { join as join51, relative as relative7, sep as sep12 } from "node:path";
|
|
7520
|
+
init_utils();
|
|
7521
|
+
var SHARED_BASELINE_KIND = "shared-links-baseline/1";
|
|
7522
|
+
var SHARED_BASELINE_CONFIG_HASH = "not-applicable";
|
|
7523
|
+
function readSharedBaseline() {
|
|
7524
|
+
const parsed = readManifest(sharedBaselinePath());
|
|
7525
|
+
if (parsed === null) return null;
|
|
7526
|
+
if (parsed.scannerVersion !== SHARED_BASELINE_KIND) return null;
|
|
7527
|
+
return parsed;
|
|
7528
|
+
}
|
|
7529
|
+
function baselineKey(claude, abs) {
|
|
7530
|
+
return relative7(claude, abs).split(sep12).join("/");
|
|
7531
|
+
}
|
|
7532
|
+
function addLocalPath(abs, claude, scan) {
|
|
7533
|
+
const key = baselineKey(claude, abs);
|
|
7534
|
+
let st;
|
|
7535
|
+
try {
|
|
7536
|
+
st = lstatSync16(abs, { throwIfNoEntry: false });
|
|
7537
|
+
} catch {
|
|
7538
|
+
scan.declined.push(key);
|
|
7539
|
+
return;
|
|
7540
|
+
}
|
|
7541
|
+
if (st === void 0) return;
|
|
7542
|
+
if (st.isSymbolicLink()) {
|
|
7543
|
+
scan.declined.push(key);
|
|
7544
|
+
return;
|
|
7545
|
+
}
|
|
7546
|
+
if (st.isDirectory()) {
|
|
7547
|
+
collectSharedFiles(abs, claude, scan);
|
|
7548
|
+
return;
|
|
7549
|
+
}
|
|
7550
|
+
scan.files[key] = { size: st.size, mtime: st.mtimeMs };
|
|
7551
|
+
}
|
|
7552
|
+
function collectSharedFiles(dir, claude, scan) {
|
|
7553
|
+
let entries;
|
|
7554
|
+
try {
|
|
7555
|
+
entries = readdirSync17(dir);
|
|
7556
|
+
} catch {
|
|
7557
|
+
scan.declined.push(baselineKey(claude, dir));
|
|
7558
|
+
return;
|
|
7559
|
+
}
|
|
7560
|
+
for (const entry of entries) {
|
|
7561
|
+
const abs = join51(dir, entry);
|
|
7562
|
+
if (isDeniedName(ALWAYS_NEVER_SYNC, entry)) {
|
|
7563
|
+
scan.declined.push(baselineKey(claude, abs));
|
|
7564
|
+
continue;
|
|
7565
|
+
}
|
|
7566
|
+
addLocalPath(abs, claude, scan);
|
|
7567
|
+
}
|
|
7568
|
+
}
|
|
7569
|
+
function enumerateLocalSharedScan(map) {
|
|
7570
|
+
const claude = claudeHome();
|
|
7571
|
+
const scan = { files: {}, declined: [] };
|
|
7572
|
+
for (const name of allSharedLinks(map)) {
|
|
7573
|
+
if (isDeniedName(ALWAYS_NEVER_SYNC, name)) {
|
|
7574
|
+
scan.declined.push(name);
|
|
7575
|
+
continue;
|
|
7576
|
+
}
|
|
7577
|
+
addLocalPath(join51(claude, name), claude, scan);
|
|
7578
|
+
}
|
|
7579
|
+
return scan;
|
|
7580
|
+
}
|
|
7581
|
+
function enumerateLocalSharedFiles(map) {
|
|
7582
|
+
return enumerateLocalSharedScan(map).files;
|
|
7583
|
+
}
|
|
7584
|
+
function buildSharedBaseline(map) {
|
|
7585
|
+
const claude = claudeHome();
|
|
7586
|
+
const files = {};
|
|
7587
|
+
for (const [key, st] of Object.entries(enumerateLocalSharedFiles(map))) {
|
|
7588
|
+
try {
|
|
7589
|
+
files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(join51(claude, key)) };
|
|
7590
|
+
} catch {
|
|
7591
|
+
}
|
|
7592
|
+
}
|
|
7593
|
+
return buildManifest(files, SHARED_BASELINE_KIND, SHARED_BASELINE_CONFIG_HASH);
|
|
7594
|
+
}
|
|
7595
|
+
function writeSharedBaseline(map) {
|
|
7596
|
+
if (process.platform !== "win32") return;
|
|
7597
|
+
try {
|
|
7598
|
+
writeManifest(sharedBaselinePath(), buildSharedBaseline(map));
|
|
7599
|
+
} catch (err) {
|
|
7600
|
+
warn(`could not record the shared-config baseline: ${err.message}`);
|
|
7601
|
+
}
|
|
7602
|
+
}
|
|
7603
|
+
|
|
7604
|
+
// src/commands.pull.win32.ts
|
|
7283
7605
|
init_config();
|
|
7284
|
-
import { existsSync as
|
|
7606
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
7607
|
+
import { join as join56 } from "node:path";
|
|
7608
|
+
|
|
7609
|
+
// src/git-probe.ts
|
|
7610
|
+
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
7611
|
+
var PROBE_TIMEOUT_MS4 = 1e4;
|
|
7612
|
+
var PROBE_MAX_BUFFER = 64 * 1024 * 1024;
|
|
7613
|
+
function gitProbe(args, repo) {
|
|
7614
|
+
try {
|
|
7615
|
+
return execFileSync21("git", args, {
|
|
7616
|
+
cwd: repo,
|
|
7617
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
7618
|
+
timeout: PROBE_TIMEOUT_MS4,
|
|
7619
|
+
maxBuffer: PROBE_MAX_BUFFER
|
|
7620
|
+
}).toString();
|
|
7621
|
+
} catch {
|
|
7622
|
+
return null;
|
|
7623
|
+
}
|
|
7624
|
+
}
|
|
7625
|
+
|
|
7626
|
+
// src/links.captures.ts
|
|
7627
|
+
init_config();
|
|
7628
|
+
import { existsSync as existsSync40, lstatSync as lstatSync17 } from "node:fs";
|
|
7285
7629
|
import { join as join52 } from "node:path";
|
|
7630
|
+
function planSharedLinkCaptures(map) {
|
|
7631
|
+
if (process.platform !== "win32") return [];
|
|
7632
|
+
if (map === null) return [];
|
|
7633
|
+
const claude = claudeHome();
|
|
7634
|
+
const repo = repoHome();
|
|
7635
|
+
const plan = [];
|
|
7636
|
+
for (const name of allSharedLinks(map)) {
|
|
7637
|
+
const localPath = join52(claude, name);
|
|
7638
|
+
let stat;
|
|
7639
|
+
try {
|
|
7640
|
+
stat = lstatSync17(localPath, { throwIfNoEntry: false });
|
|
7641
|
+
} catch {
|
|
7642
|
+
continue;
|
|
7643
|
+
}
|
|
7644
|
+
if (stat === void 0) continue;
|
|
7645
|
+
if (stat.isSymbolicLink()) continue;
|
|
7646
|
+
const repoPath = join52(repo, "shared", name);
|
|
7647
|
+
if (!existsSync40(repoPath)) continue;
|
|
7648
|
+
plan.push({ name, localPath, repoPath });
|
|
7649
|
+
}
|
|
7650
|
+
return plan;
|
|
7651
|
+
}
|
|
7652
|
+
|
|
7653
|
+
// src/links.deletions.ts
|
|
7654
|
+
init_config();
|
|
7655
|
+
import { existsSync as existsSync41, lstatSync as lstatSync18, rmSync as rmSync17 } from "node:fs";
|
|
7656
|
+
import { join as join53, relative as relative8, resolve as resolve4, sep as sep13 } from "node:path";
|
|
7657
|
+
init_utils();
|
|
7658
|
+
init_utils_fs();
|
|
7659
|
+
function isUnknown(declined, key) {
|
|
7660
|
+
return declined.some((prefix) => key === prefix || key.startsWith(`${prefix}/`));
|
|
7661
|
+
}
|
|
7662
|
+
function deletionFor(key, names, claude, sharedRoot) {
|
|
7663
|
+
const segments = key.split("/");
|
|
7664
|
+
const name = segments[0];
|
|
7665
|
+
if (!names.has(name)) return null;
|
|
7666
|
+
if (!existsSync41(join53(claude, name))) return null;
|
|
7667
|
+
if (segments.some((segment) => isDeniedName(ALWAYS_NEVER_SYNC, segment))) return null;
|
|
7668
|
+
const repoPath = resolve4(sharedRoot, key);
|
|
7669
|
+
const rel = relative8(join53(sharedRoot, name), repoPath);
|
|
7670
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep13}`)) return null;
|
|
7671
|
+
if (segments.some((segment) => segment === "" || segment === "." || segment === ".."))
|
|
7672
|
+
return null;
|
|
7673
|
+
if (!existsSync41(repoPath)) return null;
|
|
7674
|
+
return { name, localPath: join53(claude, key), repoPath };
|
|
7675
|
+
}
|
|
7676
|
+
function planSharedLinkDeletions(map) {
|
|
7677
|
+
if (process.platform !== "win32") return [];
|
|
7678
|
+
if (map === null) return [];
|
|
7679
|
+
const baseline = readSharedBaseline();
|
|
7680
|
+
if (baseline === null) return [];
|
|
7681
|
+
const claude = claudeHome();
|
|
7682
|
+
const sharedRoot = join53(repoHome(), "shared");
|
|
7683
|
+
const scan = enumerateLocalSharedScan(map);
|
|
7684
|
+
const present = new Set(Object.keys(scan.files).map((key) => key.toLowerCase()));
|
|
7685
|
+
const declined = scan.declined.map((path) => path.toLowerCase());
|
|
7686
|
+
const names = new Set(allSharedLinks(map));
|
|
7687
|
+
const plan = [];
|
|
7688
|
+
for (const key of Object.keys(baseline.files)) {
|
|
7689
|
+
const folded = key.toLowerCase();
|
|
7690
|
+
if (present.has(folded)) continue;
|
|
7691
|
+
if (isUnknown(declined, folded)) continue;
|
|
7692
|
+
const entry = deletionFor(key, names, claude, sharedRoot);
|
|
7693
|
+
if (entry !== null) plan.push(entry);
|
|
7694
|
+
}
|
|
7695
|
+
return plan;
|
|
7696
|
+
}
|
|
7697
|
+
function applySharedLinkDeletions(map, ts) {
|
|
7698
|
+
const repo = repoHome();
|
|
7699
|
+
for (const entry of planSharedLinkDeletions(map)) {
|
|
7700
|
+
try {
|
|
7701
|
+
if (!lstatSync18(entry.repoPath).isFile()) continue;
|
|
7702
|
+
backupRepoWrite(entry.repoPath, ts, repo);
|
|
7703
|
+
rmSync17(entry.repoPath, { force: true });
|
|
7704
|
+
} catch (err) {
|
|
7705
|
+
warn(`could not remove ${entry.repoPath}: ${err.message}`);
|
|
7706
|
+
}
|
|
7707
|
+
}
|
|
7708
|
+
}
|
|
7709
|
+
|
|
7710
|
+
// src/preview.ts
|
|
7711
|
+
init_config();
|
|
7712
|
+
import { existsSync as existsSync43 } from "node:fs";
|
|
7713
|
+
import { join as join55 } from "node:path";
|
|
7286
7714
|
|
|
7287
7715
|
// node_modules/diff/libesm/diff/base.js
|
|
7288
7716
|
var Diff = class {
|
|
@@ -7557,16 +7985,16 @@ function diffLinesToUnified(oldStr, newStr) {
|
|
|
7557
7985
|
|
|
7558
7986
|
// src/preview.skills.ts
|
|
7559
7987
|
init_config();
|
|
7560
|
-
import { existsSync as
|
|
7561
|
-
import { join as
|
|
7988
|
+
import { existsSync as existsSync42, readdirSync as readdirSync18 } from "node:fs";
|
|
7989
|
+
import { join as join54 } from "node:path";
|
|
7562
7990
|
function buildSkillsPreviewSection() {
|
|
7563
7991
|
const s = section("Skills");
|
|
7564
|
-
const sharedSkills =
|
|
7565
|
-
if (!
|
|
7566
|
-
const sharedNames =
|
|
7992
|
+
const sharedSkills = join54(repoHome(), "shared", "skills");
|
|
7993
|
+
if (!existsSync42(sharedSkills)) return s;
|
|
7994
|
+
const sharedNames = readdirSync18(sharedSkills, { encoding: "utf8" }).filter((name) => !isSkillExcluded(name)).sort((a, b) => a.localeCompare(b, "en"));
|
|
7567
7995
|
for (const name of sharedNames) addItem(s, name);
|
|
7568
|
-
const localSkills =
|
|
7569
|
-
const localOnly =
|
|
7996
|
+
const localSkills = join54(claudeHome(), "skills");
|
|
7997
|
+
const localOnly = existsSync42(localSkills) ? readdirSync18(localSkills, { encoding: "utf8" }).filter(
|
|
7570
7998
|
(name) => !isSkillExcluded(name) && !sharedNames.includes(name)
|
|
7571
7999
|
).length : 0;
|
|
7572
8000
|
if (localOnly > 0) {
|
|
@@ -7588,7 +8016,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
7588
8016
|
return lines.join("\n");
|
|
7589
8017
|
}
|
|
7590
8018
|
function readJsonOrNull(path) {
|
|
7591
|
-
if (!
|
|
8019
|
+
if (!existsSync43(path)) return null;
|
|
7592
8020
|
try {
|
|
7593
8021
|
return readJson(path);
|
|
7594
8022
|
} catch {
|
|
@@ -7602,12 +8030,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
7602
8030
|
}
|
|
7603
8031
|
const notes = [];
|
|
7604
8032
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
7605
|
-
if (hostOverrides === null &&
|
|
8033
|
+
if (hostOverrides === null && existsSync43(hostPath)) {
|
|
7606
8034
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
7607
8035
|
}
|
|
7608
8036
|
const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
|
|
7609
8037
|
const current = readJsonOrNull(settingsPath);
|
|
7610
|
-
if (current === null &&
|
|
8038
|
+
if (current === null && existsSync43(settingsPath)) {
|
|
7611
8039
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
7612
8040
|
}
|
|
7613
8041
|
const strippedCurrent = stripGsdHookEntries(current ?? {});
|
|
@@ -7623,6 +8051,12 @@ function formatLinkRow(e) {
|
|
|
7623
8051
|
if (e.kind === "copy") return `would copy ${e.from} -> ${e.to}`;
|
|
7624
8052
|
return `${e.kind} ${e.from} -> ${e.to}`;
|
|
7625
8053
|
}
|
|
8054
|
+
function formatCaptureRow(c) {
|
|
8055
|
+
return `would capture ${c.localPath} -> ${c.repoPath}`;
|
|
8056
|
+
}
|
|
8057
|
+
function formatDeletionRow(d) {
|
|
8058
|
+
return `would remove ${d.repoPath} (gone from ${d.localPath})`;
|
|
8059
|
+
}
|
|
7626
8060
|
function formatSessionRow(e) {
|
|
7627
8061
|
return e.kind === "overwrite" ? `overwrite ${e.dst} (from ${e.src})` : e.text;
|
|
7628
8062
|
}
|
|
@@ -7638,20 +8072,30 @@ function buildSettingsSectionForPreview(result) {
|
|
|
7638
8072
|
}
|
|
7639
8073
|
return s;
|
|
7640
8074
|
}
|
|
7641
|
-
function computePreview(ts, map, verb = "pull") {
|
|
8075
|
+
function computePreview(ts, map, verb = "pull", plans) {
|
|
7642
8076
|
const repo = repoHome();
|
|
7643
8077
|
const claude = claudeHome();
|
|
7644
8078
|
console.log(`would pull on host=${HOST} (preview; nothing applied)`);
|
|
7645
8079
|
console.log("");
|
|
7646
8080
|
const links = section("Symlinks");
|
|
8081
|
+
const { captures, deletions } = plans ?? {
|
|
8082
|
+
captures: planSharedLinkCaptures(map),
|
|
8083
|
+
deletions: planSharedLinkDeletions(map)
|
|
8084
|
+
};
|
|
8085
|
+
for (const capture of captures) {
|
|
8086
|
+
addItem(links, formatCaptureRow(capture));
|
|
8087
|
+
}
|
|
8088
|
+
for (const deletion of deletions) {
|
|
8089
|
+
addItem(links, formatDeletionRow(deletion));
|
|
8090
|
+
}
|
|
7647
8091
|
applySharedLinks(ts, map, {
|
|
7648
8092
|
dryRun: true,
|
|
7649
8093
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
7650
8094
|
});
|
|
7651
8095
|
const settingsResult = previewSettings(
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
8096
|
+
join55(repo, "shared", "settings.base.json"),
|
|
8097
|
+
join55(repo, "hosts", `${HOST}.json`),
|
|
8098
|
+
join55(claude, "settings.json")
|
|
7655
8099
|
);
|
|
7656
8100
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
7657
8101
|
const sessions = section("Sessions");
|
|
@@ -7667,7 +8111,7 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
7667
8111
|
const extras = section("Extras");
|
|
7668
8112
|
let extrasSkipped = 0;
|
|
7669
8113
|
let extrasUnmapped = 0;
|
|
7670
|
-
if (
|
|
8114
|
+
if (existsSync43(join55(repo, "path-map.json")) && existsSync43(join55(repo, "shared", "extras"))) {
|
|
7671
8115
|
const extrasResult = remapExtrasPull(ts, { dryRun: true });
|
|
7672
8116
|
for (const entry of extrasResult.wouldPull) {
|
|
7673
8117
|
addItem(extras, entry);
|
|
@@ -7684,6 +8128,135 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
7684
8128
|
return { unmapped: remapResult.unmapped, collisions: 0, localOnly };
|
|
7685
8129
|
}
|
|
7686
8130
|
|
|
8131
|
+
// src/commands.pull.win32.ts
|
|
8132
|
+
init_utils();
|
|
8133
|
+
init_utils_json();
|
|
8134
|
+
function readMapForMirror(mapPath) {
|
|
8135
|
+
if (!existsSync44(mapPath)) return { projects: {} };
|
|
8136
|
+
try {
|
|
8137
|
+
return readPathMap(mapPath);
|
|
8138
|
+
} catch {
|
|
8139
|
+
return null;
|
|
8140
|
+
}
|
|
8141
|
+
}
|
|
8142
|
+
function untrackedUnderShared(repo) {
|
|
8143
|
+
const out = gitProbe(["ls-files", "--others", "--exclude-standard", "-z", "--", "shared/"], repo);
|
|
8144
|
+
if (out === null) return null;
|
|
8145
|
+
return new Set(out.split("\0").filter((p) => p !== ""));
|
|
8146
|
+
}
|
|
8147
|
+
function newlyUntracked(before, after) {
|
|
8148
|
+
if (before === null || after === null) return [];
|
|
8149
|
+
return [...after].filter((p) => !before.has(p));
|
|
8150
|
+
}
|
|
8151
|
+
function reconcileSharedLinksBeforePull(repo, ts) {
|
|
8152
|
+
if (process.platform !== "win32") return [];
|
|
8153
|
+
const before = untrackedUnderShared(repo);
|
|
8154
|
+
try {
|
|
8155
|
+
const map = readMapForMirror(join56(repo, "path-map.json"));
|
|
8156
|
+
stageLocalSharedEdits(map, ts);
|
|
8157
|
+
applySharedLinkDeletions(map, ts);
|
|
8158
|
+
} catch (err) {
|
|
8159
|
+
warn(`could not reconcile local shared edits before the pull: ${err.message}`);
|
|
8160
|
+
}
|
|
8161
|
+
return newlyUntracked(before, untrackedUnderShared(repo));
|
|
8162
|
+
}
|
|
8163
|
+
function planSharedReconcileBeforePull(repo) {
|
|
8164
|
+
const map = readMapForMirror(join56(repo, "path-map.json"));
|
|
8165
|
+
return { captures: planSharedLinkCaptures(map), deletions: planSharedLinkDeletions(map) };
|
|
8166
|
+
}
|
|
8167
|
+
|
|
8168
|
+
// src/commands.pull.collision.ts
|
|
8169
|
+
init_config();
|
|
8170
|
+
init_exit_codes();
|
|
8171
|
+
import { existsSync as existsSync45, lstatSync as lstatSync19, readFileSync as readFileSync20, rmSync as rmSync18 } from "node:fs";
|
|
8172
|
+
import { join as join57 } from "node:path";
|
|
8173
|
+
init_utils();
|
|
8174
|
+
var SHARED_PREFIX = /^shared\//;
|
|
8175
|
+
var EXTENSION = /(\.[^.]+)?$/;
|
|
8176
|
+
function localOriginal(repoRel) {
|
|
8177
|
+
return `~/.claude/${repoRel.replace(SHARED_PREFIX, "")}`;
|
|
8178
|
+
}
|
|
8179
|
+
function renameSuggestion(localPath) {
|
|
8180
|
+
const base = localPath.slice(localPath.lastIndexOf("/") + 1);
|
|
8181
|
+
return base.replace(EXTENSION, ".local$1");
|
|
8182
|
+
}
|
|
8183
|
+
function collisionSteps(locals) {
|
|
8184
|
+
const single = locals.length === 1;
|
|
8185
|
+
const target = single ? locals[0] : "each file listed above";
|
|
8186
|
+
const rename = single ? renameSuggestion(locals[0]) : "mine.md becomes mine.local.md";
|
|
8187
|
+
const combine = single ? "combine the two files" : "combine each pair";
|
|
8188
|
+
return `Keep the incoming version (yours is set aside, not deleted):
|
|
8189
|
+
1. move ${target} outside ~/.claude/
|
|
8190
|
+
2. nomad pull
|
|
8191
|
+
|
|
8192
|
+
Keep both and merge them yourself:
|
|
8193
|
+
1. rename ${target} to a name the repo does not use (${rename})
|
|
8194
|
+
2. nomad pull (brings in the other machine's version)
|
|
8195
|
+
3. ${combine}, then nomad push`;
|
|
8196
|
+
}
|
|
8197
|
+
function collisionAdvice(single, cleared) {
|
|
8198
|
+
const copies = single ? "nomad's copy" : "nomad's copies";
|
|
8199
|
+
const them = single ? "it" : "them";
|
|
8200
|
+
const fate = cleared ? "which nomad has now removed for you" : `which nomad could not remove (the warning above says why), so delete ${them} by hand if the next pull stops here again`;
|
|
8201
|
+
const moving = single ? "Moving only that copy would not have cleared this anyway: the next pull re-copies your local file over it before fetching. The file to move is the one under ~/.claude/." : "Moving only those copies would not have cleared this anyway: the next pull re-copies your local files over them before fetching. The files to move are the ones under ~/.claude/.";
|
|
8202
|
+
return `Git's advice above refers to ${copies} inside the sync repo, ${fate}. ${moving}`;
|
|
8203
|
+
}
|
|
8204
|
+
function untrackedCollisionRunbookText(repoRelPaths, cleared) {
|
|
8205
|
+
const locals = repoRelPaths.map(localOriginal);
|
|
8206
|
+
const single = locals.length === 1;
|
|
8207
|
+
const claim = single ? "One of those copies has the same name as a file the incoming update also adds:" : "Each of the copies below has the same name as a file the incoming update also adds:";
|
|
8208
|
+
const intact = single ? "Your file is still exactly as you left it." : "Your files are still exactly as you left them.";
|
|
8209
|
+
const listed = locals.map((p) => ` ${p}`).join("\n");
|
|
8210
|
+
return `nomad pull could not fetch. Your machine had unpublished shared-config edits, so nomad copied them into the sync repo first, the way an edit on macOS or Linux is already in the repo before a pull starts. ${claim}
|
|
8211
|
+
|
|
8212
|
+
${listed}
|
|
8213
|
+
|
|
8214
|
+
Nothing changed. Your ~/.claude/ config is untouched, the update did not land, and nothing was published. ${intact}
|
|
8215
|
+
|
|
8216
|
+
${collisionAdvice(single, cleared)}
|
|
8217
|
+
|
|
8218
|
+
` + collisionSteps(locals);
|
|
8219
|
+
}
|
|
8220
|
+
function addedByIncomingUpdate(repo, created) {
|
|
8221
|
+
return created.filter((rel) => gitProbe(["cat-file", "-e", `FETCH_HEAD:${rel}`], repo) !== null);
|
|
8222
|
+
}
|
|
8223
|
+
function isContainedMirrorPath(rel) {
|
|
8224
|
+
return rel.startsWith("shared/") && isSafeRelPath(rel);
|
|
8225
|
+
}
|
|
8226
|
+
function matchesLocalOriginal(repo, rel) {
|
|
8227
|
+
try {
|
|
8228
|
+
const local = join57(claudeHome(), rel.replace(SHARED_PREFIX, ""));
|
|
8229
|
+
return readFileSync20(join57(repo, rel)).equals(readFileSync20(local));
|
|
8230
|
+
} catch {
|
|
8231
|
+
return false;
|
|
8232
|
+
}
|
|
8233
|
+
}
|
|
8234
|
+
function removeMirroredCopies(repo, repoRelPaths) {
|
|
8235
|
+
for (const rel of repoRelPaths.filter(isContainedMirrorPath)) {
|
|
8236
|
+
const abs = join57(repo, rel);
|
|
8237
|
+
try {
|
|
8238
|
+
if (lstatSync19(abs, { throwIfNoEntry: false })?.isFile() !== true) continue;
|
|
8239
|
+
if (!matchesLocalOriginal(repo, rel)) continue;
|
|
8240
|
+
rmSync18(abs, { force: true });
|
|
8241
|
+
} catch (err) {
|
|
8242
|
+
warn(`could not remove nomad's copy at ${abs}: ${err.message}`);
|
|
8243
|
+
}
|
|
8244
|
+
}
|
|
8245
|
+
return repoRelPaths.every((rel) => !existsSync45(join57(repo, rel)));
|
|
8246
|
+
}
|
|
8247
|
+
function pullWithCollisionRunbook(repo, mirrored) {
|
|
8248
|
+
try {
|
|
8249
|
+
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
8250
|
+
} catch (err) {
|
|
8251
|
+
const colliding = addedByIncomingUpdate(repo, mirrored);
|
|
8252
|
+
if (colliding.length === 0) throw err;
|
|
8253
|
+
const cleared = removeMirroredCopies(repo, colliding);
|
|
8254
|
+
throw new NomadFatal(untrackedCollisionRunbookText(colliding, cleared), {
|
|
8255
|
+
code: EXIT.GENERIC_FAILURE
|
|
8256
|
+
});
|
|
8257
|
+
}
|
|
8258
|
+
}
|
|
8259
|
+
|
|
7687
8260
|
// src/commands.pull.ts
|
|
7688
8261
|
init_commands_pull_wedge();
|
|
7689
8262
|
|
|
@@ -7694,9 +8267,9 @@ init_utils_fs();
|
|
|
7694
8267
|
|
|
7695
8268
|
// src/commands.pull.recovery.git.ts
|
|
7696
8269
|
init_utils();
|
|
7697
|
-
import { execFileSync as
|
|
8270
|
+
import { execFileSync as execFileSync22 } from "node:child_process";
|
|
7698
8271
|
function gitCapture(args, cwd) {
|
|
7699
|
-
return
|
|
8272
|
+
return execFileSync22("git", args, {
|
|
7700
8273
|
cwd,
|
|
7701
8274
|
stdio: ["ignore", "pipe", "pipe"],
|
|
7702
8275
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -7849,6 +8422,7 @@ function capturePrePostHeads(repo, rebase) {
|
|
|
7849
8422
|
}
|
|
7850
8423
|
function buildWetPullSections(ts, map, prePostHeads) {
|
|
7851
8424
|
applySharedLinks(ts, map);
|
|
8425
|
+
writeSharedBaseline(map);
|
|
7852
8426
|
const { label } = regenerateSettings(ts);
|
|
7853
8427
|
syncSkillsPull(ts, prePostHeads);
|
|
7854
8428
|
const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
|
|
@@ -7888,22 +8462,6 @@ function handleWedge(repo, forceRemote) {
|
|
|
7888
8462
|
const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
|
|
7889
8463
|
die(wedgeMarkerRunbookText(state), { code: EXIT.CONFLICT });
|
|
7890
8464
|
}
|
|
7891
|
-
function readMapForMirror(mapPath) {
|
|
7892
|
-
if (!existsSync42(mapPath)) return { projects: {} };
|
|
7893
|
-
try {
|
|
7894
|
-
return readPathMap(mapPath);
|
|
7895
|
-
} catch {
|
|
7896
|
-
return null;
|
|
7897
|
-
}
|
|
7898
|
-
}
|
|
7899
|
-
function mirrorSharedLinksBeforePull(repo, ts) {
|
|
7900
|
-
if (process.platform !== "win32") return;
|
|
7901
|
-
try {
|
|
7902
|
-
stageLocalSharedEdits(readMapForMirror(join53(repo, "path-map.json")), ts);
|
|
7903
|
-
} catch (err) {
|
|
7904
|
-
warn(`could not stage local shared edits before the pull: ${err.message}`);
|
|
7905
|
-
}
|
|
7906
|
-
}
|
|
7907
8465
|
function runPullCore(opts = {}) {
|
|
7908
8466
|
const dryRun = opts.dryRun === true;
|
|
7909
8467
|
const forceRemote = opts.forceRemote === true;
|
|
@@ -7913,7 +8471,7 @@ function runPullCore(opts = {}) {
|
|
|
7913
8471
|
const ts = freshBackupTs(backup);
|
|
7914
8472
|
handleWedge(repo, forceRemote);
|
|
7915
8473
|
if (!dryRun) {
|
|
7916
|
-
const backupRoot =
|
|
8474
|
+
const backupRoot = join58(backup, ts);
|
|
7917
8475
|
try {
|
|
7918
8476
|
mkdirSync14(backupRoot, { recursive: true });
|
|
7919
8477
|
} catch (err) {
|
|
@@ -7925,16 +8483,17 @@ function runPullCore(opts = {}) {
|
|
|
7925
8483
|
dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
|
|
7926
8484
|
);
|
|
7927
8485
|
}
|
|
7928
|
-
|
|
8486
|
+
const mirrored = !dryRun && !forceRemote ? reconcileSharedLinksBeforePull(repo, ts) : [];
|
|
8487
|
+
const sharedPlans = dryRun ? planSharedReconcileBeforePull(repo) : void 0;
|
|
7929
8488
|
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
7930
|
-
|
|
8489
|
+
pullWithCollisionRunbook(repo, mirrored);
|
|
7931
8490
|
});
|
|
7932
8491
|
assertNoAutostashConflict(repo, "nomad pull");
|
|
7933
|
-
const mapPath =
|
|
7934
|
-
const map =
|
|
8492
|
+
const mapPath = join58(repo, "path-map.json");
|
|
8493
|
+
const map = existsSync46(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
7935
8494
|
const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
|
|
7936
8495
|
if (dryRun) {
|
|
7937
|
-
computePreview(ts, map, "pull");
|
|
8496
|
+
computePreview(ts, map, "pull", sharedPlans);
|
|
7938
8497
|
return { tag: "dry" };
|
|
7939
8498
|
}
|
|
7940
8499
|
const { sections, localOnly, settingsLabel, unmapped, extrasSkipped } = buildWetPullSections(
|
|
@@ -7956,8 +8515,8 @@ function runPullCore(opts = {}) {
|
|
|
7956
8515
|
}
|
|
7957
8516
|
function cmdPull(opts = {}) {
|
|
7958
8517
|
const repo = repoHome();
|
|
7959
|
-
if (!
|
|
7960
|
-
if (!
|
|
8518
|
+
if (!existsSync46(repo)) die(`repo not cloned at ${repo}`);
|
|
8519
|
+
if (!existsSync46(join58(repo, "shared", "settings.base.json"))) {
|
|
7961
8520
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
7962
8521
|
}
|
|
7963
8522
|
const handle = acquireLock("pull");
|
|
@@ -7980,13 +8539,13 @@ function cmdPull(opts = {}) {
|
|
|
7980
8539
|
|
|
7981
8540
|
// src/commands.push.ts
|
|
7982
8541
|
init_config();
|
|
7983
|
-
import { existsSync as
|
|
7984
|
-
import { join as
|
|
8542
|
+
import { existsSync as existsSync50 } from "node:fs";
|
|
8543
|
+
import { join as join63 } from "node:path";
|
|
7985
8544
|
|
|
7986
8545
|
// src/commands.push.selection.ts
|
|
7987
8546
|
init_config();
|
|
7988
|
-
import { existsSync as
|
|
7989
|
-
import { join as
|
|
8547
|
+
import { existsSync as existsSync47, statSync as statSync11 } from "node:fs";
|
|
8548
|
+
import { join as join59 } from "node:path";
|
|
7990
8549
|
init_utils_json();
|
|
7991
8550
|
function buildCurrentMap(map) {
|
|
7992
8551
|
const current = {};
|
|
@@ -7995,8 +8554,8 @@ function buildCurrentMap(map) {
|
|
|
7995
8554
|
for (const [, hostMap] of Object.entries(map.projects)) {
|
|
7996
8555
|
const localPath = hostMap[HOST];
|
|
7997
8556
|
if (!localPath) continue;
|
|
7998
|
-
const localDir =
|
|
7999
|
-
if (!
|
|
8557
|
+
const localDir = join59(claude, "projects", encodePath(localPath));
|
|
8558
|
+
if (!existsSync47(localDir)) continue;
|
|
8000
8559
|
for (const f of enumerateSourceFiles(localDir)) {
|
|
8001
8560
|
const st = statSync11(f);
|
|
8002
8561
|
current[f] = { size: st.size, mtime: st.mtimeMs };
|
|
@@ -8027,7 +8586,7 @@ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
|
|
|
8027
8586
|
};
|
|
8028
8587
|
}
|
|
8029
8588
|
function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
|
|
8030
|
-
const map =
|
|
8589
|
+
const map = existsSync47(mapPath) ? readPathMap(mapPath) : null;
|
|
8031
8590
|
const { selection, newManifest } = computePushSelection(
|
|
8032
8591
|
map,
|
|
8033
8592
|
old,
|
|
@@ -8055,7 +8614,7 @@ function isAllowed(path, allowed) {
|
|
|
8055
8614
|
}
|
|
8056
8615
|
function blockSetFor(segments) {
|
|
8057
8616
|
if (segments[0] !== "shared" || segments[1] !== "extras") return NEVER_SYNC;
|
|
8058
|
-
return segments[3]
|
|
8617
|
+
return isClaudeExtraName(segments[3] ?? "") ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
8059
8618
|
}
|
|
8060
8619
|
function isNeverSync(path) {
|
|
8061
8620
|
const segments = path.split("/");
|
|
@@ -8105,7 +8664,11 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
8105
8664
|
...Object.entries(map.extras ?? {}).flatMap(
|
|
8106
8665
|
([l, names]) => names.filter((n) => extrasWhitelist.includes(n)).flatMap((n) => [`shared/extras/${l}/${n}`, `shared/extras/${l}/${n}/`])
|
|
8107
8666
|
),
|
|
8108
|
-
|
|
8667
|
+
// Predicate passed by reference, not wrapped in an arrow: `isValidSharedDir`
|
|
8668
|
+
// is a type guard, and an arrow returning its result is just a boolean, so
|
|
8669
|
+
// wrapping it would leave `d` as `unknown` and let a non-string reach the
|
|
8670
|
+
// template literal as "[object Object]".
|
|
8671
|
+
...sharedDirEntries(map).filter(isValidSharedDir).map((d) => `shared/${d}/`)
|
|
8109
8672
|
];
|
|
8110
8673
|
const neverSyncHits = [];
|
|
8111
8674
|
const violations = [];
|
|
@@ -8129,14 +8692,14 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
8129
8692
|
|
|
8130
8693
|
// src/commands.push.settings.ts
|
|
8131
8694
|
init_config();
|
|
8132
|
-
import { existsSync as
|
|
8133
|
-
import { join as
|
|
8695
|
+
import { existsSync as existsSync48 } from "node:fs";
|
|
8696
|
+
import { join as join60 } from "node:path";
|
|
8134
8697
|
init_utils();
|
|
8135
8698
|
init_utils_fs();
|
|
8136
8699
|
init_utils_json();
|
|
8137
8700
|
function stripGsdHooksFromBase(repo, backup) {
|
|
8138
|
-
const basePath =
|
|
8139
|
-
if (!
|
|
8701
|
+
const basePath = join60(repo, "shared", "settings.base.json");
|
|
8702
|
+
if (!existsSync48(basePath)) return;
|
|
8140
8703
|
let base;
|
|
8141
8704
|
try {
|
|
8142
8705
|
base = readJson(basePath);
|
|
@@ -8150,14 +8713,14 @@ function stripGsdHooksFromBase(repo, backup) {
|
|
|
8150
8713
|
writeJsonAtomic(basePath, stripped);
|
|
8151
8714
|
}
|
|
8152
8715
|
function reportSettingsAheadDrift(repo) {
|
|
8153
|
-
const basePath =
|
|
8154
|
-
if (!
|
|
8155
|
-
const settingsPath =
|
|
8156
|
-
if (!
|
|
8716
|
+
const basePath = join60(repo, "shared", "settings.base.json");
|
|
8717
|
+
if (!existsSync48(basePath)) return;
|
|
8718
|
+
const settingsPath = join60(claudeHome(), "settings.json");
|
|
8719
|
+
if (!existsSync48(settingsPath)) return;
|
|
8157
8720
|
try {
|
|
8158
8721
|
const base = readJson(basePath);
|
|
8159
|
-
const hostPath =
|
|
8160
|
-
const overrides =
|
|
8722
|
+
const hostPath = join60(repo, "hosts", `${HOST}.json`);
|
|
8723
|
+
const overrides = existsSync48(hostPath) ? readJson(hostPath) : {};
|
|
8161
8724
|
const merged = deepMerge(base, overrides);
|
|
8162
8725
|
const settings = readJson(settingsPath);
|
|
8163
8726
|
const { ahead } = classifySettingsDrift(merged, settings);
|
|
@@ -8174,12 +8737,12 @@ function reportSettingsAheadDrift(repo) {
|
|
|
8174
8737
|
// src/commands.push.guards.ts
|
|
8175
8738
|
init_push_checks();
|
|
8176
8739
|
init_utils();
|
|
8177
|
-
import { join as
|
|
8740
|
+
import { join as join61, relative as relative9 } from "node:path";
|
|
8178
8741
|
function guardGitlinks(repo) {
|
|
8179
|
-
const gitlinks = findGitlinks(
|
|
8742
|
+
const gitlinks = findGitlinks(join61(repo, "shared"));
|
|
8180
8743
|
if (gitlinks.length === 0) return;
|
|
8181
8744
|
for (const p of gitlinks) {
|
|
8182
|
-
const rel =
|
|
8745
|
+
const rel = relative9(repo, p).replaceAll("\\", "/");
|
|
8183
8746
|
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
8184
8747
|
}
|
|
8185
8748
|
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
@@ -8208,7 +8771,7 @@ init_config();
|
|
|
8208
8771
|
|
|
8209
8772
|
// src/push-global-config.ts
|
|
8210
8773
|
init_config();
|
|
8211
|
-
import { execFileSync as
|
|
8774
|
+
import { execFileSync as execFileSync23 } from "node:child_process";
|
|
8212
8775
|
var STATUS_LABELS = {
|
|
8213
8776
|
A: "add",
|
|
8214
8777
|
M: "modify",
|
|
@@ -8248,7 +8811,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
|
|
|
8248
8811
|
}
|
|
8249
8812
|
function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
|
|
8250
8813
|
const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
|
|
8251
|
-
const raw =
|
|
8814
|
+
const raw = execFileSync23("git", args, {
|
|
8252
8815
|
cwd: repoHome2,
|
|
8253
8816
|
stdio: ["ignore", "pipe", "pipe"]
|
|
8254
8817
|
}).toString();
|
|
@@ -8286,9 +8849,9 @@ init_color();
|
|
|
8286
8849
|
init_config();
|
|
8287
8850
|
init_config_sharedDirs_guard();
|
|
8288
8851
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
8289
|
-
import { copyFileSync, existsSync as
|
|
8852
|
+
import { copyFileSync, existsSync as existsSync49, lstatSync as lstatSync20, mkdirSync as mkdirSync15, readdirSync as readdirSync19, rmSync as rmSync19 } from "node:fs";
|
|
8290
8853
|
import { homedir as homedir7 } from "node:os";
|
|
8291
|
-
import { join as
|
|
8854
|
+
import { join as join62, relative as relative10, sep as sep14 } from "node:path";
|
|
8292
8855
|
init_push_leak_verdict();
|
|
8293
8856
|
init_push_gitleaks();
|
|
8294
8857
|
init_utils_fs();
|
|
@@ -8296,11 +8859,11 @@ init_utils_json();
|
|
|
8296
8859
|
var NOTHING_TO_SCAN_ROW = `${dim(infoGlyph)} nothing to scan, no leaks`;
|
|
8297
8860
|
function stageSessionDir(localDir, dstDir, changed) {
|
|
8298
8861
|
if (changed !== void 0) {
|
|
8299
|
-
const prefix = `${localDir}${
|
|
8862
|
+
const prefix = `${localDir}${sep14}`;
|
|
8300
8863
|
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
8301
8864
|
if (matching.length === 0) return false;
|
|
8302
8865
|
for (const src of matching) {
|
|
8303
|
-
copyFileAtomic(src,
|
|
8866
|
+
copyFileAtomic(src, join62(dstDir, relative10(localDir, src)));
|
|
8304
8867
|
}
|
|
8305
8868
|
return true;
|
|
8306
8869
|
}
|
|
@@ -8316,14 +8879,14 @@ function stageSessions(tmpRoot, map, changed) {
|
|
|
8316
8879
|
if (!p || p === "TBD") continue;
|
|
8317
8880
|
reverse.set(encodePath(p), logical);
|
|
8318
8881
|
}
|
|
8319
|
-
const localProjects =
|
|
8320
|
-
if (!
|
|
8882
|
+
const localProjects = join62(claudeHome(), "projects");
|
|
8883
|
+
if (!existsSync49(localProjects)) return 0;
|
|
8321
8884
|
let staged = 0;
|
|
8322
|
-
for (const dir of
|
|
8885
|
+
for (const dir of readdirSync19(localProjects)) {
|
|
8323
8886
|
const logical = reverse.get(dir);
|
|
8324
8887
|
if (!logical) continue;
|
|
8325
|
-
const localDir =
|
|
8326
|
-
const dstDir =
|
|
8888
|
+
const localDir = join62(localProjects, dir);
|
|
8889
|
+
const dstDir = join62(tmpRoot, "shared", "projects", logical);
|
|
8327
8890
|
if (stageSessionDir(localDir, dstDir, changed)) staged++;
|
|
8328
8891
|
}
|
|
8329
8892
|
return staged;
|
|
@@ -8339,9 +8902,9 @@ function stageExtras(tmpRoot, map) {
|
|
|
8339
8902
|
if (!localRoot || localRoot === "TBD") continue;
|
|
8340
8903
|
for (const dirname14 of dirnames) {
|
|
8341
8904
|
if (!whitelist.includes(dirname14)) continue;
|
|
8342
|
-
const src =
|
|
8343
|
-
if (!
|
|
8344
|
-
const dst =
|
|
8905
|
+
const src = join62(localRoot, dirname14);
|
|
8906
|
+
if (!existsSync49(src)) continue;
|
|
8907
|
+
const dst = join62(tmpRoot, "shared", "extras", logical, dirname14);
|
|
8345
8908
|
copyExtras(src, dst);
|
|
8346
8909
|
staged++;
|
|
8347
8910
|
}
|
|
@@ -8349,19 +8912,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
8349
8912
|
return staged;
|
|
8350
8913
|
}
|
|
8351
8914
|
function stageSkills(tmpRoot) {
|
|
8352
|
-
const localSkills =
|
|
8353
|
-
const stat =
|
|
8915
|
+
const localSkills = join62(claudeHome(), "skills");
|
|
8916
|
+
const stat = lstatSync20(localSkills, { throwIfNoEntry: false });
|
|
8354
8917
|
if (stat === void 0 || stat.isSymbolicLink()) return 0;
|
|
8355
|
-
const names =
|
|
8918
|
+
const names = readdirSync19(localSkills, { encoding: "utf8" }).filter((n) => !isSkillExcluded(n));
|
|
8356
8919
|
if (names.length === 0) return 0;
|
|
8357
|
-
copySkillsPush(localSkills,
|
|
8920
|
+
copySkillsPush(localSkills, join62(tmpRoot, "shared", "skills"));
|
|
8358
8921
|
return names.length;
|
|
8359
8922
|
}
|
|
8360
8923
|
function previewPushLeaks(map, opts = {}) {
|
|
8361
|
-
const cacheDir =
|
|
8924
|
+
const cacheDir = join62(homedir7(), ".cache", "claude-nomad");
|
|
8362
8925
|
mkdirSync15(cacheDir, { recursive: true });
|
|
8363
8926
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes4(4).toString("hex")}`;
|
|
8364
|
-
const tmpRoot =
|
|
8927
|
+
const tmpRoot = join62(cacheDir, `push-preview-tree-${stamp}`);
|
|
8365
8928
|
try {
|
|
8366
8929
|
const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
|
|
8367
8930
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
@@ -8369,9 +8932,9 @@ function previewPushLeaks(map, opts = {}) {
|
|
|
8369
8932
|
if (sessionCount + extrasCount + skillCount === 0) {
|
|
8370
8933
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
8371
8934
|
}
|
|
8372
|
-
const ignoreFile =
|
|
8373
|
-
if (
|
|
8374
|
-
copyFileSync(ignoreFile,
|
|
8935
|
+
const ignoreFile = join62(repoHome(), ".gitleaksignore");
|
|
8936
|
+
if (existsSync49(ignoreFile)) {
|
|
8937
|
+
copyFileSync(ignoreFile, join62(tmpRoot, ".gitleaksignore"));
|
|
8375
8938
|
}
|
|
8376
8939
|
let findings;
|
|
8377
8940
|
try {
|
|
@@ -8384,7 +8947,7 @@ function previewPushLeaks(map, opts = {}) {
|
|
|
8384
8947
|
}
|
|
8385
8948
|
return verdictFromFindings(findings);
|
|
8386
8949
|
} finally {
|
|
8387
|
-
|
|
8950
|
+
rmSync19(tmpRoot, { recursive: true, force: true });
|
|
8388
8951
|
}
|
|
8389
8952
|
}
|
|
8390
8953
|
|
|
@@ -8499,7 +9062,7 @@ async function runPushCore(opts = {}) {
|
|
|
8499
9062
|
const scannerVersion = probeGitleaks();
|
|
8500
9063
|
const configHash = computeConfigHash();
|
|
8501
9064
|
const old = readManifest(manifestPath());
|
|
8502
|
-
const mapPath =
|
|
9065
|
+
const mapPath = join63(repo, "path-map.json");
|
|
8503
9066
|
const { map, selection, newManifest } = loadSelectionForPush(
|
|
8504
9067
|
mapPath,
|
|
8505
9068
|
old,
|
|
@@ -8551,7 +9114,7 @@ async function cmdPush(opts = {}) {
|
|
|
8551
9114
|
opts.allowRule
|
|
8552
9115
|
);
|
|
8553
9116
|
const repo = repoHome();
|
|
8554
|
-
if (!
|
|
9117
|
+
if (!existsSync50(repo)) die(`repo not cloned at ${repo}`);
|
|
8555
9118
|
const handle = acquireLock("push");
|
|
8556
9119
|
if (handle === null) process.exit(0);
|
|
8557
9120
|
try {
|
|
@@ -8569,8 +9132,8 @@ async function cmdPush(opts = {}) {
|
|
|
8569
9132
|
}
|
|
8570
9133
|
|
|
8571
9134
|
// src/commands.sync.ts
|
|
8572
|
-
import { existsSync as
|
|
8573
|
-
import { join as
|
|
9135
|
+
import { existsSync as existsSync51 } from "node:fs";
|
|
9136
|
+
import { join as join64 } from "node:path";
|
|
8574
9137
|
init_config();
|
|
8575
9138
|
init_color();
|
|
8576
9139
|
init_utils();
|
|
@@ -8704,8 +9267,8 @@ async function cmdSync(opts = {}) {
|
|
|
8704
9267
|
const dryRun = opts.dryRun === true;
|
|
8705
9268
|
const verbose = opts.verbose === true;
|
|
8706
9269
|
const repo = repoHome();
|
|
8707
|
-
if (!
|
|
8708
|
-
if (!
|
|
9270
|
+
if (!existsSync51(repo)) die(`repo not cloned at ${repo}`);
|
|
9271
|
+
if (!existsSync51(join64(repo, "shared", "settings.base.json"))) {
|
|
8709
9272
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
8710
9273
|
}
|
|
8711
9274
|
const handle = acquireLock("sync");
|
|
@@ -8729,9 +9292,9 @@ async function cmdSync(opts = {}) {
|
|
|
8729
9292
|
}
|
|
8730
9293
|
|
|
8731
9294
|
// src/commands.update.ts
|
|
8732
|
-
import { execFileSync as
|
|
9295
|
+
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
8733
9296
|
init_utils();
|
|
8734
|
-
function readInstalledVersion(run =
|
|
9297
|
+
function readInstalledVersion(run = execFileSync24) {
|
|
8735
9298
|
const isWin = process.platform === "win32";
|
|
8736
9299
|
try {
|
|
8737
9300
|
return run(isWin ? "nomad.cmd" : "nomad", ["--version"], {
|
|
@@ -8742,7 +9305,7 @@ function readInstalledVersion(run = execFileSync23) {
|
|
|
8742
9305
|
return null;
|
|
8743
9306
|
}
|
|
8744
9307
|
}
|
|
8745
|
-
function cmdUpdate(currentVersion, run =
|
|
9308
|
+
function cmdUpdate(currentVersion, run = execFileSync24) {
|
|
8746
9309
|
console.log(`Updating claude-nomad v${currentVersion}...`);
|
|
8747
9310
|
const isWin = process.platform === "win32";
|
|
8748
9311
|
try {
|
|
@@ -8781,8 +9344,8 @@ init_config();
|
|
|
8781
9344
|
// src/crash-report.write.ts
|
|
8782
9345
|
init_config();
|
|
8783
9346
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
8784
|
-
import { mkdirSync as mkdirSync16, readdirSync as
|
|
8785
|
-
import { join as
|
|
9347
|
+
import { mkdirSync as mkdirSync16, readdirSync as readdirSync20, statSync as statSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
|
|
9348
|
+
import { join as join66 } from "node:path";
|
|
8786
9349
|
|
|
8787
9350
|
// src/crash-report.ts
|
|
8788
9351
|
var CRASH_MAX_STACK_LINES = 50;
|
|
@@ -8865,17 +9428,17 @@ function buildCrashReport(input) {
|
|
|
8865
9428
|
}
|
|
8866
9429
|
|
|
8867
9430
|
// src/crash-report.redact.ts
|
|
8868
|
-
import { mkdtempSync as mkdtempSync3, rmSync as
|
|
9431
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync20, writeFileSync as writeFileSync10 } from "node:fs";
|
|
8869
9432
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
8870
|
-
import { join as
|
|
9433
|
+
import { join as join65 } from "node:path";
|
|
8871
9434
|
init_push_gitleaks_scan();
|
|
8872
9435
|
var CRASH_SCAN_TIMEOUT_MS = 3e3;
|
|
8873
9436
|
var SCAN_UNAVAILABLE_ADVISORY = "\n\n[gitleaks value-based scan unavailable; only structural redaction applied. Review before sharing this file publicly.]\n";
|
|
8874
9437
|
function redactWithGitleaks(text, scan = scanFile) {
|
|
8875
9438
|
let dir;
|
|
8876
9439
|
try {
|
|
8877
|
-
dir = mkdtempSync3(
|
|
8878
|
-
const tmp =
|
|
9440
|
+
dir = mkdtempSync3(join65(tmpdir2(), "nomad-crash-scan-"));
|
|
9441
|
+
const tmp = join65(dir, "crash.txt");
|
|
8879
9442
|
writeFileSync10(tmp, text, { mode: 384 });
|
|
8880
9443
|
const findings = scan(tmp, false, CRASH_SCAN_TIMEOUT_MS);
|
|
8881
9444
|
if (findings === null) return text + SCAN_UNAVAILABLE_ADVISORY;
|
|
@@ -8885,7 +9448,7 @@ function redactWithGitleaks(text, scan = scanFile) {
|
|
|
8885
9448
|
} finally {
|
|
8886
9449
|
if (dir !== void 0) {
|
|
8887
9450
|
try {
|
|
8888
|
-
|
|
9451
|
+
rmSync20(dir, { recursive: true, force: true });
|
|
8889
9452
|
} catch {
|
|
8890
9453
|
}
|
|
8891
9454
|
}
|
|
@@ -8898,9 +9461,9 @@ init_utils();
|
|
|
8898
9461
|
var CRASH_RETENTION_KEEP = 20;
|
|
8899
9462
|
function listCrashFiles(dir = crashDir()) {
|
|
8900
9463
|
try {
|
|
8901
|
-
return
|
|
9464
|
+
return readdirSync20(dir).flatMap((name) => {
|
|
8902
9465
|
try {
|
|
8903
|
-
return [{ name, mtimeMs: statSync12(
|
|
9466
|
+
return [{ name, mtimeMs: statSync12(join66(dir, name)).mtimeMs }];
|
|
8904
9467
|
} catch {
|
|
8905
9468
|
return [];
|
|
8906
9469
|
}
|
|
@@ -8914,7 +9477,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
8914
9477
|
const targets = prunableByCount(files, keep);
|
|
8915
9478
|
for (const name of targets) {
|
|
8916
9479
|
try {
|
|
8917
|
-
unlinkSync2(
|
|
9480
|
+
unlinkSync2(join66(dir, name));
|
|
8918
9481
|
} catch {
|
|
8919
9482
|
}
|
|
8920
9483
|
}
|
|
@@ -8922,7 +9485,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
8922
9485
|
function writeCrashReport(text, dir = crashDir()) {
|
|
8923
9486
|
mkdirSync16(dir, { recursive: true, mode: 448 });
|
|
8924
9487
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes5(4).toString("hex")}`;
|
|
8925
|
-
const path =
|
|
9488
|
+
const path = join66(dir, `crash-${stamp}.txt`);
|
|
8926
9489
|
writeFileSync11(path, text, { mode: 384 });
|
|
8927
9490
|
pruneCrashDir(dir);
|
|
8928
9491
|
return path;
|
|
@@ -8954,18 +9517,18 @@ function handleCrash(err, argv, opts) {
|
|
|
8954
9517
|
|
|
8955
9518
|
// src/diff.ts
|
|
8956
9519
|
init_config();
|
|
8957
|
-
import { existsSync as
|
|
8958
|
-
import { join as
|
|
9520
|
+
import { existsSync as existsSync52 } from "node:fs";
|
|
9521
|
+
import { join as join67 } from "node:path";
|
|
8959
9522
|
init_utils();
|
|
8960
9523
|
init_utils_fs();
|
|
8961
9524
|
init_utils_json();
|
|
8962
9525
|
function cmdDiff() {
|
|
8963
9526
|
try {
|
|
8964
9527
|
const repo = repoHome();
|
|
8965
|
-
if (!
|
|
9528
|
+
if (!existsSync52(repo)) die(`repo not cloned at ${repo}`);
|
|
8966
9529
|
const ts = freshBackupTs(backupBase());
|
|
8967
|
-
const mapPath =
|
|
8968
|
-
const map =
|
|
9530
|
+
const mapPath = join67(repo, "path-map.json");
|
|
9531
|
+
const map = existsSync52(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
8969
9532
|
divergenceCheckExtras(ts);
|
|
8970
9533
|
computePreview(ts, map, "diff");
|
|
8971
9534
|
} catch (err) {
|
|
@@ -8980,19 +9543,19 @@ function cmdDiff() {
|
|
|
8980
9543
|
|
|
8981
9544
|
// src/init.ts
|
|
8982
9545
|
init_config();
|
|
8983
|
-
import { existsSync as
|
|
8984
|
-
import { join as
|
|
9546
|
+
import { existsSync as existsSync54, mkdirSync as mkdirSync17, writeFileSync as writeFileSync12 } from "node:fs";
|
|
9547
|
+
import { join as join69 } from "node:path";
|
|
8985
9548
|
|
|
8986
9549
|
// src/init.gh-onboard.ts
|
|
8987
9550
|
init_config();
|
|
8988
|
-
import { execFileSync as
|
|
9551
|
+
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
8989
9552
|
init_utils();
|
|
8990
9553
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
8991
9554
|
function isValidRepoName(name) {
|
|
8992
9555
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
8993
9556
|
}
|
|
8994
9557
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
8995
|
-
function ensureOriginRepo(repoName, run =
|
|
9558
|
+
function ensureOriginRepo(repoName, run = execFileSync25) {
|
|
8996
9559
|
if (!isValidRepoName(repoName)) {
|
|
8997
9560
|
die(
|
|
8998
9561
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -9063,33 +9626,33 @@ init_config();
|
|
|
9063
9626
|
init_utils();
|
|
9064
9627
|
init_utils_fs();
|
|
9065
9628
|
init_utils_json();
|
|
9066
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync10, existsSync as
|
|
9067
|
-
import { join as
|
|
9629
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync10, existsSync as existsSync53, rmSync as rmSync21, statSync as statSync13 } from "node:fs";
|
|
9630
|
+
import { join as join68 } from "node:path";
|
|
9068
9631
|
function snapshotIntoShared(map) {
|
|
9069
9632
|
const repo = repoHome();
|
|
9070
9633
|
const claude = claudeHome();
|
|
9071
9634
|
for (const name of allSharedLinks(map)) {
|
|
9072
|
-
const src =
|
|
9073
|
-
if (!
|
|
9074
|
-
const dst =
|
|
9635
|
+
const src = join68(claude, name);
|
|
9636
|
+
if (!existsSync53(src)) continue;
|
|
9637
|
+
const dst = join68(repo, "shared", name);
|
|
9075
9638
|
if (statSync13(src).isDirectory()) {
|
|
9076
|
-
const gk =
|
|
9077
|
-
if (
|
|
9639
|
+
const gk = join68(dst, ".gitkeep");
|
|
9640
|
+
if (existsSync53(gk)) rmSync21(gk);
|
|
9078
9641
|
cpSync10(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
9079
9642
|
} else {
|
|
9080
9643
|
copyFileSync2(src, dst);
|
|
9081
9644
|
}
|
|
9082
9645
|
log(`snapshotted shared/${name} from ${src}`);
|
|
9083
9646
|
}
|
|
9084
|
-
const userSettings =
|
|
9085
|
-
if (
|
|
9647
|
+
const userSettings = join68(claude, "settings.json");
|
|
9648
|
+
if (existsSync53(userSettings)) {
|
|
9086
9649
|
let parsed;
|
|
9087
9650
|
try {
|
|
9088
9651
|
parsed = readJson(userSettings);
|
|
9089
9652
|
} catch (err) {
|
|
9090
9653
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
9091
9654
|
}
|
|
9092
|
-
const hostFile =
|
|
9655
|
+
const hostFile = join68(repo, "hosts", `${HOST}.json`);
|
|
9093
9656
|
writeJsonAtomic(hostFile, parsed);
|
|
9094
9657
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
9095
9658
|
}
|
|
@@ -9103,14 +9666,14 @@ var GITATTRIBUTES = "# nomad: sync content is byte-managed, disable all line-end
|
|
|
9103
9666
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
9104
9667
|
function preflightConflict(repoHome2) {
|
|
9105
9668
|
const candidates = [
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9669
|
+
join69(repoHome2, "shared", "settings.base.json"),
|
|
9670
|
+
join69(repoHome2, "shared", "CLAUDE.md"),
|
|
9671
|
+
join69(repoHome2, "path-map.json"),
|
|
9672
|
+
join69(repoHome2, "hosts"),
|
|
9673
|
+
join69(repoHome2, "shared")
|
|
9111
9674
|
];
|
|
9112
9675
|
for (const c of candidates) {
|
|
9113
|
-
if (
|
|
9676
|
+
if (existsSync54(c)) return c;
|
|
9114
9677
|
}
|
|
9115
9678
|
return null;
|
|
9116
9679
|
}
|
|
@@ -9128,27 +9691,27 @@ function cmdInit(opts = {}) {
|
|
|
9128
9691
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
9129
9692
|
}
|
|
9130
9693
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
9131
|
-
mkdirSync17(
|
|
9132
|
-
mkdirSync17(
|
|
9694
|
+
mkdirSync17(join69(repo, "shared"), { recursive: true });
|
|
9695
|
+
mkdirSync17(join69(repo, "hosts"), { recursive: true });
|
|
9133
9696
|
for (const name of SHARED_KEEP_DIRS) {
|
|
9134
|
-
mkdirSync17(
|
|
9697
|
+
mkdirSync17(join69(repo, "shared", name), { recursive: true });
|
|
9135
9698
|
}
|
|
9136
|
-
const userClaudeMd =
|
|
9137
|
-
if (!snapshot || !
|
|
9138
|
-
writeFileSync12(
|
|
9699
|
+
const userClaudeMd = join69(claude, "CLAUDE.md");
|
|
9700
|
+
if (!snapshot || !existsSync54(userClaudeMd)) {
|
|
9701
|
+
writeFileSync12(join69(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
9139
9702
|
item("created shared/CLAUDE.md");
|
|
9140
9703
|
}
|
|
9141
9704
|
for (const name of SHARED_KEEP_DIRS) {
|
|
9142
|
-
writeFileSync12(
|
|
9705
|
+
writeFileSync12(join69(repo, "shared", name, ".gitkeep"), "");
|
|
9143
9706
|
item(`created shared/${name}/.gitkeep`);
|
|
9144
9707
|
}
|
|
9145
|
-
writeFileSync12(
|
|
9708
|
+
writeFileSync12(join69(repo, "hosts", ".gitkeep"), "");
|
|
9146
9709
|
item("created hosts/.gitkeep");
|
|
9147
|
-
writeJsonAtomic(
|
|
9710
|
+
writeJsonAtomic(join69(repo, "shared", "settings.base.json"), {});
|
|
9148
9711
|
item("created shared/settings.base.json");
|
|
9149
|
-
writeJsonAtomic(
|
|
9712
|
+
writeJsonAtomic(join69(repo, "path-map.json"), { projects: {} });
|
|
9150
9713
|
item("created path-map.json");
|
|
9151
|
-
writeFileSync12(
|
|
9714
|
+
writeFileSync12(join69(repo, ".gitattributes"), GITATTRIBUTES);
|
|
9152
9715
|
item("created .gitattributes");
|
|
9153
9716
|
if (snapshot) {
|
|
9154
9717
|
snapshotIntoShared({ projects: {} });
|
|
@@ -9214,21 +9777,21 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
9214
9777
|
// src/init.prompt.ts
|
|
9215
9778
|
init_config();
|
|
9216
9779
|
init_utils();
|
|
9217
|
-
import { existsSync as
|
|
9218
|
-
import { join as
|
|
9780
|
+
import { existsSync as existsSync55, readdirSync as readdirSync21, statSync as statSync14 } from "node:fs";
|
|
9781
|
+
import { join as join70 } from "node:path";
|
|
9219
9782
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
9220
9783
|
function nonEmptyExists(path) {
|
|
9221
|
-
if (!
|
|
9784
|
+
if (!existsSync55(path)) return false;
|
|
9222
9785
|
try {
|
|
9223
|
-
if (statSync14(path).isDirectory()) return
|
|
9786
|
+
if (statSync14(path).isDirectory()) return readdirSync21(path).length > 0;
|
|
9224
9787
|
return true;
|
|
9225
9788
|
} catch {
|
|
9226
9789
|
return false;
|
|
9227
9790
|
}
|
|
9228
9791
|
}
|
|
9229
9792
|
function hasExistingClaudeConfig(claudeHome2) {
|
|
9230
|
-
if (
|
|
9231
|
-
return SHARED_LINKS.some((name) => nonEmptyExists(
|
|
9793
|
+
if (existsSync55(join70(claudeHome2, "settings.json"))) return true;
|
|
9794
|
+
return SHARED_LINKS.some((name) => nonEmptyExists(join70(claudeHome2, name)));
|
|
9232
9795
|
}
|
|
9233
9796
|
async function confirmSnapshotDefault(claudeHome2) {
|
|
9234
9797
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
@@ -9535,7 +10098,7 @@ function parseSyncArgs(argv) {
|
|
|
9535
10098
|
// package.json
|
|
9536
10099
|
var package_default = {
|
|
9537
10100
|
name: "claude-nomad",
|
|
9538
|
-
version: "0.
|
|
10101
|
+
version: "0.64.1",
|
|
9539
10102
|
type: "module",
|
|
9540
10103
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
9541
10104
|
keywords: [
|
|
@@ -9779,15 +10342,15 @@ var DEFAULT_HELP = [
|
|
|
9779
10342
|
init_config();
|
|
9780
10343
|
init_utils();
|
|
9781
10344
|
init_utils_json();
|
|
9782
|
-
import { existsSync as
|
|
9783
|
-
import { join as
|
|
10345
|
+
import { existsSync as existsSync56, readFileSync as readFileSync21, readdirSync as readdirSync22 } from "node:fs";
|
|
10346
|
+
import { join as join71 } from "node:path";
|
|
9784
10347
|
function resumeCmd(sessionId) {
|
|
9785
10348
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
9786
10349
|
fail(`invalid session id: ${sessionId}`);
|
|
9787
10350
|
process.exit(1);
|
|
9788
10351
|
}
|
|
9789
|
-
const projectsRoot =
|
|
9790
|
-
if (!
|
|
10352
|
+
const projectsRoot = join71(claudeHome(), "projects");
|
|
10353
|
+
if (!existsSync56(projectsRoot)) {
|
|
9791
10354
|
fail(`${projectsRoot} does not exist`);
|
|
9792
10355
|
process.exit(1);
|
|
9793
10356
|
}
|
|
@@ -9801,8 +10364,8 @@ function resumeCmd(sessionId) {
|
|
|
9801
10364
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
9802
10365
|
process.exit(1);
|
|
9803
10366
|
}
|
|
9804
|
-
const mapPath =
|
|
9805
|
-
if (!
|
|
10367
|
+
const mapPath = join71(repoHome(), "path-map.json");
|
|
10368
|
+
if (!existsSync56(mapPath)) {
|
|
9806
10369
|
fail("path-map.json missing");
|
|
9807
10370
|
process.exit(1);
|
|
9808
10371
|
}
|
|
@@ -9824,14 +10387,14 @@ function resumeCmd(sessionId) {
|
|
|
9824
10387
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
9825
10388
|
}
|
|
9826
10389
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
9827
|
-
for (const dir of
|
|
9828
|
-
const candidate =
|
|
9829
|
-
if (
|
|
10390
|
+
for (const dir of readdirSync22(projectsRoot)) {
|
|
10391
|
+
const candidate = join71(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
10392
|
+
if (existsSync56(candidate)) return candidate;
|
|
9830
10393
|
}
|
|
9831
10394
|
return null;
|
|
9832
10395
|
}
|
|
9833
10396
|
function extractRecordedCwd(jsonlPath) {
|
|
9834
|
-
for (const line of
|
|
10397
|
+
for (const line of readFileSync21(jsonlPath, "utf8").split("\n")) {
|
|
9835
10398
|
if (!line.trim()) continue;
|
|
9836
10399
|
try {
|
|
9837
10400
|
const obj = JSON.parse(line);
|