claude-setup 1.1.5 → 1.1.7
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/dist/builder.d.ts +6 -0
- package/dist/builder.js +28 -0
- package/dist/commands/add.d.ts +3 -1
- package/dist/commands/add.js +5 -13
- package/dist/commands/init.js +61 -30
- package/dist/commands/remove.d.ts +3 -1
- package/dist/commands/remove.js +5 -9
- package/dist/commands/restore.d.ts +4 -1
- package/dist/commands/restore.js +199 -29
- package/dist/commands/sync.js +62 -98
- package/dist/doctor.js +9 -4
- package/dist/index.js +39 -9
- package/dist/marketplace.js +3 -2
- package/dist/snapshot.d.ts +14 -6
- package/dist/snapshot.js +194 -58
- package/dist/tokens.js +2 -2
- package/package.json +1 -1
- package/templates/sync.md +13 -5
package/dist/commands/sync.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
|
-
import { glob } from "glob";
|
|
4
3
|
import { collectProjectFiles } from "../collect.js";
|
|
5
4
|
import { readState } from "../state.js";
|
|
6
5
|
import { readManifest, sha256, updateManifest } from "../manifest.js";
|
|
7
6
|
import { buildSyncCommand } from "../builder.js";
|
|
8
|
-
import { createSnapshot, collectFilesForSnapshot } from "../snapshot.js";
|
|
9
|
-
import { estimateTokens, estimateCost,
|
|
7
|
+
import { createSnapshot, collectFilesForSnapshot, readTimeline, readNodeData } from "../snapshot.js";
|
|
8
|
+
import { estimateTokens, estimateCost, formatTokenReport, buildTokenEstimate, getTokenHookScript } from "../tokens.js";
|
|
10
9
|
import { loadConfig } from "../config.js";
|
|
11
10
|
import { c, section } from "../output.js";
|
|
12
11
|
function ensureDir(dir) {
|
|
@@ -14,12 +13,10 @@ function ensureDir(dir) {
|
|
|
14
13
|
mkdirSync(dir, { recursive: true });
|
|
15
14
|
}
|
|
16
15
|
function installTokenHook(cwd = process.cwd()) {
|
|
17
|
-
// Write the hook script
|
|
18
16
|
const hooksDir = join(cwd, ".claude", "hooks");
|
|
19
17
|
if (!existsSync(hooksDir))
|
|
20
18
|
mkdirSync(hooksDir, { recursive: true });
|
|
21
19
|
writeFileSync(join(hooksDir, "track-tokens.cjs"), getTokenHookScript(), "utf8");
|
|
22
|
-
// Merge Stop hook into settings.json
|
|
23
20
|
const settingsPath = join(cwd, ".claude", "settings.json");
|
|
24
21
|
let settings = {};
|
|
25
22
|
if (existsSync(settingsPath)) {
|
|
@@ -31,13 +28,11 @@ function installTokenHook(cwd = process.cwd()) {
|
|
|
31
28
|
const hookEntry = {
|
|
32
29
|
hooks: [{ type: "command", command: "node \".claude/hooks/track-tokens.cjs\"" }]
|
|
33
30
|
};
|
|
34
|
-
// Merge into settings.hooks.Stop
|
|
35
31
|
if (!settings.hooks)
|
|
36
32
|
settings.hooks = {};
|
|
37
33
|
const hooks = settings.hooks;
|
|
38
34
|
if (!Array.isArray(hooks.Stop))
|
|
39
35
|
hooks.Stop = [];
|
|
40
|
-
// Only add if not already present
|
|
41
36
|
const alreadyPresent = hooks.Stop.some(e => Array.isArray(e.hooks) && e.hooks.some((h) => typeof h.command === "string" && h.command.includes("track-tokens")));
|
|
42
37
|
if (!alreadyPresent) {
|
|
43
38
|
hooks.Stop.push(hookEntry);
|
|
@@ -51,7 +46,11 @@ function truncate(content, maxChars) {
|
|
|
51
46
|
return content;
|
|
52
47
|
return content.slice(0, maxChars) + "\n[... truncated for sync diff]";
|
|
53
48
|
}
|
|
54
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Legacy diff — compares manifest hashes against collected files.
|
|
51
|
+
* Only used when no snapshot data is available (e.g., old projects).
|
|
52
|
+
*/
|
|
53
|
+
function computeLegacyDiff(snapshot, collected, cwd) {
|
|
55
54
|
const current = {
|
|
56
55
|
...collected.configs,
|
|
57
56
|
...Object.fromEntries(collected.source.map(f => [f.path, f.content])),
|
|
@@ -60,7 +59,6 @@ function computeDiff(snapshot, collected, cwd) {
|
|
|
60
59
|
const changed = [];
|
|
61
60
|
const deleted = [];
|
|
62
61
|
for (const [path, content] of Object.entries(current)) {
|
|
63
|
-
// Skip virtual keys — they're not real files
|
|
64
62
|
if (path === "__digest__")
|
|
65
63
|
continue;
|
|
66
64
|
const hash = sha256(content);
|
|
@@ -71,57 +69,58 @@ function computeDiff(snapshot, collected, cwd) {
|
|
|
71
69
|
changed.push({ path, current: truncate(content, 2000) });
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
|
-
// BUG 1 FIX: Verify file existence on disk before reporting deletions.
|
|
75
|
-
// Files may appear "deleted" because they weren't in the current collection set
|
|
76
|
-
// (different collect mode, or CLI-managed files like CLAUDE.md/settings.json).
|
|
77
|
-
// If the file still exists on disk, it was "modified outside the CLI", not deleted.
|
|
78
72
|
for (const path of Object.keys(snapshot)) {
|
|
79
|
-
// Skip virtual keys
|
|
80
73
|
if (path === "__digest__")
|
|
81
74
|
continue;
|
|
82
75
|
if (!current[path]) {
|
|
83
|
-
// Check if file actually exists on disk
|
|
84
76
|
const fullPath = join(cwd, path);
|
|
85
77
|
if (existsSync(fullPath)) {
|
|
86
|
-
// File exists but wasn't in our collection — it was modified outside CLI
|
|
87
|
-
// Read it and check if its hash changed
|
|
88
78
|
try {
|
|
89
79
|
const diskContent = readFileSync(fullPath, "utf8");
|
|
90
80
|
const diskHash = sha256(diskContent);
|
|
91
81
|
if (snapshot[path] !== diskHash) {
|
|
92
82
|
changed.push({ path, current: truncate(diskContent, 2000) });
|
|
93
83
|
}
|
|
94
|
-
// If hash matches, file is unchanged — don't report anything
|
|
95
84
|
}
|
|
96
85
|
catch {
|
|
97
|
-
// Can't read — treat as changed
|
|
98
86
|
changed.push({ path, current: "[file exists but could not be read]" });
|
|
99
87
|
}
|
|
100
88
|
}
|
|
101
89
|
else {
|
|
102
|
-
// File genuinely does not exist on disk — truly deleted
|
|
103
90
|
deleted.push(path);
|
|
104
91
|
}
|
|
105
92
|
}
|
|
106
93
|
}
|
|
107
94
|
return { added, changed, deleted };
|
|
108
95
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Full-scan diff — compares every file on disk against a reference snapshot.
|
|
98
|
+
* This is the authoritative diff: catches ALL file changes, no sampling.
|
|
99
|
+
*/
|
|
100
|
+
function computeFullDiff(currentFiles, referenceFiles) {
|
|
101
|
+
const added = [];
|
|
102
|
+
const changed = [];
|
|
103
|
+
const deleted = [];
|
|
104
|
+
const currentPathSet = new Set();
|
|
105
|
+
for (const f of currentFiles) {
|
|
106
|
+
currentPathSet.add(f.path);
|
|
107
|
+
if (!referenceFiles[f.path]) {
|
|
108
|
+
added.push({ path: f.path, content: truncate(f.content, 2000) });
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const currentHash = sha256(f.content);
|
|
112
|
+
const refHash = sha256(referenceFiles[f.path]);
|
|
113
|
+
if (currentHash !== refHash) {
|
|
114
|
+
changed.push({ path: f.path, current: truncate(f.content, 2000) });
|
|
119
115
|
}
|
|
120
|
-
catch { /* skip unreadable */ }
|
|
121
116
|
}
|
|
122
117
|
}
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
for (const path of Object.keys(referenceFiles)) {
|
|
119
|
+
if (!currentPathSet.has(path)) {
|
|
120
|
+
deleted.push(path);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { added, changed, deleted };
|
|
125
124
|
}
|
|
126
125
|
export async function runSync(opts = {}) {
|
|
127
126
|
const dryRun = opts.dryRun ?? false;
|
|
@@ -133,11 +132,10 @@ export async function runSync(opts = {}) {
|
|
|
133
132
|
const lastRun = manifest.runs.at(-1);
|
|
134
133
|
const cwd = process.cwd();
|
|
135
134
|
const config = loadConfig(cwd);
|
|
136
|
-
// Apply --budget override if provided
|
|
137
135
|
if (opts.budget) {
|
|
138
136
|
config.tokenBudget.sync = opts.budget;
|
|
139
137
|
}
|
|
140
|
-
// --- Out-of-band edit detection ---
|
|
138
|
+
// --- Out-of-band edit detection (early warning) ---
|
|
141
139
|
const managedFiles = [
|
|
142
140
|
{ label: "CLAUDE.md", path: join(cwd, "CLAUDE.md"), snapshotKey: "CLAUDE.md" },
|
|
143
141
|
{ label: ".mcp.json", path: join(cwd, ".mcp.json"), snapshotKey: ".mcp.json" },
|
|
@@ -161,39 +159,39 @@ export async function runSync(opts = {}) {
|
|
|
161
159
|
}
|
|
162
160
|
if (oobDetected)
|
|
163
161
|
console.log("");
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
162
|
+
// --- Full project scan (single scan, used for both diff and snapshot) ---
|
|
163
|
+
const currentFiles = collectFilesForSnapshot(cwd, []);
|
|
164
|
+
// --- Determine reference snapshot ---
|
|
165
|
+
// After restore: compare against the restored-to snapshot
|
|
166
|
+
// Normal: compare against the latest snapshot
|
|
167
|
+
const timeline = readTimeline(cwd);
|
|
168
|
+
const referenceNodeId = timeline.restoredTo ?? timeline.nodes.at(-1)?.id;
|
|
169
|
+
let referenceFiles = null;
|
|
170
|
+
if (referenceNodeId) {
|
|
171
|
+
const data = readNodeData(cwd, referenceNodeId);
|
|
172
|
+
if (data)
|
|
173
|
+
referenceFiles = data.files;
|
|
174
|
+
}
|
|
175
|
+
// --- Compute diff ---
|
|
176
|
+
let diff;
|
|
177
|
+
if (referenceFiles) {
|
|
178
|
+
// Full-scan comparison (authoritative — catches ALL changes)
|
|
179
|
+
diff = computeFullDiff(currentFiles, referenceFiles);
|
|
176
180
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
diff.changed.some(f => f.path === path) ||
|
|
182
|
-
diff.deleted.includes(path);
|
|
183
|
-
if (!alreadyInDiff && !claudeInternalFiles.some(f => f.path === path)) {
|
|
184
|
-
if (!existsSync(join(cwd, path))) {
|
|
185
|
-
diff.deleted.push(path);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
181
|
+
else {
|
|
182
|
+
// Legacy fallback — no snapshot data available
|
|
183
|
+
const collected = await collectProjectFiles(cwd, "normal");
|
|
184
|
+
diff = computeLegacyDiff(lastRun.snapshot, collected, cwd);
|
|
189
185
|
}
|
|
190
|
-
|
|
186
|
+
const hasChanges = diff.added.length > 0 || diff.changed.length > 0 || diff.deleted.length > 0 || oobDetected;
|
|
187
|
+
if (!hasChanges) {
|
|
191
188
|
console.log(`${c.green("✅")} No changes since ${c.dim(lastRun.at)}. Setup is current.`);
|
|
192
189
|
return;
|
|
193
190
|
}
|
|
191
|
+
// --- Build sync command (needs collected project context for template) ---
|
|
192
|
+
const collected = await collectProjectFiles(cwd, "normal");
|
|
194
193
|
const state = await readState();
|
|
195
194
|
const content = buildSyncCommand(diff, collected, state);
|
|
196
|
-
// Token tracking
|
|
197
195
|
const tokens = estimateTokens(content);
|
|
198
196
|
const cost = estimateCost(tokens);
|
|
199
197
|
if (dryRun) {
|
|
@@ -214,57 +212,23 @@ export async function runSync(opts = {}) {
|
|
|
214
212
|
console.log(` ${f}`);
|
|
215
213
|
}
|
|
216
214
|
console.log(`\n Would write: .claude/commands/stack-sync.md (~${tokens.toLocaleString()} tokens)`);
|
|
217
|
-
// Token cost display
|
|
218
215
|
section("Token cost estimate");
|
|
219
216
|
const estimate = buildTokenEstimate([{ label: "sync command", content }]);
|
|
220
217
|
console.log(formatTokenReport(estimate));
|
|
221
218
|
return;
|
|
222
219
|
}
|
|
223
|
-
// Add .claude/ internal files to snapshot
|
|
224
|
-
for (const f of claudeInternalFiles) {
|
|
225
|
-
collected.configs[f.path] = f.content;
|
|
226
|
-
}
|
|
227
220
|
ensureDir(".claude/commands");
|
|
228
221
|
writeFileSync(".claude/commands/stack-sync.md", content, "utf8");
|
|
229
222
|
await updateManifest("sync", collected, { estimatedTokens: tokens, estimatedCost: cost });
|
|
230
223
|
installTokenHook();
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
...Object.keys(collected.configs),
|
|
234
|
-
...collected.source.map(s => s.path),
|
|
235
|
-
...claudeInternalFiles.map(f => f.path),
|
|
236
|
-
];
|
|
237
|
-
const snapshotFiles = collectFilesForSnapshot(cwd, allPaths);
|
|
238
|
-
const changeCount = diff.added.length + diff.changed.length + diff.deleted.length;
|
|
239
|
-
createSnapshot(cwd, "sync", snapshotFiles, {
|
|
224
|
+
// Create snapshot — reuse the full scan data (no second scan needed)
|
|
225
|
+
createSnapshot(cwd, "sync", currentFiles, {
|
|
240
226
|
summary: `+${diff.added.length} added, ~${diff.changed.length} modified, -${diff.deleted.length} deleted`,
|
|
241
227
|
});
|
|
242
228
|
console.log(`
|
|
243
229
|
Changes since ${c.dim(lastRun.at)}:
|
|
244
230
|
${c.green(`+${diff.added.length}`)} added ${c.yellow(`~${diff.changed.length}`)} modified ${c.red(`-${diff.deleted.length}`)} deleted
|
|
245
231
|
|
|
246
|
-
${c.green("✅")}
|
|
247
|
-
${c.cyan("/stack-sync")}
|
|
232
|
+
${c.green("✅")} Run ${c.cyan("/stack-sync")} in Claude Code to apply.
|
|
248
233
|
`);
|
|
249
|
-
// Token cost display
|
|
250
|
-
section("Token cost");
|
|
251
|
-
const realSummary = formatRealCostSummary(cwd);
|
|
252
|
-
if (realSummary) {
|
|
253
|
-
console.log(realSummary);
|
|
254
|
-
console.log(` ${c.dim(`This command estimate: ~${tokens.toLocaleString()} input tokens (${formatCost(cost)})`)}`);
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
console.log(` ~${tokens.toLocaleString()} input tokens (${c.dim(`${formatCost(cost)}`)})`);
|
|
258
|
-
console.log(` ${c.dim("Estimates only — real costs tracked after first Claude Code session")}`);
|
|
259
|
-
}
|
|
260
|
-
// Optimization hints
|
|
261
|
-
const runs = manifest.runs.map(r => ({ command: r.command, estimatedTokens: r.estimatedTokens }));
|
|
262
|
-
const hints = generateHints(runs, tokens, config.tokenBudget.sync);
|
|
263
|
-
if (hints.length) {
|
|
264
|
-
section("Optimization hints");
|
|
265
|
-
for (const hint of hints) {
|
|
266
|
-
console.log(` ${c.yellow("💡")} ${hint}`);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
console.log("");
|
|
270
234
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -375,9 +375,14 @@ export async function runDoctor(verbose = false, fix = false, testHooks = false)
|
|
|
375
375
|
counts.warnings++;
|
|
376
376
|
}
|
|
377
377
|
else if (result.status === "error") {
|
|
378
|
+
const stderr = result.stderr ?? "";
|
|
379
|
+
const isFileMissing = stderr.includes("Cannot find module") || stderr.includes("MODULE_NOT_FOUND");
|
|
380
|
+
const hint = isFileMissing
|
|
381
|
+
? `\n Hint: hook file not found — run ${c.cyan("npx claude-setup init")} to reinstall it.`
|
|
382
|
+
: "";
|
|
378
383
|
statusLine("⚠️ ", label, c.yellow(`FAIL (exit ${result.exitCode}, ${result.timeMs}ms)\n` +
|
|
379
|
-
` Command: ${hook.command.slice(0,
|
|
380
|
-
` ${
|
|
384
|
+
` Command: ${hook.command.slice(0, 60)}\n` +
|
|
385
|
+
` ${stderr ? `stderr: ${stderr.slice(0, 200)}` : ""}${hint}`));
|
|
381
386
|
counts.warnings++;
|
|
382
387
|
}
|
|
383
388
|
else if (result.status === "permission") {
|
|
@@ -409,13 +414,13 @@ export async function runDoctor(verbose = false, fix = false, testHooks = false)
|
|
|
409
414
|
counts.healthy++;
|
|
410
415
|
}
|
|
411
416
|
else if (isInTemplate) {
|
|
412
|
-
statusLine("🔴", `\${${v}}`, c.red(`NOT SET — MCP server will fail at runtime.\n` +
|
|
417
|
+
statusLine("🔴", `\${${v}}`, c.red(`NOT SET — MCP server will fail at runtime and won't appear in /mcp.\n` +
|
|
413
418
|
` Documented in .env.example but not loaded into environment.\n` +
|
|
414
419
|
` Fix: set ${v} in your shell or .env file, then restart Claude Code.`));
|
|
415
420
|
counts.critical++;
|
|
416
421
|
}
|
|
417
422
|
else {
|
|
418
|
-
statusLine("🔴", `\${${v}}`, c.red(`NOT SET — MCP server will fail at runtime.\n` +
|
|
423
|
+
statusLine("🔴", `\${${v}}`, c.red(`NOT SET — MCP server will fail at runtime and won't appear in /mcp.\n` +
|
|
419
424
|
` Missing from both environment and .env.example.\n` +
|
|
420
425
|
` Fix: add ${v} to .env.example and set its value in your shell or .env file.`));
|
|
421
426
|
counts.critical++;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
+
import { createInterface } from "readline";
|
|
3
4
|
import { createRequire } from "module";
|
|
4
5
|
import { runInit } from "./commands/init.js";
|
|
5
6
|
import { runAdd } from "./commands/add.js";
|
|
@@ -10,6 +11,7 @@ import { runRemove } from "./commands/remove.js";
|
|
|
10
11
|
import { runRestore } from "./commands/restore.js";
|
|
11
12
|
import { runCompare } from "./commands/compare.js";
|
|
12
13
|
import { runExport } from "./commands/export.js";
|
|
14
|
+
import { c } from "./output.js";
|
|
13
15
|
const require = createRequire(import.meta.url);
|
|
14
16
|
const pkg = require("../package.json");
|
|
15
17
|
const program = new Command();
|
|
@@ -24,9 +26,9 @@ program
|
|
|
24
26
|
.option("--template <path>", "Apply a template instead of scanning (local path or URL)")
|
|
25
27
|
.action((opts) => runInit({ dryRun: opts.dryRun, template: opts.template }));
|
|
26
28
|
program
|
|
27
|
-
.command("add")
|
|
29
|
+
.command("add [input...]")
|
|
28
30
|
.description("Add a multi-file capability")
|
|
29
|
-
.action(runAdd);
|
|
31
|
+
.action((input) => runAdd({ input: input?.length ? input.join(" ") : undefined }));
|
|
30
32
|
program
|
|
31
33
|
.command("sync")
|
|
32
34
|
.description("Update setup after project changes")
|
|
@@ -49,23 +51,51 @@ program
|
|
|
49
51
|
testHooks: opts.testHooks,
|
|
50
52
|
}));
|
|
51
53
|
program
|
|
52
|
-
.command("remove")
|
|
54
|
+
.command("remove [input...]")
|
|
53
55
|
.description("Remove a capability cleanly")
|
|
54
|
-
.action(runRemove);
|
|
55
|
-
// Feature A: Time-travel snapshot commands
|
|
56
|
+
.action((input) => runRemove({ input: input?.length ? input.join(" ") : undefined }));
|
|
56
57
|
program
|
|
57
58
|
.command("restore")
|
|
58
59
|
.description("Jump to any snapshot node, restore files to that state")
|
|
59
|
-
.
|
|
60
|
+
.option("--list", "Show snapshot timeline without prompting")
|
|
61
|
+
.option("--id <snapshotId>", "Restore directly to a specific snapshot ID")
|
|
62
|
+
.action((opts) => runRestore({ list: opts.list, id: opts.id }));
|
|
60
63
|
program
|
|
61
64
|
.command("compare")
|
|
62
65
|
.description("Diff between any two snapshot nodes to see what changed")
|
|
63
66
|
.action(runCompare);
|
|
64
|
-
// Feature H: Config template export
|
|
65
67
|
program
|
|
66
68
|
.command("export")
|
|
67
69
|
.description("Save current project config as a reusable template")
|
|
68
70
|
.action(runExport);
|
|
69
|
-
// Default action when no command given
|
|
70
|
-
program.action(() =>
|
|
71
|
+
// Default action — interactive menu when no command given
|
|
72
|
+
program.action(async () => {
|
|
73
|
+
const choices = [
|
|
74
|
+
{ key: "1", label: "init", desc: "Full project setup", run: () => runInit({}) },
|
|
75
|
+
{ key: "2", label: "add", desc: "Add a capability", run: () => runAdd({}) },
|
|
76
|
+
{ key: "3", label: "sync", desc: "Update after changes", run: () => runSync({}) },
|
|
77
|
+
{ key: "4", label: "status", desc: "Show current state", run: () => runStatus() },
|
|
78
|
+
{ key: "5", label: "doctor", desc: "Validate environment", run: () => runDoctorCommand({}) },
|
|
79
|
+
{ key: "6", label: "restore", desc: "Time-travel to snapshot", run: () => runRestore({}) },
|
|
80
|
+
{ key: "7", label: "compare", desc: "Diff between snapshots", run: () => runCompare() },
|
|
81
|
+
{ key: "8", label: "remove", desc: "Remove a capability", run: () => runRemove({}) },
|
|
82
|
+
{ key: "9", label: "export", desc: "Save as template", run: () => runExport() },
|
|
83
|
+
];
|
|
84
|
+
console.log(`\n${c.bold("Claude Setup")} ${c.dim(`v${pkg.version}`)}\n`);
|
|
85
|
+
for (const ch of choices) {
|
|
86
|
+
console.log(` ${c.cyan(ch.key)} ${ch.label.padEnd(10)} ${c.dim(ch.desc)}`);
|
|
87
|
+
}
|
|
88
|
+
console.log("");
|
|
89
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
90
|
+
const answer = await new Promise(resolve => {
|
|
91
|
+
rl.question(` ${c.bold("Choose (1-9):")} `, a => { rl.close(); resolve(a.trim()); });
|
|
92
|
+
});
|
|
93
|
+
const choice = choices.find(ch => ch.key === answer || ch.label === answer.toLowerCase());
|
|
94
|
+
if (!choice) {
|
|
95
|
+
console.log(`\n Invalid choice. Run ${c.cyan("npx claude-setup <command>")} or pick 1-9.\n`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
console.log("");
|
|
99
|
+
await choice.run();
|
|
100
|
+
});
|
|
71
101
|
program.parse();
|
package/dist/marketplace.js
CHANGED
|
@@ -138,11 +138,12 @@ export function buildMarketplaceInstructions(input) {
|
|
|
138
138
|
lines.push(``);
|
|
139
139
|
lines.push(`NOTE: /plugin marketplace add FAILS for this repo (one entry has broken schema).`);
|
|
140
140
|
lines.push(`Use the DIRECT FETCH approach below instead — no marketplace add needed.`);
|
|
141
|
+
lines.push(`If the curl command fails or returns an error, skip to STEP 3 immediately — do NOT stop.`);
|
|
141
142
|
lines.push(``);
|
|
142
143
|
lines.push(`**2a. Fetch catalog and find matching plugin:**`);
|
|
143
144
|
lines.push(`\`\`\`bash`);
|
|
144
145
|
lines.push(`curl -s "${MARKETPLACE_CATALOG_URL}" \\`);
|
|
145
|
-
lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(
|
|
146
|
+
lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));const q='${categoryFilter}';const r=d.plugins.filter(p=>(q===''||p.category.includes(q))&&p.name&&p.source).slice(0,10).map(p=>({name:p.name,source:p.source,desc:p.description}));console.log(JSON.stringify(r,null,2));"`);
|
|
146
147
|
lines.push(`\`\`\``);
|
|
147
148
|
lines.push(``);
|
|
148
149
|
lines.push(`**2b. Pick the best match — get its source path (e.g. \`./plugins/productivity/my-skill\`)**`);
|
|
@@ -152,7 +153,7 @@ export function buildMarketplaceInstructions(input) {
|
|
|
152
153
|
lines.push(`# Replace PLUGIN_SOURCE_PATH with value from step 2b (e.g. plugins/productivity/my-skill)`);
|
|
153
154
|
lines.push(`PLUGIN_SOURCE_PATH="plugins/productivity/my-skill"`);
|
|
154
155
|
lines.push(`curl -s "https://api.github.com/repos/${MARKETPLACE_REPO}/contents/\${PLUGIN_SOURCE_PATH}/skills" \\`);
|
|
155
|
-
lines.push(` | node -e "const a=JSON.parse(require('fs').readFileSync(
|
|
156
|
+
lines.push(` | node -e "const a=JSON.parse(require('fs').readFileSync(0,'utf8'));console.log(a.map(x=>x.name).join('\\n'));"`);
|
|
156
157
|
lines.push(`\`\`\``);
|
|
157
158
|
lines.push(``);
|
|
158
159
|
lines.push(`**2d. For each skill listed, download and install it:**`);
|
package/dist/snapshot.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface SnapshotNode {
|
|
|
19
19
|
input?: string;
|
|
20
20
|
changedFiles: string[];
|
|
21
21
|
summary: string;
|
|
22
|
+
fullSnapshot?: boolean;
|
|
22
23
|
}
|
|
23
24
|
export interface SnapshotTimeline {
|
|
24
25
|
nodes: SnapshotNode[];
|
|
@@ -41,9 +42,14 @@ export declare function createSnapshot(cwd: string, command: string, changedFile
|
|
|
41
42
|
summary?: string;
|
|
42
43
|
}): SnapshotNode;
|
|
43
44
|
/**
|
|
44
|
-
* Build the
|
|
45
|
-
*
|
|
46
|
-
*
|
|
45
|
+
* Build the complete file state at a given node.
|
|
46
|
+
*
|
|
47
|
+
* Full snapshots (fullSnapshot: true) store the entire project state — used directly.
|
|
48
|
+
* Legacy delta snapshots accumulate from node 0 to target (last-write-wins).
|
|
49
|
+
*
|
|
50
|
+
* Why the distinction matters: with delta snapshots, if a file was deleted between A→B,
|
|
51
|
+
* it would wrongly appear in cumulative state at B (still present from A). Full snapshots
|
|
52
|
+
* avoid this because the target node's data IS the complete truth at that point.
|
|
47
53
|
*/
|
|
48
54
|
export declare function buildCumulativeState(cwd: string, nodeId: string, timeline: SnapshotTimeline): Record<string, string> | null;
|
|
49
55
|
/**
|
|
@@ -56,6 +62,7 @@ export declare function buildCumulativeState(cwd: string, nodeId: string, timeli
|
|
|
56
62
|
export declare function restoreSnapshot(cwd: string, nodeId: string, timeline?: SnapshotTimeline): {
|
|
57
63
|
restored: string[];
|
|
58
64
|
failed: string[];
|
|
65
|
+
deleted: string[];
|
|
59
66
|
stale: string[];
|
|
60
67
|
};
|
|
61
68
|
/**
|
|
@@ -76,10 +83,11 @@ export declare function compareSnapshots(cwd: string, nodeIdA: string, nodeIdB:
|
|
|
76
83
|
identical: string[];
|
|
77
84
|
};
|
|
78
85
|
/**
|
|
79
|
-
* Collect
|
|
80
|
-
*
|
|
86
|
+
* Collect ALL project files for snapshot — full git-like coverage.
|
|
87
|
+
* Respects .gitignore + hard exclusions (node_modules, .git, binaries, .env).
|
|
88
|
+
* The trackedPaths param is kept for API compat but ignored.
|
|
81
89
|
*/
|
|
82
|
-
export declare function collectFilesForSnapshot(cwd: string,
|
|
90
|
+
export declare function collectFilesForSnapshot(cwd: string, _trackedPaths: string[]): Array<{
|
|
83
91
|
path: string;
|
|
84
92
|
content: string;
|
|
85
93
|
}>;
|