claude-nomad 0.50.2 → 0.50.3
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/.gitleaks.toml +13 -1
- package/CHANGELOG.md +30 -0
- package/README.md +2 -0
- package/dist/nomad.mjs +277 -213
- package/package.json +1 -1
package/.gitleaks.toml
CHANGED
|
@@ -6,14 +6,26 @@
|
|
|
6
6
|
[extend]
|
|
7
7
|
useDefault = true
|
|
8
8
|
|
|
9
|
+
# Path-scoped: this tool-output noise (gitleaks fingerprints, coverage
|
|
10
|
+
# line-keys, npm audit JSON id hashes, Sonar issue keys) accumulates in Claude
|
|
11
|
+
# Code session transcripts when a conversation runs those tools. Two of these
|
|
12
|
+
# regexes are structurally generic enough that, left global, they could suppress
|
|
13
|
+
# a real credential that happened to sit in a `path:word:digit` or hex-`id`
|
|
14
|
+
# context anywhere in the repo. Scoping to `shared/projects/<logical>/.../*.jsonl`
|
|
15
|
+
# with `condition = "AND"` (matching every other block below) confines the
|
|
16
|
+
# suppression to synced transcripts: a real secret in a source file still fires.
|
|
9
17
|
[[allowlists]]
|
|
10
|
-
description = "claude-nomad: structurally-distinguishable tool-output noise"
|
|
18
|
+
description = "claude-nomad: structurally-distinguishable tool-output noise in synced session transcripts"
|
|
11
19
|
regexes = [
|
|
12
20
|
'''AY[A-Za-z0-9_-]{20,}''',
|
|
13
21
|
'''[\w./-]+\.[A-Za-z0-9]+:[\w-]+:\d+''',
|
|
14
22
|
'''"id"\s*:\s*"[a-f0-9]{40,64}"''',
|
|
15
23
|
'''key=[a-f0-9]{8,} [\w./-]+\.\w+:\d+''',
|
|
16
24
|
]
|
|
25
|
+
paths = [
|
|
26
|
+
'''^shared/projects/[^/]+/.*\.jsonl$''',
|
|
27
|
+
]
|
|
28
|
+
condition = "AND"
|
|
17
29
|
|
|
18
30
|
# Path-scoped: the documented test-fixture github-pat literal AND the three
|
|
19
31
|
# entropy-variant placeholders (zero-suffix, alphabet, repeat-A) accumulate
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.50.3](https://github.com/funkadelic/claude-nomad/compare/v0.50.2...v0.50.3) (2026-06-17)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
* **gitleaks:** scope tool-output noise allowlist to session transcripts ([#305](https://github.com/funkadelic/claude-nomad/issues/305)) ([ca76680](https://github.com/funkadelic/claude-nomad/commit/ca766803a425d21747f959606d6436989ce249e6))
|
|
9
|
+
* **push:** make --redact-all all-or-nothing ([#302](https://github.com/funkadelic/claude-nomad/issues/302)) ([f2cffeb](https://github.com/funkadelic/claude-nomad/commit/f2cffeb9204b68fb046ee55c9768e065272fb431))
|
|
10
|
+
* **push:** warn when drop-session/redact target is already in pushed history ([#304](https://github.com/funkadelic/claude-nomad/issues/304)) ([739676f](https://github.com/funkadelic/claude-nomad/commit/739676ffbf1345fc7ff124ac7eafb9802ba09be4))
|
|
11
|
+
* **redact:** warn when a finding match is not located in the file ([#310](https://github.com/funkadelic/claude-nomad/issues/310)) ([db1442b](https://github.com/funkadelic/claude-nomad/commit/db1442b311db04944b655ece91a56ef23a39abe1))
|
|
12
|
+
* **remap:** make session-transcript mirror copy atomic ([4fc518f](https://github.com/funkadelic/claude-nomad/commit/4fc518fd80ce7fffb56ba2a6d135e6f6b795bc91))
|
|
13
|
+
* **utils:** guard deepMerge against prototype pollution ([#299](https://github.com/funkadelic/claude-nomad/issues/299)) ([8e57539](https://github.com/funkadelic/claude-nomad/commit/8e575393a320386dd7329aab8071fd84557ec4e9))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
* **config:** centralize path-map.json shape validation ([#306](https://github.com/funkadelic/claude-nomad/issues/306)) ([88edda3](https://github.com/funkadelic/claude-nomad/commit/88edda3f45954d4dc89d65e2ec55a7d9d5b6b3f8))
|
|
19
|
+
* **dispatch:** share argv token-parser primitives ([#307](https://github.com/funkadelic/claude-nomad/issues/307)) ([9f62b51](https://github.com/funkadelic/claude-nomad/commit/9f62b5156bbc3b7e03f8a81fe0f844cbeb1da3fa))
|
|
20
|
+
* **doctor:** rename repository check module to git-state ([#309](https://github.com/funkadelic/claude-nomad/issues/309)) ([b6a0d5c](https://github.com/funkadelic/claude-nomad/commit/b6a0d5c8461c2f63af7d77464db9558271d46f46))
|
|
21
|
+
* **utils:** dedup backup helpers and document lock/exit conventions ([#308](https://github.com/funkadelic/claude-nomad/issues/308)) ([4c2d59d](https://github.com/funkadelic/claude-nomad/commit/4c2d59d97c875851733175e4b4346e5714c48a32))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Documentation
|
|
25
|
+
|
|
26
|
+
* **recovery:** reflect session-scoped allowlist and redact no-match warning ([#311](https://github.com/funkadelic/claude-nomad/issues/311)) ([7faf564](https://github.com/funkadelic/claude-nomad/commit/7faf5647d7dfd30822950d687fd8be15b5c49910))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Testing
|
|
30
|
+
|
|
31
|
+
* **push:** add full-pipeline cmdPush E2E against real git and gitleaks ([#303](https://github.com/funkadelic/claude-nomad/issues/303)) ([3c01262](https://github.com/funkadelic/claude-nomad/commit/3c012629e7b99dcf4c5e679d93acb353ee6c3409))
|
|
32
|
+
|
|
3
33
|
## [0.50.2](https://github.com/funkadelic/claude-nomad/compare/v0.50.1...v0.50.2) (2026-06-15)
|
|
4
34
|
|
|
5
35
|
|
package/README.md
CHANGED
|
@@ -100,6 +100,8 @@ When `nomad push` detects a potential secret, it drops into an interactive menu
|
|
|
100
100
|
a recovery hint (non-TTY/CI). Three non-interactive recovery paths are available without the menu:
|
|
101
101
|
|
|
102
102
|
- `nomad push --redact-all` -- scrub every finding from the local transcript in place, then push.
|
|
103
|
+
All-or-nothing: if any finding cannot be redacted (an active session, or one that does not map to
|
|
104
|
+
a synced transcript), nothing is changed and the push stops so you can handle those sessions.
|
|
103
105
|
- `nomad push --allow <rule>` -- record findings matching one gitleaks rule id as false positives
|
|
104
106
|
(appends their fingerprints to `.gitleaksignore`), then re-scan and push.
|
|
105
107
|
- `nomad push --allow-all` -- record every current finding as a false positive, then re-scan and
|
package/dist/nomad.mjs
CHANGED
|
@@ -451,16 +451,41 @@ function readJson(path) {
|
|
|
451
451
|
return data;
|
|
452
452
|
}
|
|
453
453
|
function readPathMap(mapPath) {
|
|
454
|
+
let parsed;
|
|
454
455
|
try {
|
|
455
|
-
|
|
456
|
+
parsed = readJson(mapPath);
|
|
456
457
|
} catch (err) {
|
|
457
458
|
const verb = err instanceof SyntaxError ? "parse" : "read";
|
|
458
459
|
throw new NomadFatal(`could not ${verb} path-map.json: ${err.message}`);
|
|
459
460
|
}
|
|
461
|
+
const shapeError = validatePathMapShape(parsed);
|
|
462
|
+
if (shapeError !== null) throw new NomadFatal(shapeError);
|
|
463
|
+
return parsed;
|
|
464
|
+
}
|
|
465
|
+
function validatePathMapShape(raw) {
|
|
466
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
467
|
+
return "path-map.json invalid schema: top-level value must be an object";
|
|
468
|
+
}
|
|
469
|
+
const projects = raw.projects;
|
|
470
|
+
if (projects === null || typeof projects !== "object" || Array.isArray(projects)) {
|
|
471
|
+
return 'path-map.json invalid schema: "projects" must be an object';
|
|
472
|
+
}
|
|
473
|
+
for (const [name, hosts] of Object.entries(projects)) {
|
|
474
|
+
if (hosts === null || typeof hosts !== "object" || Array.isArray(hosts)) {
|
|
475
|
+
return `path-map.json invalid schema: project "${name}" hosts must be an object`;
|
|
476
|
+
}
|
|
477
|
+
for (const [host, value] of Object.entries(hosts)) {
|
|
478
|
+
if (typeof value !== "string") {
|
|
479
|
+
return `path-map.json invalid schema: project "${name}" host "${host}" path must be a string`;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return null;
|
|
460
484
|
}
|
|
461
485
|
function deepMerge(target, source) {
|
|
462
486
|
const out = { ...target };
|
|
463
487
|
for (const [key, value] of Object.entries(source)) {
|
|
488
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
464
489
|
const existing = out[key];
|
|
465
490
|
const bothObjects = value !== null && typeof value === "object" && !Array.isArray(value) && existing !== null && typeof existing === "object" && !Array.isArray(existing);
|
|
466
491
|
out[key] = bothObjects ? deepMerge(existing, value) : value;
|
|
@@ -502,7 +527,7 @@ import {
|
|
|
502
527
|
symlinkSync,
|
|
503
528
|
writeFileSync
|
|
504
529
|
} from "node:fs";
|
|
505
|
-
import { dirname, join as join2, relative } from "node:path";
|
|
530
|
+
import { dirname, join as join2, relative, sep } from "node:path";
|
|
506
531
|
function writeJsonAtomic(path, data) {
|
|
507
532
|
const mode = existsSync(path) ? statSync(path).mode & 511 : 384;
|
|
508
533
|
const tmp = `${path}.tmp.${process.pid}`;
|
|
@@ -542,33 +567,22 @@ function ensureSymlink(linkPath, target) {
|
|
|
542
567
|
symlinkSync(target, linkPath);
|
|
543
568
|
log(`linked ${linkPath} -> ${target}`);
|
|
544
569
|
}
|
|
545
|
-
function
|
|
570
|
+
function backupUnder(absPath, anchor, destRoot) {
|
|
546
571
|
if (!existsSync(absPath)) return;
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
const backupRoot = join2(backupBase(), ts);
|
|
551
|
-
const dst = join2(backupRoot, rel);
|
|
572
|
+
const rel = relative(anchor, absPath);
|
|
573
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`)) return;
|
|
574
|
+
const dst = join2(destRoot, rel);
|
|
552
575
|
mkdirSync(dirname(dst), { recursive: true });
|
|
553
576
|
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
554
577
|
}
|
|
578
|
+
function backupBeforeWrite(absPath, ts) {
|
|
579
|
+
backupUnder(absPath, claudeHome(), join2(backupBase(), ts));
|
|
580
|
+
}
|
|
555
581
|
function backupRepoWrite(absPath, ts, repoHome2) {
|
|
556
|
-
|
|
557
|
-
const rel = relative(repoHome2, absPath);
|
|
558
|
-
if (rel.startsWith("..") || rel === "") return;
|
|
559
|
-
const backupRoot = join2(backupBase(), ts, "repo");
|
|
560
|
-
const dst = join2(backupRoot, rel);
|
|
561
|
-
mkdirSync(dirname(dst), { recursive: true });
|
|
562
|
-
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
582
|
+
backupUnder(absPath, repoHome2, join2(backupBase(), ts, "repo"));
|
|
563
583
|
}
|
|
564
584
|
function backupExtrasWrite(absPath, ts, projectRoot) {
|
|
565
|
-
|
|
566
|
-
const rel = relative(projectRoot, absPath);
|
|
567
|
-
if (rel.startsWith("..") || rel === "") return;
|
|
568
|
-
const backupRoot = join2(backupBase(), ts, "extras");
|
|
569
|
-
const dst = join2(backupRoot, encodePath(projectRoot), rel);
|
|
570
|
-
mkdirSync(dirname(dst), { recursive: true });
|
|
571
|
-
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
585
|
+
backupUnder(absPath, projectRoot, join2(backupBase(), ts, "extras", encodePath(projectRoot)));
|
|
572
586
|
}
|
|
573
587
|
var init_utils_fs = __esm({
|
|
574
588
|
"src/utils.fs.ts"() {
|
|
@@ -1319,7 +1333,7 @@ init_config();
|
|
|
1319
1333
|
init_utils();
|
|
1320
1334
|
init_utils_json();
|
|
1321
1335
|
import { cpSync as cpSync3, existsSync as existsSync5, lstatSync as lstatSync4, realpathSync, renameSync as renameSync2, rmSync as rmSync3 } from "node:fs";
|
|
1322
|
-
import { join as join6, sep } from "node:path";
|
|
1336
|
+
import { join as join6, sep as sep2 } from "node:path";
|
|
1323
1337
|
function ejectChecklist() {
|
|
1324
1338
|
return [
|
|
1325
1339
|
"Manual steps remaining to finish leaving claude-nomad on this host:",
|
|
@@ -1361,7 +1375,7 @@ function resolveSharedRoot(repoHome2) {
|
|
|
1361
1375
|
}
|
|
1362
1376
|
}
|
|
1363
1377
|
function isManagedTarget(target, sharedRoot) {
|
|
1364
|
-
return target.startsWith(sharedRoot +
|
|
1378
|
+
return target.startsWith(sharedRoot + sep2);
|
|
1365
1379
|
}
|
|
1366
1380
|
function materializeOne(name, linkPath, sharedRoot) {
|
|
1367
1381
|
const target = realpathSync(linkPath);
|
|
@@ -1841,35 +1855,12 @@ function reportPathMap(section2) {
|
|
|
1841
1855
|
}
|
|
1842
1856
|
const map = readJsonSafe(mapPath, mapPath, section2);
|
|
1843
1857
|
if (map === null) return;
|
|
1844
|
-
const
|
|
1845
|
-
if (
|
|
1846
|
-
addItem(
|
|
1847
|
-
section2,
|
|
1848
|
-
`${red(failGlyph)} path-map.json invalid schema: "projects" must be an object`
|
|
1849
|
-
);
|
|
1858
|
+
const shapeError = validatePathMapShape(map);
|
|
1859
|
+
if (shapeError !== null) {
|
|
1860
|
+
addItem(section2, `${red(failGlyph)} ${shapeError}`);
|
|
1850
1861
|
process.exitCode = 1;
|
|
1851
1862
|
return;
|
|
1852
1863
|
}
|
|
1853
|
-
for (const [name, hosts] of Object.entries(projects)) {
|
|
1854
|
-
if (hosts === null || typeof hosts !== "object" || Array.isArray(hosts)) {
|
|
1855
|
-
addItem(
|
|
1856
|
-
section2,
|
|
1857
|
-
`${red(failGlyph)} path-map.json invalid schema: project "${name}" hosts must be an object`
|
|
1858
|
-
);
|
|
1859
|
-
process.exitCode = 1;
|
|
1860
|
-
return;
|
|
1861
|
-
}
|
|
1862
|
-
for (const [hostName, mappedPath] of Object.entries(hosts)) {
|
|
1863
|
-
if (typeof mappedPath !== "string") {
|
|
1864
|
-
addItem(
|
|
1865
|
-
section2,
|
|
1866
|
-
`${red(failGlyph)} path-map.json invalid schema: project "${name}" host "${hostName}" path must be a string`
|
|
1867
|
-
);
|
|
1868
|
-
process.exitCode = 1;
|
|
1869
|
-
return;
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
1864
|
reportMappedProjects(section2, map);
|
|
1874
1865
|
reportUnmappedProjects(section2, map);
|
|
1875
1866
|
reportPathCollisions(section2, map);
|
|
@@ -1883,7 +1874,7 @@ function reportNeverSync(section2) {
|
|
|
1883
1874
|
);
|
|
1884
1875
|
}
|
|
1885
1876
|
|
|
1886
|
-
// src/commands.doctor.checks.
|
|
1877
|
+
// src/commands.doctor.checks.git-state.ts
|
|
1887
1878
|
init_color();
|
|
1888
1879
|
init_config();
|
|
1889
1880
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
@@ -2203,21 +2194,27 @@ init_config();
|
|
|
2203
2194
|
init_utils();
|
|
2204
2195
|
init_utils_fs();
|
|
2205
2196
|
init_utils_json();
|
|
2206
|
-
import { cpSync as cpSync4, existsSync as existsSync15, mkdirSync as mkdirSync3, readdirSync as readdirSync6, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
|
|
2207
|
-
import { join as join19, relative as relative3, sep as
|
|
2208
|
-
|
|
2197
|
+
import { cpSync as cpSync4, existsSync as existsSync15, mkdirSync as mkdirSync3, readdirSync as readdirSync6, renameSync as renameSync3, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
|
|
2198
|
+
import { join as join19, relative as relative3, sep as sep3 } from "node:path";
|
|
2199
|
+
var TMP_SUFFIX = ".nomad-tmp";
|
|
2200
|
+
function atomicMirror(src, dst, options) {
|
|
2201
|
+
const tmp = `${dst}${TMP_SUFFIX}`;
|
|
2202
|
+
rmSync6(tmp, { recursive: true, force: true });
|
|
2203
|
+
cpSync4(src, tmp, options);
|
|
2209
2204
|
rmSync6(dst, { recursive: true, force: true });
|
|
2210
|
-
|
|
2205
|
+
renameSync3(tmp, dst);
|
|
2206
|
+
}
|
|
2207
|
+
function copyDir(src, dst) {
|
|
2208
|
+
atomicMirror(src, dst, { recursive: true, force: true });
|
|
2211
2209
|
}
|
|
2212
2210
|
function copyDirJsonlOnly(src, dst) {
|
|
2213
|
-
|
|
2214
|
-
cpSync4(src, dst, {
|
|
2211
|
+
atomicMirror(src, dst, {
|
|
2215
2212
|
recursive: true,
|
|
2216
2213
|
force: true,
|
|
2217
2214
|
filter: (srcPath) => {
|
|
2218
2215
|
const rel = relative3(src, srcPath);
|
|
2219
2216
|
if (rel === "") return true;
|
|
2220
|
-
if (rel.split(
|
|
2217
|
+
if (rel.split(sep3).length > 1) return true;
|
|
2221
2218
|
if (statSync4(srcPath).isDirectory()) return true;
|
|
2222
2219
|
if (srcPath.endsWith(".jsonl")) return true;
|
|
2223
2220
|
item(`skip ${rel}: extension not in allowlist`);
|
|
@@ -2243,7 +2240,7 @@ function remapPull(ts, opts = {}) {
|
|
|
2243
2240
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
2244
2241
|
return { unmapped: 0, pulled, wouldPull };
|
|
2245
2242
|
}
|
|
2246
|
-
const map =
|
|
2243
|
+
const map = readPathMap(mapPath);
|
|
2247
2244
|
const localProjects = join19(claude, "projects");
|
|
2248
2245
|
if (!dryRun) mkdirSync3(localProjects, { recursive: true });
|
|
2249
2246
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
@@ -2307,13 +2304,14 @@ function remapPush(ts, opts = {}) {
|
|
|
2307
2304
|
log("no path-map.json; skipping session export");
|
|
2308
2305
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
2309
2306
|
}
|
|
2310
|
-
const map =
|
|
2307
|
+
const map = readPathMap(mapPath);
|
|
2311
2308
|
const localProjects = join19(claude, "projects");
|
|
2312
2309
|
const repoProjects = join19(repo, "shared", "projects");
|
|
2313
2310
|
const reverse = buildReverseMap(map);
|
|
2314
2311
|
if (!existsSync15(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
2315
2312
|
if (!dryRun) mkdirSync3(repoProjects, { recursive: true });
|
|
2316
2313
|
for (const dir of readdirSync6(localProjects)) {
|
|
2314
|
+
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
2317
2315
|
const logical = reverse.get(dir);
|
|
2318
2316
|
if (!logical) {
|
|
2319
2317
|
unmapped++;
|
|
@@ -3051,13 +3049,13 @@ import { createInterface } from "node:readline/promises";
|
|
|
3051
3049
|
// src/commands.push.recovery.actions.ts
|
|
3052
3050
|
init_config();
|
|
3053
3051
|
import { readFileSync as readFileSync12 } from "node:fs";
|
|
3054
|
-
import { isAbsolute, resolve as resolve3, sep as
|
|
3052
|
+
import { isAbsolute, resolve as resolve3, sep as sep5 } from "node:path";
|
|
3055
3053
|
|
|
3056
3054
|
// src/commands.push.recovery.redact.ts
|
|
3057
3055
|
init_config();
|
|
3058
3056
|
init_config_sharedDirs_guard();
|
|
3059
3057
|
import { cpSync as cpSync5, existsSync as existsSync24, mkdirSync as mkdirSync6, statSync as statSync7 } from "node:fs";
|
|
3060
|
-
import { dirname as dirname6, join as join29, sep as
|
|
3058
|
+
import { dirname as dirname6, join as join29, sep as sep4 } from "node:path";
|
|
3061
3059
|
|
|
3062
3060
|
// src/commands.redact.ts
|
|
3063
3061
|
init_config();
|
|
@@ -3068,6 +3066,7 @@ import { dirname as dirname5, join as join28 } from "node:path";
|
|
|
3068
3066
|
import { existsSync as existsSync22, lstatSync as lstatSync7, readFileSync as readFileSync10, readdirSync as readdirSync9, statSync as statSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3069
3067
|
import { join as join26 } from "node:path";
|
|
3070
3068
|
init_utils_fs();
|
|
3069
|
+
init_utils();
|
|
3071
3070
|
function collectFiles(dir, out) {
|
|
3072
3071
|
if (!existsSync22(dir)) return;
|
|
3073
3072
|
const st = lstatSync7(dir);
|
|
@@ -3110,12 +3109,67 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
3110
3109
|
if (!dryRun && total > 0) {
|
|
3111
3110
|
for (const { path: filePath, findings } of dirty) {
|
|
3112
3111
|
backupBeforeWrite(filePath, ts);
|
|
3113
|
-
|
|
3112
|
+
const before = readFileSync10(filePath, "utf8");
|
|
3113
|
+
const after = applyRedactions(before, findings);
|
|
3114
|
+
if (after === before) {
|
|
3115
|
+
log(
|
|
3116
|
+
`warning: no redaction applied to ${filePath}: finding match values were not located in the file. Inspect it manually; the push re-scan still blocks a real leak.`
|
|
3117
|
+
);
|
|
3118
|
+
}
|
|
3119
|
+
writeFileSync3(filePath, after, "utf8");
|
|
3114
3120
|
}
|
|
3115
3121
|
}
|
|
3116
3122
|
return { total, dirty };
|
|
3117
3123
|
}
|
|
3118
3124
|
|
|
3125
|
+
// src/commands.pushed-history.ts
|
|
3126
|
+
init_utils();
|
|
3127
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
3128
|
+
function pushedRef(repo) {
|
|
3129
|
+
try {
|
|
3130
|
+
const ref = execFileSync8("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
|
|
3131
|
+
cwd: repo,
|
|
3132
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3133
|
+
}).toString().trim();
|
|
3134
|
+
return ref.length > 0 ? ref : null;
|
|
3135
|
+
} catch {
|
|
3136
|
+
return null;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
function sessionInPushedHistory(id, repo) {
|
|
3140
|
+
const ref = pushedRef(repo);
|
|
3141
|
+
if (ref === null) return false;
|
|
3142
|
+
try {
|
|
3143
|
+
const out = execFileSync8(
|
|
3144
|
+
"git",
|
|
3145
|
+
[
|
|
3146
|
+
"log",
|
|
3147
|
+
ref,
|
|
3148
|
+
"--oneline",
|
|
3149
|
+
"-1",
|
|
3150
|
+
"--",
|
|
3151
|
+
`shared/projects/*/${id}.jsonl`,
|
|
3152
|
+
`shared/projects/*/${id}/*`
|
|
3153
|
+
],
|
|
3154
|
+
{ cwd: repo, stdio: ["ignore", "pipe", "ignore"] }
|
|
3155
|
+
).toString().trim();
|
|
3156
|
+
return out.length > 0;
|
|
3157
|
+
} catch {
|
|
3158
|
+
return false;
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
function warnIfSessionPushed(id, repo) {
|
|
3162
|
+
if (!sessionInPushedHistory(id, repo)) return;
|
|
3163
|
+
log(
|
|
3164
|
+
`warning: session ${id} is already in pushed history (origin).
|
|
3165
|
+
This command only changes your local copy and the next push; it does NOT
|
|
3166
|
+
remove the secret from commits already on the remote.
|
|
3167
|
+
To fully remediate a real secret: rotate the credential, then rewrite
|
|
3168
|
+
history (e.g. with git filter-repo) and force-push, coordinating with
|
|
3169
|
+
anyone else who has cloned the repo.`
|
|
3170
|
+
);
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3119
3173
|
// src/commands.redact.ts
|
|
3120
3174
|
init_push_gitleaks_scan();
|
|
3121
3175
|
init_utils_fs();
|
|
@@ -3317,6 +3371,7 @@ ${lines}`);
|
|
|
3317
3371
|
return;
|
|
3318
3372
|
}
|
|
3319
3373
|
log(`redacted ${totalCount} finding(s) in ${localPath} (backup: ${ts})`);
|
|
3374
|
+
warnIfSessionPushed(id, repo);
|
|
3320
3375
|
} catch (err) {
|
|
3321
3376
|
if (!(err instanceof NomadFatal)) {
|
|
3322
3377
|
throw err;
|
|
@@ -3391,12 +3446,28 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
3391
3446
|
assertSafeLogical(logical);
|
|
3392
3447
|
const abs = hostMap[HOST];
|
|
3393
3448
|
if (abs === void 0) continue;
|
|
3394
|
-
if (localPath.startsWith(join29(claude, "projects", encodePath(abs)) +
|
|
3449
|
+
if (localPath.startsWith(join29(claude, "projects", encodePath(abs)) + sep4)) {
|
|
3395
3450
|
return join29(repo, "shared", "projects", logical);
|
|
3396
3451
|
}
|
|
3397
3452
|
}
|
|
3398
3453
|
return null;
|
|
3399
3454
|
}
|
|
3455
|
+
function preflightRedactable(f, map, nowMs) {
|
|
3456
|
+
const sid = sessionIdFromFinding(f);
|
|
3457
|
+
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
3458
|
+
const localPath = resolveLiveTranscript(sid);
|
|
3459
|
+
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
3460
|
+
const sessionDir = join29(dirname6(localPath), sid);
|
|
3461
|
+
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3462
|
+
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
3463
|
+
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
3464
|
+
return `session ${sid}: looks active (modified within the last 5 minutes)`;
|
|
3465
|
+
}
|
|
3466
|
+
if (resolveStagedDir(localPath, map, claudeHome(), repoHome()) === null) {
|
|
3467
|
+
return `session ${sid}: not mapped to a staged copy`;
|
|
3468
|
+
}
|
|
3469
|
+
return null;
|
|
3470
|
+
}
|
|
3400
3471
|
function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
3401
3472
|
const refuse = (msg) => {
|
|
3402
3473
|
log(msg);
|
|
@@ -3500,7 +3571,7 @@ function makeDefaultReadLine(repo) {
|
|
|
3500
3571
|
try {
|
|
3501
3572
|
const repoRoot = resolve3(repo);
|
|
3502
3573
|
const target = resolve3(repoRoot, file);
|
|
3503
|
-
if (isAbsolute(file) || target !== repoRoot && !target.startsWith(repoRoot +
|
|
3574
|
+
if (isAbsolute(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
|
|
3504
3575
|
return null;
|
|
3505
3576
|
}
|
|
3506
3577
|
const content = readFileSync12(target, "utf8");
|
|
@@ -3567,6 +3638,22 @@ function dispatchActions(findings, actions, opts) {
|
|
|
3567
3638
|
}
|
|
3568
3639
|
}
|
|
3569
3640
|
function redactAllFindings(findings, ts, map, nowMs, scan = scanFile) {
|
|
3641
|
+
const refusals = [];
|
|
3642
|
+
const preflighted = /* @__PURE__ */ new Set();
|
|
3643
|
+
for (const f of findings) {
|
|
3644
|
+
const dedupeKey = sessionIdFromFinding(f) ?? findingKey(f);
|
|
3645
|
+
if (preflighted.has(dedupeKey)) continue;
|
|
3646
|
+
preflighted.add(dedupeKey);
|
|
3647
|
+
const reason = preflightRedactable(f, map, nowMs);
|
|
3648
|
+
if (reason !== null) refusals.push(reason);
|
|
3649
|
+
}
|
|
3650
|
+
if (refusals.length > 0) {
|
|
3651
|
+
throw new NomadFatal(
|
|
3652
|
+
`--redact-all cannot redact every finding, so no changes were made:
|
|
3653
|
+
` + refusals.map((r) => ` - ${r}`).join("\n") + `
|
|
3654
|
+
Re-run without --redact-all to triage these interactively (Drop session / Skip), or end any active session and retry.`
|
|
3655
|
+
);
|
|
3656
|
+
}
|
|
3570
3657
|
const redactedSids = /* @__PURE__ */ new Set();
|
|
3571
3658
|
for (const f of findings) {
|
|
3572
3659
|
const sid = sessionIdFromFinding(f);
|
|
@@ -3773,7 +3860,7 @@ function withSpinner(label, fn, deps) {
|
|
|
3773
3860
|
|
|
3774
3861
|
// src/commands.doctor.gitleaks-version.ts
|
|
3775
3862
|
init_color();
|
|
3776
|
-
import { execFileSync as
|
|
3863
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
3777
3864
|
import { existsSync as existsSync26 } from "node:fs";
|
|
3778
3865
|
import { join as join32 } from "node:path";
|
|
3779
3866
|
init_config();
|
|
@@ -3796,7 +3883,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
3796
3883
|
return null;
|
|
3797
3884
|
}
|
|
3798
3885
|
}
|
|
3799
|
-
function reportGitleaksVersionCheck(section2, run =
|
|
3886
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync9, tomlExists = existsSync26) {
|
|
3800
3887
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
3801
3888
|
if (raw === null) return;
|
|
3802
3889
|
const local = majorMinorOf(raw);
|
|
@@ -3816,7 +3903,7 @@ function reportGitleaksVersionCheck(section2, run = execFileSync8, tomlExists =
|
|
|
3816
3903
|
|
|
3817
3904
|
// src/commands.doctor.checks.deps.ts
|
|
3818
3905
|
init_color();
|
|
3819
|
-
import { execFileSync as
|
|
3906
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
3820
3907
|
var VERSION_TOKEN = /(\d{1,9}\.\d{1,9}\.\d{1,9})/;
|
|
3821
3908
|
var PROBE_TIMEOUT_MS = 3e3;
|
|
3822
3909
|
var FETCHER_BASE = "HTTP fetcher";
|
|
@@ -3853,7 +3940,7 @@ function reportFetcherRow(section2, run) {
|
|
|
3853
3940
|
);
|
|
3854
3941
|
}
|
|
3855
3942
|
}
|
|
3856
|
-
function reportOptionalDeps(section2, run =
|
|
3943
|
+
function reportOptionalDeps(section2, run = execFileSync10) {
|
|
3857
3944
|
const gh = probeOptionalDep("gh", run);
|
|
3858
3945
|
if (gh.status === "present") {
|
|
3859
3946
|
addItem(section2, `${green(okGlyph)} gh: ${gh.version ?? "present"}`);
|
|
@@ -3868,11 +3955,11 @@ function reportOptionalDeps(section2, run = execFileSync9) {
|
|
|
3868
3955
|
|
|
3869
3956
|
// src/commands.doctor.actions-drift.ts
|
|
3870
3957
|
init_color();
|
|
3871
|
-
import { execFileSync as
|
|
3958
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
3872
3959
|
init_config();
|
|
3873
3960
|
|
|
3874
3961
|
// src/gh-actions.ts
|
|
3875
|
-
import { execFileSync as
|
|
3962
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
3876
3963
|
var GH_TIMEOUT_MS = 5e3;
|
|
3877
3964
|
function parseGitHubRemote(remoteUrl) {
|
|
3878
3965
|
const normalized = remoteUrl.trim().replace(/\/$/, "");
|
|
@@ -3880,7 +3967,7 @@ function parseGitHubRemote(remoteUrl) {
|
|
|
3880
3967
|
if (m === null) return null;
|
|
3881
3968
|
return { owner: m[1], repo: m[2] };
|
|
3882
3969
|
}
|
|
3883
|
-
function ghAuthStatus(run =
|
|
3970
|
+
function ghAuthStatus(run = execFileSync11) {
|
|
3884
3971
|
try {
|
|
3885
3972
|
run("gh", ["auth", "status"], {
|
|
3886
3973
|
stdio: ["ignore", "ignore", "ignore"],
|
|
@@ -3894,7 +3981,7 @@ function ghAuthStatus(run = execFileSync10) {
|
|
|
3894
3981
|
return "gh-probe-error";
|
|
3895
3982
|
}
|
|
3896
3983
|
}
|
|
3897
|
-
function isRepoPrivate(ref, run =
|
|
3984
|
+
function isRepoPrivate(ref, run = execFileSync11) {
|
|
3898
3985
|
const out = run("gh", ["repo", "view", `${ref.owner}/${ref.repo}`, "--json", "isPrivate"], {
|
|
3899
3986
|
stdio: ["ignore", "pipe", "ignore"],
|
|
3900
3987
|
timeout: GH_TIMEOUT_MS
|
|
@@ -3902,7 +3989,7 @@ function isRepoPrivate(ref, run = execFileSync10) {
|
|
|
3902
3989
|
const parsed = JSON.parse(out);
|
|
3903
3990
|
return parsed.isPrivate === true;
|
|
3904
3991
|
}
|
|
3905
|
-
function isActionsEnabled(ref, run =
|
|
3992
|
+
function isActionsEnabled(ref, run = execFileSync11) {
|
|
3906
3993
|
const out = run(
|
|
3907
3994
|
"gh",
|
|
3908
3995
|
["api", `repos/${ref.owner}/${ref.repo}/actions/permissions`, "--jq", ".enabled"],
|
|
@@ -3910,7 +3997,7 @@ function isActionsEnabled(ref, run = execFileSync10) {
|
|
|
3910
3997
|
).toString().trim();
|
|
3911
3998
|
return out === "true";
|
|
3912
3999
|
}
|
|
3913
|
-
function disableActions(ref, run =
|
|
4000
|
+
function disableActions(ref, run = execFileSync11) {
|
|
3914
4001
|
run(
|
|
3915
4002
|
"gh",
|
|
3916
4003
|
[
|
|
@@ -3924,7 +4011,7 @@ function disableActions(ref, run = execFileSync10) {
|
|
|
3924
4011
|
{ stdio: ["ignore", "ignore", "pipe"], timeout: GH_TIMEOUT_MS }
|
|
3925
4012
|
);
|
|
3926
4013
|
}
|
|
3927
|
-
function readOriginRemote(cwd, run =
|
|
4014
|
+
function readOriginRemote(cwd, run = execFileSync11) {
|
|
3928
4015
|
return run("git", ["remote", "get-url", "origin"], {
|
|
3929
4016
|
cwd,
|
|
3930
4017
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -3932,7 +4019,7 @@ function readOriginRemote(cwd, run = execFileSync10) {
|
|
|
3932
4019
|
}
|
|
3933
4020
|
|
|
3934
4021
|
// src/commands.doctor.actions-drift.ts
|
|
3935
|
-
function reportActionsDrift(section2, run =
|
|
4022
|
+
function reportActionsDrift(section2, run = execFileSync12) {
|
|
3936
4023
|
let remote;
|
|
3937
4024
|
try {
|
|
3938
4025
|
remote = readOriginRemote(repoHome(), run);
|
|
@@ -4061,15 +4148,15 @@ function cmdDoctor(opts = {}) {
|
|
|
4061
4148
|
|
|
4062
4149
|
// src/commands.drop-session.ts
|
|
4063
4150
|
init_config();
|
|
4064
|
-
import { execFileSync as
|
|
4151
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
4065
4152
|
import { existsSync as existsSync29, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
|
|
4066
4153
|
import { join as join35, relative as relative4 } from "node:path";
|
|
4067
4154
|
|
|
4068
4155
|
// src/commands.drop-session.git.ts
|
|
4069
|
-
import { execFileSync as
|
|
4156
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
4070
4157
|
function expandStagedDir(dirRel, repo) {
|
|
4071
4158
|
try {
|
|
4072
|
-
const out =
|
|
4159
|
+
const out = execFileSync13("git", ["ls-files", "-z", "--", dirRel], {
|
|
4073
4160
|
cwd: repo,
|
|
4074
4161
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4075
4162
|
});
|
|
@@ -4080,7 +4167,7 @@ function expandStagedDir(dirRel, repo) {
|
|
|
4080
4167
|
}
|
|
4081
4168
|
function isTrackedInHead(rel, repo) {
|
|
4082
4169
|
try {
|
|
4083
|
-
|
|
4170
|
+
execFileSync13("git", ["cat-file", "-e", `HEAD:${rel}`], {
|
|
4084
4171
|
cwd: repo,
|
|
4085
4172
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4086
4173
|
});
|
|
@@ -4091,7 +4178,7 @@ function isTrackedInHead(rel, repo) {
|
|
|
4091
4178
|
}
|
|
4092
4179
|
function isInIndex(rel, repo) {
|
|
4093
4180
|
try {
|
|
4094
|
-
const out =
|
|
4181
|
+
const out = execFileSync13("git", ["ls-files", "--", rel], {
|
|
4095
4182
|
cwd: repo,
|
|
4096
4183
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4097
4184
|
});
|
|
@@ -4162,6 +4249,7 @@ function cmdDropSession(id) {
|
|
|
4162
4249
|
}
|
|
4163
4250
|
for (const rel of matches) unstageOne(rel, repo);
|
|
4164
4251
|
reportScrubHint(id, matches);
|
|
4252
|
+
warnIfSessionPushed(id, repo);
|
|
4165
4253
|
} catch (err) {
|
|
4166
4254
|
if (!(err instanceof NomadFatal)) {
|
|
4167
4255
|
throw err;
|
|
@@ -4196,12 +4284,12 @@ function unstageOne(rel, repo) {
|
|
|
4196
4284
|
}
|
|
4197
4285
|
try {
|
|
4198
4286
|
if (isTrackedInHead(rel, repo)) {
|
|
4199
|
-
|
|
4287
|
+
execFileSync14("git", ["restore", "--staged", "--worktree", "--", rel], {
|
|
4200
4288
|
cwd: repo,
|
|
4201
4289
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4202
4290
|
});
|
|
4203
4291
|
} else {
|
|
4204
|
-
|
|
4292
|
+
execFileSync14("git", ["rm", "--cached", "-f", "--", rel], {
|
|
4205
4293
|
cwd: repo,
|
|
4206
4294
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4207
4295
|
});
|
|
@@ -4316,7 +4404,7 @@ import { join as join39 } from "node:path";
|
|
|
4316
4404
|
|
|
4317
4405
|
// src/extras-sync.diff.ts
|
|
4318
4406
|
init_utils();
|
|
4319
|
-
import { execFileSync as
|
|
4407
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
4320
4408
|
function labelDiffLine(line) {
|
|
4321
4409
|
const tab = line.indexOf(" ");
|
|
4322
4410
|
if (tab === -1) return line;
|
|
@@ -4331,7 +4419,7 @@ function parseDiffOutput(stdout) {
|
|
|
4331
4419
|
}
|
|
4332
4420
|
function listDivergingFiles(a, b) {
|
|
4333
4421
|
try {
|
|
4334
|
-
const stdout =
|
|
4422
|
+
const stdout = execFileSync15("git", ["diff", "--no-index", "--name-status", a, b], {
|
|
4335
4423
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4336
4424
|
}).toString();
|
|
4337
4425
|
return parseDiffOutput(stdout);
|
|
@@ -4511,10 +4599,10 @@ init_utils_json();
|
|
|
4511
4599
|
// src/extras-sync.remap.ts
|
|
4512
4600
|
init_config();
|
|
4513
4601
|
import { existsSync as existsSync31, mkdirSync as mkdirSync7, readdirSync as readdirSync12, realpathSync as realpathSync4, rmSync as rmSync11 } from "node:fs";
|
|
4514
|
-
import { dirname as dirname7, join as join38, sep as
|
|
4602
|
+
import { dirname as dirname7, join as join38, sep as sep7 } from "node:path";
|
|
4515
4603
|
|
|
4516
4604
|
// src/extras-sync.planning-diff.ts
|
|
4517
|
-
import { join as join37, normalize as normalize2, sep as
|
|
4605
|
+
import { join as join37, normalize as normalize2, sep as sep6 } from "node:path";
|
|
4518
4606
|
init_utils();
|
|
4519
4607
|
function processRecord(fields, i, changed, deleted) {
|
|
4520
4608
|
const status = fields[i];
|
|
@@ -4567,7 +4655,7 @@ function planningDeleteTargets(opts) {
|
|
|
4567
4655
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
4568
4656
|
const prefix = logicalPrefix + ".planning/";
|
|
4569
4657
|
const planningRoot = join37(localRoot, ".planning");
|
|
4570
|
-
const planningRootBoundary = planningRoot +
|
|
4658
|
+
const planningRootBoundary = planningRoot + sep6;
|
|
4571
4659
|
const targets = [];
|
|
4572
4660
|
for (const repoPath of deleted) {
|
|
4573
4661
|
if (!repoPath.startsWith(prefix)) {
|
|
@@ -4609,7 +4697,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
4609
4697
|
}
|
|
4610
4698
|
function pruneEmptyAncestors(target, planningRoot) {
|
|
4611
4699
|
let dir = dirname7(target);
|
|
4612
|
-
while (dir !== planningRoot && dir.startsWith(planningRoot +
|
|
4700
|
+
while (dir !== planningRoot && dir.startsWith(planningRoot + sep7)) {
|
|
4613
4701
|
try {
|
|
4614
4702
|
if (readdirSync12(dir).length > 0) break;
|
|
4615
4703
|
rmSync11(dir, { recursive: true, force: true });
|
|
@@ -4627,7 +4715,7 @@ function tryRealpath(dir) {
|
|
|
4627
4715
|
}
|
|
4628
4716
|
}
|
|
4629
4717
|
function isInsidePlanningRoot(parentReal, rootReal) {
|
|
4630
|
-
return parentReal === rootReal || parentReal.startsWith(rootReal +
|
|
4718
|
+
return parentReal === rootReal || parentReal.startsWith(rootReal + sep7);
|
|
4631
4719
|
}
|
|
4632
4720
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
4633
4721
|
if (existsSync31(repoCounterpart)) return;
|
|
@@ -4669,7 +4757,7 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
4669
4757
|
backupExtrasWrite(join38(t.localRoot, t.dirname), ts, t.localRoot);
|
|
4670
4758
|
const planningRoot = join38(t.localRoot, ".planning");
|
|
4671
4759
|
for (const target of targets) {
|
|
4672
|
-
const relToLocal = target.slice(t.localRoot.length +
|
|
4760
|
+
const relToLocal = target.slice(t.localRoot.length + sep7.length);
|
|
4673
4761
|
deletePlanningTarget(target, planningRoot, join38(repoExtras, t.logical, relToLocal));
|
|
4674
4762
|
}
|
|
4675
4763
|
}
|
|
@@ -5274,9 +5362,9 @@ init_config();
|
|
|
5274
5362
|
init_commands_pull_wedge();
|
|
5275
5363
|
init_utils();
|
|
5276
5364
|
init_utils_fs();
|
|
5277
|
-
import { execFileSync as
|
|
5365
|
+
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
5278
5366
|
function gitCapture(args, cwd) {
|
|
5279
|
-
return
|
|
5367
|
+
return execFileSync16("git", args, {
|
|
5280
5368
|
cwd,
|
|
5281
5369
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5282
5370
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -5593,7 +5681,7 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
5593
5681
|
|
|
5594
5682
|
// src/push-global-config.ts
|
|
5595
5683
|
init_config();
|
|
5596
|
-
import { execFileSync as
|
|
5684
|
+
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
5597
5685
|
var STATUS_LABELS = {
|
|
5598
5686
|
A: "add",
|
|
5599
5687
|
M: "modify",
|
|
@@ -5633,7 +5721,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
|
|
|
5633
5721
|
}
|
|
5634
5722
|
function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
|
|
5635
5723
|
const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
|
|
5636
|
-
const raw =
|
|
5724
|
+
const raw = execFileSync17("git", args, {
|
|
5637
5725
|
cwd: repoHome2,
|
|
5638
5726
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5639
5727
|
}).toString();
|
|
@@ -5862,16 +5950,16 @@ async function cmdPush(opts = {}) {
|
|
|
5862
5950
|
}
|
|
5863
5951
|
|
|
5864
5952
|
// src/commands.update.ts
|
|
5865
|
-
import { execFileSync as
|
|
5953
|
+
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
5866
5954
|
init_utils();
|
|
5867
|
-
function readInstalledVersion(run =
|
|
5955
|
+
function readInstalledVersion(run = execFileSync18) {
|
|
5868
5956
|
try {
|
|
5869
5957
|
return run("nomad", ["--version"], { encoding: "utf8" }).toString().trim() || null;
|
|
5870
5958
|
} catch {
|
|
5871
5959
|
return null;
|
|
5872
5960
|
}
|
|
5873
5961
|
}
|
|
5874
|
-
function cmdUpdate(run =
|
|
5962
|
+
function cmdUpdate(run = execFileSync18) {
|
|
5875
5963
|
console.log("Updating claude-nomad CLI via npm...");
|
|
5876
5964
|
try {
|
|
5877
5965
|
run("npm", ["update", "-g", "claude-nomad"], { stdio: "inherit" });
|
|
@@ -5925,14 +6013,14 @@ import { join as join48 } from "node:path";
|
|
|
5925
6013
|
|
|
5926
6014
|
// src/init.gh-onboard.ts
|
|
5927
6015
|
init_config();
|
|
5928
|
-
import { execFileSync as
|
|
6016
|
+
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
5929
6017
|
init_utils();
|
|
5930
6018
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
5931
6019
|
function isValidRepoName(name) {
|
|
5932
6020
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
5933
6021
|
}
|
|
5934
6022
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
5935
|
-
function ensureOriginRepo(repoName, run =
|
|
6023
|
+
function ensureOriginRepo(repoName, run = execFileSync19) {
|
|
5936
6024
|
if (!isValidRepoName(repoName)) {
|
|
5937
6025
|
die(
|
|
5938
6026
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -6145,86 +6233,20 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
6145
6233
|
}
|
|
6146
6234
|
}
|
|
6147
6235
|
|
|
6148
|
-
// src/nomad.dispatch.ts
|
|
6236
|
+
// src/nomad.dispatch.helpers.ts
|
|
6237
|
+
var REJECT = { ok: false, advance: 0 };
|
|
6238
|
+
function applyBool(seen, set) {
|
|
6239
|
+
if (seen) return REJECT;
|
|
6240
|
+
set();
|
|
6241
|
+
return { ok: true, advance: 1 };
|
|
6242
|
+
}
|
|
6149
6243
|
function extractFlagValue(argv, i) {
|
|
6150
6244
|
const val = argv[i + 1];
|
|
6151
6245
|
if (val === void 0 || val.startsWith("--")) return null;
|
|
6152
6246
|
return val;
|
|
6153
6247
|
}
|
|
6154
|
-
function applyInitToken(argv, i, st) {
|
|
6155
|
-
const token = argv[i];
|
|
6156
|
-
if (token === "--snapshot") {
|
|
6157
|
-
if (st.sawSnapshot) return { ok: false, advance: 0 };
|
|
6158
|
-
st.sawSnapshot = true;
|
|
6159
|
-
st.snapshot = true;
|
|
6160
|
-
return { ok: true, advance: 1 };
|
|
6161
|
-
}
|
|
6162
|
-
if (token === "--keep-actions") {
|
|
6163
|
-
if (st.sawKeepActions) return { ok: false, advance: 0 };
|
|
6164
|
-
st.sawKeepActions = true;
|
|
6165
|
-
st.keepActions = true;
|
|
6166
|
-
return { ok: true, advance: 1 };
|
|
6167
|
-
}
|
|
6168
|
-
if (token === "--repo") {
|
|
6169
|
-
if (st.sawRepo) return { ok: false, advance: 0 };
|
|
6170
|
-
st.sawRepo = true;
|
|
6171
|
-
const val = extractFlagValue(argv, i);
|
|
6172
|
-
if (val === null) return { ok: false, advance: 0 };
|
|
6173
|
-
st.repoName = val;
|
|
6174
|
-
return { ok: true, advance: 2 };
|
|
6175
|
-
}
|
|
6176
|
-
return { ok: false, advance: 0 };
|
|
6177
|
-
}
|
|
6178
|
-
function parseInitArgs(argv) {
|
|
6179
|
-
const st = {
|
|
6180
|
-
snapshot: false,
|
|
6181
|
-
keepActions: false,
|
|
6182
|
-
repoName: void 0,
|
|
6183
|
-
sawSnapshot: false,
|
|
6184
|
-
sawKeepActions: false,
|
|
6185
|
-
sawRepo: false
|
|
6186
|
-
};
|
|
6187
|
-
let i = 3;
|
|
6188
|
-
while (i < argv.length) {
|
|
6189
|
-
const { ok: ok2, advance } = applyInitToken(argv, i, st);
|
|
6190
|
-
if (!ok2) return null;
|
|
6191
|
-
i += advance;
|
|
6192
|
-
}
|
|
6193
|
-
return { snapshot: st.snapshot, keepActions: st.keepActions, repoName: st.repoName };
|
|
6194
|
-
}
|
|
6195
|
-
function parseRedactArgs(argv) {
|
|
6196
|
-
const id = argv[3];
|
|
6197
|
-
if (typeof id !== "string" || !/^\w[\w-]{0,127}$/.test(id)) {
|
|
6198
|
-
return null;
|
|
6199
|
-
}
|
|
6200
|
-
let rule;
|
|
6201
|
-
let dryRun = false;
|
|
6202
|
-
let sawRule = false;
|
|
6203
|
-
let sawDryRun = false;
|
|
6204
|
-
let i = 4;
|
|
6205
|
-
while (i < argv.length) {
|
|
6206
|
-
const token = argv[i];
|
|
6207
|
-
if (token === "--dry-run") {
|
|
6208
|
-
if (sawDryRun) return null;
|
|
6209
|
-
sawDryRun = true;
|
|
6210
|
-
dryRun = true;
|
|
6211
|
-
i++;
|
|
6212
|
-
} else if (token === "--rule") {
|
|
6213
|
-
if (sawRule) return null;
|
|
6214
|
-
sawRule = true;
|
|
6215
|
-
const val = argv[i + 1];
|
|
6216
|
-
if (val === void 0 || val.startsWith("--")) return null;
|
|
6217
|
-
rule = val;
|
|
6218
|
-
i += 2;
|
|
6219
|
-
} else {
|
|
6220
|
-
return null;
|
|
6221
|
-
}
|
|
6222
|
-
}
|
|
6223
|
-
return { id, rule, dryRun };
|
|
6224
|
-
}
|
|
6225
6248
|
|
|
6226
6249
|
// src/nomad.dispatch.clean.ts
|
|
6227
|
-
var REJECT = { ok: false, advance: 0 };
|
|
6228
6250
|
function applyOlderThan(argv, i, st) {
|
|
6229
6251
|
if (st.olderThan !== void 0) return REJECT;
|
|
6230
6252
|
const val = extractFlagValue(argv, i);
|
|
@@ -6239,11 +6261,6 @@ function applyKeep(argv, i, st) {
|
|
|
6239
6261
|
st.keep = Number(val);
|
|
6240
6262
|
return { ok: true, advance: 2 };
|
|
6241
6263
|
}
|
|
6242
|
-
function applyBool(seen, set) {
|
|
6243
|
-
if (seen) return REJECT;
|
|
6244
|
-
set();
|
|
6245
|
-
return { ok: true, advance: 1 };
|
|
6246
|
-
}
|
|
6247
6264
|
function applyCleanToken(argv, i, st) {
|
|
6248
6265
|
switch (argv[i]) {
|
|
6249
6266
|
case "--backups":
|
|
@@ -6293,6 +6310,79 @@ function parseEjectArgs(argv) {
|
|
|
6293
6310
|
return { dryRun };
|
|
6294
6311
|
}
|
|
6295
6312
|
|
|
6313
|
+
// src/nomad.dispatch.ts
|
|
6314
|
+
function applyInitToken(argv, i, st) {
|
|
6315
|
+
const token = argv[i];
|
|
6316
|
+
if (token === "--snapshot") {
|
|
6317
|
+
if (st.sawSnapshot) return REJECT;
|
|
6318
|
+
st.sawSnapshot = true;
|
|
6319
|
+
st.snapshot = true;
|
|
6320
|
+
return { ok: true, advance: 1 };
|
|
6321
|
+
}
|
|
6322
|
+
if (token === "--keep-actions") {
|
|
6323
|
+
if (st.sawKeepActions) return REJECT;
|
|
6324
|
+
st.sawKeepActions = true;
|
|
6325
|
+
st.keepActions = true;
|
|
6326
|
+
return { ok: true, advance: 1 };
|
|
6327
|
+
}
|
|
6328
|
+
if (token === "--repo") {
|
|
6329
|
+
if (st.sawRepo) return REJECT;
|
|
6330
|
+
st.sawRepo = true;
|
|
6331
|
+
const val = extractFlagValue(argv, i);
|
|
6332
|
+
if (val === null) return REJECT;
|
|
6333
|
+
st.repoName = val;
|
|
6334
|
+
return { ok: true, advance: 2 };
|
|
6335
|
+
}
|
|
6336
|
+
return REJECT;
|
|
6337
|
+
}
|
|
6338
|
+
function parseInitArgs(argv) {
|
|
6339
|
+
const st = {
|
|
6340
|
+
snapshot: false,
|
|
6341
|
+
keepActions: false,
|
|
6342
|
+
repoName: void 0,
|
|
6343
|
+
sawSnapshot: false,
|
|
6344
|
+
sawKeepActions: false,
|
|
6345
|
+
sawRepo: false
|
|
6346
|
+
};
|
|
6347
|
+
let i = 3;
|
|
6348
|
+
while (i < argv.length) {
|
|
6349
|
+
const { ok: ok2, advance } = applyInitToken(argv, i, st);
|
|
6350
|
+
if (!ok2) return null;
|
|
6351
|
+
i += advance;
|
|
6352
|
+
}
|
|
6353
|
+
return { snapshot: st.snapshot, keepActions: st.keepActions, repoName: st.repoName };
|
|
6354
|
+
}
|
|
6355
|
+
function parseRedactArgs(argv) {
|
|
6356
|
+
const id = argv[3];
|
|
6357
|
+
if (typeof id !== "string" || !/^\w[\w-]{0,127}$/.test(id)) {
|
|
6358
|
+
return null;
|
|
6359
|
+
}
|
|
6360
|
+
let rule;
|
|
6361
|
+
let dryRun = false;
|
|
6362
|
+
let sawRule = false;
|
|
6363
|
+
let sawDryRun = false;
|
|
6364
|
+
let i = 4;
|
|
6365
|
+
while (i < argv.length) {
|
|
6366
|
+
const token = argv[i];
|
|
6367
|
+
if (token === "--dry-run") {
|
|
6368
|
+
if (sawDryRun) return null;
|
|
6369
|
+
sawDryRun = true;
|
|
6370
|
+
dryRun = true;
|
|
6371
|
+
i++;
|
|
6372
|
+
} else if (token === "--rule") {
|
|
6373
|
+
if (sawRule) return null;
|
|
6374
|
+
sawRule = true;
|
|
6375
|
+
const val = extractFlagValue(argv, i);
|
|
6376
|
+
if (val === null) return null;
|
|
6377
|
+
rule = val;
|
|
6378
|
+
i += 2;
|
|
6379
|
+
} else {
|
|
6380
|
+
return null;
|
|
6381
|
+
}
|
|
6382
|
+
}
|
|
6383
|
+
return { id, rule, dryRun };
|
|
6384
|
+
}
|
|
6385
|
+
|
|
6296
6386
|
// src/nomad.dispatch.allow.ts
|
|
6297
6387
|
function parseAllowArgs(argv) {
|
|
6298
6388
|
const positionals = argv.slice(3);
|
|
@@ -6326,32 +6416,26 @@ function parsePullArgs(argv) {
|
|
|
6326
6416
|
}
|
|
6327
6417
|
|
|
6328
6418
|
// src/nomad.dispatch.push.ts
|
|
6329
|
-
var REJECT2 = { ok: false, advance: 0 };
|
|
6330
|
-
function applyBool2(seen, set) {
|
|
6331
|
-
if (seen) return REJECT2;
|
|
6332
|
-
set();
|
|
6333
|
-
return { ok: true, advance: 1 };
|
|
6334
|
-
}
|
|
6335
6419
|
var RULE_ID_RE = /^\w[\w-]*$/;
|
|
6336
6420
|
function applyAllow2(argv, i, st) {
|
|
6337
|
-
if (st.allowRule !== void 0) return
|
|
6421
|
+
if (st.allowRule !== void 0) return REJECT;
|
|
6338
6422
|
const val = extractFlagValue(argv, i);
|
|
6339
|
-
if (val === null || !RULE_ID_RE.test(val)) return
|
|
6423
|
+
if (val === null || !RULE_ID_RE.test(val)) return REJECT;
|
|
6340
6424
|
st.allowRule = val;
|
|
6341
6425
|
return { ok: true, advance: 2 };
|
|
6342
6426
|
}
|
|
6343
6427
|
function applyPushToken(argv, i, st) {
|
|
6344
6428
|
switch (argv[i]) {
|
|
6345
6429
|
case "--dry-run":
|
|
6346
|
-
return
|
|
6430
|
+
return applyBool(st.dryRun, () => st.dryRun = true);
|
|
6347
6431
|
case "--redact-all":
|
|
6348
|
-
return
|
|
6432
|
+
return applyBool(st.redactAll, () => st.redactAll = true);
|
|
6349
6433
|
case "--allow-all":
|
|
6350
|
-
return
|
|
6434
|
+
return applyBool(st.allowAll, () => st.allowAll = true);
|
|
6351
6435
|
case "--allow":
|
|
6352
6436
|
return applyAllow2(argv, i, st);
|
|
6353
6437
|
default:
|
|
6354
|
-
return
|
|
6438
|
+
return REJECT;
|
|
6355
6439
|
}
|
|
6356
6440
|
}
|
|
6357
6441
|
function parsePushArgs(argv) {
|
|
@@ -6383,7 +6467,7 @@ function parsePushArgs(argv) {
|
|
|
6383
6467
|
// package.json
|
|
6384
6468
|
var package_default = {
|
|
6385
6469
|
name: "claude-nomad",
|
|
6386
|
-
version: "0.50.
|
|
6470
|
+
version: "0.50.3",
|
|
6387
6471
|
type: "module",
|
|
6388
6472
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
6389
6473
|
keywords: [
|
|
@@ -6612,7 +6696,7 @@ function resumeCmd(sessionId) {
|
|
|
6612
6696
|
process.exit(1);
|
|
6613
6697
|
}
|
|
6614
6698
|
const map = readJson(mapPath);
|
|
6615
|
-
const schemaError =
|
|
6699
|
+
const schemaError = validatePathMapShape(map);
|
|
6616
6700
|
if (schemaError !== null) {
|
|
6617
6701
|
fail(schemaError);
|
|
6618
6702
|
process.exit(1);
|
|
@@ -6647,26 +6731,6 @@ function extractRecordedCwd(jsonlPath) {
|
|
|
6647
6731
|
}
|
|
6648
6732
|
return null;
|
|
6649
6733
|
}
|
|
6650
|
-
function validatePathMap(raw) {
|
|
6651
|
-
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
6652
|
-
return "path-map.json invalid schema: top-level value must be an object";
|
|
6653
|
-
}
|
|
6654
|
-
const projects = raw.projects;
|
|
6655
|
-
if (projects === null || typeof projects !== "object" || Array.isArray(projects)) {
|
|
6656
|
-
return 'path-map.json invalid schema: "projects" must be an object';
|
|
6657
|
-
}
|
|
6658
|
-
for (const [name, hosts] of Object.entries(projects)) {
|
|
6659
|
-
if (hosts === null || typeof hosts !== "object" || Array.isArray(hosts)) {
|
|
6660
|
-
return `path-map.json invalid schema: project "${name}" hosts must be an object`;
|
|
6661
|
-
}
|
|
6662
|
-
for (const [host, value] of Object.entries(hosts)) {
|
|
6663
|
-
if (typeof value !== "string") {
|
|
6664
|
-
return `path-map.json invalid schema: project "${name}" host "${host}" path must be a string`;
|
|
6665
|
-
}
|
|
6666
|
-
}
|
|
6667
|
-
}
|
|
6668
|
-
return null;
|
|
6669
|
-
}
|
|
6670
6734
|
function lookupLocalPath(map, recordedCwd) {
|
|
6671
6735
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
6672
6736
|
const isUnderMappedPath = Object.values(hosts).some(
|
package/package.json
CHANGED