claude-nomad 0.32.3 → 0.33.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 +15 -0
- package/README.md +5 -5
- package/package.json +5 -6
- package/src/commands.push.recovery.actions.ts +46 -36
- package/src/commands.redact.ts +2 -1
- package/src/nomad.help.ts +4 -0
- package/src/nomad.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.33.0](https://github.com/funkadelic/claude-nomad/compare/v0.32.4...v0.33.0) (2026-05-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
* **dist:** run bin under node native type-stripping, drop tsx ([#198](https://github.com/funkadelic/claude-nomad/issues/198)) ([79b437d](https://github.com/funkadelic/claude-nomad/commit/79b437d8fee69d4f0f25394de9236f7c67289291))
|
|
9
|
+
* **help:** show CLI version at the top of default help ([#196](https://github.com/funkadelic/claude-nomad/issues/196)) ([f095d35](https://github.com/funkadelic/claude-nomad/commit/f095d354273122bc530aac5f486fdb9ad771341e))
|
|
10
|
+
|
|
11
|
+
## [0.32.4](https://github.com/funkadelic/claude-nomad/compare/v0.32.3...v0.32.4) (2026-05-30)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
* **push:** honor drop-wins for allow in recovery dispatch ([#194](https://github.com/funkadelic/claude-nomad/issues/194)) ([9731ecf](https://github.com/funkadelic/claude-nomad/commit/9731ecf9b3d65a6f10fd3e7fc8e520f839640b25))
|
|
17
|
+
|
|
3
18
|
## [0.32.3](https://github.com/funkadelic/claude-nomad/compare/v0.32.2...v0.32.3) (2026-05-30)
|
|
4
19
|
|
|
5
20
|
|
package/README.md
CHANGED
|
@@ -381,8 +381,6 @@ Read these before adopting so you opt in with eyes open.
|
|
|
381
381
|
- Node.js 22.22.1 or newer (24 LTS recommended; the npm `engines` field declares the 22.22.1 floor
|
|
382
382
|
and surfaces a warning on older runtimes - npm only blocks the install when `engine-strict=true`
|
|
383
383
|
is configured)
|
|
384
|
-
- `tsx` (ships as a runtime dependency of the published package; no separate global install
|
|
385
|
-
required)
|
|
386
384
|
- Git
|
|
387
385
|
- [`gitleaks`](https://github.com/gitleaks/gitleaks) (required for `nomad push`, which exits with an
|
|
388
386
|
error if it is not on PATH; `nomad doctor` also checks it against the pinned 8.30.x and warns when
|
|
@@ -455,9 +453,11 @@ $ git clone git@github.com:<your-username>/claude-nomad.git ~/claude-nomad
|
|
|
455
453
|
export NOMAD_HOST=<your-host-label> # any short, stable label; nomad reads this instead of os.hostname()
|
|
456
454
|
```
|
|
457
455
|
|
|
458
|
-
`npm i -g claude-nomad` puts a `nomad` binary on your PATH. The
|
|
459
|
-
|
|
460
|
-
|
|
456
|
+
`npm i -g claude-nomad` puts a `nomad` binary on your PATH. The binary runs the `src/nomad.ts`
|
|
457
|
+
source directly under Node's built-in type-stripping. What this means for you: there is no compile
|
|
458
|
+
step, no extra transpiler to install, and nothing is fetched from the network the first time you run
|
|
459
|
+
`nomad`, so the first run works offline. (The Node version floor and the `engine-strict` caveat are
|
|
460
|
+
in [Requirements](#requirements).)
|
|
461
461
|
|
|
462
462
|
On every additional host you repeat only steps 3-4; steps 1-2 are already done, since your private
|
|
463
463
|
repo lives on the remote from step 2.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-nomad",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
6
6
|
"keywords": [
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"node": ">=22.22.1"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
|
-
"pull": "
|
|
37
|
-
"push": "
|
|
38
|
-
"doctor": "
|
|
36
|
+
"pull": "node src/nomad.ts pull",
|
|
37
|
+
"push": "node src/nomad.ts push",
|
|
38
|
+
"doctor": "node src/nomad.ts doctor",
|
|
39
39
|
"update": "bash scripts/update.sh",
|
|
40
40
|
"test": "vitest run",
|
|
41
41
|
"coverage": "vitest run --coverage",
|
|
@@ -81,7 +81,6 @@
|
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"diff": "^9.0.0",
|
|
84
|
-
"picocolors": "^1.1.1"
|
|
85
|
-
"tsx": "^4.22.2"
|
|
84
|
+
"picocolors": "^1.1.1"
|
|
86
85
|
}
|
|
87
86
|
}
|
|
@@ -46,64 +46,65 @@ export async function collectActions(
|
|
|
46
46
|
const sid = sessionIdFromFinding(f);
|
|
47
47
|
const header =
|
|
48
48
|
`\nFinding: ${f.RuleID} in ${f.File} line ${f.StartLine}` +
|
|
49
|
-
(sid
|
|
49
|
+
(sid === null ? '' : ` (session: ${sid})`) +
|
|
50
50
|
'\n [R]edact [A]llow [D]rop session [S]kip (default)\n';
|
|
51
51
|
actions.set(findingKey(f), parseAction(await prompt(header + '> ')));
|
|
52
52
|
}
|
|
53
53
|
return actions;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Loop-invariant context for `dispatchOne`, built once by `dispatchActions`
|
|
58
|
+
* before iterating findings. Bundling these keeps `dispatchOne` to two
|
|
59
|
+
* parameters. The `redactedSids` and `droppedSids` sets are mutated in place so
|
|
60
|
+
* per-session de-duplication is maintained across the caller's loop.
|
|
61
|
+
*/
|
|
62
|
+
type DispatchCtx = {
|
|
63
|
+
findings: Finding[];
|
|
64
|
+
actions: Map<string, FindingAction>;
|
|
65
|
+
ts: string;
|
|
66
|
+
map: PathMap;
|
|
67
|
+
nowMs: () => number;
|
|
68
|
+
scan: (p: string) => Finding[] | null;
|
|
69
|
+
drop: (sid: string, map: PathMap) => boolean;
|
|
70
|
+
redactedSids: Set<string>;
|
|
71
|
+
droppedSids: Set<string>;
|
|
72
|
+
};
|
|
73
|
+
|
|
56
74
|
/**
|
|
57
75
|
* Apply one finding's triaged action against local state. Extracted from
|
|
58
76
|
* `dispatchActions` so each function stays under the cognitive-complexity gate.
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* session id appears in `droppedSids`, subsequent redact or allow actions for
|
|
62
|
-
* findings in that session are skipped.
|
|
77
|
+
* Drop wins: once a session id appears in `ctx.droppedSids`, subsequent redact
|
|
78
|
+
* or allow actions for findings in that session are skipped.
|
|
63
79
|
*
|
|
64
80
|
* @param f The finding to act on.
|
|
65
|
-
* @param
|
|
66
|
-
* @param actions The action map returned by `collectActions`.
|
|
67
|
-
* @param ts Backup timestamp.
|
|
68
|
-
* @param map Parsed path-map.
|
|
69
|
-
* @param nowMs Injectable clock.
|
|
70
|
-
* @param scan Injectable scan function for `applyRedact`.
|
|
71
|
-
* @param drop Injectable staged-copy remover for the Drop action.
|
|
72
|
-
* @param redactedSids Set of already-redacted session ids (mutated in place).
|
|
73
|
-
* @param droppedSids Set of already-dropped session ids (mutated in place).
|
|
81
|
+
* @param ctx Loop-invariant dispatch context (see `DispatchCtx`).
|
|
74
82
|
*/
|
|
75
|
-
function dispatchOne(
|
|
76
|
-
f
|
|
77
|
-
findings: Finding[],
|
|
78
|
-
actions: Map<string, FindingAction>,
|
|
79
|
-
ts: string,
|
|
80
|
-
map: PathMap,
|
|
81
|
-
nowMs: () => number,
|
|
82
|
-
scan: (p: string) => Finding[] | null,
|
|
83
|
-
drop: (sid: string, map: PathMap) => boolean,
|
|
84
|
-
redactedSids: Set<string>,
|
|
85
|
-
droppedSids: Set<string>,
|
|
86
|
-
): void {
|
|
87
|
-
const action = actions.get(findingKey(f)) ?? 'skip';
|
|
83
|
+
function dispatchOne(f: Finding, ctx: DispatchCtx): void {
|
|
84
|
+
const action = ctx.actions.get(findingKey(f)) ?? 'skip';
|
|
88
85
|
if (action === 'skip') return;
|
|
86
|
+
const sid = sessionIdFromFinding(f);
|
|
87
|
+
// Drop wins: a dropped session short-circuits every later action for it,
|
|
88
|
+
// including allow, so a stale fingerprint is never written for content that
|
|
89
|
+
// was held back from the push.
|
|
90
|
+
if (sid !== null && ctx.droppedSids.has(sid)) return;
|
|
89
91
|
if (action === 'allow') {
|
|
90
92
|
applyAllow(f);
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
93
|
-
const sid = sessionIdFromFinding(f);
|
|
94
95
|
if (sid === null) return;
|
|
95
|
-
if (droppedSids.has(sid)) return;
|
|
96
96
|
if (action === 'drop') {
|
|
97
|
-
droppedSids.add(sid);
|
|
98
|
-
if (drop(sid, map)) {
|
|
97
|
+
ctx.droppedSids.add(sid);
|
|
98
|
+
if (ctx.drop(sid, ctx.map)) {
|
|
99
99
|
log(
|
|
100
100
|
`dropped session ${sid} from this push (local transcript kept; the secret remains in your local copy)`,
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
|
-
if (action === 'redact' && !redactedSids.has(sid)) {
|
|
106
|
-
if (applyRedact(f, findings, ts, map, nowMs, scan))
|
|
105
|
+
if (action === 'redact' && !ctx.redactedSids.has(sid)) {
|
|
106
|
+
if (applyRedact(f, ctx.findings, ctx.ts, ctx.map, ctx.nowMs, ctx.scan))
|
|
107
|
+
ctx.redactedSids.add(sid);
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
|
|
@@ -130,10 +131,19 @@ export function dispatchActions(
|
|
|
130
131
|
scan: (p: string) => Finding[] | null = scanFile,
|
|
131
132
|
drop: (sid: string, map: PathMap) => boolean = dropSessionFromStaged,
|
|
132
133
|
): void {
|
|
133
|
-
const
|
|
134
|
-
|
|
134
|
+
const ctx: DispatchCtx = {
|
|
135
|
+
findings,
|
|
136
|
+
actions,
|
|
137
|
+
ts,
|
|
138
|
+
map,
|
|
139
|
+
nowMs,
|
|
140
|
+
scan,
|
|
141
|
+
drop,
|
|
142
|
+
redactedSids: new Set<string>(),
|
|
143
|
+
droppedSids: new Set<string>(),
|
|
144
|
+
};
|
|
135
145
|
for (const f of findings) {
|
|
136
|
-
dispatchOne(f,
|
|
146
|
+
dispatchOne(f, ctx);
|
|
137
147
|
}
|
|
138
148
|
}
|
|
139
149
|
|
package/src/commands.redact.ts
CHANGED
|
@@ -153,7 +153,8 @@ export function cmdRedact(
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
if (findings.length === 0) {
|
|
156
|
-
|
|
156
|
+
const ruleClause = rule === undefined ? '' : ` for rule ${rule}`;
|
|
157
|
+
log(`no findings${ruleClause} in session ${id}`);
|
|
157
158
|
return;
|
|
158
159
|
}
|
|
159
160
|
|
package/src/nomad.help.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* into the README. Channel is stderr, exit code is 1.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* Column (0-indexed) at which every command and flag description starts. Sized
|
|
11
13
|
* to clear the longest label (`--resume-cmd <id>`, which ends at column 24)
|
|
@@ -28,6 +30,8 @@ const row = (label: string, desc: string): string => label.padEnd(DESC_COL) + de
|
|
|
28
30
|
const cont = (text: string): string => ' '.repeat(DESC_COL) + text;
|
|
29
31
|
|
|
30
32
|
export const DEFAULT_HELP = [
|
|
33
|
+
`claude-nomad v${pkg.version}`,
|
|
34
|
+
'',
|
|
31
35
|
'usage: nomad <command> [flags]',
|
|
32
36
|
'',
|
|
33
37
|
'Commands:',
|
package/src/nomad.ts
CHANGED