claude-nomad 0.63.1 → 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 +84 -0
- package/README.md +43 -29
- package/dist/nomad.mjs +622 -248
- package/package.json +1 -1
package/dist/nomad.mjs
CHANGED
|
@@ -353,8 +353,11 @@ var init_settings_keys = __esm({
|
|
|
353
353
|
"allowManagedPermissionRulesOnly",
|
|
354
354
|
"alwaysThinkingEnabled",
|
|
355
355
|
"apiKeyHelper",
|
|
356
|
+
"askUserQuestionTimeout",
|
|
356
357
|
"attribution",
|
|
357
358
|
"autoCompactEnabled",
|
|
359
|
+
"autoConnectIde",
|
|
360
|
+
"autoInstallIdeExtension",
|
|
358
361
|
"autoMemoryDirectory",
|
|
359
362
|
"autoMemoryEnabled",
|
|
360
363
|
"autoMode",
|
|
@@ -366,6 +369,7 @@ var init_settings_keys = __esm({
|
|
|
366
369
|
"awsCredentialExport",
|
|
367
370
|
"axScreenReader",
|
|
368
371
|
"blockedMarketplaces",
|
|
372
|
+
"browserExternalPageTools",
|
|
369
373
|
"channelsEnabled",
|
|
370
374
|
"claudeMd",
|
|
371
375
|
"claudeMdExcludes",
|
|
@@ -373,24 +377,31 @@ var init_settings_keys = __esm({
|
|
|
373
377
|
"companyAnnouncements",
|
|
374
378
|
"defaultShell",
|
|
375
379
|
"deniedMcpServers",
|
|
380
|
+
"diffTool",
|
|
376
381
|
"disableAgentView",
|
|
377
382
|
"disableAllHooks",
|
|
378
383
|
"disableArtifact",
|
|
379
384
|
"disableAutoMode",
|
|
385
|
+
"disableBrowserExternalNavigation",
|
|
380
386
|
"disableBundledSkills",
|
|
381
387
|
"disableClaudeAiConnectors",
|
|
382
388
|
"disableDeepLinkRegistration",
|
|
383
389
|
"disabledMcpjsonServers",
|
|
390
|
+
"disableMobileSimulatorTools",
|
|
384
391
|
"disableRemoteControl",
|
|
392
|
+
"disableSideloadFlags",
|
|
385
393
|
"disableSkillShellExecution",
|
|
386
394
|
"disableWorkflows",
|
|
387
395
|
"editorMode",
|
|
388
396
|
"effortLevel",
|
|
397
|
+
"emojiCompletionEnabled",
|
|
389
398
|
"enableAllProjectMcpServers",
|
|
399
|
+
"enableArtifact",
|
|
390
400
|
"enabledMcpjsonServers",
|
|
391
401
|
"enabledPlugins",
|
|
392
402
|
"enforceAvailableModels",
|
|
393
403
|
"env",
|
|
404
|
+
"externalEditorContext",
|
|
394
405
|
"extraKnownMarketplaces",
|
|
395
406
|
"fallbackModel",
|
|
396
407
|
"fastMode",
|
|
@@ -399,6 +410,7 @@ var init_settings_keys = __esm({
|
|
|
399
410
|
"fileCheckpointingEnabled",
|
|
400
411
|
"fileSuggestion",
|
|
401
412
|
"footerLinksRegexes",
|
|
413
|
+
"forceLoginGatewayUrl",
|
|
402
414
|
"forceLoginMethod",
|
|
403
415
|
"forceLoginOrgUUID",
|
|
404
416
|
"forceRemoteSettingsRefresh",
|
|
@@ -409,15 +421,14 @@ var init_settings_keys = __esm({
|
|
|
409
421
|
"includeGitInstructions",
|
|
410
422
|
"inputNeededNotifEnabled",
|
|
411
423
|
"language",
|
|
412
|
-
"leftArrowOpensAgents",
|
|
413
424
|
"managedMcpServers",
|
|
414
|
-
"maxSkillDescriptionChars",
|
|
415
425
|
"minimumVersion",
|
|
416
426
|
"model",
|
|
417
427
|
"modelOverrides",
|
|
418
428
|
"otelHeadersHelper",
|
|
419
429
|
"outputStyle",
|
|
420
430
|
"parentSettingsBehavior",
|
|
431
|
+
"permissionExplainerEnabled",
|
|
421
432
|
"permissions",
|
|
422
433
|
"plansDirectory",
|
|
423
434
|
"pluginConfigs",
|
|
@@ -426,8 +437,10 @@ var init_settings_keys = __esm({
|
|
|
426
437
|
"policyHelper",
|
|
427
438
|
"preferredNotifChannel",
|
|
428
439
|
"prefersReducedMotion",
|
|
440
|
+
"processWrapper",
|
|
429
441
|
"prUrlTemplate",
|
|
430
442
|
"remoteControlAtStartup",
|
|
443
|
+
"requireCoworkFullVmSandbox",
|
|
431
444
|
"requiredMaximumVersion",
|
|
432
445
|
"requiredMinimumVersion",
|
|
433
446
|
"respectGitignore",
|
|
@@ -437,6 +450,7 @@ var init_settings_keys = __esm({
|
|
|
437
450
|
"showThinkingSummaries",
|
|
438
451
|
"showTurnDuration",
|
|
439
452
|
"skillListingBudgetFraction",
|
|
453
|
+
"skillListingMaxDescChars",
|
|
440
454
|
"skillOverrides",
|
|
441
455
|
"skipDangerousModePermissionPrompt",
|
|
442
456
|
"skippedMarketplaces",
|
|
@@ -452,6 +466,7 @@ var init_settings_keys = __esm({
|
|
|
452
466
|
"strictPluginOnlyCustomization",
|
|
453
467
|
"subagentStatusLine",
|
|
454
468
|
"syntaxHighlightingDisabled",
|
|
469
|
+
"teammateDefaultModel",
|
|
455
470
|
"teammateMode",
|
|
456
471
|
"terminalProgressBarEnabled",
|
|
457
472
|
"theme",
|
|
@@ -459,29 +474,41 @@ var init_settings_keys = __esm({
|
|
|
459
474
|
"useAutoModeDuringPlan",
|
|
460
475
|
"verbose",
|
|
461
476
|
"viewMode",
|
|
477
|
+
"vimInsertModeRemaps",
|
|
462
478
|
"voice",
|
|
463
479
|
"voiceEnabled",
|
|
464
480
|
"wheelScrollAccelerationEnabled",
|
|
465
481
|
"workflowKeywordTriggerEnabled",
|
|
482
|
+
"workflowSizeGuideline",
|
|
466
483
|
"worktree",
|
|
467
484
|
"wslInheritsWindowsSettings"
|
|
468
485
|
];
|
|
469
486
|
APP_ONLY_KEYS = [
|
|
470
|
-
"
|
|
471
|
-
"
|
|
472
|
-
"
|
|
473
|
-
"
|
|
474
|
-
"
|
|
475
|
-
"
|
|
476
|
-
"
|
|
477
|
-
"
|
|
478
|
-
"
|
|
479
|
-
"
|
|
480
|
-
"
|
|
481
|
-
"
|
|
487
|
+
"autoCompactWindow",
|
|
488
|
+
"autoDreamEnabled",
|
|
489
|
+
"autoUploadSessions",
|
|
490
|
+
"breakReminder",
|
|
491
|
+
"daemonColdStart",
|
|
492
|
+
"defaultView",
|
|
493
|
+
"doneMeansMerged",
|
|
494
|
+
"enableWorkflows",
|
|
495
|
+
"feedbackDrafts",
|
|
496
|
+
"isolatePeerMachines",
|
|
497
|
+
"precomputeCompactionEnabled",
|
|
498
|
+
"promptSuggestionEnabled",
|
|
499
|
+
"proxyAuthHelper",
|
|
500
|
+
"quietHours",
|
|
501
|
+
"remote",
|
|
502
|
+
"showMessageTimestamps",
|
|
482
503
|
"skipAutoPermissionPrompt",
|
|
483
|
-
"
|
|
484
|
-
"
|
|
504
|
+
"skipWorkflowUsageWarning",
|
|
505
|
+
"switchModelsOnFlag",
|
|
506
|
+
"terminalTitleFromRename",
|
|
507
|
+
"todoFeatureEnabled",
|
|
508
|
+
"totalTokensReminder",
|
|
509
|
+
"totalTokensReminderAfterUserTurn",
|
|
510
|
+
"totalTokensReminderBudget",
|
|
511
|
+
"ultracode"
|
|
485
512
|
];
|
|
486
513
|
KNOWN_SETTINGS_KEYS = /* @__PURE__ */ new Set([...SCHEMA_KEYS, ...APP_ONLY_KEYS]);
|
|
487
514
|
}
|
|
@@ -511,6 +538,9 @@ function crashDir() {
|
|
|
511
538
|
function manifestPath() {
|
|
512
539
|
return join(home(), ".cache", "claude-nomad", `push-manifest-${encodeURIComponent(HOST)}.json`);
|
|
513
540
|
}
|
|
541
|
+
function sharedBaselinePath() {
|
|
542
|
+
return join(home(), ".cache", "claude-nomad", `shared-baseline-${encodeURIComponent(HOST)}.json`);
|
|
543
|
+
}
|
|
514
544
|
function allSharedLinks(map) {
|
|
515
545
|
const extras = [];
|
|
516
546
|
for (const entry of map.sharedDirs ?? []) {
|
|
@@ -2715,7 +2745,7 @@ import { join as join45 } from "node:path";
|
|
|
2715
2745
|
init_color();
|
|
2716
2746
|
init_config();
|
|
2717
2747
|
import { existsSync as existsSync11, lstatSync as lstatSync8, readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
|
|
2718
|
-
import { join as join13 } from "node:path";
|
|
2748
|
+
import { basename as basename3, join as join13 } from "node:path";
|
|
2719
2749
|
|
|
2720
2750
|
// src/commands.doctor.format.ts
|
|
2721
2751
|
init_color();
|
|
@@ -2790,6 +2820,61 @@ function readJsonSafe(path, label, section2) {
|
|
|
2790
2820
|
}
|
|
2791
2821
|
}
|
|
2792
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
|
+
|
|
2793
2878
|
// src/init.classify.ts
|
|
2794
2879
|
init_config();
|
|
2795
2880
|
init_utils_json();
|
|
@@ -2914,6 +2999,25 @@ function reportRepoState(section2) {
|
|
|
2914
2999
|
function repoHasSharedSource(name) {
|
|
2915
3000
|
return existsSync11(join13(repoHome(), "shared", name));
|
|
2916
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
|
+
}
|
|
2917
3021
|
function classifySharedLink(name, p) {
|
|
2918
3022
|
let stat;
|
|
2919
3023
|
try {
|
|
@@ -2930,10 +3034,7 @@ function classifySharedLink(name, p) {
|
|
|
2930
3034
|
}
|
|
2931
3035
|
if (!stat.isSymbolicLink()) {
|
|
2932
3036
|
if (process.platform === "win32") {
|
|
2933
|
-
return
|
|
2934
|
-
line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)`,
|
|
2935
|
-
fail: false
|
|
2936
|
-
};
|
|
3037
|
+
return classifyWin32Copy(name, p);
|
|
2937
3038
|
}
|
|
2938
3039
|
return {
|
|
2939
3040
|
line: `${red(failGlyph)} ${name}: NOT a symlink (blocks sync); run \`nomad adopt ${name}\` to fix`,
|
|
@@ -2967,9 +3068,12 @@ function reportSharedLinks(section2, map) {
|
|
|
2967
3068
|
const claude = claudeHome();
|
|
2968
3069
|
for (const name of allSharedLinks(map)) {
|
|
2969
3070
|
const p = join13(claude, name);
|
|
2970
|
-
const { line, fail: fail2 } = classifySharedLink(name, p);
|
|
3071
|
+
const { line, fail: fail2, children } = classifySharedLink(name, p);
|
|
2971
3072
|
addItem(section2, line);
|
|
2972
3073
|
if (fail2) process.exitCode = 1;
|
|
3074
|
+
if (children) {
|
|
3075
|
+
for (const child of children) addChildItem(section2, child);
|
|
3076
|
+
}
|
|
2973
3077
|
}
|
|
2974
3078
|
}
|
|
2975
3079
|
function reportDroppedNamesMigration(section2) {
|
|
@@ -3151,61 +3255,6 @@ init_color();
|
|
|
3151
3255
|
import { existsSync as existsSync14 } from "node:fs";
|
|
3152
3256
|
import { join as join16 } from "node:path";
|
|
3153
3257
|
init_config();
|
|
3154
|
-
|
|
3155
|
-
// src/extras-sync.diff.ts
|
|
3156
|
-
init_utils();
|
|
3157
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3158
|
-
import { relative as relative3 } from "node:path";
|
|
3159
|
-
function parseNameStatus(stdout) {
|
|
3160
|
-
const fields = stdout.split("\0").filter((f) => f.length > 0);
|
|
3161
|
-
const entries = [];
|
|
3162
|
-
for (let i = 0; i + 1 < fields.length; i += 2) {
|
|
3163
|
-
entries.push({ status: fields[i], path: fields[i + 1] });
|
|
3164
|
-
}
|
|
3165
|
-
return entries;
|
|
3166
|
-
}
|
|
3167
|
-
function labelEntry(entry) {
|
|
3168
|
-
if (entry.status === "D") return `${entry.path} (local only)`;
|
|
3169
|
-
if (entry.status === "A") return `${entry.path} (repo only)`;
|
|
3170
|
-
return entry.path;
|
|
3171
|
-
}
|
|
3172
|
-
function parseDiffOutput(stdout) {
|
|
3173
|
-
return parseNameStatus(stdout).map(labelEntry);
|
|
3174
|
-
}
|
|
3175
|
-
function parseModifiedPaths(stdout, a) {
|
|
3176
|
-
return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative3(a, entry.path));
|
|
3177
|
-
}
|
|
3178
|
-
function runNameStatusDiff(a, b, parse) {
|
|
3179
|
-
try {
|
|
3180
|
-
const stdout = execFileSync2(
|
|
3181
|
-
"git",
|
|
3182
|
-
["diff", "--no-index", "--no-renames", "-z", "--name-status", a, b],
|
|
3183
|
-
{
|
|
3184
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3185
|
-
}
|
|
3186
|
-
).toString();
|
|
3187
|
-
return parse(stdout);
|
|
3188
|
-
} catch (err) {
|
|
3189
|
-
const e = err;
|
|
3190
|
-
if (e.status === 1 && e.stdout !== void 0) {
|
|
3191
|
-
return parse(e.stdout.toString());
|
|
3192
|
-
}
|
|
3193
|
-
if (e.code === "ENOENT") {
|
|
3194
|
-
warn(`git not on PATH; divergence check skipped for ${a}`);
|
|
3195
|
-
return [];
|
|
3196
|
-
}
|
|
3197
|
-
warn(`divergence check failed for ${a}: ${e.message ?? String(err)}`);
|
|
3198
|
-
return [];
|
|
3199
|
-
}
|
|
3200
|
-
}
|
|
3201
|
-
function listDivergingFiles(a, b) {
|
|
3202
|
-
return runNameStatusDiff(a, b, parseDiffOutput);
|
|
3203
|
-
}
|
|
3204
|
-
function listDivergingModified(a, b) {
|
|
3205
|
-
return runNameStatusDiff(a, b, (stdout) => parseModifiedPaths(stdout, a));
|
|
3206
|
-
}
|
|
3207
|
-
|
|
3208
|
-
// src/commands.doctor.checks.skills.ts
|
|
3209
3258
|
function stripSideIndicator(line) {
|
|
3210
3259
|
if (line.endsWith(" (local only)")) return line.slice(0, -" (local only)".length);
|
|
3211
3260
|
if (line.endsWith(" (repo only)")) return line.slice(0, -" (repo only)".length);
|
|
@@ -3213,15 +3262,15 @@ function stripSideIndicator(line) {
|
|
|
3213
3262
|
}
|
|
3214
3263
|
function isGsdDiffLine(line, localBase, sharedBase) {
|
|
3215
3264
|
const bare = stripSideIndicator(line);
|
|
3216
|
-
let
|
|
3265
|
+
let relative11;
|
|
3217
3266
|
if (bare.startsWith(localBase + "/")) {
|
|
3218
|
-
|
|
3267
|
+
relative11 = bare.slice(localBase.length + 1);
|
|
3219
3268
|
} else if (bare.startsWith(sharedBase + "/")) {
|
|
3220
|
-
|
|
3269
|
+
relative11 = bare.slice(sharedBase.length + 1);
|
|
3221
3270
|
} else {
|
|
3222
|
-
|
|
3271
|
+
relative11 = bare;
|
|
3223
3272
|
}
|
|
3224
|
-
return
|
|
3273
|
+
return relative11.split("/")[0].startsWith(GSD_PREFIX);
|
|
3225
3274
|
}
|
|
3226
3275
|
function reportSkillsDivergence(section2) {
|
|
3227
3276
|
const sharedSkills = join16(repoHome(), "shared", "skills");
|
|
@@ -5127,12 +5176,12 @@ import { join as join37 } from "node:path";
|
|
|
5127
5176
|
|
|
5128
5177
|
// src/skills-sync.tracked.ts
|
|
5129
5178
|
init_utils();
|
|
5130
|
-
import { basename as
|
|
5179
|
+
import { basename as basename4 } from "node:path";
|
|
5131
5180
|
function trackedRootSkillsAt(ref, repo) {
|
|
5132
5181
|
try {
|
|
5133
5182
|
const raw = gitCaptureRaw(["ls-tree", "--name-only", "-z", ref, "--", "shared/skills/"], repo);
|
|
5134
5183
|
const entries = raw.split("\0").filter((entry) => entry !== "");
|
|
5135
|
-
return new Set(entries.map((entry) =>
|
|
5184
|
+
return new Set(entries.map((entry) => basename4(entry)));
|
|
5136
5185
|
} catch {
|
|
5137
5186
|
return /* @__PURE__ */ new Set();
|
|
5138
5187
|
}
|
|
@@ -5848,8 +5897,8 @@ function sessionSuffix(finding) {
|
|
|
5848
5897
|
const sid = sessionIdFromFinding(finding);
|
|
5849
5898
|
if (sid === null) return "";
|
|
5850
5899
|
const rawBasename = finding.File.replace(/^.*\//, "");
|
|
5851
|
-
const
|
|
5852
|
-
return
|
|
5900
|
+
const basename5 = rawBasename.endsWith(".jsonl") ? rawBasename.slice(0, -".jsonl".length) : rawBasename;
|
|
5901
|
+
return basename5 === sid ? "" : ` (session: ${sid})`;
|
|
5853
5902
|
}
|
|
5854
5903
|
function renderFindingBlock(finding, readLine, siblings = []) {
|
|
5855
5904
|
const lines = [` file: ${finding.File}:${finding.StartLine}${sessionSuffix(finding)}`];
|
|
@@ -6815,8 +6864,8 @@ function unstageOne(rel, repo) {
|
|
|
6815
6864
|
|
|
6816
6865
|
// src/commands.pull.ts
|
|
6817
6866
|
init_autostash_guard();
|
|
6818
|
-
import { existsSync as
|
|
6819
|
-
import { join as
|
|
6867
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync14 } from "node:fs";
|
|
6868
|
+
import { join as join58 } from "node:path";
|
|
6820
6869
|
|
|
6821
6870
|
// src/commands.push.sections.ts
|
|
6822
6871
|
init_color();
|
|
@@ -7251,10 +7300,204 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
7251
7300
|
return divergedCount;
|
|
7252
7301
|
}
|
|
7253
7302
|
|
|
7254
|
-
// 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
|
|
7255
7414
|
init_config();
|
|
7256
|
-
import { existsSync as
|
|
7415
|
+
import { existsSync as existsSync40, lstatSync as lstatSync16 } from "node:fs";
|
|
7257
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";
|
|
7258
7501
|
|
|
7259
7502
|
// node_modules/diff/libesm/diff/base.js
|
|
7260
7503
|
var Diff = class {
|
|
@@ -7529,16 +7772,16 @@ function diffLinesToUnified(oldStr, newStr) {
|
|
|
7529
7772
|
|
|
7530
7773
|
// src/preview.skills.ts
|
|
7531
7774
|
init_config();
|
|
7532
|
-
import { existsSync as
|
|
7533
|
-
import { join as
|
|
7775
|
+
import { existsSync as existsSync42, readdirSync as readdirSync18 } from "node:fs";
|
|
7776
|
+
import { join as join54 } from "node:path";
|
|
7534
7777
|
function buildSkillsPreviewSection() {
|
|
7535
7778
|
const s = section("Skills");
|
|
7536
|
-
const sharedSkills =
|
|
7537
|
-
if (!
|
|
7538
|
-
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"));
|
|
7539
7782
|
for (const name of sharedNames) addItem(s, name);
|
|
7540
|
-
const localSkills =
|
|
7541
|
-
const localOnly =
|
|
7783
|
+
const localSkills = join54(claudeHome(), "skills");
|
|
7784
|
+
const localOnly = existsSync42(localSkills) ? readdirSync18(localSkills, { encoding: "utf8" }).filter(
|
|
7542
7785
|
(name) => !isSkillExcluded(name) && !sharedNames.includes(name)
|
|
7543
7786
|
).length : 0;
|
|
7544
7787
|
if (localOnly > 0) {
|
|
@@ -7560,7 +7803,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
7560
7803
|
return lines.join("\n");
|
|
7561
7804
|
}
|
|
7562
7805
|
function readJsonOrNull(path) {
|
|
7563
|
-
if (!
|
|
7806
|
+
if (!existsSync43(path)) return null;
|
|
7564
7807
|
try {
|
|
7565
7808
|
return readJson(path);
|
|
7566
7809
|
} catch {
|
|
@@ -7574,12 +7817,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
7574
7817
|
}
|
|
7575
7818
|
const notes = [];
|
|
7576
7819
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
7577
|
-
if (hostOverrides === null &&
|
|
7820
|
+
if (hostOverrides === null && existsSync43(hostPath)) {
|
|
7578
7821
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
7579
7822
|
}
|
|
7580
7823
|
const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
|
|
7581
7824
|
const current = readJsonOrNull(settingsPath);
|
|
7582
|
-
if (current === null &&
|
|
7825
|
+
if (current === null && existsSync43(settingsPath)) {
|
|
7583
7826
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
7584
7827
|
}
|
|
7585
7828
|
const strippedCurrent = stripGsdHookEntries(current ?? {});
|
|
@@ -7595,6 +7838,12 @@ function formatLinkRow(e) {
|
|
|
7595
7838
|
if (e.kind === "copy") return `would copy ${e.from} -> ${e.to}`;
|
|
7596
7839
|
return `${e.kind} ${e.from} -> ${e.to}`;
|
|
7597
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
|
+
}
|
|
7598
7847
|
function formatSessionRow(e) {
|
|
7599
7848
|
return e.kind === "overwrite" ? `overwrite ${e.dst} (from ${e.src})` : e.text;
|
|
7600
7849
|
}
|
|
@@ -7610,20 +7859,30 @@ function buildSettingsSectionForPreview(result) {
|
|
|
7610
7859
|
}
|
|
7611
7860
|
return s;
|
|
7612
7861
|
}
|
|
7613
|
-
function computePreview(ts, map, verb = "pull") {
|
|
7862
|
+
function computePreview(ts, map, verb = "pull", plans) {
|
|
7614
7863
|
const repo = repoHome();
|
|
7615
7864
|
const claude = claudeHome();
|
|
7616
7865
|
console.log(`would pull on host=${HOST} (preview; nothing applied)`);
|
|
7617
7866
|
console.log("");
|
|
7618
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
|
+
}
|
|
7619
7878
|
applySharedLinks(ts, map, {
|
|
7620
7879
|
dryRun: true,
|
|
7621
7880
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
7622
7881
|
});
|
|
7623
7882
|
const settingsResult = previewSettings(
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7883
|
+
join55(repo, "shared", "settings.base.json"),
|
|
7884
|
+
join55(repo, "hosts", `${HOST}.json`),
|
|
7885
|
+
join55(claude, "settings.json")
|
|
7627
7886
|
);
|
|
7628
7887
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
7629
7888
|
const sessions = section("Sessions");
|
|
@@ -7639,7 +7898,7 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
7639
7898
|
const extras = section("Extras");
|
|
7640
7899
|
let extrasSkipped = 0;
|
|
7641
7900
|
let extrasUnmapped = 0;
|
|
7642
|
-
if (
|
|
7901
|
+
if (existsSync43(join55(repo, "path-map.json")) && existsSync43(join55(repo, "shared", "extras"))) {
|
|
7643
7902
|
const extrasResult = remapExtrasPull(ts, { dryRun: true });
|
|
7644
7903
|
for (const entry of extrasResult.wouldPull) {
|
|
7645
7904
|
addItem(extras, entry);
|
|
@@ -7656,6 +7915,135 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
7656
7915
|
return { unmapped: remapResult.unmapped, collisions: 0, localOnly };
|
|
7657
7916
|
}
|
|
7658
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
|
+
|
|
7659
8047
|
// src/commands.pull.ts
|
|
7660
8048
|
init_commands_pull_wedge();
|
|
7661
8049
|
|
|
@@ -7666,9 +8054,9 @@ init_utils_fs();
|
|
|
7666
8054
|
|
|
7667
8055
|
// src/commands.pull.recovery.git.ts
|
|
7668
8056
|
init_utils();
|
|
7669
|
-
import { execFileSync as
|
|
8057
|
+
import { execFileSync as execFileSync22 } from "node:child_process";
|
|
7670
8058
|
function gitCapture(args, cwd) {
|
|
7671
|
-
return
|
|
8059
|
+
return execFileSync22("git", args, {
|
|
7672
8060
|
cwd,
|
|
7673
8061
|
stdio: ["ignore", "pipe", "pipe"],
|
|
7674
8062
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -7821,6 +8209,7 @@ function capturePrePostHeads(repo, rebase) {
|
|
|
7821
8209
|
}
|
|
7822
8210
|
function buildWetPullSections(ts, map, prePostHeads) {
|
|
7823
8211
|
applySharedLinks(ts, map);
|
|
8212
|
+
writeSharedBaseline(map);
|
|
7824
8213
|
const { label } = regenerateSettings(ts);
|
|
7825
8214
|
syncSkillsPull(ts, prePostHeads);
|
|
7826
8215
|
const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
|
|
@@ -7860,22 +8249,6 @@ function handleWedge(repo, forceRemote) {
|
|
|
7860
8249
|
const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
|
|
7861
8250
|
die(wedgeMarkerRunbookText(state), { code: EXIT.CONFLICT });
|
|
7862
8251
|
}
|
|
7863
|
-
function readMapForMirror(mapPath) {
|
|
7864
|
-
if (!existsSync42(mapPath)) return { projects: {} };
|
|
7865
|
-
try {
|
|
7866
|
-
return readPathMap(mapPath);
|
|
7867
|
-
} catch {
|
|
7868
|
-
return null;
|
|
7869
|
-
}
|
|
7870
|
-
}
|
|
7871
|
-
function mirrorSharedLinksBeforePull(repo, ts) {
|
|
7872
|
-
if (process.platform !== "win32") return;
|
|
7873
|
-
try {
|
|
7874
|
-
stageLocalSharedEdits(readMapForMirror(join53(repo, "path-map.json")), ts);
|
|
7875
|
-
} catch (err) {
|
|
7876
|
-
warn(`could not stage local shared edits before the pull: ${err.message}`);
|
|
7877
|
-
}
|
|
7878
|
-
}
|
|
7879
8252
|
function runPullCore(opts = {}) {
|
|
7880
8253
|
const dryRun = opts.dryRun === true;
|
|
7881
8254
|
const forceRemote = opts.forceRemote === true;
|
|
@@ -7885,7 +8258,7 @@ function runPullCore(opts = {}) {
|
|
|
7885
8258
|
const ts = freshBackupTs(backup);
|
|
7886
8259
|
handleWedge(repo, forceRemote);
|
|
7887
8260
|
if (!dryRun) {
|
|
7888
|
-
const backupRoot =
|
|
8261
|
+
const backupRoot = join58(backup, ts);
|
|
7889
8262
|
try {
|
|
7890
8263
|
mkdirSync14(backupRoot, { recursive: true });
|
|
7891
8264
|
} catch (err) {
|
|
@@ -7897,16 +8270,17 @@ function runPullCore(opts = {}) {
|
|
|
7897
8270
|
dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
|
|
7898
8271
|
);
|
|
7899
8272
|
}
|
|
7900
|
-
|
|
8273
|
+
const mirrored = !dryRun && !forceRemote ? reconcileSharedLinksBeforePull(repo, ts) : [];
|
|
8274
|
+
const sharedPlans = dryRun ? planSharedReconcileBeforePull(repo) : void 0;
|
|
7901
8275
|
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
7902
|
-
|
|
8276
|
+
pullWithCollisionRunbook(repo, mirrored);
|
|
7903
8277
|
});
|
|
7904
8278
|
assertNoAutostashConflict(repo, "nomad pull");
|
|
7905
|
-
const mapPath =
|
|
7906
|
-
const map =
|
|
8279
|
+
const mapPath = join58(repo, "path-map.json");
|
|
8280
|
+
const map = existsSync46(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
7907
8281
|
const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
|
|
7908
8282
|
if (dryRun) {
|
|
7909
|
-
computePreview(ts, map, "pull");
|
|
8283
|
+
computePreview(ts, map, "pull", sharedPlans);
|
|
7910
8284
|
return { tag: "dry" };
|
|
7911
8285
|
}
|
|
7912
8286
|
const { sections, localOnly, settingsLabel, unmapped, extrasSkipped } = buildWetPullSections(
|
|
@@ -7928,8 +8302,8 @@ function runPullCore(opts = {}) {
|
|
|
7928
8302
|
}
|
|
7929
8303
|
function cmdPull(opts = {}) {
|
|
7930
8304
|
const repo = repoHome();
|
|
7931
|
-
if (!
|
|
7932
|
-
if (!
|
|
8305
|
+
if (!existsSync46(repo)) die(`repo not cloned at ${repo}`);
|
|
8306
|
+
if (!existsSync46(join58(repo, "shared", "settings.base.json"))) {
|
|
7933
8307
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
7934
8308
|
}
|
|
7935
8309
|
const handle = acquireLock("pull");
|
|
@@ -7952,13 +8326,13 @@ function cmdPull(opts = {}) {
|
|
|
7952
8326
|
|
|
7953
8327
|
// src/commands.push.ts
|
|
7954
8328
|
init_config();
|
|
7955
|
-
import { existsSync as
|
|
7956
|
-
import { join as
|
|
8329
|
+
import { existsSync as existsSync50 } from "node:fs";
|
|
8330
|
+
import { join as join63 } from "node:path";
|
|
7957
8331
|
|
|
7958
8332
|
// src/commands.push.selection.ts
|
|
7959
8333
|
init_config();
|
|
7960
|
-
import { existsSync as
|
|
7961
|
-
import { join as
|
|
8334
|
+
import { existsSync as existsSync47, statSync as statSync11 } from "node:fs";
|
|
8335
|
+
import { join as join59 } from "node:path";
|
|
7962
8336
|
init_utils_json();
|
|
7963
8337
|
function buildCurrentMap(map) {
|
|
7964
8338
|
const current = {};
|
|
@@ -7967,8 +8341,8 @@ function buildCurrentMap(map) {
|
|
|
7967
8341
|
for (const [, hostMap] of Object.entries(map.projects)) {
|
|
7968
8342
|
const localPath = hostMap[HOST];
|
|
7969
8343
|
if (!localPath) continue;
|
|
7970
|
-
const localDir =
|
|
7971
|
-
if (!
|
|
8344
|
+
const localDir = join59(claude, "projects", encodePath(localPath));
|
|
8345
|
+
if (!existsSync47(localDir)) continue;
|
|
7972
8346
|
for (const f of enumerateSourceFiles(localDir)) {
|
|
7973
8347
|
const st = statSync11(f);
|
|
7974
8348
|
current[f] = { size: st.size, mtime: st.mtimeMs };
|
|
@@ -7999,7 +8373,7 @@ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
|
|
|
7999
8373
|
};
|
|
8000
8374
|
}
|
|
8001
8375
|
function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
|
|
8002
|
-
const map =
|
|
8376
|
+
const map = existsSync47(mapPath) ? readPathMap(mapPath) : null;
|
|
8003
8377
|
const { selection, newManifest } = computePushSelection(
|
|
8004
8378
|
map,
|
|
8005
8379
|
old,
|
|
@@ -8101,14 +8475,14 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
8101
8475
|
|
|
8102
8476
|
// src/commands.push.settings.ts
|
|
8103
8477
|
init_config();
|
|
8104
|
-
import { existsSync as
|
|
8105
|
-
import { join as
|
|
8478
|
+
import { existsSync as existsSync48 } from "node:fs";
|
|
8479
|
+
import { join as join60 } from "node:path";
|
|
8106
8480
|
init_utils();
|
|
8107
8481
|
init_utils_fs();
|
|
8108
8482
|
init_utils_json();
|
|
8109
8483
|
function stripGsdHooksFromBase(repo, backup) {
|
|
8110
|
-
const basePath =
|
|
8111
|
-
if (!
|
|
8484
|
+
const basePath = join60(repo, "shared", "settings.base.json");
|
|
8485
|
+
if (!existsSync48(basePath)) return;
|
|
8112
8486
|
let base;
|
|
8113
8487
|
try {
|
|
8114
8488
|
base = readJson(basePath);
|
|
@@ -8122,14 +8496,14 @@ function stripGsdHooksFromBase(repo, backup) {
|
|
|
8122
8496
|
writeJsonAtomic(basePath, stripped);
|
|
8123
8497
|
}
|
|
8124
8498
|
function reportSettingsAheadDrift(repo) {
|
|
8125
|
-
const basePath =
|
|
8126
|
-
if (!
|
|
8127
|
-
const settingsPath =
|
|
8128
|
-
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;
|
|
8129
8503
|
try {
|
|
8130
8504
|
const base = readJson(basePath);
|
|
8131
|
-
const hostPath =
|
|
8132
|
-
const overrides =
|
|
8505
|
+
const hostPath = join60(repo, "hosts", `${HOST}.json`);
|
|
8506
|
+
const overrides = existsSync48(hostPath) ? readJson(hostPath) : {};
|
|
8133
8507
|
const merged = deepMerge(base, overrides);
|
|
8134
8508
|
const settings = readJson(settingsPath);
|
|
8135
8509
|
const { ahead } = classifySettingsDrift(merged, settings);
|
|
@@ -8146,12 +8520,12 @@ function reportSettingsAheadDrift(repo) {
|
|
|
8146
8520
|
// src/commands.push.guards.ts
|
|
8147
8521
|
init_push_checks();
|
|
8148
8522
|
init_utils();
|
|
8149
|
-
import { join as
|
|
8523
|
+
import { join as join61, relative as relative9 } from "node:path";
|
|
8150
8524
|
function guardGitlinks(repo) {
|
|
8151
|
-
const gitlinks = findGitlinks(
|
|
8525
|
+
const gitlinks = findGitlinks(join61(repo, "shared"));
|
|
8152
8526
|
if (gitlinks.length === 0) return;
|
|
8153
8527
|
for (const p of gitlinks) {
|
|
8154
|
-
const rel =
|
|
8528
|
+
const rel = relative9(repo, p).replaceAll("\\", "/");
|
|
8155
8529
|
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
8156
8530
|
}
|
|
8157
8531
|
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
@@ -8180,7 +8554,7 @@ init_config();
|
|
|
8180
8554
|
|
|
8181
8555
|
// src/push-global-config.ts
|
|
8182
8556
|
init_config();
|
|
8183
|
-
import { execFileSync as
|
|
8557
|
+
import { execFileSync as execFileSync23 } from "node:child_process";
|
|
8184
8558
|
var STATUS_LABELS = {
|
|
8185
8559
|
A: "add",
|
|
8186
8560
|
M: "modify",
|
|
@@ -8220,7 +8594,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
|
|
|
8220
8594
|
}
|
|
8221
8595
|
function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
|
|
8222
8596
|
const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
|
|
8223
|
-
const raw =
|
|
8597
|
+
const raw = execFileSync23("git", args, {
|
|
8224
8598
|
cwd: repoHome2,
|
|
8225
8599
|
stdio: ["ignore", "pipe", "pipe"]
|
|
8226
8600
|
}).toString();
|
|
@@ -8258,9 +8632,9 @@ init_color();
|
|
|
8258
8632
|
init_config();
|
|
8259
8633
|
init_config_sharedDirs_guard();
|
|
8260
8634
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
8261
|
-
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";
|
|
8262
8636
|
import { homedir as homedir7 } from "node:os";
|
|
8263
|
-
import { join as
|
|
8637
|
+
import { join as join62, relative as relative10, sep as sep13 } from "node:path";
|
|
8264
8638
|
init_push_leak_verdict();
|
|
8265
8639
|
init_push_gitleaks();
|
|
8266
8640
|
init_utils_fs();
|
|
@@ -8268,11 +8642,11 @@ init_utils_json();
|
|
|
8268
8642
|
var NOTHING_TO_SCAN_ROW = `${dim(infoGlyph)} nothing to scan, no leaks`;
|
|
8269
8643
|
function stageSessionDir(localDir, dstDir, changed) {
|
|
8270
8644
|
if (changed !== void 0) {
|
|
8271
|
-
const prefix = `${localDir}${
|
|
8645
|
+
const prefix = `${localDir}${sep13}`;
|
|
8272
8646
|
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
8273
8647
|
if (matching.length === 0) return false;
|
|
8274
8648
|
for (const src of matching) {
|
|
8275
|
-
copyFileAtomic(src,
|
|
8649
|
+
copyFileAtomic(src, join62(dstDir, relative10(localDir, src)));
|
|
8276
8650
|
}
|
|
8277
8651
|
return true;
|
|
8278
8652
|
}
|
|
@@ -8288,14 +8662,14 @@ function stageSessions(tmpRoot, map, changed) {
|
|
|
8288
8662
|
if (!p || p === "TBD") continue;
|
|
8289
8663
|
reverse.set(encodePath(p), logical);
|
|
8290
8664
|
}
|
|
8291
|
-
const localProjects =
|
|
8292
|
-
if (!
|
|
8665
|
+
const localProjects = join62(claudeHome(), "projects");
|
|
8666
|
+
if (!existsSync49(localProjects)) return 0;
|
|
8293
8667
|
let staged = 0;
|
|
8294
|
-
for (const dir of
|
|
8668
|
+
for (const dir of readdirSync19(localProjects)) {
|
|
8295
8669
|
const logical = reverse.get(dir);
|
|
8296
8670
|
if (!logical) continue;
|
|
8297
|
-
const localDir =
|
|
8298
|
-
const dstDir =
|
|
8671
|
+
const localDir = join62(localProjects, dir);
|
|
8672
|
+
const dstDir = join62(tmpRoot, "shared", "projects", logical);
|
|
8299
8673
|
if (stageSessionDir(localDir, dstDir, changed)) staged++;
|
|
8300
8674
|
}
|
|
8301
8675
|
return staged;
|
|
@@ -8311,9 +8685,9 @@ function stageExtras(tmpRoot, map) {
|
|
|
8311
8685
|
if (!localRoot || localRoot === "TBD") continue;
|
|
8312
8686
|
for (const dirname14 of dirnames) {
|
|
8313
8687
|
if (!whitelist.includes(dirname14)) continue;
|
|
8314
|
-
const src =
|
|
8315
|
-
if (!
|
|
8316
|
-
const dst =
|
|
8688
|
+
const src = join62(localRoot, dirname14);
|
|
8689
|
+
if (!existsSync49(src)) continue;
|
|
8690
|
+
const dst = join62(tmpRoot, "shared", "extras", logical, dirname14);
|
|
8317
8691
|
copyExtras(src, dst);
|
|
8318
8692
|
staged++;
|
|
8319
8693
|
}
|
|
@@ -8321,19 +8695,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
8321
8695
|
return staged;
|
|
8322
8696
|
}
|
|
8323
8697
|
function stageSkills(tmpRoot) {
|
|
8324
|
-
const localSkills =
|
|
8325
|
-
const stat =
|
|
8698
|
+
const localSkills = join62(claudeHome(), "skills");
|
|
8699
|
+
const stat = lstatSync19(localSkills, { throwIfNoEntry: false });
|
|
8326
8700
|
if (stat === void 0 || stat.isSymbolicLink()) return 0;
|
|
8327
|
-
const names =
|
|
8701
|
+
const names = readdirSync19(localSkills, { encoding: "utf8" }).filter((n) => !isSkillExcluded(n));
|
|
8328
8702
|
if (names.length === 0) return 0;
|
|
8329
|
-
copySkillsPush(localSkills,
|
|
8703
|
+
copySkillsPush(localSkills, join62(tmpRoot, "shared", "skills"));
|
|
8330
8704
|
return names.length;
|
|
8331
8705
|
}
|
|
8332
8706
|
function previewPushLeaks(map, opts = {}) {
|
|
8333
|
-
const cacheDir =
|
|
8707
|
+
const cacheDir = join62(homedir7(), ".cache", "claude-nomad");
|
|
8334
8708
|
mkdirSync15(cacheDir, { recursive: true });
|
|
8335
8709
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes4(4).toString("hex")}`;
|
|
8336
|
-
const tmpRoot =
|
|
8710
|
+
const tmpRoot = join62(cacheDir, `push-preview-tree-${stamp}`);
|
|
8337
8711
|
try {
|
|
8338
8712
|
const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
|
|
8339
8713
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
@@ -8341,9 +8715,9 @@ function previewPushLeaks(map, opts = {}) {
|
|
|
8341
8715
|
if (sessionCount + extrasCount + skillCount === 0) {
|
|
8342
8716
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
8343
8717
|
}
|
|
8344
|
-
const ignoreFile =
|
|
8345
|
-
if (
|
|
8346
|
-
copyFileSync(ignoreFile,
|
|
8718
|
+
const ignoreFile = join62(repoHome(), ".gitleaksignore");
|
|
8719
|
+
if (existsSync49(ignoreFile)) {
|
|
8720
|
+
copyFileSync(ignoreFile, join62(tmpRoot, ".gitleaksignore"));
|
|
8347
8721
|
}
|
|
8348
8722
|
let findings;
|
|
8349
8723
|
try {
|
|
@@ -8356,7 +8730,7 @@ function previewPushLeaks(map, opts = {}) {
|
|
|
8356
8730
|
}
|
|
8357
8731
|
return verdictFromFindings(findings);
|
|
8358
8732
|
} finally {
|
|
8359
|
-
|
|
8733
|
+
rmSync19(tmpRoot, { recursive: true, force: true });
|
|
8360
8734
|
}
|
|
8361
8735
|
}
|
|
8362
8736
|
|
|
@@ -8471,7 +8845,7 @@ async function runPushCore(opts = {}) {
|
|
|
8471
8845
|
const scannerVersion = probeGitleaks();
|
|
8472
8846
|
const configHash = computeConfigHash();
|
|
8473
8847
|
const old = readManifest(manifestPath());
|
|
8474
|
-
const mapPath =
|
|
8848
|
+
const mapPath = join63(repo, "path-map.json");
|
|
8475
8849
|
const { map, selection, newManifest } = loadSelectionForPush(
|
|
8476
8850
|
mapPath,
|
|
8477
8851
|
old,
|
|
@@ -8523,7 +8897,7 @@ async function cmdPush(opts = {}) {
|
|
|
8523
8897
|
opts.allowRule
|
|
8524
8898
|
);
|
|
8525
8899
|
const repo = repoHome();
|
|
8526
|
-
if (!
|
|
8900
|
+
if (!existsSync50(repo)) die(`repo not cloned at ${repo}`);
|
|
8527
8901
|
const handle = acquireLock("push");
|
|
8528
8902
|
if (handle === null) process.exit(0);
|
|
8529
8903
|
try {
|
|
@@ -8541,8 +8915,8 @@ async function cmdPush(opts = {}) {
|
|
|
8541
8915
|
}
|
|
8542
8916
|
|
|
8543
8917
|
// src/commands.sync.ts
|
|
8544
|
-
import { existsSync as
|
|
8545
|
-
import { join as
|
|
8918
|
+
import { existsSync as existsSync51 } from "node:fs";
|
|
8919
|
+
import { join as join64 } from "node:path";
|
|
8546
8920
|
init_config();
|
|
8547
8921
|
init_color();
|
|
8548
8922
|
init_utils();
|
|
@@ -8676,8 +9050,8 @@ async function cmdSync(opts = {}) {
|
|
|
8676
9050
|
const dryRun = opts.dryRun === true;
|
|
8677
9051
|
const verbose = opts.verbose === true;
|
|
8678
9052
|
const repo = repoHome();
|
|
8679
|
-
if (!
|
|
8680
|
-
if (!
|
|
9053
|
+
if (!existsSync51(repo)) die(`repo not cloned at ${repo}`);
|
|
9054
|
+
if (!existsSync51(join64(repo, "shared", "settings.base.json"))) {
|
|
8681
9055
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
8682
9056
|
}
|
|
8683
9057
|
const handle = acquireLock("sync");
|
|
@@ -8701,9 +9075,9 @@ async function cmdSync(opts = {}) {
|
|
|
8701
9075
|
}
|
|
8702
9076
|
|
|
8703
9077
|
// src/commands.update.ts
|
|
8704
|
-
import { execFileSync as
|
|
9078
|
+
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
8705
9079
|
init_utils();
|
|
8706
|
-
function readInstalledVersion(run =
|
|
9080
|
+
function readInstalledVersion(run = execFileSync24) {
|
|
8707
9081
|
const isWin = process.platform === "win32";
|
|
8708
9082
|
try {
|
|
8709
9083
|
return run(isWin ? "nomad.cmd" : "nomad", ["--version"], {
|
|
@@ -8714,7 +9088,7 @@ function readInstalledVersion(run = execFileSync23) {
|
|
|
8714
9088
|
return null;
|
|
8715
9089
|
}
|
|
8716
9090
|
}
|
|
8717
|
-
function cmdUpdate(currentVersion, run =
|
|
9091
|
+
function cmdUpdate(currentVersion, run = execFileSync24) {
|
|
8718
9092
|
console.log(`Updating claude-nomad v${currentVersion}...`);
|
|
8719
9093
|
const isWin = process.platform === "win32";
|
|
8720
9094
|
try {
|
|
@@ -8753,8 +9127,8 @@ init_config();
|
|
|
8753
9127
|
// src/crash-report.write.ts
|
|
8754
9128
|
init_config();
|
|
8755
9129
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
8756
|
-
import { mkdirSync as mkdirSync16, readdirSync as
|
|
8757
|
-
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";
|
|
8758
9132
|
|
|
8759
9133
|
// src/crash-report.ts
|
|
8760
9134
|
var CRASH_MAX_STACK_LINES = 50;
|
|
@@ -8837,17 +9211,17 @@ function buildCrashReport(input) {
|
|
|
8837
9211
|
}
|
|
8838
9212
|
|
|
8839
9213
|
// src/crash-report.redact.ts
|
|
8840
|
-
import { mkdtempSync as mkdtempSync3, rmSync as
|
|
9214
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync20, writeFileSync as writeFileSync10 } from "node:fs";
|
|
8841
9215
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
8842
|
-
import { join as
|
|
9216
|
+
import { join as join65 } from "node:path";
|
|
8843
9217
|
init_push_gitleaks_scan();
|
|
8844
9218
|
var CRASH_SCAN_TIMEOUT_MS = 3e3;
|
|
8845
9219
|
var SCAN_UNAVAILABLE_ADVISORY = "\n\n[gitleaks value-based scan unavailable; only structural redaction applied. Review before sharing this file publicly.]\n";
|
|
8846
9220
|
function redactWithGitleaks(text, scan = scanFile) {
|
|
8847
9221
|
let dir;
|
|
8848
9222
|
try {
|
|
8849
|
-
dir = mkdtempSync3(
|
|
8850
|
-
const tmp =
|
|
9223
|
+
dir = mkdtempSync3(join65(tmpdir2(), "nomad-crash-scan-"));
|
|
9224
|
+
const tmp = join65(dir, "crash.txt");
|
|
8851
9225
|
writeFileSync10(tmp, text, { mode: 384 });
|
|
8852
9226
|
const findings = scan(tmp, false, CRASH_SCAN_TIMEOUT_MS);
|
|
8853
9227
|
if (findings === null) return text + SCAN_UNAVAILABLE_ADVISORY;
|
|
@@ -8857,7 +9231,7 @@ function redactWithGitleaks(text, scan = scanFile) {
|
|
|
8857
9231
|
} finally {
|
|
8858
9232
|
if (dir !== void 0) {
|
|
8859
9233
|
try {
|
|
8860
|
-
|
|
9234
|
+
rmSync20(dir, { recursive: true, force: true });
|
|
8861
9235
|
} catch {
|
|
8862
9236
|
}
|
|
8863
9237
|
}
|
|
@@ -8870,9 +9244,9 @@ init_utils();
|
|
|
8870
9244
|
var CRASH_RETENTION_KEEP = 20;
|
|
8871
9245
|
function listCrashFiles(dir = crashDir()) {
|
|
8872
9246
|
try {
|
|
8873
|
-
return
|
|
9247
|
+
return readdirSync20(dir).flatMap((name) => {
|
|
8874
9248
|
try {
|
|
8875
|
-
return [{ name, mtimeMs: statSync12(
|
|
9249
|
+
return [{ name, mtimeMs: statSync12(join66(dir, name)).mtimeMs }];
|
|
8876
9250
|
} catch {
|
|
8877
9251
|
return [];
|
|
8878
9252
|
}
|
|
@@ -8886,7 +9260,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
8886
9260
|
const targets = prunableByCount(files, keep);
|
|
8887
9261
|
for (const name of targets) {
|
|
8888
9262
|
try {
|
|
8889
|
-
unlinkSync2(
|
|
9263
|
+
unlinkSync2(join66(dir, name));
|
|
8890
9264
|
} catch {
|
|
8891
9265
|
}
|
|
8892
9266
|
}
|
|
@@ -8894,7 +9268,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
8894
9268
|
function writeCrashReport(text, dir = crashDir()) {
|
|
8895
9269
|
mkdirSync16(dir, { recursive: true, mode: 448 });
|
|
8896
9270
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes5(4).toString("hex")}`;
|
|
8897
|
-
const path =
|
|
9271
|
+
const path = join66(dir, `crash-${stamp}.txt`);
|
|
8898
9272
|
writeFileSync11(path, text, { mode: 384 });
|
|
8899
9273
|
pruneCrashDir(dir);
|
|
8900
9274
|
return path;
|
|
@@ -8926,18 +9300,18 @@ function handleCrash(err, argv, opts) {
|
|
|
8926
9300
|
|
|
8927
9301
|
// src/diff.ts
|
|
8928
9302
|
init_config();
|
|
8929
|
-
import { existsSync as
|
|
8930
|
-
import { join as
|
|
9303
|
+
import { existsSync as existsSync52 } from "node:fs";
|
|
9304
|
+
import { join as join67 } from "node:path";
|
|
8931
9305
|
init_utils();
|
|
8932
9306
|
init_utils_fs();
|
|
8933
9307
|
init_utils_json();
|
|
8934
9308
|
function cmdDiff() {
|
|
8935
9309
|
try {
|
|
8936
9310
|
const repo = repoHome();
|
|
8937
|
-
if (!
|
|
9311
|
+
if (!existsSync52(repo)) die(`repo not cloned at ${repo}`);
|
|
8938
9312
|
const ts = freshBackupTs(backupBase());
|
|
8939
|
-
const mapPath =
|
|
8940
|
-
const map =
|
|
9313
|
+
const mapPath = join67(repo, "path-map.json");
|
|
9314
|
+
const map = existsSync52(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
8941
9315
|
divergenceCheckExtras(ts);
|
|
8942
9316
|
computePreview(ts, map, "diff");
|
|
8943
9317
|
} catch (err) {
|
|
@@ -8952,19 +9326,19 @@ function cmdDiff() {
|
|
|
8952
9326
|
|
|
8953
9327
|
// src/init.ts
|
|
8954
9328
|
init_config();
|
|
8955
|
-
import { existsSync as
|
|
8956
|
-
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";
|
|
8957
9331
|
|
|
8958
9332
|
// src/init.gh-onboard.ts
|
|
8959
9333
|
init_config();
|
|
8960
|
-
import { execFileSync as
|
|
9334
|
+
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
8961
9335
|
init_utils();
|
|
8962
9336
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
8963
9337
|
function isValidRepoName(name) {
|
|
8964
9338
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
8965
9339
|
}
|
|
8966
9340
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
8967
|
-
function ensureOriginRepo(repoName, run =
|
|
9341
|
+
function ensureOriginRepo(repoName, run = execFileSync25) {
|
|
8968
9342
|
if (!isValidRepoName(repoName)) {
|
|
8969
9343
|
die(
|
|
8970
9344
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -9035,33 +9409,33 @@ init_config();
|
|
|
9035
9409
|
init_utils();
|
|
9036
9410
|
init_utils_fs();
|
|
9037
9411
|
init_utils_json();
|
|
9038
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync10, existsSync as
|
|
9039
|
-
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";
|
|
9040
9414
|
function snapshotIntoShared(map) {
|
|
9041
9415
|
const repo = repoHome();
|
|
9042
9416
|
const claude = claudeHome();
|
|
9043
9417
|
for (const name of allSharedLinks(map)) {
|
|
9044
|
-
const src =
|
|
9045
|
-
if (!
|
|
9046
|
-
const dst =
|
|
9418
|
+
const src = join68(claude, name);
|
|
9419
|
+
if (!existsSync53(src)) continue;
|
|
9420
|
+
const dst = join68(repo, "shared", name);
|
|
9047
9421
|
if (statSync13(src).isDirectory()) {
|
|
9048
|
-
const gk =
|
|
9049
|
-
if (
|
|
9422
|
+
const gk = join68(dst, ".gitkeep");
|
|
9423
|
+
if (existsSync53(gk)) rmSync21(gk);
|
|
9050
9424
|
cpSync10(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
9051
9425
|
} else {
|
|
9052
9426
|
copyFileSync2(src, dst);
|
|
9053
9427
|
}
|
|
9054
9428
|
log(`snapshotted shared/${name} from ${src}`);
|
|
9055
9429
|
}
|
|
9056
|
-
const userSettings =
|
|
9057
|
-
if (
|
|
9430
|
+
const userSettings = join68(claude, "settings.json");
|
|
9431
|
+
if (existsSync53(userSettings)) {
|
|
9058
9432
|
let parsed;
|
|
9059
9433
|
try {
|
|
9060
9434
|
parsed = readJson(userSettings);
|
|
9061
9435
|
} catch (err) {
|
|
9062
9436
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
9063
9437
|
}
|
|
9064
|
-
const hostFile =
|
|
9438
|
+
const hostFile = join68(repo, "hosts", `${HOST}.json`);
|
|
9065
9439
|
writeJsonAtomic(hostFile, parsed);
|
|
9066
9440
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
9067
9441
|
}
|
|
@@ -9075,14 +9449,14 @@ var GITATTRIBUTES = "# nomad: sync content is byte-managed, disable all line-end
|
|
|
9075
9449
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
9076
9450
|
function preflightConflict(repoHome2) {
|
|
9077
9451
|
const candidates = [
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
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")
|
|
9083
9457
|
];
|
|
9084
9458
|
for (const c of candidates) {
|
|
9085
|
-
if (
|
|
9459
|
+
if (existsSync54(c)) return c;
|
|
9086
9460
|
}
|
|
9087
9461
|
return null;
|
|
9088
9462
|
}
|
|
@@ -9100,27 +9474,27 @@ function cmdInit(opts = {}) {
|
|
|
9100
9474
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
9101
9475
|
}
|
|
9102
9476
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
9103
|
-
mkdirSync17(
|
|
9104
|
-
mkdirSync17(
|
|
9477
|
+
mkdirSync17(join69(repo, "shared"), { recursive: true });
|
|
9478
|
+
mkdirSync17(join69(repo, "hosts"), { recursive: true });
|
|
9105
9479
|
for (const name of SHARED_KEEP_DIRS) {
|
|
9106
|
-
mkdirSync17(
|
|
9480
|
+
mkdirSync17(join69(repo, "shared", name), { recursive: true });
|
|
9107
9481
|
}
|
|
9108
|
-
const userClaudeMd =
|
|
9109
|
-
if (!snapshot || !
|
|
9110
|
-
writeFileSync12(
|
|
9482
|
+
const userClaudeMd = join69(claude, "CLAUDE.md");
|
|
9483
|
+
if (!snapshot || !existsSync54(userClaudeMd)) {
|
|
9484
|
+
writeFileSync12(join69(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
9111
9485
|
item("created shared/CLAUDE.md");
|
|
9112
9486
|
}
|
|
9113
9487
|
for (const name of SHARED_KEEP_DIRS) {
|
|
9114
|
-
writeFileSync12(
|
|
9488
|
+
writeFileSync12(join69(repo, "shared", name, ".gitkeep"), "");
|
|
9115
9489
|
item(`created shared/${name}/.gitkeep`);
|
|
9116
9490
|
}
|
|
9117
|
-
writeFileSync12(
|
|
9491
|
+
writeFileSync12(join69(repo, "hosts", ".gitkeep"), "");
|
|
9118
9492
|
item("created hosts/.gitkeep");
|
|
9119
|
-
writeJsonAtomic(
|
|
9493
|
+
writeJsonAtomic(join69(repo, "shared", "settings.base.json"), {});
|
|
9120
9494
|
item("created shared/settings.base.json");
|
|
9121
|
-
writeJsonAtomic(
|
|
9495
|
+
writeJsonAtomic(join69(repo, "path-map.json"), { projects: {} });
|
|
9122
9496
|
item("created path-map.json");
|
|
9123
|
-
writeFileSync12(
|
|
9497
|
+
writeFileSync12(join69(repo, ".gitattributes"), GITATTRIBUTES);
|
|
9124
9498
|
item("created .gitattributes");
|
|
9125
9499
|
if (snapshot) {
|
|
9126
9500
|
snapshotIntoShared({ projects: {} });
|
|
@@ -9186,21 +9560,21 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
9186
9560
|
// src/init.prompt.ts
|
|
9187
9561
|
init_config();
|
|
9188
9562
|
init_utils();
|
|
9189
|
-
import { existsSync as
|
|
9190
|
-
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";
|
|
9191
9565
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
9192
9566
|
function nonEmptyExists(path) {
|
|
9193
|
-
if (!
|
|
9567
|
+
if (!existsSync55(path)) return false;
|
|
9194
9568
|
try {
|
|
9195
|
-
if (statSync14(path).isDirectory()) return
|
|
9569
|
+
if (statSync14(path).isDirectory()) return readdirSync21(path).length > 0;
|
|
9196
9570
|
return true;
|
|
9197
9571
|
} catch {
|
|
9198
9572
|
return false;
|
|
9199
9573
|
}
|
|
9200
9574
|
}
|
|
9201
9575
|
function hasExistingClaudeConfig(claudeHome2) {
|
|
9202
|
-
if (
|
|
9203
|
-
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)));
|
|
9204
9578
|
}
|
|
9205
9579
|
async function confirmSnapshotDefault(claudeHome2) {
|
|
9206
9580
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
@@ -9507,7 +9881,7 @@ function parseSyncArgs(argv) {
|
|
|
9507
9881
|
// package.json
|
|
9508
9882
|
var package_default = {
|
|
9509
9883
|
name: "claude-nomad",
|
|
9510
|
-
version: "0.
|
|
9884
|
+
version: "0.64.0",
|
|
9511
9885
|
type: "module",
|
|
9512
9886
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
9513
9887
|
keywords: [
|
|
@@ -9751,15 +10125,15 @@ var DEFAULT_HELP = [
|
|
|
9751
10125
|
init_config();
|
|
9752
10126
|
init_utils();
|
|
9753
10127
|
init_utils_json();
|
|
9754
|
-
import { existsSync as
|
|
9755
|
-
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";
|
|
9756
10130
|
function resumeCmd(sessionId) {
|
|
9757
10131
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
9758
10132
|
fail(`invalid session id: ${sessionId}`);
|
|
9759
10133
|
process.exit(1);
|
|
9760
10134
|
}
|
|
9761
|
-
const projectsRoot =
|
|
9762
|
-
if (!
|
|
10135
|
+
const projectsRoot = join71(claudeHome(), "projects");
|
|
10136
|
+
if (!existsSync56(projectsRoot)) {
|
|
9763
10137
|
fail(`${projectsRoot} does not exist`);
|
|
9764
10138
|
process.exit(1);
|
|
9765
10139
|
}
|
|
@@ -9773,8 +10147,8 @@ function resumeCmd(sessionId) {
|
|
|
9773
10147
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
9774
10148
|
process.exit(1);
|
|
9775
10149
|
}
|
|
9776
|
-
const mapPath =
|
|
9777
|
-
if (!
|
|
10150
|
+
const mapPath = join71(repoHome(), "path-map.json");
|
|
10151
|
+
if (!existsSync56(mapPath)) {
|
|
9778
10152
|
fail("path-map.json missing");
|
|
9779
10153
|
process.exit(1);
|
|
9780
10154
|
}
|
|
@@ -9796,14 +10170,14 @@ function resumeCmd(sessionId) {
|
|
|
9796
10170
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
9797
10171
|
}
|
|
9798
10172
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
9799
|
-
for (const dir of
|
|
9800
|
-
const candidate =
|
|
9801
|
-
if (
|
|
10173
|
+
for (const dir of readdirSync22(projectsRoot)) {
|
|
10174
|
+
const candidate = join71(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
10175
|
+
if (existsSync56(candidate)) return candidate;
|
|
9802
10176
|
}
|
|
9803
10177
|
return null;
|
|
9804
10178
|
}
|
|
9805
10179
|
function extractRecordedCwd(jsonlPath) {
|
|
9806
|
-
for (const line of
|
|
10180
|
+
for (const line of readFileSync21(jsonlPath, "utf8").split("\n")) {
|
|
9807
10181
|
if (!line.trim()) continue;
|
|
9808
10182
|
try {
|
|
9809
10183
|
const obj = JSON.parse(line);
|