claude-nomad 0.63.2 → 0.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +43 -29
- package/dist/nomad.mjs +579 -233
- package/package.json +1 -1
package/dist/nomad.mjs
CHANGED
|
@@ -421,7 +421,6 @@ var init_settings_keys = __esm({
|
|
|
421
421
|
"includeGitInstructions",
|
|
422
422
|
"inputNeededNotifEnabled",
|
|
423
423
|
"language",
|
|
424
|
-
"leftArrowOpensAgents",
|
|
425
424
|
"managedMcpServers",
|
|
426
425
|
"minimumVersion",
|
|
427
426
|
"model",
|
|
@@ -539,6 +538,9 @@ function crashDir() {
|
|
|
539
538
|
function manifestPath() {
|
|
540
539
|
return join(home(), ".cache", "claude-nomad", `push-manifest-${encodeURIComponent(HOST)}.json`);
|
|
541
540
|
}
|
|
541
|
+
function sharedBaselinePath() {
|
|
542
|
+
return join(home(), ".cache", "claude-nomad", `shared-baseline-${encodeURIComponent(HOST)}.json`);
|
|
543
|
+
}
|
|
542
544
|
function allSharedLinks(map) {
|
|
543
545
|
const extras = [];
|
|
544
546
|
for (const entry of map.sharedDirs ?? []) {
|
|
@@ -2743,7 +2745,7 @@ import { join as join45 } from "node:path";
|
|
|
2743
2745
|
init_color();
|
|
2744
2746
|
init_config();
|
|
2745
2747
|
import { existsSync as existsSync11, lstatSync as lstatSync8, readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
|
|
2746
|
-
import { join as join13 } from "node:path";
|
|
2748
|
+
import { basename as basename3, join as join13 } from "node:path";
|
|
2747
2749
|
|
|
2748
2750
|
// src/commands.doctor.format.ts
|
|
2749
2751
|
init_color();
|
|
@@ -2818,6 +2820,61 @@ function readJsonSafe(path, label, section2) {
|
|
|
2818
2820
|
}
|
|
2819
2821
|
}
|
|
2820
2822
|
|
|
2823
|
+
// src/extras-sync.diff.ts
|
|
2824
|
+
init_utils();
|
|
2825
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2826
|
+
import { relative as relative3 } from "node:path";
|
|
2827
|
+
var NAME_STATUS_DIFF_TIMEOUT_MS = 3e3;
|
|
2828
|
+
function parseNameStatus(stdout) {
|
|
2829
|
+
const fields = stdout.split("\0").filter((f) => f.length > 0);
|
|
2830
|
+
const entries = [];
|
|
2831
|
+
for (let i = 0; i + 1 < fields.length; i += 2) {
|
|
2832
|
+
entries.push({ status: fields[i], path: fields[i + 1] });
|
|
2833
|
+
}
|
|
2834
|
+
return entries;
|
|
2835
|
+
}
|
|
2836
|
+
function labelEntry(entry) {
|
|
2837
|
+
if (entry.status === "D") return `${entry.path} (local only)`;
|
|
2838
|
+
if (entry.status === "A") return `${entry.path} (repo only)`;
|
|
2839
|
+
return entry.path;
|
|
2840
|
+
}
|
|
2841
|
+
function parseDiffOutput(stdout) {
|
|
2842
|
+
return parseNameStatus(stdout).map(labelEntry);
|
|
2843
|
+
}
|
|
2844
|
+
function parseModifiedPaths(stdout, a) {
|
|
2845
|
+
return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative3(a, entry.path));
|
|
2846
|
+
}
|
|
2847
|
+
function runNameStatusDiff(a, b, parse) {
|
|
2848
|
+
try {
|
|
2849
|
+
const stdout = execFileSync2(
|
|
2850
|
+
"git",
|
|
2851
|
+
["diff", "--no-index", "--no-renames", "-z", "--name-status", a, b],
|
|
2852
|
+
{
|
|
2853
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2854
|
+
timeout: NAME_STATUS_DIFF_TIMEOUT_MS
|
|
2855
|
+
}
|
|
2856
|
+
).toString();
|
|
2857
|
+
return parse(stdout);
|
|
2858
|
+
} catch (err) {
|
|
2859
|
+
const e = err;
|
|
2860
|
+
if (e.status === 1 && e.stdout !== void 0) {
|
|
2861
|
+
return parse(e.stdout.toString());
|
|
2862
|
+
}
|
|
2863
|
+
if (e.code === "ENOENT") {
|
|
2864
|
+
warn(`git not on PATH; divergence check skipped for ${a}`);
|
|
2865
|
+
return [];
|
|
2866
|
+
}
|
|
2867
|
+
warn(`divergence check failed for ${a}: ${e.message ?? String(err)}`);
|
|
2868
|
+
return [];
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
function listDivergingFiles(a, b) {
|
|
2872
|
+
return runNameStatusDiff(a, b, parseDiffOutput);
|
|
2873
|
+
}
|
|
2874
|
+
function listDivergingModified(a, b) {
|
|
2875
|
+
return runNameStatusDiff(a, b, (stdout) => parseModifiedPaths(stdout, a));
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2821
2878
|
// src/init.classify.ts
|
|
2822
2879
|
init_config();
|
|
2823
2880
|
init_utils_json();
|
|
@@ -2942,6 +2999,25 @@ function reportRepoState(section2) {
|
|
|
2942
2999
|
function repoHasSharedSource(name) {
|
|
2943
3000
|
return existsSync11(join13(repoHome(), "shared", name));
|
|
2944
3001
|
}
|
|
3002
|
+
function win32CopyOkRow(name) {
|
|
3003
|
+
return { line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)`, fail: false };
|
|
3004
|
+
}
|
|
3005
|
+
function divergingBasename(line) {
|
|
3006
|
+
return basename3(line.replace(/ \((?:local|repo) only\)$/, ""));
|
|
3007
|
+
}
|
|
3008
|
+
function classifyWin32Copy(name, p) {
|
|
3009
|
+
const sharedPath = join13(repoHome(), "shared", name);
|
|
3010
|
+
if (!existsSync11(sharedPath)) return win32CopyOkRow(name);
|
|
3011
|
+
const diverging = listDivergingFiles(p, sharedPath).filter(
|
|
3012
|
+
(line) => !isDeniedName(ALWAYS_NEVER_SYNC, divergingBasename(line))
|
|
3013
|
+
);
|
|
3014
|
+
if (diverging.length === 0) return win32CopyOkRow(name);
|
|
3015
|
+
return {
|
|
3016
|
+
line: `${yellow(warnGlyph)} ${name}: ${diverging.length} file(s) diverge from shared/${name}`,
|
|
3017
|
+
fail: false,
|
|
3018
|
+
children: diverging
|
|
3019
|
+
};
|
|
3020
|
+
}
|
|
2945
3021
|
function classifySharedLink(name, p) {
|
|
2946
3022
|
let stat;
|
|
2947
3023
|
try {
|
|
@@ -2958,10 +3034,7 @@ function classifySharedLink(name, p) {
|
|
|
2958
3034
|
}
|
|
2959
3035
|
if (!stat.isSymbolicLink()) {
|
|
2960
3036
|
if (process.platform === "win32") {
|
|
2961
|
-
return
|
|
2962
|
-
line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)`,
|
|
2963
|
-
fail: false
|
|
2964
|
-
};
|
|
3037
|
+
return classifyWin32Copy(name, p);
|
|
2965
3038
|
}
|
|
2966
3039
|
return {
|
|
2967
3040
|
line: `${red(failGlyph)} ${name}: NOT a symlink (blocks sync); run \`nomad adopt ${name}\` to fix`,
|
|
@@ -2995,9 +3068,12 @@ function reportSharedLinks(section2, map) {
|
|
|
2995
3068
|
const claude = claudeHome();
|
|
2996
3069
|
for (const name of allSharedLinks(map)) {
|
|
2997
3070
|
const p = join13(claude, name);
|
|
2998
|
-
const { line, fail: fail2 } = classifySharedLink(name, p);
|
|
3071
|
+
const { line, fail: fail2, children } = classifySharedLink(name, p);
|
|
2999
3072
|
addItem(section2, line);
|
|
3000
3073
|
if (fail2) process.exitCode = 1;
|
|
3074
|
+
if (children) {
|
|
3075
|
+
for (const child of children) addChildItem(section2, child);
|
|
3076
|
+
}
|
|
3001
3077
|
}
|
|
3002
3078
|
}
|
|
3003
3079
|
function reportDroppedNamesMigration(section2) {
|
|
@@ -3179,61 +3255,6 @@ init_color();
|
|
|
3179
3255
|
import { existsSync as existsSync14 } from "node:fs";
|
|
3180
3256
|
import { join as join16 } from "node:path";
|
|
3181
3257
|
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
3258
|
function stripSideIndicator(line) {
|
|
3238
3259
|
if (line.endsWith(" (local only)")) return line.slice(0, -" (local only)".length);
|
|
3239
3260
|
if (line.endsWith(" (repo only)")) return line.slice(0, -" (repo only)".length);
|
|
@@ -3241,15 +3262,15 @@ function stripSideIndicator(line) {
|
|
|
3241
3262
|
}
|
|
3242
3263
|
function isGsdDiffLine(line, localBase, sharedBase) {
|
|
3243
3264
|
const bare = stripSideIndicator(line);
|
|
3244
|
-
let
|
|
3265
|
+
let relative11;
|
|
3245
3266
|
if (bare.startsWith(localBase + "/")) {
|
|
3246
|
-
|
|
3267
|
+
relative11 = bare.slice(localBase.length + 1);
|
|
3247
3268
|
} else if (bare.startsWith(sharedBase + "/")) {
|
|
3248
|
-
|
|
3269
|
+
relative11 = bare.slice(sharedBase.length + 1);
|
|
3249
3270
|
} else {
|
|
3250
|
-
|
|
3271
|
+
relative11 = bare;
|
|
3251
3272
|
}
|
|
3252
|
-
return
|
|
3273
|
+
return relative11.split("/")[0].startsWith(GSD_PREFIX);
|
|
3253
3274
|
}
|
|
3254
3275
|
function reportSkillsDivergence(section2) {
|
|
3255
3276
|
const sharedSkills = join16(repoHome(), "shared", "skills");
|
|
@@ -5155,12 +5176,12 @@ import { join as join37 } from "node:path";
|
|
|
5155
5176
|
|
|
5156
5177
|
// src/skills-sync.tracked.ts
|
|
5157
5178
|
init_utils();
|
|
5158
|
-
import { basename as
|
|
5179
|
+
import { basename as basename4 } from "node:path";
|
|
5159
5180
|
function trackedRootSkillsAt(ref, repo) {
|
|
5160
5181
|
try {
|
|
5161
5182
|
const raw = gitCaptureRaw(["ls-tree", "--name-only", "-z", ref, "--", "shared/skills/"], repo);
|
|
5162
5183
|
const entries = raw.split("\0").filter((entry) => entry !== "");
|
|
5163
|
-
return new Set(entries.map((entry) =>
|
|
5184
|
+
return new Set(entries.map((entry) => basename4(entry)));
|
|
5164
5185
|
} catch {
|
|
5165
5186
|
return /* @__PURE__ */ new Set();
|
|
5166
5187
|
}
|
|
@@ -5876,8 +5897,8 @@ function sessionSuffix(finding) {
|
|
|
5876
5897
|
const sid = sessionIdFromFinding(finding);
|
|
5877
5898
|
if (sid === null) return "";
|
|
5878
5899
|
const rawBasename = finding.File.replace(/^.*\//, "");
|
|
5879
|
-
const
|
|
5880
|
-
return
|
|
5900
|
+
const basename5 = rawBasename.endsWith(".jsonl") ? rawBasename.slice(0, -".jsonl".length) : rawBasename;
|
|
5901
|
+
return basename5 === sid ? "" : ` (session: ${sid})`;
|
|
5881
5902
|
}
|
|
5882
5903
|
function renderFindingBlock(finding, readLine, siblings = []) {
|
|
5883
5904
|
const lines = [` file: ${finding.File}:${finding.StartLine}${sessionSuffix(finding)}`];
|
|
@@ -6843,8 +6864,8 @@ function unstageOne(rel, repo) {
|
|
|
6843
6864
|
|
|
6844
6865
|
// src/commands.pull.ts
|
|
6845
6866
|
init_autostash_guard();
|
|
6846
|
-
import { existsSync as
|
|
6847
|
-
import { join as
|
|
6867
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync14 } from "node:fs";
|
|
6868
|
+
import { join as join58 } from "node:path";
|
|
6848
6869
|
|
|
6849
6870
|
// src/commands.push.sections.ts
|
|
6850
6871
|
init_color();
|
|
@@ -7279,10 +7300,204 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
7279
7300
|
return divergedCount;
|
|
7280
7301
|
}
|
|
7281
7302
|
|
|
7282
|
-
// src/
|
|
7303
|
+
// src/links.baseline.ts
|
|
7304
|
+
init_config();
|
|
7305
|
+
import { lstatSync as lstatSync15, readdirSync as readdirSync17 } from "node:fs";
|
|
7306
|
+
import { join as join51, relative as relative7, sep as sep11 } from "node:path";
|
|
7307
|
+
init_utils();
|
|
7308
|
+
var SHARED_BASELINE_KIND = "shared-links-baseline/1";
|
|
7309
|
+
var SHARED_BASELINE_CONFIG_HASH = "not-applicable";
|
|
7310
|
+
function readSharedBaseline() {
|
|
7311
|
+
const parsed = readManifest(sharedBaselinePath());
|
|
7312
|
+
if (parsed === null) return null;
|
|
7313
|
+
if (parsed.scannerVersion !== SHARED_BASELINE_KIND) return null;
|
|
7314
|
+
return parsed;
|
|
7315
|
+
}
|
|
7316
|
+
function baselineKey(claude, abs) {
|
|
7317
|
+
return relative7(claude, abs).split(sep11).join("/");
|
|
7318
|
+
}
|
|
7319
|
+
function addLocalPath(abs, claude, scan) {
|
|
7320
|
+
const key = baselineKey(claude, abs);
|
|
7321
|
+
let st;
|
|
7322
|
+
try {
|
|
7323
|
+
st = lstatSync15(abs, { throwIfNoEntry: false });
|
|
7324
|
+
} catch {
|
|
7325
|
+
scan.declined.push(key);
|
|
7326
|
+
return;
|
|
7327
|
+
}
|
|
7328
|
+
if (st === void 0) return;
|
|
7329
|
+
if (st.isSymbolicLink()) {
|
|
7330
|
+
scan.declined.push(key);
|
|
7331
|
+
return;
|
|
7332
|
+
}
|
|
7333
|
+
if (st.isDirectory()) {
|
|
7334
|
+
collectSharedFiles(abs, claude, scan);
|
|
7335
|
+
return;
|
|
7336
|
+
}
|
|
7337
|
+
scan.files[key] = { size: st.size, mtime: st.mtimeMs };
|
|
7338
|
+
}
|
|
7339
|
+
function collectSharedFiles(dir, claude, scan) {
|
|
7340
|
+
let entries;
|
|
7341
|
+
try {
|
|
7342
|
+
entries = readdirSync17(dir);
|
|
7343
|
+
} catch {
|
|
7344
|
+
scan.declined.push(baselineKey(claude, dir));
|
|
7345
|
+
return;
|
|
7346
|
+
}
|
|
7347
|
+
for (const entry of entries) {
|
|
7348
|
+
const abs = join51(dir, entry);
|
|
7349
|
+
if (isDeniedName(ALWAYS_NEVER_SYNC, entry)) {
|
|
7350
|
+
scan.declined.push(baselineKey(claude, abs));
|
|
7351
|
+
continue;
|
|
7352
|
+
}
|
|
7353
|
+
addLocalPath(abs, claude, scan);
|
|
7354
|
+
}
|
|
7355
|
+
}
|
|
7356
|
+
function enumerateLocalSharedScan(map) {
|
|
7357
|
+
const claude = claudeHome();
|
|
7358
|
+
const scan = { files: {}, declined: [] };
|
|
7359
|
+
for (const name of allSharedLinks(map)) {
|
|
7360
|
+
if (isDeniedName(ALWAYS_NEVER_SYNC, name)) {
|
|
7361
|
+
scan.declined.push(name);
|
|
7362
|
+
continue;
|
|
7363
|
+
}
|
|
7364
|
+
addLocalPath(join51(claude, name), claude, scan);
|
|
7365
|
+
}
|
|
7366
|
+
return scan;
|
|
7367
|
+
}
|
|
7368
|
+
function enumerateLocalSharedFiles(map) {
|
|
7369
|
+
return enumerateLocalSharedScan(map).files;
|
|
7370
|
+
}
|
|
7371
|
+
function buildSharedBaseline(map) {
|
|
7372
|
+
const claude = claudeHome();
|
|
7373
|
+
const files = {};
|
|
7374
|
+
for (const [key, st] of Object.entries(enumerateLocalSharedFiles(map))) {
|
|
7375
|
+
try {
|
|
7376
|
+
files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(join51(claude, key)) };
|
|
7377
|
+
} catch {
|
|
7378
|
+
}
|
|
7379
|
+
}
|
|
7380
|
+
return buildManifest(files, SHARED_BASELINE_KIND, SHARED_BASELINE_CONFIG_HASH);
|
|
7381
|
+
}
|
|
7382
|
+
function writeSharedBaseline(map) {
|
|
7383
|
+
if (process.platform !== "win32") return;
|
|
7384
|
+
try {
|
|
7385
|
+
writeManifest(sharedBaselinePath(), buildSharedBaseline(map));
|
|
7386
|
+
} catch (err) {
|
|
7387
|
+
warn(`could not record the shared-config baseline: ${err.message}`);
|
|
7388
|
+
}
|
|
7389
|
+
}
|
|
7390
|
+
|
|
7391
|
+
// src/commands.pull.win32.ts
|
|
7392
|
+
init_config();
|
|
7393
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
7394
|
+
import { join as join56 } from "node:path";
|
|
7395
|
+
|
|
7396
|
+
// src/git-probe.ts
|
|
7397
|
+
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
7398
|
+
var PROBE_TIMEOUT_MS4 = 1e4;
|
|
7399
|
+
var PROBE_MAX_BUFFER = 64 * 1024 * 1024;
|
|
7400
|
+
function gitProbe(args, repo) {
|
|
7401
|
+
try {
|
|
7402
|
+
return execFileSync21("git", args, {
|
|
7403
|
+
cwd: repo,
|
|
7404
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
7405
|
+
timeout: PROBE_TIMEOUT_MS4,
|
|
7406
|
+
maxBuffer: PROBE_MAX_BUFFER
|
|
7407
|
+
}).toString();
|
|
7408
|
+
} catch {
|
|
7409
|
+
return null;
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
7412
|
+
|
|
7413
|
+
// src/links.captures.ts
|
|
7283
7414
|
init_config();
|
|
7284
|
-
import { existsSync as
|
|
7415
|
+
import { existsSync as existsSync40, lstatSync as lstatSync16 } from "node:fs";
|
|
7285
7416
|
import { join as join52 } from "node:path";
|
|
7417
|
+
function planSharedLinkCaptures(map) {
|
|
7418
|
+
if (process.platform !== "win32") return [];
|
|
7419
|
+
if (map === null) return [];
|
|
7420
|
+
const claude = claudeHome();
|
|
7421
|
+
const repo = repoHome();
|
|
7422
|
+
const plan = [];
|
|
7423
|
+
for (const name of allSharedLinks(map)) {
|
|
7424
|
+
const localPath = join52(claude, name);
|
|
7425
|
+
let stat;
|
|
7426
|
+
try {
|
|
7427
|
+
stat = lstatSync16(localPath, { throwIfNoEntry: false });
|
|
7428
|
+
} catch {
|
|
7429
|
+
continue;
|
|
7430
|
+
}
|
|
7431
|
+
if (stat === void 0) continue;
|
|
7432
|
+
if (stat.isSymbolicLink()) continue;
|
|
7433
|
+
const repoPath = join52(repo, "shared", name);
|
|
7434
|
+
if (!existsSync40(repoPath)) continue;
|
|
7435
|
+
plan.push({ name, localPath, repoPath });
|
|
7436
|
+
}
|
|
7437
|
+
return plan;
|
|
7438
|
+
}
|
|
7439
|
+
|
|
7440
|
+
// src/links.deletions.ts
|
|
7441
|
+
init_config();
|
|
7442
|
+
import { existsSync as existsSync41, lstatSync as lstatSync17, rmSync as rmSync17 } from "node:fs";
|
|
7443
|
+
import { join as join53, relative as relative8, resolve as resolve4, sep as sep12 } from "node:path";
|
|
7444
|
+
init_utils();
|
|
7445
|
+
init_utils_fs();
|
|
7446
|
+
function isUnknown(declined, key) {
|
|
7447
|
+
return declined.some((prefix) => key === prefix || key.startsWith(`${prefix}/`));
|
|
7448
|
+
}
|
|
7449
|
+
function deletionFor(key, names, claude, sharedRoot) {
|
|
7450
|
+
const segments = key.split("/");
|
|
7451
|
+
const name = segments[0];
|
|
7452
|
+
if (!names.has(name)) return null;
|
|
7453
|
+
if (!existsSync41(join53(claude, name))) return null;
|
|
7454
|
+
if (segments.some((segment) => isDeniedName(ALWAYS_NEVER_SYNC, segment))) return null;
|
|
7455
|
+
const repoPath = resolve4(sharedRoot, key);
|
|
7456
|
+
const rel = relative8(join53(sharedRoot, name), repoPath);
|
|
7457
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep12}`)) return null;
|
|
7458
|
+
if (segments.some((segment) => segment === "" || segment === "." || segment === ".."))
|
|
7459
|
+
return null;
|
|
7460
|
+
if (!existsSync41(repoPath)) return null;
|
|
7461
|
+
return { name, localPath: join53(claude, key), repoPath };
|
|
7462
|
+
}
|
|
7463
|
+
function planSharedLinkDeletions(map) {
|
|
7464
|
+
if (process.platform !== "win32") return [];
|
|
7465
|
+
if (map === null) return [];
|
|
7466
|
+
const baseline = readSharedBaseline();
|
|
7467
|
+
if (baseline === null) return [];
|
|
7468
|
+
const claude = claudeHome();
|
|
7469
|
+
const sharedRoot = join53(repoHome(), "shared");
|
|
7470
|
+
const scan = enumerateLocalSharedScan(map);
|
|
7471
|
+
const present = new Set(Object.keys(scan.files).map((key) => key.toLowerCase()));
|
|
7472
|
+
const declined = scan.declined.map((path) => path.toLowerCase());
|
|
7473
|
+
const names = new Set(allSharedLinks(map));
|
|
7474
|
+
const plan = [];
|
|
7475
|
+
for (const key of Object.keys(baseline.files)) {
|
|
7476
|
+
const folded = key.toLowerCase();
|
|
7477
|
+
if (present.has(folded)) continue;
|
|
7478
|
+
if (isUnknown(declined, folded)) continue;
|
|
7479
|
+
const entry = deletionFor(key, names, claude, sharedRoot);
|
|
7480
|
+
if (entry !== null) plan.push(entry);
|
|
7481
|
+
}
|
|
7482
|
+
return plan;
|
|
7483
|
+
}
|
|
7484
|
+
function applySharedLinkDeletions(map, ts) {
|
|
7485
|
+
const repo = repoHome();
|
|
7486
|
+
for (const entry of planSharedLinkDeletions(map)) {
|
|
7487
|
+
try {
|
|
7488
|
+
if (!lstatSync17(entry.repoPath).isFile()) continue;
|
|
7489
|
+
backupRepoWrite(entry.repoPath, ts, repo);
|
|
7490
|
+
rmSync17(entry.repoPath, { force: true });
|
|
7491
|
+
} catch (err) {
|
|
7492
|
+
warn(`could not remove ${entry.repoPath}: ${err.message}`);
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
}
|
|
7496
|
+
|
|
7497
|
+
// src/preview.ts
|
|
7498
|
+
init_config();
|
|
7499
|
+
import { existsSync as existsSync43 } from "node:fs";
|
|
7500
|
+
import { join as join55 } from "node:path";
|
|
7286
7501
|
|
|
7287
7502
|
// node_modules/diff/libesm/diff/base.js
|
|
7288
7503
|
var Diff = class {
|
|
@@ -7557,16 +7772,16 @@ function diffLinesToUnified(oldStr, newStr) {
|
|
|
7557
7772
|
|
|
7558
7773
|
// src/preview.skills.ts
|
|
7559
7774
|
init_config();
|
|
7560
|
-
import { existsSync as
|
|
7561
|
-
import { join as
|
|
7775
|
+
import { existsSync as existsSync42, readdirSync as readdirSync18 } from "node:fs";
|
|
7776
|
+
import { join as join54 } from "node:path";
|
|
7562
7777
|
function buildSkillsPreviewSection() {
|
|
7563
7778
|
const s = section("Skills");
|
|
7564
|
-
const sharedSkills =
|
|
7565
|
-
if (!
|
|
7566
|
-
const sharedNames =
|
|
7779
|
+
const sharedSkills = join54(repoHome(), "shared", "skills");
|
|
7780
|
+
if (!existsSync42(sharedSkills)) return s;
|
|
7781
|
+
const sharedNames = readdirSync18(sharedSkills, { encoding: "utf8" }).filter((name) => !isSkillExcluded(name)).sort((a, b) => a.localeCompare(b, "en"));
|
|
7567
7782
|
for (const name of sharedNames) addItem(s, name);
|
|
7568
|
-
const localSkills =
|
|
7569
|
-
const localOnly =
|
|
7783
|
+
const localSkills = join54(claudeHome(), "skills");
|
|
7784
|
+
const localOnly = existsSync42(localSkills) ? readdirSync18(localSkills, { encoding: "utf8" }).filter(
|
|
7570
7785
|
(name) => !isSkillExcluded(name) && !sharedNames.includes(name)
|
|
7571
7786
|
).length : 0;
|
|
7572
7787
|
if (localOnly > 0) {
|
|
@@ -7588,7 +7803,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
7588
7803
|
return lines.join("\n");
|
|
7589
7804
|
}
|
|
7590
7805
|
function readJsonOrNull(path) {
|
|
7591
|
-
if (!
|
|
7806
|
+
if (!existsSync43(path)) return null;
|
|
7592
7807
|
try {
|
|
7593
7808
|
return readJson(path);
|
|
7594
7809
|
} catch {
|
|
@@ -7602,12 +7817,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
7602
7817
|
}
|
|
7603
7818
|
const notes = [];
|
|
7604
7819
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
7605
|
-
if (hostOverrides === null &&
|
|
7820
|
+
if (hostOverrides === null && existsSync43(hostPath)) {
|
|
7606
7821
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
7607
7822
|
}
|
|
7608
7823
|
const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
|
|
7609
7824
|
const current = readJsonOrNull(settingsPath);
|
|
7610
|
-
if (current === null &&
|
|
7825
|
+
if (current === null && existsSync43(settingsPath)) {
|
|
7611
7826
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
7612
7827
|
}
|
|
7613
7828
|
const strippedCurrent = stripGsdHookEntries(current ?? {});
|
|
@@ -7623,6 +7838,12 @@ function formatLinkRow(e) {
|
|
|
7623
7838
|
if (e.kind === "copy") return `would copy ${e.from} -> ${e.to}`;
|
|
7624
7839
|
return `${e.kind} ${e.from} -> ${e.to}`;
|
|
7625
7840
|
}
|
|
7841
|
+
function formatCaptureRow(c) {
|
|
7842
|
+
return `would capture ${c.localPath} -> ${c.repoPath}`;
|
|
7843
|
+
}
|
|
7844
|
+
function formatDeletionRow(d) {
|
|
7845
|
+
return `would remove ${d.repoPath} (gone from ${d.localPath})`;
|
|
7846
|
+
}
|
|
7626
7847
|
function formatSessionRow(e) {
|
|
7627
7848
|
return e.kind === "overwrite" ? `overwrite ${e.dst} (from ${e.src})` : e.text;
|
|
7628
7849
|
}
|
|
@@ -7638,20 +7859,30 @@ function buildSettingsSectionForPreview(result) {
|
|
|
7638
7859
|
}
|
|
7639
7860
|
return s;
|
|
7640
7861
|
}
|
|
7641
|
-
function computePreview(ts, map, verb = "pull") {
|
|
7862
|
+
function computePreview(ts, map, verb = "pull", plans) {
|
|
7642
7863
|
const repo = repoHome();
|
|
7643
7864
|
const claude = claudeHome();
|
|
7644
7865
|
console.log(`would pull on host=${HOST} (preview; nothing applied)`);
|
|
7645
7866
|
console.log("");
|
|
7646
7867
|
const links = section("Symlinks");
|
|
7868
|
+
const { captures, deletions } = plans ?? {
|
|
7869
|
+
captures: planSharedLinkCaptures(map),
|
|
7870
|
+
deletions: planSharedLinkDeletions(map)
|
|
7871
|
+
};
|
|
7872
|
+
for (const capture of captures) {
|
|
7873
|
+
addItem(links, formatCaptureRow(capture));
|
|
7874
|
+
}
|
|
7875
|
+
for (const deletion of deletions) {
|
|
7876
|
+
addItem(links, formatDeletionRow(deletion));
|
|
7877
|
+
}
|
|
7647
7878
|
applySharedLinks(ts, map, {
|
|
7648
7879
|
dryRun: true,
|
|
7649
7880
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
7650
7881
|
});
|
|
7651
7882
|
const settingsResult = previewSettings(
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7883
|
+
join55(repo, "shared", "settings.base.json"),
|
|
7884
|
+
join55(repo, "hosts", `${HOST}.json`),
|
|
7885
|
+
join55(claude, "settings.json")
|
|
7655
7886
|
);
|
|
7656
7887
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
7657
7888
|
const sessions = section("Sessions");
|
|
@@ -7667,7 +7898,7 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
7667
7898
|
const extras = section("Extras");
|
|
7668
7899
|
let extrasSkipped = 0;
|
|
7669
7900
|
let extrasUnmapped = 0;
|
|
7670
|
-
if (
|
|
7901
|
+
if (existsSync43(join55(repo, "path-map.json")) && existsSync43(join55(repo, "shared", "extras"))) {
|
|
7671
7902
|
const extrasResult = remapExtrasPull(ts, { dryRun: true });
|
|
7672
7903
|
for (const entry of extrasResult.wouldPull) {
|
|
7673
7904
|
addItem(extras, entry);
|
|
@@ -7684,6 +7915,135 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
7684
7915
|
return { unmapped: remapResult.unmapped, collisions: 0, localOnly };
|
|
7685
7916
|
}
|
|
7686
7917
|
|
|
7918
|
+
// src/commands.pull.win32.ts
|
|
7919
|
+
init_utils();
|
|
7920
|
+
init_utils_json();
|
|
7921
|
+
function readMapForMirror(mapPath) {
|
|
7922
|
+
if (!existsSync44(mapPath)) return { projects: {} };
|
|
7923
|
+
try {
|
|
7924
|
+
return readPathMap(mapPath);
|
|
7925
|
+
} catch {
|
|
7926
|
+
return null;
|
|
7927
|
+
}
|
|
7928
|
+
}
|
|
7929
|
+
function untrackedUnderShared(repo) {
|
|
7930
|
+
const out = gitProbe(["ls-files", "--others", "--exclude-standard", "-z", "--", "shared/"], repo);
|
|
7931
|
+
if (out === null) return null;
|
|
7932
|
+
return new Set(out.split("\0").filter((p) => p !== ""));
|
|
7933
|
+
}
|
|
7934
|
+
function newlyUntracked(before, after) {
|
|
7935
|
+
if (before === null || after === null) return [];
|
|
7936
|
+
return [...after].filter((p) => !before.has(p));
|
|
7937
|
+
}
|
|
7938
|
+
function reconcileSharedLinksBeforePull(repo, ts) {
|
|
7939
|
+
if (process.platform !== "win32") return [];
|
|
7940
|
+
const before = untrackedUnderShared(repo);
|
|
7941
|
+
try {
|
|
7942
|
+
const map = readMapForMirror(join56(repo, "path-map.json"));
|
|
7943
|
+
stageLocalSharedEdits(map, ts);
|
|
7944
|
+
applySharedLinkDeletions(map, ts);
|
|
7945
|
+
} catch (err) {
|
|
7946
|
+
warn(`could not reconcile local shared edits before the pull: ${err.message}`);
|
|
7947
|
+
}
|
|
7948
|
+
return newlyUntracked(before, untrackedUnderShared(repo));
|
|
7949
|
+
}
|
|
7950
|
+
function planSharedReconcileBeforePull(repo) {
|
|
7951
|
+
const map = readMapForMirror(join56(repo, "path-map.json"));
|
|
7952
|
+
return { captures: planSharedLinkCaptures(map), deletions: planSharedLinkDeletions(map) };
|
|
7953
|
+
}
|
|
7954
|
+
|
|
7955
|
+
// src/commands.pull.collision.ts
|
|
7956
|
+
init_config();
|
|
7957
|
+
init_exit_codes();
|
|
7958
|
+
import { existsSync as existsSync45, lstatSync as lstatSync18, readFileSync as readFileSync20, rmSync as rmSync18 } from "node:fs";
|
|
7959
|
+
import { join as join57 } from "node:path";
|
|
7960
|
+
init_utils();
|
|
7961
|
+
var SHARED_PREFIX = /^shared\//;
|
|
7962
|
+
var EXTENSION = /(\.[^.]+)?$/;
|
|
7963
|
+
function localOriginal(repoRel) {
|
|
7964
|
+
return `~/.claude/${repoRel.replace(SHARED_PREFIX, "")}`;
|
|
7965
|
+
}
|
|
7966
|
+
function renameSuggestion(localPath) {
|
|
7967
|
+
const base = localPath.slice(localPath.lastIndexOf("/") + 1);
|
|
7968
|
+
return base.replace(EXTENSION, ".local$1");
|
|
7969
|
+
}
|
|
7970
|
+
function collisionSteps(locals) {
|
|
7971
|
+
const single = locals.length === 1;
|
|
7972
|
+
const target = single ? locals[0] : "each file listed above";
|
|
7973
|
+
const rename = single ? renameSuggestion(locals[0]) : "mine.md becomes mine.local.md";
|
|
7974
|
+
const combine = single ? "combine the two files" : "combine each pair";
|
|
7975
|
+
return `Keep the incoming version (yours is set aside, not deleted):
|
|
7976
|
+
1. move ${target} outside ~/.claude/
|
|
7977
|
+
2. nomad pull
|
|
7978
|
+
|
|
7979
|
+
Keep both and merge them yourself:
|
|
7980
|
+
1. rename ${target} to a name the repo does not use (${rename})
|
|
7981
|
+
2. nomad pull (brings in the other machine's version)
|
|
7982
|
+
3. ${combine}, then nomad push`;
|
|
7983
|
+
}
|
|
7984
|
+
function collisionAdvice(single, cleared) {
|
|
7985
|
+
const copies = single ? "nomad's copy" : "nomad's copies";
|
|
7986
|
+
const them = single ? "it" : "them";
|
|
7987
|
+
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`;
|
|
7988
|
+
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/.";
|
|
7989
|
+
return `Git's advice above refers to ${copies} inside the sync repo, ${fate}. ${moving}`;
|
|
7990
|
+
}
|
|
7991
|
+
function untrackedCollisionRunbookText(repoRelPaths, cleared) {
|
|
7992
|
+
const locals = repoRelPaths.map(localOriginal);
|
|
7993
|
+
const single = locals.length === 1;
|
|
7994
|
+
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:";
|
|
7995
|
+
const intact = single ? "Your file is still exactly as you left it." : "Your files are still exactly as you left them.";
|
|
7996
|
+
const listed = locals.map((p) => ` ${p}`).join("\n");
|
|
7997
|
+
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}
|
|
7998
|
+
|
|
7999
|
+
${listed}
|
|
8000
|
+
|
|
8001
|
+
Nothing changed. Your ~/.claude/ config is untouched, the update did not land, and nothing was published. ${intact}
|
|
8002
|
+
|
|
8003
|
+
${collisionAdvice(single, cleared)}
|
|
8004
|
+
|
|
8005
|
+
` + collisionSteps(locals);
|
|
8006
|
+
}
|
|
8007
|
+
function addedByIncomingUpdate(repo, created) {
|
|
8008
|
+
return created.filter((rel) => gitProbe(["cat-file", "-e", `FETCH_HEAD:${rel}`], repo) !== null);
|
|
8009
|
+
}
|
|
8010
|
+
function isContainedMirrorPath(rel) {
|
|
8011
|
+
return rel.startsWith("shared/") && isSafeRelPath(rel);
|
|
8012
|
+
}
|
|
8013
|
+
function matchesLocalOriginal(repo, rel) {
|
|
8014
|
+
try {
|
|
8015
|
+
const local = join57(claudeHome(), rel.replace(SHARED_PREFIX, ""));
|
|
8016
|
+
return readFileSync20(join57(repo, rel)).equals(readFileSync20(local));
|
|
8017
|
+
} catch {
|
|
8018
|
+
return false;
|
|
8019
|
+
}
|
|
8020
|
+
}
|
|
8021
|
+
function removeMirroredCopies(repo, repoRelPaths) {
|
|
8022
|
+
for (const rel of repoRelPaths.filter(isContainedMirrorPath)) {
|
|
8023
|
+
const abs = join57(repo, rel);
|
|
8024
|
+
try {
|
|
8025
|
+
if (lstatSync18(abs, { throwIfNoEntry: false })?.isFile() !== true) continue;
|
|
8026
|
+
if (!matchesLocalOriginal(repo, rel)) continue;
|
|
8027
|
+
rmSync18(abs, { force: true });
|
|
8028
|
+
} catch (err) {
|
|
8029
|
+
warn(`could not remove nomad's copy at ${abs}: ${err.message}`);
|
|
8030
|
+
}
|
|
8031
|
+
}
|
|
8032
|
+
return repoRelPaths.every((rel) => !existsSync45(join57(repo, rel)));
|
|
8033
|
+
}
|
|
8034
|
+
function pullWithCollisionRunbook(repo, mirrored) {
|
|
8035
|
+
try {
|
|
8036
|
+
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
8037
|
+
} catch (err) {
|
|
8038
|
+
const colliding = addedByIncomingUpdate(repo, mirrored);
|
|
8039
|
+
if (colliding.length === 0) throw err;
|
|
8040
|
+
const cleared = removeMirroredCopies(repo, colliding);
|
|
8041
|
+
throw new NomadFatal(untrackedCollisionRunbookText(colliding, cleared), {
|
|
8042
|
+
code: EXIT.GENERIC_FAILURE
|
|
8043
|
+
});
|
|
8044
|
+
}
|
|
8045
|
+
}
|
|
8046
|
+
|
|
7687
8047
|
// src/commands.pull.ts
|
|
7688
8048
|
init_commands_pull_wedge();
|
|
7689
8049
|
|
|
@@ -7694,9 +8054,9 @@ init_utils_fs();
|
|
|
7694
8054
|
|
|
7695
8055
|
// src/commands.pull.recovery.git.ts
|
|
7696
8056
|
init_utils();
|
|
7697
|
-
import { execFileSync as
|
|
8057
|
+
import { execFileSync as execFileSync22 } from "node:child_process";
|
|
7698
8058
|
function gitCapture(args, cwd) {
|
|
7699
|
-
return
|
|
8059
|
+
return execFileSync22("git", args, {
|
|
7700
8060
|
cwd,
|
|
7701
8061
|
stdio: ["ignore", "pipe", "pipe"],
|
|
7702
8062
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -7849,6 +8209,7 @@ function capturePrePostHeads(repo, rebase) {
|
|
|
7849
8209
|
}
|
|
7850
8210
|
function buildWetPullSections(ts, map, prePostHeads) {
|
|
7851
8211
|
applySharedLinks(ts, map);
|
|
8212
|
+
writeSharedBaseline(map);
|
|
7852
8213
|
const { label } = regenerateSettings(ts);
|
|
7853
8214
|
syncSkillsPull(ts, prePostHeads);
|
|
7854
8215
|
const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
|
|
@@ -7888,22 +8249,6 @@ function handleWedge(repo, forceRemote) {
|
|
|
7888
8249
|
const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
|
|
7889
8250
|
die(wedgeMarkerRunbookText(state), { code: EXIT.CONFLICT });
|
|
7890
8251
|
}
|
|
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
8252
|
function runPullCore(opts = {}) {
|
|
7908
8253
|
const dryRun = opts.dryRun === true;
|
|
7909
8254
|
const forceRemote = opts.forceRemote === true;
|
|
@@ -7913,7 +8258,7 @@ function runPullCore(opts = {}) {
|
|
|
7913
8258
|
const ts = freshBackupTs(backup);
|
|
7914
8259
|
handleWedge(repo, forceRemote);
|
|
7915
8260
|
if (!dryRun) {
|
|
7916
|
-
const backupRoot =
|
|
8261
|
+
const backupRoot = join58(backup, ts);
|
|
7917
8262
|
try {
|
|
7918
8263
|
mkdirSync14(backupRoot, { recursive: true });
|
|
7919
8264
|
} catch (err) {
|
|
@@ -7925,16 +8270,17 @@ function runPullCore(opts = {}) {
|
|
|
7925
8270
|
dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
|
|
7926
8271
|
);
|
|
7927
8272
|
}
|
|
7928
|
-
|
|
8273
|
+
const mirrored = !dryRun && !forceRemote ? reconcileSharedLinksBeforePull(repo, ts) : [];
|
|
8274
|
+
const sharedPlans = dryRun ? planSharedReconcileBeforePull(repo) : void 0;
|
|
7929
8275
|
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
7930
|
-
|
|
8276
|
+
pullWithCollisionRunbook(repo, mirrored);
|
|
7931
8277
|
});
|
|
7932
8278
|
assertNoAutostashConflict(repo, "nomad pull");
|
|
7933
|
-
const mapPath =
|
|
7934
|
-
const map =
|
|
8279
|
+
const mapPath = join58(repo, "path-map.json");
|
|
8280
|
+
const map = existsSync46(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
7935
8281
|
const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
|
|
7936
8282
|
if (dryRun) {
|
|
7937
|
-
computePreview(ts, map, "pull");
|
|
8283
|
+
computePreview(ts, map, "pull", sharedPlans);
|
|
7938
8284
|
return { tag: "dry" };
|
|
7939
8285
|
}
|
|
7940
8286
|
const { sections, localOnly, settingsLabel, unmapped, extrasSkipped } = buildWetPullSections(
|
|
@@ -7956,8 +8302,8 @@ function runPullCore(opts = {}) {
|
|
|
7956
8302
|
}
|
|
7957
8303
|
function cmdPull(opts = {}) {
|
|
7958
8304
|
const repo = repoHome();
|
|
7959
|
-
if (!
|
|
7960
|
-
if (!
|
|
8305
|
+
if (!existsSync46(repo)) die(`repo not cloned at ${repo}`);
|
|
8306
|
+
if (!existsSync46(join58(repo, "shared", "settings.base.json"))) {
|
|
7961
8307
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
7962
8308
|
}
|
|
7963
8309
|
const handle = acquireLock("pull");
|
|
@@ -7980,13 +8326,13 @@ function cmdPull(opts = {}) {
|
|
|
7980
8326
|
|
|
7981
8327
|
// src/commands.push.ts
|
|
7982
8328
|
init_config();
|
|
7983
|
-
import { existsSync as
|
|
7984
|
-
import { join as
|
|
8329
|
+
import { existsSync as existsSync50 } from "node:fs";
|
|
8330
|
+
import { join as join63 } from "node:path";
|
|
7985
8331
|
|
|
7986
8332
|
// src/commands.push.selection.ts
|
|
7987
8333
|
init_config();
|
|
7988
|
-
import { existsSync as
|
|
7989
|
-
import { join as
|
|
8334
|
+
import { existsSync as existsSync47, statSync as statSync11 } from "node:fs";
|
|
8335
|
+
import { join as join59 } from "node:path";
|
|
7990
8336
|
init_utils_json();
|
|
7991
8337
|
function buildCurrentMap(map) {
|
|
7992
8338
|
const current = {};
|
|
@@ -7995,8 +8341,8 @@ function buildCurrentMap(map) {
|
|
|
7995
8341
|
for (const [, hostMap] of Object.entries(map.projects)) {
|
|
7996
8342
|
const localPath = hostMap[HOST];
|
|
7997
8343
|
if (!localPath) continue;
|
|
7998
|
-
const localDir =
|
|
7999
|
-
if (!
|
|
8344
|
+
const localDir = join59(claude, "projects", encodePath(localPath));
|
|
8345
|
+
if (!existsSync47(localDir)) continue;
|
|
8000
8346
|
for (const f of enumerateSourceFiles(localDir)) {
|
|
8001
8347
|
const st = statSync11(f);
|
|
8002
8348
|
current[f] = { size: st.size, mtime: st.mtimeMs };
|
|
@@ -8027,7 +8373,7 @@ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
|
|
|
8027
8373
|
};
|
|
8028
8374
|
}
|
|
8029
8375
|
function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
|
|
8030
|
-
const map =
|
|
8376
|
+
const map = existsSync47(mapPath) ? readPathMap(mapPath) : null;
|
|
8031
8377
|
const { selection, newManifest } = computePushSelection(
|
|
8032
8378
|
map,
|
|
8033
8379
|
old,
|
|
@@ -8129,14 +8475,14 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
8129
8475
|
|
|
8130
8476
|
// src/commands.push.settings.ts
|
|
8131
8477
|
init_config();
|
|
8132
|
-
import { existsSync as
|
|
8133
|
-
import { join as
|
|
8478
|
+
import { existsSync as existsSync48 } from "node:fs";
|
|
8479
|
+
import { join as join60 } from "node:path";
|
|
8134
8480
|
init_utils();
|
|
8135
8481
|
init_utils_fs();
|
|
8136
8482
|
init_utils_json();
|
|
8137
8483
|
function stripGsdHooksFromBase(repo, backup) {
|
|
8138
|
-
const basePath =
|
|
8139
|
-
if (!
|
|
8484
|
+
const basePath = join60(repo, "shared", "settings.base.json");
|
|
8485
|
+
if (!existsSync48(basePath)) return;
|
|
8140
8486
|
let base;
|
|
8141
8487
|
try {
|
|
8142
8488
|
base = readJson(basePath);
|
|
@@ -8150,14 +8496,14 @@ function stripGsdHooksFromBase(repo, backup) {
|
|
|
8150
8496
|
writeJsonAtomic(basePath, stripped);
|
|
8151
8497
|
}
|
|
8152
8498
|
function reportSettingsAheadDrift(repo) {
|
|
8153
|
-
const basePath =
|
|
8154
|
-
if (!
|
|
8155
|
-
const settingsPath =
|
|
8156
|
-
if (!
|
|
8499
|
+
const basePath = join60(repo, "shared", "settings.base.json");
|
|
8500
|
+
if (!existsSync48(basePath)) return;
|
|
8501
|
+
const settingsPath = join60(claudeHome(), "settings.json");
|
|
8502
|
+
if (!existsSync48(settingsPath)) return;
|
|
8157
8503
|
try {
|
|
8158
8504
|
const base = readJson(basePath);
|
|
8159
|
-
const hostPath =
|
|
8160
|
-
const overrides =
|
|
8505
|
+
const hostPath = join60(repo, "hosts", `${HOST}.json`);
|
|
8506
|
+
const overrides = existsSync48(hostPath) ? readJson(hostPath) : {};
|
|
8161
8507
|
const merged = deepMerge(base, overrides);
|
|
8162
8508
|
const settings = readJson(settingsPath);
|
|
8163
8509
|
const { ahead } = classifySettingsDrift(merged, settings);
|
|
@@ -8174,12 +8520,12 @@ function reportSettingsAheadDrift(repo) {
|
|
|
8174
8520
|
// src/commands.push.guards.ts
|
|
8175
8521
|
init_push_checks();
|
|
8176
8522
|
init_utils();
|
|
8177
|
-
import { join as
|
|
8523
|
+
import { join as join61, relative as relative9 } from "node:path";
|
|
8178
8524
|
function guardGitlinks(repo) {
|
|
8179
|
-
const gitlinks = findGitlinks(
|
|
8525
|
+
const gitlinks = findGitlinks(join61(repo, "shared"));
|
|
8180
8526
|
if (gitlinks.length === 0) return;
|
|
8181
8527
|
for (const p of gitlinks) {
|
|
8182
|
-
const rel =
|
|
8528
|
+
const rel = relative9(repo, p).replaceAll("\\", "/");
|
|
8183
8529
|
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
8184
8530
|
}
|
|
8185
8531
|
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
@@ -8208,7 +8554,7 @@ init_config();
|
|
|
8208
8554
|
|
|
8209
8555
|
// src/push-global-config.ts
|
|
8210
8556
|
init_config();
|
|
8211
|
-
import { execFileSync as
|
|
8557
|
+
import { execFileSync as execFileSync23 } from "node:child_process";
|
|
8212
8558
|
var STATUS_LABELS = {
|
|
8213
8559
|
A: "add",
|
|
8214
8560
|
M: "modify",
|
|
@@ -8248,7 +8594,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
|
|
|
8248
8594
|
}
|
|
8249
8595
|
function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
|
|
8250
8596
|
const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
|
|
8251
|
-
const raw =
|
|
8597
|
+
const raw = execFileSync23("git", args, {
|
|
8252
8598
|
cwd: repoHome2,
|
|
8253
8599
|
stdio: ["ignore", "pipe", "pipe"]
|
|
8254
8600
|
}).toString();
|
|
@@ -8286,9 +8632,9 @@ init_color();
|
|
|
8286
8632
|
init_config();
|
|
8287
8633
|
init_config_sharedDirs_guard();
|
|
8288
8634
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
8289
|
-
import { copyFileSync, existsSync as
|
|
8635
|
+
import { copyFileSync, existsSync as existsSync49, lstatSync as lstatSync19, mkdirSync as mkdirSync15, readdirSync as readdirSync19, rmSync as rmSync19 } from "node:fs";
|
|
8290
8636
|
import { homedir as homedir7 } from "node:os";
|
|
8291
|
-
import { join as
|
|
8637
|
+
import { join as join62, relative as relative10, sep as sep13 } from "node:path";
|
|
8292
8638
|
init_push_leak_verdict();
|
|
8293
8639
|
init_push_gitleaks();
|
|
8294
8640
|
init_utils_fs();
|
|
@@ -8296,11 +8642,11 @@ init_utils_json();
|
|
|
8296
8642
|
var NOTHING_TO_SCAN_ROW = `${dim(infoGlyph)} nothing to scan, no leaks`;
|
|
8297
8643
|
function stageSessionDir(localDir, dstDir, changed) {
|
|
8298
8644
|
if (changed !== void 0) {
|
|
8299
|
-
const prefix = `${localDir}${
|
|
8645
|
+
const prefix = `${localDir}${sep13}`;
|
|
8300
8646
|
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
8301
8647
|
if (matching.length === 0) return false;
|
|
8302
8648
|
for (const src of matching) {
|
|
8303
|
-
copyFileAtomic(src,
|
|
8649
|
+
copyFileAtomic(src, join62(dstDir, relative10(localDir, src)));
|
|
8304
8650
|
}
|
|
8305
8651
|
return true;
|
|
8306
8652
|
}
|
|
@@ -8316,14 +8662,14 @@ function stageSessions(tmpRoot, map, changed) {
|
|
|
8316
8662
|
if (!p || p === "TBD") continue;
|
|
8317
8663
|
reverse.set(encodePath(p), logical);
|
|
8318
8664
|
}
|
|
8319
|
-
const localProjects =
|
|
8320
|
-
if (!
|
|
8665
|
+
const localProjects = join62(claudeHome(), "projects");
|
|
8666
|
+
if (!existsSync49(localProjects)) return 0;
|
|
8321
8667
|
let staged = 0;
|
|
8322
|
-
for (const dir of
|
|
8668
|
+
for (const dir of readdirSync19(localProjects)) {
|
|
8323
8669
|
const logical = reverse.get(dir);
|
|
8324
8670
|
if (!logical) continue;
|
|
8325
|
-
const localDir =
|
|
8326
|
-
const dstDir =
|
|
8671
|
+
const localDir = join62(localProjects, dir);
|
|
8672
|
+
const dstDir = join62(tmpRoot, "shared", "projects", logical);
|
|
8327
8673
|
if (stageSessionDir(localDir, dstDir, changed)) staged++;
|
|
8328
8674
|
}
|
|
8329
8675
|
return staged;
|
|
@@ -8339,9 +8685,9 @@ function stageExtras(tmpRoot, map) {
|
|
|
8339
8685
|
if (!localRoot || localRoot === "TBD") continue;
|
|
8340
8686
|
for (const dirname14 of dirnames) {
|
|
8341
8687
|
if (!whitelist.includes(dirname14)) continue;
|
|
8342
|
-
const src =
|
|
8343
|
-
if (!
|
|
8344
|
-
const dst =
|
|
8688
|
+
const src = join62(localRoot, dirname14);
|
|
8689
|
+
if (!existsSync49(src)) continue;
|
|
8690
|
+
const dst = join62(tmpRoot, "shared", "extras", logical, dirname14);
|
|
8345
8691
|
copyExtras(src, dst);
|
|
8346
8692
|
staged++;
|
|
8347
8693
|
}
|
|
@@ -8349,19 +8695,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
8349
8695
|
return staged;
|
|
8350
8696
|
}
|
|
8351
8697
|
function stageSkills(tmpRoot) {
|
|
8352
|
-
const localSkills =
|
|
8353
|
-
const stat =
|
|
8698
|
+
const localSkills = join62(claudeHome(), "skills");
|
|
8699
|
+
const stat = lstatSync19(localSkills, { throwIfNoEntry: false });
|
|
8354
8700
|
if (stat === void 0 || stat.isSymbolicLink()) return 0;
|
|
8355
|
-
const names =
|
|
8701
|
+
const names = readdirSync19(localSkills, { encoding: "utf8" }).filter((n) => !isSkillExcluded(n));
|
|
8356
8702
|
if (names.length === 0) return 0;
|
|
8357
|
-
copySkillsPush(localSkills,
|
|
8703
|
+
copySkillsPush(localSkills, join62(tmpRoot, "shared", "skills"));
|
|
8358
8704
|
return names.length;
|
|
8359
8705
|
}
|
|
8360
8706
|
function previewPushLeaks(map, opts = {}) {
|
|
8361
|
-
const cacheDir =
|
|
8707
|
+
const cacheDir = join62(homedir7(), ".cache", "claude-nomad");
|
|
8362
8708
|
mkdirSync15(cacheDir, { recursive: true });
|
|
8363
8709
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes4(4).toString("hex")}`;
|
|
8364
|
-
const tmpRoot =
|
|
8710
|
+
const tmpRoot = join62(cacheDir, `push-preview-tree-${stamp}`);
|
|
8365
8711
|
try {
|
|
8366
8712
|
const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
|
|
8367
8713
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
@@ -8369,9 +8715,9 @@ function previewPushLeaks(map, opts = {}) {
|
|
|
8369
8715
|
if (sessionCount + extrasCount + skillCount === 0) {
|
|
8370
8716
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
8371
8717
|
}
|
|
8372
|
-
const ignoreFile =
|
|
8373
|
-
if (
|
|
8374
|
-
copyFileSync(ignoreFile,
|
|
8718
|
+
const ignoreFile = join62(repoHome(), ".gitleaksignore");
|
|
8719
|
+
if (existsSync49(ignoreFile)) {
|
|
8720
|
+
copyFileSync(ignoreFile, join62(tmpRoot, ".gitleaksignore"));
|
|
8375
8721
|
}
|
|
8376
8722
|
let findings;
|
|
8377
8723
|
try {
|
|
@@ -8384,7 +8730,7 @@ function previewPushLeaks(map, opts = {}) {
|
|
|
8384
8730
|
}
|
|
8385
8731
|
return verdictFromFindings(findings);
|
|
8386
8732
|
} finally {
|
|
8387
|
-
|
|
8733
|
+
rmSync19(tmpRoot, { recursive: true, force: true });
|
|
8388
8734
|
}
|
|
8389
8735
|
}
|
|
8390
8736
|
|
|
@@ -8499,7 +8845,7 @@ async function runPushCore(opts = {}) {
|
|
|
8499
8845
|
const scannerVersion = probeGitleaks();
|
|
8500
8846
|
const configHash = computeConfigHash();
|
|
8501
8847
|
const old = readManifest(manifestPath());
|
|
8502
|
-
const mapPath =
|
|
8848
|
+
const mapPath = join63(repo, "path-map.json");
|
|
8503
8849
|
const { map, selection, newManifest } = loadSelectionForPush(
|
|
8504
8850
|
mapPath,
|
|
8505
8851
|
old,
|
|
@@ -8551,7 +8897,7 @@ async function cmdPush(opts = {}) {
|
|
|
8551
8897
|
opts.allowRule
|
|
8552
8898
|
);
|
|
8553
8899
|
const repo = repoHome();
|
|
8554
|
-
if (!
|
|
8900
|
+
if (!existsSync50(repo)) die(`repo not cloned at ${repo}`);
|
|
8555
8901
|
const handle = acquireLock("push");
|
|
8556
8902
|
if (handle === null) process.exit(0);
|
|
8557
8903
|
try {
|
|
@@ -8569,8 +8915,8 @@ async function cmdPush(opts = {}) {
|
|
|
8569
8915
|
}
|
|
8570
8916
|
|
|
8571
8917
|
// src/commands.sync.ts
|
|
8572
|
-
import { existsSync as
|
|
8573
|
-
import { join as
|
|
8918
|
+
import { existsSync as existsSync51 } from "node:fs";
|
|
8919
|
+
import { join as join64 } from "node:path";
|
|
8574
8920
|
init_config();
|
|
8575
8921
|
init_color();
|
|
8576
8922
|
init_utils();
|
|
@@ -8704,8 +9050,8 @@ async function cmdSync(opts = {}) {
|
|
|
8704
9050
|
const dryRun = opts.dryRun === true;
|
|
8705
9051
|
const verbose = opts.verbose === true;
|
|
8706
9052
|
const repo = repoHome();
|
|
8707
|
-
if (!
|
|
8708
|
-
if (!
|
|
9053
|
+
if (!existsSync51(repo)) die(`repo not cloned at ${repo}`);
|
|
9054
|
+
if (!existsSync51(join64(repo, "shared", "settings.base.json"))) {
|
|
8709
9055
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
8710
9056
|
}
|
|
8711
9057
|
const handle = acquireLock("sync");
|
|
@@ -8729,9 +9075,9 @@ async function cmdSync(opts = {}) {
|
|
|
8729
9075
|
}
|
|
8730
9076
|
|
|
8731
9077
|
// src/commands.update.ts
|
|
8732
|
-
import { execFileSync as
|
|
9078
|
+
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
8733
9079
|
init_utils();
|
|
8734
|
-
function readInstalledVersion(run =
|
|
9080
|
+
function readInstalledVersion(run = execFileSync24) {
|
|
8735
9081
|
const isWin = process.platform === "win32";
|
|
8736
9082
|
try {
|
|
8737
9083
|
return run(isWin ? "nomad.cmd" : "nomad", ["--version"], {
|
|
@@ -8742,7 +9088,7 @@ function readInstalledVersion(run = execFileSync23) {
|
|
|
8742
9088
|
return null;
|
|
8743
9089
|
}
|
|
8744
9090
|
}
|
|
8745
|
-
function cmdUpdate(currentVersion, run =
|
|
9091
|
+
function cmdUpdate(currentVersion, run = execFileSync24) {
|
|
8746
9092
|
console.log(`Updating claude-nomad v${currentVersion}...`);
|
|
8747
9093
|
const isWin = process.platform === "win32";
|
|
8748
9094
|
try {
|
|
@@ -8781,8 +9127,8 @@ init_config();
|
|
|
8781
9127
|
// src/crash-report.write.ts
|
|
8782
9128
|
init_config();
|
|
8783
9129
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
8784
|
-
import { mkdirSync as mkdirSync16, readdirSync as
|
|
8785
|
-
import { join as
|
|
9130
|
+
import { mkdirSync as mkdirSync16, readdirSync as readdirSync20, statSync as statSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
|
|
9131
|
+
import { join as join66 } from "node:path";
|
|
8786
9132
|
|
|
8787
9133
|
// src/crash-report.ts
|
|
8788
9134
|
var CRASH_MAX_STACK_LINES = 50;
|
|
@@ -8865,17 +9211,17 @@ function buildCrashReport(input) {
|
|
|
8865
9211
|
}
|
|
8866
9212
|
|
|
8867
9213
|
// src/crash-report.redact.ts
|
|
8868
|
-
import { mkdtempSync as mkdtempSync3, rmSync as
|
|
9214
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync20, writeFileSync as writeFileSync10 } from "node:fs";
|
|
8869
9215
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
8870
|
-
import { join as
|
|
9216
|
+
import { join as join65 } from "node:path";
|
|
8871
9217
|
init_push_gitleaks_scan();
|
|
8872
9218
|
var CRASH_SCAN_TIMEOUT_MS = 3e3;
|
|
8873
9219
|
var SCAN_UNAVAILABLE_ADVISORY = "\n\n[gitleaks value-based scan unavailable; only structural redaction applied. Review before sharing this file publicly.]\n";
|
|
8874
9220
|
function redactWithGitleaks(text, scan = scanFile) {
|
|
8875
9221
|
let dir;
|
|
8876
9222
|
try {
|
|
8877
|
-
dir = mkdtempSync3(
|
|
8878
|
-
const tmp =
|
|
9223
|
+
dir = mkdtempSync3(join65(tmpdir2(), "nomad-crash-scan-"));
|
|
9224
|
+
const tmp = join65(dir, "crash.txt");
|
|
8879
9225
|
writeFileSync10(tmp, text, { mode: 384 });
|
|
8880
9226
|
const findings = scan(tmp, false, CRASH_SCAN_TIMEOUT_MS);
|
|
8881
9227
|
if (findings === null) return text + SCAN_UNAVAILABLE_ADVISORY;
|
|
@@ -8885,7 +9231,7 @@ function redactWithGitleaks(text, scan = scanFile) {
|
|
|
8885
9231
|
} finally {
|
|
8886
9232
|
if (dir !== void 0) {
|
|
8887
9233
|
try {
|
|
8888
|
-
|
|
9234
|
+
rmSync20(dir, { recursive: true, force: true });
|
|
8889
9235
|
} catch {
|
|
8890
9236
|
}
|
|
8891
9237
|
}
|
|
@@ -8898,9 +9244,9 @@ init_utils();
|
|
|
8898
9244
|
var CRASH_RETENTION_KEEP = 20;
|
|
8899
9245
|
function listCrashFiles(dir = crashDir()) {
|
|
8900
9246
|
try {
|
|
8901
|
-
return
|
|
9247
|
+
return readdirSync20(dir).flatMap((name) => {
|
|
8902
9248
|
try {
|
|
8903
|
-
return [{ name, mtimeMs: statSync12(
|
|
9249
|
+
return [{ name, mtimeMs: statSync12(join66(dir, name)).mtimeMs }];
|
|
8904
9250
|
} catch {
|
|
8905
9251
|
return [];
|
|
8906
9252
|
}
|
|
@@ -8914,7 +9260,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
8914
9260
|
const targets = prunableByCount(files, keep);
|
|
8915
9261
|
for (const name of targets) {
|
|
8916
9262
|
try {
|
|
8917
|
-
unlinkSync2(
|
|
9263
|
+
unlinkSync2(join66(dir, name));
|
|
8918
9264
|
} catch {
|
|
8919
9265
|
}
|
|
8920
9266
|
}
|
|
@@ -8922,7 +9268,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
8922
9268
|
function writeCrashReport(text, dir = crashDir()) {
|
|
8923
9269
|
mkdirSync16(dir, { recursive: true, mode: 448 });
|
|
8924
9270
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes5(4).toString("hex")}`;
|
|
8925
|
-
const path =
|
|
9271
|
+
const path = join66(dir, `crash-${stamp}.txt`);
|
|
8926
9272
|
writeFileSync11(path, text, { mode: 384 });
|
|
8927
9273
|
pruneCrashDir(dir);
|
|
8928
9274
|
return path;
|
|
@@ -8954,18 +9300,18 @@ function handleCrash(err, argv, opts) {
|
|
|
8954
9300
|
|
|
8955
9301
|
// src/diff.ts
|
|
8956
9302
|
init_config();
|
|
8957
|
-
import { existsSync as
|
|
8958
|
-
import { join as
|
|
9303
|
+
import { existsSync as existsSync52 } from "node:fs";
|
|
9304
|
+
import { join as join67 } from "node:path";
|
|
8959
9305
|
init_utils();
|
|
8960
9306
|
init_utils_fs();
|
|
8961
9307
|
init_utils_json();
|
|
8962
9308
|
function cmdDiff() {
|
|
8963
9309
|
try {
|
|
8964
9310
|
const repo = repoHome();
|
|
8965
|
-
if (!
|
|
9311
|
+
if (!existsSync52(repo)) die(`repo not cloned at ${repo}`);
|
|
8966
9312
|
const ts = freshBackupTs(backupBase());
|
|
8967
|
-
const mapPath =
|
|
8968
|
-
const map =
|
|
9313
|
+
const mapPath = join67(repo, "path-map.json");
|
|
9314
|
+
const map = existsSync52(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
8969
9315
|
divergenceCheckExtras(ts);
|
|
8970
9316
|
computePreview(ts, map, "diff");
|
|
8971
9317
|
} catch (err) {
|
|
@@ -8980,19 +9326,19 @@ function cmdDiff() {
|
|
|
8980
9326
|
|
|
8981
9327
|
// src/init.ts
|
|
8982
9328
|
init_config();
|
|
8983
|
-
import { existsSync as
|
|
8984
|
-
import { join as
|
|
9329
|
+
import { existsSync as existsSync54, mkdirSync as mkdirSync17, writeFileSync as writeFileSync12 } from "node:fs";
|
|
9330
|
+
import { join as join69 } from "node:path";
|
|
8985
9331
|
|
|
8986
9332
|
// src/init.gh-onboard.ts
|
|
8987
9333
|
init_config();
|
|
8988
|
-
import { execFileSync as
|
|
9334
|
+
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
8989
9335
|
init_utils();
|
|
8990
9336
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
8991
9337
|
function isValidRepoName(name) {
|
|
8992
9338
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
8993
9339
|
}
|
|
8994
9340
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
8995
|
-
function ensureOriginRepo(repoName, run =
|
|
9341
|
+
function ensureOriginRepo(repoName, run = execFileSync25) {
|
|
8996
9342
|
if (!isValidRepoName(repoName)) {
|
|
8997
9343
|
die(
|
|
8998
9344
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -9063,33 +9409,33 @@ init_config();
|
|
|
9063
9409
|
init_utils();
|
|
9064
9410
|
init_utils_fs();
|
|
9065
9411
|
init_utils_json();
|
|
9066
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync10, existsSync as
|
|
9067
|
-
import { join as
|
|
9412
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync10, existsSync as existsSync53, rmSync as rmSync21, statSync as statSync13 } from "node:fs";
|
|
9413
|
+
import { join as join68 } from "node:path";
|
|
9068
9414
|
function snapshotIntoShared(map) {
|
|
9069
9415
|
const repo = repoHome();
|
|
9070
9416
|
const claude = claudeHome();
|
|
9071
9417
|
for (const name of allSharedLinks(map)) {
|
|
9072
|
-
const src =
|
|
9073
|
-
if (!
|
|
9074
|
-
const dst =
|
|
9418
|
+
const src = join68(claude, name);
|
|
9419
|
+
if (!existsSync53(src)) continue;
|
|
9420
|
+
const dst = join68(repo, "shared", name);
|
|
9075
9421
|
if (statSync13(src).isDirectory()) {
|
|
9076
|
-
const gk =
|
|
9077
|
-
if (
|
|
9422
|
+
const gk = join68(dst, ".gitkeep");
|
|
9423
|
+
if (existsSync53(gk)) rmSync21(gk);
|
|
9078
9424
|
cpSync10(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
9079
9425
|
} else {
|
|
9080
9426
|
copyFileSync2(src, dst);
|
|
9081
9427
|
}
|
|
9082
9428
|
log(`snapshotted shared/${name} from ${src}`);
|
|
9083
9429
|
}
|
|
9084
|
-
const userSettings =
|
|
9085
|
-
if (
|
|
9430
|
+
const userSettings = join68(claude, "settings.json");
|
|
9431
|
+
if (existsSync53(userSettings)) {
|
|
9086
9432
|
let parsed;
|
|
9087
9433
|
try {
|
|
9088
9434
|
parsed = readJson(userSettings);
|
|
9089
9435
|
} catch (err) {
|
|
9090
9436
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
9091
9437
|
}
|
|
9092
|
-
const hostFile =
|
|
9438
|
+
const hostFile = join68(repo, "hosts", `${HOST}.json`);
|
|
9093
9439
|
writeJsonAtomic(hostFile, parsed);
|
|
9094
9440
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
9095
9441
|
}
|
|
@@ -9103,14 +9449,14 @@ var GITATTRIBUTES = "# nomad: sync content is byte-managed, disable all line-end
|
|
|
9103
9449
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
9104
9450
|
function preflightConflict(repoHome2) {
|
|
9105
9451
|
const candidates = [
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9452
|
+
join69(repoHome2, "shared", "settings.base.json"),
|
|
9453
|
+
join69(repoHome2, "shared", "CLAUDE.md"),
|
|
9454
|
+
join69(repoHome2, "path-map.json"),
|
|
9455
|
+
join69(repoHome2, "hosts"),
|
|
9456
|
+
join69(repoHome2, "shared")
|
|
9111
9457
|
];
|
|
9112
9458
|
for (const c of candidates) {
|
|
9113
|
-
if (
|
|
9459
|
+
if (existsSync54(c)) return c;
|
|
9114
9460
|
}
|
|
9115
9461
|
return null;
|
|
9116
9462
|
}
|
|
@@ -9128,27 +9474,27 @@ function cmdInit(opts = {}) {
|
|
|
9128
9474
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
9129
9475
|
}
|
|
9130
9476
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
9131
|
-
mkdirSync17(
|
|
9132
|
-
mkdirSync17(
|
|
9477
|
+
mkdirSync17(join69(repo, "shared"), { recursive: true });
|
|
9478
|
+
mkdirSync17(join69(repo, "hosts"), { recursive: true });
|
|
9133
9479
|
for (const name of SHARED_KEEP_DIRS) {
|
|
9134
|
-
mkdirSync17(
|
|
9480
|
+
mkdirSync17(join69(repo, "shared", name), { recursive: true });
|
|
9135
9481
|
}
|
|
9136
|
-
const userClaudeMd =
|
|
9137
|
-
if (!snapshot || !
|
|
9138
|
-
writeFileSync12(
|
|
9482
|
+
const userClaudeMd = join69(claude, "CLAUDE.md");
|
|
9483
|
+
if (!snapshot || !existsSync54(userClaudeMd)) {
|
|
9484
|
+
writeFileSync12(join69(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
9139
9485
|
item("created shared/CLAUDE.md");
|
|
9140
9486
|
}
|
|
9141
9487
|
for (const name of SHARED_KEEP_DIRS) {
|
|
9142
|
-
writeFileSync12(
|
|
9488
|
+
writeFileSync12(join69(repo, "shared", name, ".gitkeep"), "");
|
|
9143
9489
|
item(`created shared/${name}/.gitkeep`);
|
|
9144
9490
|
}
|
|
9145
|
-
writeFileSync12(
|
|
9491
|
+
writeFileSync12(join69(repo, "hosts", ".gitkeep"), "");
|
|
9146
9492
|
item("created hosts/.gitkeep");
|
|
9147
|
-
writeJsonAtomic(
|
|
9493
|
+
writeJsonAtomic(join69(repo, "shared", "settings.base.json"), {});
|
|
9148
9494
|
item("created shared/settings.base.json");
|
|
9149
|
-
writeJsonAtomic(
|
|
9495
|
+
writeJsonAtomic(join69(repo, "path-map.json"), { projects: {} });
|
|
9150
9496
|
item("created path-map.json");
|
|
9151
|
-
writeFileSync12(
|
|
9497
|
+
writeFileSync12(join69(repo, ".gitattributes"), GITATTRIBUTES);
|
|
9152
9498
|
item("created .gitattributes");
|
|
9153
9499
|
if (snapshot) {
|
|
9154
9500
|
snapshotIntoShared({ projects: {} });
|
|
@@ -9214,21 +9560,21 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
9214
9560
|
// src/init.prompt.ts
|
|
9215
9561
|
init_config();
|
|
9216
9562
|
init_utils();
|
|
9217
|
-
import { existsSync as
|
|
9218
|
-
import { join as
|
|
9563
|
+
import { existsSync as existsSync55, readdirSync as readdirSync21, statSync as statSync14 } from "node:fs";
|
|
9564
|
+
import { join as join70 } from "node:path";
|
|
9219
9565
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
9220
9566
|
function nonEmptyExists(path) {
|
|
9221
|
-
if (!
|
|
9567
|
+
if (!existsSync55(path)) return false;
|
|
9222
9568
|
try {
|
|
9223
|
-
if (statSync14(path).isDirectory()) return
|
|
9569
|
+
if (statSync14(path).isDirectory()) return readdirSync21(path).length > 0;
|
|
9224
9570
|
return true;
|
|
9225
9571
|
} catch {
|
|
9226
9572
|
return false;
|
|
9227
9573
|
}
|
|
9228
9574
|
}
|
|
9229
9575
|
function hasExistingClaudeConfig(claudeHome2) {
|
|
9230
|
-
if (
|
|
9231
|
-
return SHARED_LINKS.some((name) => nonEmptyExists(
|
|
9576
|
+
if (existsSync55(join70(claudeHome2, "settings.json"))) return true;
|
|
9577
|
+
return SHARED_LINKS.some((name) => nonEmptyExists(join70(claudeHome2, name)));
|
|
9232
9578
|
}
|
|
9233
9579
|
async function confirmSnapshotDefault(claudeHome2) {
|
|
9234
9580
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
@@ -9535,7 +9881,7 @@ function parseSyncArgs(argv) {
|
|
|
9535
9881
|
// package.json
|
|
9536
9882
|
var package_default = {
|
|
9537
9883
|
name: "claude-nomad",
|
|
9538
|
-
version: "0.
|
|
9884
|
+
version: "0.64.0",
|
|
9539
9885
|
type: "module",
|
|
9540
9886
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
9541
9887
|
keywords: [
|
|
@@ -9779,15 +10125,15 @@ var DEFAULT_HELP = [
|
|
|
9779
10125
|
init_config();
|
|
9780
10126
|
init_utils();
|
|
9781
10127
|
init_utils_json();
|
|
9782
|
-
import { existsSync as
|
|
9783
|
-
import { join as
|
|
10128
|
+
import { existsSync as existsSync56, readFileSync as readFileSync21, readdirSync as readdirSync22 } from "node:fs";
|
|
10129
|
+
import { join as join71 } from "node:path";
|
|
9784
10130
|
function resumeCmd(sessionId) {
|
|
9785
10131
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
9786
10132
|
fail(`invalid session id: ${sessionId}`);
|
|
9787
10133
|
process.exit(1);
|
|
9788
10134
|
}
|
|
9789
|
-
const projectsRoot =
|
|
9790
|
-
if (!
|
|
10135
|
+
const projectsRoot = join71(claudeHome(), "projects");
|
|
10136
|
+
if (!existsSync56(projectsRoot)) {
|
|
9791
10137
|
fail(`${projectsRoot} does not exist`);
|
|
9792
10138
|
process.exit(1);
|
|
9793
10139
|
}
|
|
@@ -9801,8 +10147,8 @@ function resumeCmd(sessionId) {
|
|
|
9801
10147
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
9802
10148
|
process.exit(1);
|
|
9803
10149
|
}
|
|
9804
|
-
const mapPath =
|
|
9805
|
-
if (!
|
|
10150
|
+
const mapPath = join71(repoHome(), "path-map.json");
|
|
10151
|
+
if (!existsSync56(mapPath)) {
|
|
9806
10152
|
fail("path-map.json missing");
|
|
9807
10153
|
process.exit(1);
|
|
9808
10154
|
}
|
|
@@ -9824,14 +10170,14 @@ function resumeCmd(sessionId) {
|
|
|
9824
10170
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
9825
10171
|
}
|
|
9826
10172
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
9827
|
-
for (const dir of
|
|
9828
|
-
const candidate =
|
|
9829
|
-
if (
|
|
10173
|
+
for (const dir of readdirSync22(projectsRoot)) {
|
|
10174
|
+
const candidate = join71(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
10175
|
+
if (existsSync56(candidate)) return candidate;
|
|
9830
10176
|
}
|
|
9831
10177
|
return null;
|
|
9832
10178
|
}
|
|
9833
10179
|
function extractRecordedCwd(jsonlPath) {
|
|
9834
|
-
for (const line of
|
|
10180
|
+
for (const line of readFileSync21(jsonlPath, "utf8").split("\n")) {
|
|
9835
10181
|
if (!line.trim()) continue;
|
|
9836
10182
|
try {
|
|
9837
10183
|
const obj = JSON.parse(line);
|