claude-setup 1.1.4 → 1.1.6
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 +41 -0
- package/dist/commands/add.js +2 -2
- package/dist/commands/init.js +62 -13
- package/dist/commands/remove.js +2 -2
- package/dist/commands/restore.js +179 -12
- package/dist/commands/status.js +65 -25
- package/dist/commands/sync.js +110 -20
- package/dist/doctor.js +34 -6
- package/dist/marketplace.d.ts +14 -1
- package/dist/marketplace.js +117 -61
- package/dist/snapshot.d.ts +27 -5
- package/dist/snapshot.js +230 -40
- package/dist/tokens.d.ts +75 -6
- package/dist/tokens.js +542 -9
- package/package.json +1 -1
- package/templates/init-empty.md +49 -7
- package/templates/remove.md +6 -2
- package/templates/sync.md +4 -0
package/dist/commands/sync.js
CHANGED
|
@@ -1,17 +1,51 @@
|
|
|
1
1
|
import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
|
+
import { glob } from "glob";
|
|
3
4
|
import { collectProjectFiles } from "../collect.js";
|
|
4
5
|
import { readState } from "../state.js";
|
|
5
6
|
import { readManifest, sha256, updateManifest } from "../manifest.js";
|
|
6
7
|
import { buildSyncCommand } from "../builder.js";
|
|
7
8
|
import { createSnapshot, collectFilesForSnapshot } from "../snapshot.js";
|
|
8
|
-
import { estimateTokens, estimateCost, formatTokenReport, buildTokenEstimate, generateHints } from "../tokens.js";
|
|
9
|
+
import { estimateTokens, estimateCost, formatCost, formatTokenReport, buildTokenEstimate, generateHints, getTokenHookScript, formatRealCostSummary } from "../tokens.js";
|
|
9
10
|
import { loadConfig } from "../config.js";
|
|
10
11
|
import { c, section } from "../output.js";
|
|
11
12
|
function ensureDir(dir) {
|
|
12
13
|
if (!existsSync(dir))
|
|
13
14
|
mkdirSync(dir, { recursive: true });
|
|
14
15
|
}
|
|
16
|
+
function installTokenHook(cwd = process.cwd()) {
|
|
17
|
+
// Write the hook script
|
|
18
|
+
const hooksDir = join(cwd, ".claude", "hooks");
|
|
19
|
+
if (!existsSync(hooksDir))
|
|
20
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
21
|
+
writeFileSync(join(hooksDir, "track-tokens.cjs"), getTokenHookScript(), "utf8");
|
|
22
|
+
// Merge Stop hook into settings.json
|
|
23
|
+
const settingsPath = join(cwd, ".claude", "settings.json");
|
|
24
|
+
let settings = {};
|
|
25
|
+
if (existsSync(settingsPath)) {
|
|
26
|
+
try {
|
|
27
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf8") ?? "{}");
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
}
|
|
31
|
+
const hookEntry = {
|
|
32
|
+
hooks: [{ type: "command", command: "node \".claude/hooks/track-tokens.cjs\"" }]
|
|
33
|
+
};
|
|
34
|
+
// Merge into settings.hooks.Stop
|
|
35
|
+
if (!settings.hooks)
|
|
36
|
+
settings.hooks = {};
|
|
37
|
+
const hooks = settings.hooks;
|
|
38
|
+
if (!Array.isArray(hooks.Stop))
|
|
39
|
+
hooks.Stop = [];
|
|
40
|
+
// Only add if not already present
|
|
41
|
+
const alreadyPresent = hooks.Stop.some(e => Array.isArray(e.hooks) && e.hooks.some((h) => typeof h.command === "string" && h.command.includes("track-tokens")));
|
|
42
|
+
if (!alreadyPresent) {
|
|
43
|
+
hooks.Stop.push(hookEntry);
|
|
44
|
+
if (!existsSync(join(cwd, ".claude")))
|
|
45
|
+
mkdirSync(join(cwd, ".claude"), { recursive: true });
|
|
46
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf8");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
15
49
|
function truncate(content, maxChars) {
|
|
16
50
|
if (content.length <= maxChars)
|
|
17
51
|
return content;
|
|
@@ -72,6 +106,23 @@ function computeDiff(snapshot, collected, cwd) {
|
|
|
72
106
|
}
|
|
73
107
|
return { added, changed, deleted };
|
|
74
108
|
}
|
|
109
|
+
async function collectClaudeInternalFiles(cwd) {
|
|
110
|
+
const files = [];
|
|
111
|
+
try {
|
|
112
|
+
const skillFiles = await glob(".claude/skills/**/*.md", { cwd, posix: true });
|
|
113
|
+
const allCmds = await glob(".claude/commands/*.md", { cwd, posix: true });
|
|
114
|
+
const commandFiles = allCmds.filter(f => !f.split("/").pop().startsWith("stack-"));
|
|
115
|
+
for (const f of [...skillFiles, ...commandFiles]) {
|
|
116
|
+
try {
|
|
117
|
+
const content = readFileSync(join(cwd, f), "utf8");
|
|
118
|
+
files.push({ path: f, content });
|
|
119
|
+
}
|
|
120
|
+
catch { /* skip unreadable */ }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch { /* skip */ }
|
|
124
|
+
return files;
|
|
125
|
+
}
|
|
75
126
|
export async function runSync(opts = {}) {
|
|
76
127
|
const dryRun = opts.dryRun ?? false;
|
|
77
128
|
const manifest = await readManifest();
|
|
@@ -112,9 +163,34 @@ export async function runSync(opts = {}) {
|
|
|
112
163
|
console.log("");
|
|
113
164
|
const collected = await collectProjectFiles(cwd, "normal");
|
|
114
165
|
const diff = computeDiff(lastRun.snapshot, collected, cwd);
|
|
115
|
-
|
|
166
|
+
// Bug 3 fix: Also detect changes inside .claude/ (skills, commands)
|
|
167
|
+
const claudeInternalFiles = await collectClaudeInternalFiles(cwd);
|
|
168
|
+
for (const f of claudeInternalFiles) {
|
|
169
|
+
const hash = sha256(f.content);
|
|
170
|
+
if (!lastRun.snapshot[f.path]) {
|
|
171
|
+
diff.added.push({ path: f.path, content: truncate(f.content, 2000) });
|
|
172
|
+
}
|
|
173
|
+
else if (lastRun.snapshot[f.path] !== hash) {
|
|
174
|
+
diff.changed.push({ path: f.path, current: truncate(f.content, 2000) });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Also detect deleted .claude/ files (were in snapshot but no longer exist)
|
|
178
|
+
for (const path of Object.keys(lastRun.snapshot)) {
|
|
179
|
+
if ((path.startsWith(".claude/skills/") || (path.startsWith(".claude/commands/") && !path.split("/").pop().startsWith("stack-"))) && !path.includes("__digest__")) {
|
|
180
|
+
const alreadyInDiff = diff.added.some(f => f.path === path) ||
|
|
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
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const hasChanges = diff.added.length > 0 || diff.changed.length > 0 || diff.deleted.length > 0 || oobDetected;
|
|
191
|
+
if (!hasChanges) {
|
|
116
192
|
console.log(`${c.green("✅")} No changes since ${c.dim(lastRun.at)}. Setup is current.`);
|
|
117
|
-
|
|
193
|
+
// Still regenerate the command file so /stack-sync self-refresh always gets an up-to-date "no changes" state
|
|
118
194
|
}
|
|
119
195
|
const state = await readState();
|
|
120
196
|
const content = buildSyncCommand(diff, collected, state);
|
|
@@ -145,37 +221,51 @@ export async function runSync(opts = {}) {
|
|
|
145
221
|
console.log(formatTokenReport(estimate));
|
|
146
222
|
return;
|
|
147
223
|
}
|
|
224
|
+
// Add .claude/ internal files to snapshot
|
|
225
|
+
for (const f of claudeInternalFiles) {
|
|
226
|
+
collected.configs[f.path] = f.content;
|
|
227
|
+
}
|
|
148
228
|
ensureDir(".claude/commands");
|
|
149
229
|
writeFileSync(".claude/commands/stack-sync.md", content, "utf8");
|
|
150
230
|
await updateManifest("sync", collected, { estimatedTokens: tokens, estimatedCost: cost });
|
|
151
|
-
|
|
231
|
+
installTokenHook();
|
|
232
|
+
// Create snapshot node — collectFilesForSnapshot scans all .claude/ automatically
|
|
152
233
|
const allPaths = [
|
|
153
234
|
...Object.keys(collected.configs),
|
|
154
235
|
...collected.source.map(s => s.path),
|
|
155
236
|
];
|
|
156
237
|
const snapshotFiles = collectFilesForSnapshot(cwd, allPaths);
|
|
157
|
-
const changeCount = diff.added.length + diff.changed.length + diff.deleted.length;
|
|
158
238
|
createSnapshot(cwd, "sync", snapshotFiles, {
|
|
159
239
|
summary: `+${diff.added.length} added, ~${diff.changed.length} modified, -${diff.deleted.length} deleted`,
|
|
160
240
|
});
|
|
161
|
-
|
|
241
|
+
if (hasChanges) {
|
|
242
|
+
console.log(`
|
|
162
243
|
Changes since ${c.dim(lastRun.at)}:
|
|
163
244
|
${c.green(`+${diff.added.length}`)} added ${c.yellow(`~${diff.changed.length}`)} modified ${c.red(`-${diff.deleted.length}`)} deleted
|
|
164
245
|
|
|
165
|
-
${c.green("✅")}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
console.log(` ${c.
|
|
246
|
+
${c.green("✅")} Run ${c.cyan("/stack-sync")} in Claude Code to apply.
|
|
247
|
+
`);
|
|
248
|
+
}
|
|
249
|
+
if (hasChanges) {
|
|
250
|
+
// Token cost display
|
|
251
|
+
section("Token cost");
|
|
252
|
+
const realSummary = formatRealCostSummary(cwd);
|
|
253
|
+
if (realSummary) {
|
|
254
|
+
console.log(realSummary);
|
|
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")}`);
|
|
178
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("");
|
|
179
270
|
}
|
|
180
|
-
console.log("");
|
|
181
271
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -116,6 +116,7 @@ export async function runDoctor(verbose = false, fix = false, testHooks = false)
|
|
|
116
116
|
// --- Check 3: OS/MCP format mismatch ---
|
|
117
117
|
if (state.mcpJson.content) {
|
|
118
118
|
section("MCP servers");
|
|
119
|
+
const mcpWarningsBefore = counts.warnings + counts.critical;
|
|
119
120
|
const mcp = safeJsonParse(state.mcpJson.content);
|
|
120
121
|
if (mcp && typeof mcp.mcpServers === "object" && mcp.mcpServers !== null) {
|
|
121
122
|
const servers = mcp.mcpServers;
|
|
@@ -182,6 +183,12 @@ export async function runDoctor(verbose = false, fix = false, testHooks = false)
|
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
}
|
|
186
|
+
// Check if server has no env vars but uses npx (might fail on first run due to npm download)
|
|
187
|
+
if (!config.env && (cmd === "npx" || (cmd === "cmd" && config.args?.includes("npx")))) {
|
|
188
|
+
if (verbose) {
|
|
189
|
+
statusLine("💡", name, c.dim("uses npx — will download package on first run (requires internet)"));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
185
192
|
}
|
|
186
193
|
// Check for channel-type servers
|
|
187
194
|
const channelNames = ["telegram", "discord", "fakechat"];
|
|
@@ -203,6 +210,12 @@ export async function runDoctor(verbose = false, fix = false, testHooks = false)
|
|
|
203
210
|
if (verbose)
|
|
204
211
|
statusLine("⚠️ ", ".mcp.json", "no mcpServers key found");
|
|
205
212
|
}
|
|
213
|
+
if (counts.warnings + counts.critical > mcpWarningsBefore) {
|
|
214
|
+
console.log(`\n ${c.dim("MCP self-correction:")}`);
|
|
215
|
+
console.log(` • ${c.cyan("npx claude-setup doctor --fix")} — auto-fix OS format and -y flag`);
|
|
216
|
+
console.log(` • Set missing env vars, then re-run ${c.cyan("npx claude-setup doctor")}`);
|
|
217
|
+
console.log(` • Verify server packages: https://github.com/modelcontextprotocol/servers`);
|
|
218
|
+
}
|
|
206
219
|
}
|
|
207
220
|
else if (verbose) {
|
|
208
221
|
section("MCP servers");
|
|
@@ -362,9 +375,14 @@ export async function runDoctor(verbose = false, fix = false, testHooks = false)
|
|
|
362
375
|
counts.warnings++;
|
|
363
376
|
}
|
|
364
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
|
+
: "";
|
|
365
383
|
statusLine("⚠️ ", label, c.yellow(`FAIL (exit ${result.exitCode}, ${result.timeMs}ms)\n` +
|
|
366
|
-
` Command: ${hook.command.slice(0,
|
|
367
|
-
` ${
|
|
384
|
+
` Command: ${hook.command.slice(0, 60)}\n` +
|
|
385
|
+
` ${stderr ? `stderr: ${stderr.slice(0, 200)}` : ""}${hint}`));
|
|
368
386
|
counts.warnings++;
|
|
369
387
|
}
|
|
370
388
|
else if (result.status === "permission") {
|
|
@@ -389,13 +407,23 @@ export async function runDoctor(verbose = false, fix = false, testHooks = false)
|
|
|
389
407
|
const template = readIfExists(".env.example") ?? readIfExists(".env.sample") ?? readIfExists(".env.template") ?? "";
|
|
390
408
|
section("Env vars");
|
|
391
409
|
for (const v of unique) {
|
|
392
|
-
|
|
393
|
-
|
|
410
|
+
const isActuallySet = process.env[v] !== undefined && process.env[v] !== "";
|
|
411
|
+
const isInTemplate = template.includes(v);
|
|
412
|
+
if (isActuallySet) {
|
|
413
|
+
statusLine("✅", `\${${v}}`, "set in environment");
|
|
394
414
|
counts.healthy++;
|
|
395
415
|
}
|
|
416
|
+
else if (isInTemplate) {
|
|
417
|
+
statusLine("🔴", `\${${v}}`, c.red(`NOT SET — MCP server will fail at runtime and won't appear in /mcp.\n` +
|
|
418
|
+
` Documented in .env.example but not loaded into environment.\n` +
|
|
419
|
+
` Fix: set ${v} in your shell or .env file, then restart Claude Code.`));
|
|
420
|
+
counts.critical++;
|
|
421
|
+
}
|
|
396
422
|
else {
|
|
397
|
-
statusLine("
|
|
398
|
-
|
|
423
|
+
statusLine("🔴", `\${${v}}`, c.red(`NOT SET — MCP server will fail at runtime and won't appear in /mcp.\n` +
|
|
424
|
+
` Missing from both environment and .env.example.\n` +
|
|
425
|
+
` Fix: add ${v} to .env.example and set its value in your shell or .env file.`));
|
|
426
|
+
counts.critical++;
|
|
399
427
|
}
|
|
400
428
|
}
|
|
401
429
|
}
|
package/dist/marketplace.d.ts
CHANGED
|
@@ -6,6 +6,19 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export declare const MARKETPLACE_REPO = "jeremylongshore/claude-code-plugins-plus-skills";
|
|
8
8
|
export declare const MARKETPLACE_CATALOG_URL = "https://raw.githubusercontent.com/jeremylongshore/claude-code-plugins-plus-skills/main/.claude-plugin/marketplace.extended.json";
|
|
9
|
+
/** Additional marketplace sources for broader coverage */
|
|
10
|
+
export declare const ADDITIONAL_MARKETPLACE_SOURCES: readonly [{
|
|
11
|
+
readonly name: "claude-plugins-official";
|
|
12
|
+
readonly description: "Official Anthropic plugins (GitHub, Slack, Linear, Notion, etc.)";
|
|
13
|
+
readonly installPrefix: "claude-plugins-official";
|
|
14
|
+
readonly note: "No marketplace add needed — available by default";
|
|
15
|
+
}, {
|
|
16
|
+
readonly name: "awesome-claude-code";
|
|
17
|
+
readonly description: "Community collection of Claude Code skills and workflows";
|
|
18
|
+
readonly catalogUrl: "https://raw.githubusercontent.com/hesreallyhim/awesome-claude-code/main/catalog.json";
|
|
19
|
+
readonly installPrefix: null;
|
|
20
|
+
readonly note: "Browse and manually install skills";
|
|
21
|
+
}];
|
|
9
22
|
/** The 20 skill categories in the marketplace */
|
|
10
23
|
export declare const SKILL_CATEGORIES: readonly ["01-code-quality", "02-testing", "03-security", "04-devops", "05-api-development", "06-database", "07-frontend", "08-backend", "09-mobile", "10-data-science", "11-documentation", "12-project-management", "13-communication", "14-research", "15-content-creation", "16-business", "17-finance", "18-visual-content", "19-legal", "20-productivity"];
|
|
11
24
|
/** SaaS packs available in the marketplace */
|
|
@@ -17,5 +30,5 @@ export declare function classifyRequest(input: string): {
|
|
|
17
30
|
categories: string[];
|
|
18
31
|
saasMatches: string[];
|
|
19
32
|
};
|
|
20
|
-
/** Generate marketplace search
|
|
33
|
+
/** Generate fully-automated marketplace search and install instructions */
|
|
21
34
|
export declare function buildMarketplaceInstructions(input: string): string;
|
package/dist/marketplace.js
CHANGED
|
@@ -6,6 +6,22 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export const MARKETPLACE_REPO = "jeremylongshore/claude-code-plugins-plus-skills";
|
|
8
8
|
export const MARKETPLACE_CATALOG_URL = `https://raw.githubusercontent.com/${MARKETPLACE_REPO}/main/.claude-plugin/marketplace.extended.json`;
|
|
9
|
+
/** Additional marketplace sources for broader coverage */
|
|
10
|
+
export const ADDITIONAL_MARKETPLACE_SOURCES = [
|
|
11
|
+
{
|
|
12
|
+
name: "claude-plugins-official",
|
|
13
|
+
description: "Official Anthropic plugins (GitHub, Slack, Linear, Notion, etc.)",
|
|
14
|
+
installPrefix: "claude-plugins-official",
|
|
15
|
+
note: "No marketplace add needed — available by default"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "awesome-claude-code",
|
|
19
|
+
description: "Community collection of Claude Code skills and workflows",
|
|
20
|
+
catalogUrl: "https://raw.githubusercontent.com/hesreallyhim/awesome-claude-code/main/catalog.json",
|
|
21
|
+
installPrefix: null,
|
|
22
|
+
note: "Browse and manually install skills"
|
|
23
|
+
}
|
|
24
|
+
];
|
|
9
25
|
/** The 20 skill categories in the marketplace */
|
|
10
26
|
export const SKILL_CATEGORIES = [
|
|
11
27
|
"01-code-quality", "02-testing", "03-security",
|
|
@@ -83,77 +99,117 @@ export function classifyRequest(input) {
|
|
|
83
99
|
}
|
|
84
100
|
return { categories: [...categories], saasMatches };
|
|
85
101
|
}
|
|
86
|
-
/** Generate marketplace search
|
|
102
|
+
/** Generate fully-automated marketplace search and install instructions */
|
|
87
103
|
export function buildMarketplaceInstructions(input) {
|
|
88
104
|
const { categories, saasMatches } = classifyRequest(input);
|
|
105
|
+
const categoryFilter = categories[0] ?? "";
|
|
89
106
|
const lines = [];
|
|
90
|
-
lines.push(`## Marketplace
|
|
107
|
+
lines.push(`## Marketplace — fully automated install`);
|
|
91
108
|
lines.push(``);
|
|
92
|
-
lines.push(`A plugin marketplace
|
|
109
|
+
lines.push(`A plugin marketplace has 416 community skills + 13 official Anthropic plugins.`);
|
|
110
|
+
lines.push(`Follow the steps below IN ORDER. Do not stop until a skill is installed.`);
|
|
93
111
|
lines.push(``);
|
|
94
112
|
if (categories.length > 0 || saasMatches.length > 0) {
|
|
95
|
-
lines.push(`### Matched
|
|
96
|
-
for (const cat of categories)
|
|
97
|
-
lines.push(`- ${cat}`);
|
|
113
|
+
lines.push(`### Matched for "${input}":`);
|
|
114
|
+
for (const cat of categories)
|
|
115
|
+
lines.push(`- category: ${cat}`);
|
|
116
|
+
for (const saas of saasMatches)
|
|
117
|
+
lines.push(`- SaaS: ${saas}`);
|
|
118
|
+
lines.push(``);
|
|
119
|
+
}
|
|
120
|
+
lines.push(`---`);
|
|
121
|
+
lines.push(``);
|
|
122
|
+
lines.push(`### STEP 1 — Try official Anthropic plugins first (no schema errors, always works)`);
|
|
123
|
+
lines.push(``);
|
|
124
|
+
lines.push(`\`\`\``);
|
|
125
|
+
lines.push(`/plugin marketplace add anthropics/claude-code`);
|
|
126
|
+
lines.push(`\`\`\``);
|
|
127
|
+
lines.push(`Then search installed plugins for a match. If found, install:`);
|
|
128
|
+
lines.push(`\`\`\``);
|
|
129
|
+
lines.push(`/plugin install <name>@claude-code-plugins`);
|
|
130
|
+
lines.push(`\`\`\``);
|
|
131
|
+
lines.push(`Official plugins: agent-sdk-dev, claude-opus-4-5-migration, code-review, commit-commands,`);
|
|
132
|
+
lines.push(`explanatory-output-style, feature-dev, frontend-design, hookify, learning-output-style,`);
|
|
133
|
+
lines.push(`plugin-dev, pr-review-toolkit, ralph-wiggum, security-guidance`);
|
|
134
|
+
lines.push(``);
|
|
135
|
+
lines.push(`---`);
|
|
136
|
+
lines.push(``);
|
|
137
|
+
lines.push(`### STEP 2 — Search community catalog (jeremylongshore — 416 plugins)`);
|
|
138
|
+
lines.push(``);
|
|
139
|
+
lines.push(`NOTE: /plugin marketplace add FAILS for this repo (one entry has broken schema).`);
|
|
140
|
+
lines.push(`Use the DIRECT FETCH approach below instead — no marketplace add needed.`);
|
|
141
|
+
lines.push(``);
|
|
142
|
+
lines.push(`**2a. Fetch catalog and find matching plugin:**`);
|
|
143
|
+
lines.push(`\`\`\`bash`);
|
|
144
|
+
lines.push(`curl -s "${MARKETPLACE_CATALOG_URL}" \\`);
|
|
145
|
+
lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));const q='${categoryFilter}';const r=d.plugins.filter(p=>(!q||p.category.includes(q))&&p.name&&p.source).slice(0,5).map(p=>({name:p.name,source:p.source,desc:p.description}));console.log(JSON.stringify(r,null,2));"`);
|
|
146
|
+
lines.push(`\`\`\``);
|
|
147
|
+
lines.push(``);
|
|
148
|
+
lines.push(`**2b. Pick the best match — get its source path (e.g. \`./plugins/productivity/my-skill\`)**`);
|
|
149
|
+
lines.push(``);
|
|
150
|
+
lines.push(`**2c. List the skills inside that plugin:**`);
|
|
151
|
+
lines.push(`\`\`\`bash`);
|
|
152
|
+
lines.push(`# Replace PLUGIN_SOURCE_PATH with value from step 2b (e.g. plugins/productivity/my-skill)`);
|
|
153
|
+
lines.push(`PLUGIN_SOURCE_PATH="plugins/productivity/my-skill"`);
|
|
154
|
+
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('/dev/stdin','utf8'));console.log(a.map(x=>x.name).join('\\n'));"`);
|
|
156
|
+
lines.push(`\`\`\``);
|
|
157
|
+
lines.push(``);
|
|
158
|
+
lines.push(`**2d. For each skill listed, download and install it:**`);
|
|
159
|
+
lines.push(`\`\`\`bash`);
|
|
160
|
+
lines.push(`# Replace PLUGIN_SOURCE_PATH and SKILL_NAME with actual values`);
|
|
161
|
+
lines.push(`PLUGIN_SOURCE_PATH="plugins/productivity/my-skill"`);
|
|
162
|
+
lines.push(`SKILL_NAME="skill-directory-name"`);
|
|
163
|
+
lines.push(`mkdir -p ".claude/skills/\${SKILL_NAME}"`);
|
|
164
|
+
lines.push(`curl -s "https://raw.githubusercontent.com/${MARKETPLACE_REPO}/main/\${PLUGIN_SOURCE_PATH}/skills/\${SKILL_NAME}/SKILL.md" \\`);
|
|
165
|
+
lines.push(` -o ".claude/skills/\${SKILL_NAME}/SKILL.md"`);
|
|
166
|
+
lines.push(`echo "Installed: .claude/skills/\${SKILL_NAME}/SKILL.md"`);
|
|
167
|
+
lines.push(`\`\`\``);
|
|
168
|
+
lines.push(``);
|
|
169
|
+
lines.push(`**On Windows, replace curl with:**`);
|
|
170
|
+
lines.push(`\`\`\`powershell`);
|
|
171
|
+
lines.push(`$url = "https://raw.githubusercontent.com/${MARKETPLACE_REPO}/main/$PLUGIN_SOURCE_PATH/skills/$SKILL_NAME/SKILL.md"`);
|
|
172
|
+
lines.push(`New-Item -ItemType Directory -Force ".claude/skills/$SKILL_NAME" | Out-Null`);
|
|
173
|
+
lines.push(`Invoke-WebRequest $url -OutFile ".claude/skills/$SKILL_NAME/SKILL.md"`);
|
|
174
|
+
lines.push(`\`\`\``);
|
|
175
|
+
lines.push(``);
|
|
176
|
+
lines.push(`---`);
|
|
177
|
+
lines.push(``);
|
|
178
|
+
lines.push(`### STEP 3 — Search additional sources`);
|
|
179
|
+
lines.push(``);
|
|
180
|
+
for (const source of ADDITIONAL_MARKETPLACE_SOURCES) {
|
|
181
|
+
lines.push(`**${source.name}** — ${source.description}`);
|
|
182
|
+
if ("catalogUrl" in source && source.catalogUrl) {
|
|
183
|
+
lines.push(`Catalog: ${source.catalogUrl}`);
|
|
98
184
|
}
|
|
99
|
-
|
|
100
|
-
lines.push(
|
|
185
|
+
if (source.note) {
|
|
186
|
+
lines.push(`Note: ${source.note}`);
|
|
101
187
|
}
|
|
102
188
|
lines.push(``);
|
|
103
189
|
}
|
|
104
|
-
lines.push(
|
|
105
|
-
lines.push(``);
|
|
106
|
-
lines.push(
|
|
107
|
-
lines.push(
|
|
108
|
-
lines.push(
|
|
109
|
-
lines.push("
|
|
110
|
-
lines.push(
|
|
111
|
-
lines.push(
|
|
112
|
-
lines.push(
|
|
113
|
-
lines.push(
|
|
114
|
-
lines.push(`
|
|
115
|
-
lines.push(
|
|
116
|
-
lines.push(
|
|
117
|
-
lines.push(
|
|
118
|
-
lines.push(
|
|
119
|
-
lines.push(
|
|
120
|
-
lines.push(
|
|
121
|
-
lines.push(
|
|
122
|
-
lines.push(
|
|
123
|
-
lines.push(
|
|
124
|
-
lines.push(
|
|
125
|
-
lines.push(
|
|
126
|
-
lines.push(
|
|
127
|
-
lines.push(``);
|
|
128
|
-
lines.push(`### Suggestion format:`);
|
|
129
|
-
lines.push("```");
|
|
130
|
-
lines.push(`📦 Suggested from [claude-code-plugins-plus-skills]`);
|
|
131
|
-
lines.push(``);
|
|
132
|
-
lines.push(` [plugin/skill name]`);
|
|
133
|
-
lines.push(` Category : [category]`);
|
|
134
|
-
lines.push(` What it does: [one sentence from catalog description]`);
|
|
135
|
-
lines.push(` Requires : [nothing / MCP: name / Paid API: service name]`);
|
|
136
|
-
lines.push(``);
|
|
137
|
-
lines.push(` Install:`);
|
|
138
|
-
lines.push(` /plugin marketplace add ${MARKETPLACE_REPO}`);
|
|
139
|
-
lines.push(` /plugin install [name]@claude-code-plugins-plus`);
|
|
140
|
-
lines.push("```");
|
|
141
|
-
lines.push(``);
|
|
142
|
-
// Official Anthropic marketplace plugins (always available)
|
|
143
|
-
lines.push(`### Official Anthropic plugins (always available, no marketplace add needed):`);
|
|
144
|
-
lines.push(`These are installed via \`/plugin install <name>@claude-plugins-official\`:`);
|
|
145
|
-
lines.push(`- **github** — GitHub integration (PRs, issues, repos)`);
|
|
146
|
-
lines.push(`- **gitlab** — GitLab integration`);
|
|
147
|
-
lines.push(`- **slack** — Slack messaging`);
|
|
148
|
-
lines.push(`- **linear** — Linear project management`);
|
|
149
|
-
lines.push(`- **notion** — Notion workspace`);
|
|
150
|
-
lines.push(`- **sentry** — Error monitoring`);
|
|
151
|
-
lines.push(`- **figma** — Design files`);
|
|
152
|
-
lines.push(`- **vercel** — Deployment`);
|
|
153
|
-
lines.push(`- **firebase** — Firebase services`);
|
|
154
|
-
lines.push(`- **supabase** — Supabase backend`);
|
|
155
|
-
lines.push(`- **atlassian** — Jira/Confluence`);
|
|
156
|
-
lines.push(`- **asana** — Project management`);
|
|
190
|
+
lines.push(`---`);
|
|
191
|
+
lines.push(``);
|
|
192
|
+
lines.push(`### STEP 4 — If no match found in any source, create a custom skill`);
|
|
193
|
+
lines.push(``);
|
|
194
|
+
lines.push(`\`\`\`bash`);
|
|
195
|
+
lines.push(`mkdir -p ".claude/skills/${input.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')}"`);
|
|
196
|
+
lines.push(`\`\`\``);
|
|
197
|
+
lines.push(`Then create SKILL.md with:`);
|
|
198
|
+
lines.push(`\`\`\`yaml`);
|
|
199
|
+
lines.push(`---`);
|
|
200
|
+
lines.push(`name: ${input.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')}`);
|
|
201
|
+
lines.push(`description: ${input}`);
|
|
202
|
+
lines.push(`---`);
|
|
203
|
+
lines.push(``);
|
|
204
|
+
lines.push(`[Skill instructions here]`);
|
|
205
|
+
lines.push(`\`\`\``);
|
|
206
|
+
lines.push(``);
|
|
207
|
+
lines.push(`---`);
|
|
208
|
+
lines.push(``);
|
|
209
|
+
lines.push(`### Install result format`);
|
|
210
|
+
lines.push(`After installing, confirm:`);
|
|
211
|
+
lines.push(`✅ Installed: .claude/skills/<name>/SKILL.md — [one line: what it does]`);
|
|
212
|
+
lines.push(`⏭ No match: searched [categories], created custom skill instead`);
|
|
157
213
|
lines.push(``);
|
|
158
214
|
return lines.join("\n");
|
|
159
215
|
}
|
package/dist/snapshot.d.ts
CHANGED
|
@@ -19,9 +19,11 @@ 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[];
|
|
26
|
+
restoredTo?: string;
|
|
25
27
|
}
|
|
26
28
|
export interface SnapshotData {
|
|
27
29
|
files: Record<string, string>;
|
|
@@ -39,15 +41,34 @@ export declare function createSnapshot(cwd: string, command: string, changedFile
|
|
|
39
41
|
input?: string;
|
|
40
42
|
summary?: string;
|
|
41
43
|
}): SnapshotNode;
|
|
44
|
+
/**
|
|
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.
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildCumulativeState(cwd: string, nodeId: string, timeline: SnapshotTimeline): Record<string, string> | null;
|
|
42
55
|
/**
|
|
43
56
|
* Restore files from a snapshot node.
|
|
44
|
-
*
|
|
57
|
+
* Accumulates all file states from node 0 through the target node,
|
|
58
|
+
* then writes them to disk. This reconstructs the full project state
|
|
59
|
+
* at that point in time, not just the delta.
|
|
45
60
|
* Does NOT delete other nodes — all nodes are preserved (like git).
|
|
46
61
|
*/
|
|
47
|
-
export declare function restoreSnapshot(cwd: string, nodeId: string): {
|
|
62
|
+
export declare function restoreSnapshot(cwd: string, nodeId: string, timeline?: SnapshotTimeline): {
|
|
48
63
|
restored: string[];
|
|
49
64
|
failed: string[];
|
|
65
|
+
deleted: string[];
|
|
66
|
+
stale: string[];
|
|
50
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* Record the last restored node in the timeline (for display purposes).
|
|
70
|
+
*/
|
|
71
|
+
export declare function updateRestoredNode(cwd: string, nodeId: string): void;
|
|
51
72
|
/**
|
|
52
73
|
* Compare two snapshot nodes. Returns files that differ between them.
|
|
53
74
|
*/
|
|
@@ -62,10 +83,11 @@ export declare function compareSnapshots(cwd: string, nodeIdA: string, nodeIdB:
|
|
|
62
83
|
identical: string[];
|
|
63
84
|
};
|
|
64
85
|
/**
|
|
65
|
-
* Collect
|
|
66
|
-
*
|
|
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.
|
|
67
89
|
*/
|
|
68
|
-
export declare function collectFilesForSnapshot(cwd: string,
|
|
90
|
+
export declare function collectFilesForSnapshot(cwd: string, _trackedPaths: string[]): Array<{
|
|
69
91
|
path: string;
|
|
70
92
|
content: string;
|
|
71
93
|
}>;
|