claude-setup 1.0.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/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/builder.d.ts +24 -0
- package/dist/builder.js +259 -0
- package/dist/collect.d.ts +13 -0
- package/dist/collect.js +266 -0
- package/dist/commands/add.d.ts +1 -0
- package/dist/commands/add.js +49 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +42 -0
- package/dist/commands/remove.d.ts +1 -0
- package/dist/commands/remove.js +33 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +23 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +63 -0
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +72 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +24 -0
- package/dist/manifest.d.ts +20 -0
- package/dist/manifest.js +84 -0
- package/dist/state.d.ts +24 -0
- package/dist/state.js +55 -0
- package/package.json +49 -0
- package/templates/add.md +64 -0
- package/templates/init-empty.md +53 -0
- package/templates/init.md +132 -0
- package/templates/remove.md +53 -0
- package/templates/sync.md +70 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
2
|
+
import { createInterface } from "readline";
|
|
3
|
+
import { collectProjectFiles } from "../collect.js";
|
|
4
|
+
import { readState } from "../state.js";
|
|
5
|
+
import { updateManifest } from "../manifest.js";
|
|
6
|
+
import { buildAddCommand } from "../builder.js";
|
|
7
|
+
function ensureDir(dir) {
|
|
8
|
+
if (!existsSync(dir))
|
|
9
|
+
mkdirSync(dir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
async function promptFreeText(question) {
|
|
12
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
rl.question(question + " ", (answer) => {
|
|
15
|
+
rl.close();
|
|
16
|
+
resolve(answer.trim());
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
// Conservative — only redirect when unambiguously single-file
|
|
21
|
+
function isSingleFileOperation(input) {
|
|
22
|
+
return (/to \.mcp\.json\s*$/i.test(input) ||
|
|
23
|
+
/to settings\.json\s*$/i.test(input) ||
|
|
24
|
+
/to claude\.md\s*$/i.test(input));
|
|
25
|
+
}
|
|
26
|
+
export async function runAdd() {
|
|
27
|
+
const userInput = await promptFreeText("What do you want to add to your Claude Code setup?");
|
|
28
|
+
if (!userInput) {
|
|
29
|
+
console.log("No input provided.");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (isSingleFileOperation(userInput)) {
|
|
33
|
+
console.log(`
|
|
34
|
+
For single changes, Claude Code is faster:
|
|
35
|
+
Just tell it: "${userInput}"
|
|
36
|
+
|
|
37
|
+
Use claude-setup add when the change spans multiple files —
|
|
38
|
+
capabilities that need documentation, MCP servers, skills, and hooks together.
|
|
39
|
+
`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const state = await readState();
|
|
43
|
+
const collected = await collectProjectFiles();
|
|
44
|
+
const content = buildAddCommand(userInput, collected, state);
|
|
45
|
+
ensureDir(".claude/commands");
|
|
46
|
+
writeFileSync(".claude/commands/stack-add.md", content, "utf8");
|
|
47
|
+
await updateManifest("add", collected, { input: userInput });
|
|
48
|
+
console.log(`\n✅ Ready. Open Claude Code and run:\n /stack-add\n`);
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { runDoctor } from "../doctor.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { runDoctor } from "../doctor.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runInit(): Promise<void>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { collectProjectFiles, isEmptyProject } from "../collect.js";
|
|
4
|
+
import { readState } from "../state.js";
|
|
5
|
+
import { updateManifest } from "../manifest.js";
|
|
6
|
+
import { buildEmptyProjectCommand, buildAtomicSteps, buildOrchestratorCommand, } from "../builder.js";
|
|
7
|
+
function ensureDir(dir) {
|
|
8
|
+
if (!existsSync(dir))
|
|
9
|
+
mkdirSync(dir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
export async function runInit() {
|
|
12
|
+
const state = await readState();
|
|
13
|
+
const collected = await collectProjectFiles();
|
|
14
|
+
ensureDir(".claude/commands");
|
|
15
|
+
if (isEmptyProject(collected)) {
|
|
16
|
+
const content = buildEmptyProjectCommand();
|
|
17
|
+
writeFileSync(".claude/commands/stack-init.md", content, "utf8");
|
|
18
|
+
await updateManifest("init", collected);
|
|
19
|
+
console.log(`
|
|
20
|
+
✅ New project detected.
|
|
21
|
+
|
|
22
|
+
Open Claude Code and run:
|
|
23
|
+
/stack-init
|
|
24
|
+
|
|
25
|
+
Claude Code will ask 3 questions, then set up your environment.
|
|
26
|
+
`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Standard init — 6 atomic steps + orchestrator
|
|
30
|
+
const steps = buildAtomicSteps(collected, state);
|
|
31
|
+
for (const step of steps) {
|
|
32
|
+
writeFileSync(join(".claude/commands", step.filename), step.content, "utf8");
|
|
33
|
+
}
|
|
34
|
+
writeFileSync(".claude/commands/stack-init.md", buildOrchestratorCommand(steps), "utf8");
|
|
35
|
+
await updateManifest("init", collected);
|
|
36
|
+
console.log(`
|
|
37
|
+
✅ Ready. Open Claude Code and run:
|
|
38
|
+
/stack-init
|
|
39
|
+
|
|
40
|
+
Runs 6 atomic steps. If one fails, re-run only that step.
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runRemove(): Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
2
|
+
import { createInterface } from "readline";
|
|
3
|
+
import { collectProjectFiles } from "../collect.js";
|
|
4
|
+
import { readState } from "../state.js";
|
|
5
|
+
import { updateManifest } from "../manifest.js";
|
|
6
|
+
import { buildRemoveCommand } from "../builder.js";
|
|
7
|
+
function ensureDir(dir) {
|
|
8
|
+
if (!existsSync(dir))
|
|
9
|
+
mkdirSync(dir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
async function promptFreeText(question) {
|
|
12
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
rl.question(question + " ", (answer) => {
|
|
15
|
+
rl.close();
|
|
16
|
+
resolve(answer.trim());
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export async function runRemove() {
|
|
21
|
+
const userInput = await promptFreeText("What do you want to remove from your Claude Code setup?");
|
|
22
|
+
if (!userInput) {
|
|
23
|
+
console.log("No input provided.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const state = await readState();
|
|
27
|
+
const collected = await collectProjectFiles();
|
|
28
|
+
const content = buildRemoveCommand(userInput, state);
|
|
29
|
+
ensureDir(".claude/commands");
|
|
30
|
+
writeFileSync(".claude/commands/stack-remove.md", content, "utf8");
|
|
31
|
+
await updateManifest("remove", collected, { input: userInput });
|
|
32
|
+
console.log(`\n✅ Ready. Open Claude Code and run:\n /stack-remove\n`);
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runStatus(): Promise<void>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readManifest } from "../manifest.js";
|
|
2
|
+
import { readState } from "../state.js";
|
|
3
|
+
export async function runStatus() {
|
|
4
|
+
const manifest = await readManifest();
|
|
5
|
+
const state = await readState();
|
|
6
|
+
if (!manifest) {
|
|
7
|
+
console.log("No setup found.\n Run: npx claude-setup init");
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const last = manifest.runs.at(-1);
|
|
11
|
+
console.log(`Last: ${last.command} at ${last.at} (v${last.claudeStackVersion})\n`);
|
|
12
|
+
console.log(`CLAUDE.md ${state.claudeMd.exists ? "✅" : "❌ missing"}`);
|
|
13
|
+
console.log(`.mcp.json ${state.mcpJson.exists ? "✅" : "❌ missing"}`);
|
|
14
|
+
console.log(`settings.json ${state.settings.exists ? "✅" : "❌ missing"}`);
|
|
15
|
+
console.log(`Skills ${state.skills.length || "none"}`);
|
|
16
|
+
console.log(`Workflows ${state.workflows.length || "none"}`);
|
|
17
|
+
console.log(`\nHistory (last 5):`);
|
|
18
|
+
for (const r of manifest.runs.slice(-5)) {
|
|
19
|
+
console.log(` ${r.at} ${r.command}${r.input ? ` — "${r.input}"` : ""}`);
|
|
20
|
+
}
|
|
21
|
+
console.log("\n npx claude-setup sync — update after changes");
|
|
22
|
+
console.log(" npx claude-setup doctor — validate environment");
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runSync(): Promise<void>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
2
|
+
import { collectProjectFiles } from "../collect.js";
|
|
3
|
+
import { readState } from "../state.js";
|
|
4
|
+
import { readManifest, sha256, updateManifest } from "../manifest.js";
|
|
5
|
+
import { buildSyncCommand } from "../builder.js";
|
|
6
|
+
function ensureDir(dir) {
|
|
7
|
+
if (!existsSync(dir))
|
|
8
|
+
mkdirSync(dir, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
function truncate(content, maxChars) {
|
|
11
|
+
if (content.length <= maxChars)
|
|
12
|
+
return content;
|
|
13
|
+
return content.slice(0, maxChars) + "\n[... truncated for sync diff]";
|
|
14
|
+
}
|
|
15
|
+
function computeDiff(snapshot, collected) {
|
|
16
|
+
const current = {
|
|
17
|
+
...collected.configs,
|
|
18
|
+
...Object.fromEntries(collected.source.map(f => [f.path, f.content])),
|
|
19
|
+
};
|
|
20
|
+
const added = [];
|
|
21
|
+
const changed = [];
|
|
22
|
+
const deleted = [];
|
|
23
|
+
for (const [path, content] of Object.entries(current)) {
|
|
24
|
+
const hash = sha256(content);
|
|
25
|
+
if (!snapshot[path]) {
|
|
26
|
+
added.push({ path, content: truncate(content, 2000) });
|
|
27
|
+
}
|
|
28
|
+
else if (snapshot[path] !== hash) {
|
|
29
|
+
changed.push({ path, current: truncate(content, 2000) });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const path of Object.keys(snapshot)) {
|
|
33
|
+
if (!current[path])
|
|
34
|
+
deleted.push(path);
|
|
35
|
+
}
|
|
36
|
+
return { added, changed, deleted };
|
|
37
|
+
}
|
|
38
|
+
export async function runSync() {
|
|
39
|
+
const manifest = await readManifest();
|
|
40
|
+
if (!manifest?.runs.length) {
|
|
41
|
+
console.log("No previous run found. Start with: npx claude-setup init");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const lastRun = manifest.runs.at(-1);
|
|
45
|
+
const collected = await collectProjectFiles();
|
|
46
|
+
const diff = computeDiff(lastRun.snapshot, collected);
|
|
47
|
+
if (!diff.added.length && !diff.changed.length && !diff.deleted.length) {
|
|
48
|
+
console.log(`✅ No changes since ${lastRun.at}. Setup is current.`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const state = await readState();
|
|
52
|
+
const content = buildSyncCommand(diff, collected, state);
|
|
53
|
+
ensureDir(".claude/commands");
|
|
54
|
+
writeFileSync(".claude/commands/stack-sync.md", content, "utf8");
|
|
55
|
+
await updateManifest("sync", collected);
|
|
56
|
+
console.log(`
|
|
57
|
+
Changes since ${lastRun.at}:
|
|
58
|
+
+${diff.added.length} added ~${diff.changed.length} modified -${diff.deleted.length} deleted
|
|
59
|
+
|
|
60
|
+
✅ Ready. Open Claude Code and run:
|
|
61
|
+
/stack-sync
|
|
62
|
+
`);
|
|
63
|
+
}
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDoctor(): Promise<void>;
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { readManifest } from "./manifest.js";
|
|
4
|
+
import { readState } from "./state.js";
|
|
5
|
+
function tryExec(cmd) {
|
|
6
|
+
try {
|
|
7
|
+
return execSync(cmd, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function readIfExists(filePath) {
|
|
14
|
+
if (!existsSync(filePath))
|
|
15
|
+
return null;
|
|
16
|
+
try {
|
|
17
|
+
return readFileSync(filePath, "utf8");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function line(icon, label, detail) {
|
|
24
|
+
console.log(` ${icon} ${label}${detail ? ` — ${detail}` : ""}`);
|
|
25
|
+
}
|
|
26
|
+
export async function runDoctor() {
|
|
27
|
+
const manifest = await readManifest();
|
|
28
|
+
const state = await readState();
|
|
29
|
+
console.log("claude-setup doctor\n");
|
|
30
|
+
// Claude Code installed?
|
|
31
|
+
const cv = tryExec("claude --version");
|
|
32
|
+
line(cv ? "✅" : "❌", "Claude Code", cv?.trim() ?? "not found");
|
|
33
|
+
// Manifest?
|
|
34
|
+
const lastRun = manifest?.runs.at(-1);
|
|
35
|
+
line(manifest ? "✅" : "⚠️ ", ".claude/claude-setup.json", manifest ? `last: ${lastRun?.command} at ${lastRun?.at}` : "not found — run: npx claude-setup init");
|
|
36
|
+
// Files from last run still on disk?
|
|
37
|
+
if (lastRun?.filesRead.length) {
|
|
38
|
+
console.log("\nFiles from last run (sample):");
|
|
39
|
+
for (const f of lastRun.filesRead.slice(0, 8)) {
|
|
40
|
+
line(existsSync(f) ? "✅" : "⚠️ ", f, existsSync(f) ? "" : "not found on disk");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Env vars in .mcp.json present in env template?
|
|
44
|
+
if (state.mcpJson.content) {
|
|
45
|
+
const refs = [...state.mcpJson.content.matchAll(/\$\{?([A-Z_][A-Z0-9_]+)\}?/g)]
|
|
46
|
+
.map(m => m[1]);
|
|
47
|
+
const unique = [...new Set(refs)];
|
|
48
|
+
if (unique.length) {
|
|
49
|
+
const template = readIfExists(".env.example") ?? readIfExists(".env.sample") ?? "";
|
|
50
|
+
console.log("\nMCP environment variables:");
|
|
51
|
+
for (const v of unique) {
|
|
52
|
+
line(template.includes(v) ? "✅" : "⚠️ ", v, template.includes(v) ? "found in env template" : "missing from .env.example");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Workflow secrets
|
|
57
|
+
if (state.workflows.length) {
|
|
58
|
+
const secrets = new Set();
|
|
59
|
+
for (const wf of state.workflows) {
|
|
60
|
+
const content = readIfExists(wf) ?? "";
|
|
61
|
+
for (const m of content.matchAll(/\$\{\{\s*secrets\.([A-Z_]+)\s*\}\}/g)) {
|
|
62
|
+
secrets.add(m[1]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (secrets.size) {
|
|
66
|
+
console.log("\nWorkflow secrets (add to GitHub Settings → Secrets):");
|
|
67
|
+
for (const s of secrets)
|
|
68
|
+
console.log(` ⚠️ ${s}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
console.log("\n✅ Done.");
|
|
72
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
import { runInit } from "./commands/init.js";
|
|
5
|
+
import { runAdd } from "./commands/add.js";
|
|
6
|
+
import { runSync } from "./commands/sync.js";
|
|
7
|
+
import { runStatus } from "./commands/status.js";
|
|
8
|
+
import { runDoctor } from "./commands/doctor.js";
|
|
9
|
+
import { runRemove } from "./commands/remove.js";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const pkg = require("../package.json");
|
|
12
|
+
const program = new Command();
|
|
13
|
+
program
|
|
14
|
+
.name("claude-setup")
|
|
15
|
+
.description("Setup layer for Claude Code")
|
|
16
|
+
.version(pkg.version);
|
|
17
|
+
program.command("init").description("Full project setup — new or existing").action(runInit);
|
|
18
|
+
program.command("add").description("Add a multi-file capability").action(runAdd);
|
|
19
|
+
program.command("sync").description("Update setup after project changes").action(runSync);
|
|
20
|
+
program.command("status").description("Show current setup (instant)").action(runStatus);
|
|
21
|
+
program.command("doctor").description("Validate environment").action(runDoctor);
|
|
22
|
+
program.command("remove").description("Remove a capability cleanly").action(runRemove);
|
|
23
|
+
program.action(runInit);
|
|
24
|
+
program.parse();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CollectedFiles } from "./collect.js";
|
|
2
|
+
export interface ManifestRun {
|
|
3
|
+
command: string;
|
|
4
|
+
at: string;
|
|
5
|
+
claudeStackVersion: string;
|
|
6
|
+
input?: string;
|
|
7
|
+
filesRead: string[];
|
|
8
|
+
snapshot: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export interface Manifest {
|
|
11
|
+
version: string;
|
|
12
|
+
created: string;
|
|
13
|
+
runs: ManifestRun[];
|
|
14
|
+
}
|
|
15
|
+
export declare function sha256(content: string): string;
|
|
16
|
+
export declare function readManifest(cwd?: string): Promise<Manifest | null>;
|
|
17
|
+
export declare function updateManifest(command: string, collected: CollectedFiles, opts?: {
|
|
18
|
+
input?: string;
|
|
19
|
+
cwd?: string;
|
|
20
|
+
}): Promise<void>;
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { createHash } from "crypto";
|
|
4
|
+
const MANIFEST_FILENAME = ".claude/claude-setup.json";
|
|
5
|
+
export function sha256(content) {
|
|
6
|
+
return createHash("sha256").update(content).digest("hex");
|
|
7
|
+
}
|
|
8
|
+
export async function readManifest(cwd = process.cwd()) {
|
|
9
|
+
const filePath = join(cwd, MANIFEST_FILENAME);
|
|
10
|
+
if (!existsSync(filePath))
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
const raw = readFileSync(filePath, "utf8");
|
|
14
|
+
return JSON.parse(raw);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Corrupted — back it up and start fresh
|
|
18
|
+
const backupPath = filePath + ".bak";
|
|
19
|
+
try {
|
|
20
|
+
renameSync(filePath, backupPath);
|
|
21
|
+
console.warn(`⚠️ Manifest was corrupted. Backed up to ${backupPath}`);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
console.warn(`⚠️ Manifest was corrupted and could not be backed up.`);
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function updateManifest(command, collected, opts = {}) {
|
|
30
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
31
|
+
const filePath = join(cwd, MANIFEST_FILENAME);
|
|
32
|
+
// Read version from package.json
|
|
33
|
+
let version = "0.0.0";
|
|
34
|
+
try {
|
|
35
|
+
const pkgPath = join(dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1")), "..", "package.json");
|
|
36
|
+
if (existsSync(pkgPath)) {
|
|
37
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
38
|
+
version = pkg.version ?? "0.0.0";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch { /* use default */ }
|
|
42
|
+
// Build snapshot — hash of every file we read
|
|
43
|
+
const snapshot = {};
|
|
44
|
+
for (const [path, content] of Object.entries(collected.configs)) {
|
|
45
|
+
snapshot[path] = sha256(content);
|
|
46
|
+
}
|
|
47
|
+
for (const { path, content } of collected.source) {
|
|
48
|
+
snapshot[path] = sha256(content);
|
|
49
|
+
}
|
|
50
|
+
const filesRead = [
|
|
51
|
+
...Object.keys(collected.configs),
|
|
52
|
+
...collected.source.map(s => s.path),
|
|
53
|
+
];
|
|
54
|
+
const run = {
|
|
55
|
+
command,
|
|
56
|
+
at: new Date().toISOString(),
|
|
57
|
+
claudeStackVersion: version,
|
|
58
|
+
...(opts.input ? { input: opts.input } : {}),
|
|
59
|
+
filesRead,
|
|
60
|
+
snapshot,
|
|
61
|
+
};
|
|
62
|
+
let manifest;
|
|
63
|
+
const existing = await readManifest(cwd);
|
|
64
|
+
if (existing) {
|
|
65
|
+
existing.runs.push(run);
|
|
66
|
+
manifest = existing;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
manifest = {
|
|
70
|
+
version: "1",
|
|
71
|
+
created: new Date().toISOString(),
|
|
72
|
+
runs: [run],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const dir = dirname(filePath);
|
|
77
|
+
if (!existsSync(dir))
|
|
78
|
+
mkdirSync(dir, { recursive: true });
|
|
79
|
+
writeFileSync(filePath, JSON.stringify(manifest, null, 2), "utf8");
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.warn(`⚠️ Could not write manifest to ${filePath}: ${err}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Manifest } from "./manifest.js";
|
|
2
|
+
export interface ExistingState {
|
|
3
|
+
claudeMd: {
|
|
4
|
+
exists: boolean;
|
|
5
|
+
content?: string;
|
|
6
|
+
};
|
|
7
|
+
mcpJson: {
|
|
8
|
+
exists: boolean;
|
|
9
|
+
content?: string;
|
|
10
|
+
};
|
|
11
|
+
settings: {
|
|
12
|
+
exists: boolean;
|
|
13
|
+
content?: string;
|
|
14
|
+
};
|
|
15
|
+
skills: string[];
|
|
16
|
+
commands: string[];
|
|
17
|
+
workflows: string[];
|
|
18
|
+
hasGithubDir: boolean;
|
|
19
|
+
hasDotClaude: boolean;
|
|
20
|
+
manifest: Manifest | null;
|
|
21
|
+
}
|
|
22
|
+
export declare function readState(cwd?: string): Promise<ExistingState>;
|
|
23
|
+
declare function readIfExists(filePath: string): string | null;
|
|
24
|
+
export { readIfExists };
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { glob } from "glob";
|
|
4
|
+
import { readManifest } from "./manifest.js";
|
|
5
|
+
export async function readState(cwd = process.cwd()) {
|
|
6
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
7
|
+
const mcpJsonPath = join(cwd, ".mcp.json");
|
|
8
|
+
const settingsPath = join(cwd, ".claude", "settings.json");
|
|
9
|
+
const claudeMd = readIfExists(claudeMdPath);
|
|
10
|
+
const mcpJson = readIfExists(mcpJsonPath);
|
|
11
|
+
const settings = readIfExists(settingsPath);
|
|
12
|
+
let skills = [];
|
|
13
|
+
try {
|
|
14
|
+
skills = await glob(".claude/skills/*/SKILL.md", { cwd, posix: true });
|
|
15
|
+
}
|
|
16
|
+
catch { /* no skills */ }
|
|
17
|
+
let commands = [];
|
|
18
|
+
try {
|
|
19
|
+
const allCmds = await glob(".claude/commands/*.md", { cwd, posix: true });
|
|
20
|
+
commands = allCmds.filter(c => !c.includes("stack-"));
|
|
21
|
+
}
|
|
22
|
+
catch { /* no commands */ }
|
|
23
|
+
let workflows = [];
|
|
24
|
+
try {
|
|
25
|
+
workflows = await glob(".github/workflows/*.yml", { cwd, posix: true });
|
|
26
|
+
const yamlWorkflows = await glob(".github/workflows/*.yaml", { cwd, posix: true });
|
|
27
|
+
workflows = [...workflows, ...yamlWorkflows];
|
|
28
|
+
}
|
|
29
|
+
catch { /* no workflows */ }
|
|
30
|
+
const hasGithubDir = existsSync(join(cwd, ".github"));
|
|
31
|
+
const hasDotClaude = existsSync(join(cwd, ".claude"));
|
|
32
|
+
const manifest = await readManifest(cwd);
|
|
33
|
+
return {
|
|
34
|
+
claudeMd: { exists: claudeMd !== null, content: claudeMd ?? undefined },
|
|
35
|
+
mcpJson: { exists: mcpJson !== null, content: mcpJson ?? undefined },
|
|
36
|
+
settings: { exists: settings !== null, content: settings ?? undefined },
|
|
37
|
+
skills,
|
|
38
|
+
commands,
|
|
39
|
+
workflows,
|
|
40
|
+
hasGithubDir,
|
|
41
|
+
hasDotClaude,
|
|
42
|
+
manifest,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function readIfExists(filePath) {
|
|
46
|
+
if (!existsSync(filePath))
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
return readFileSync(filePath, "utf8");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export { readIfExists };
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-setup",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Setup layer for Claude Code — reads your project, writes command files, Claude Code does the rest",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-setup": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"prepublishOnly": "tsc"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"templates"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"claude",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"cli",
|
|
22
|
+
"developer-tools",
|
|
23
|
+
"ai",
|
|
24
|
+
"anthropic",
|
|
25
|
+
"automation",
|
|
26
|
+
"scaffolding",
|
|
27
|
+
"project-setup",
|
|
28
|
+
"mcp",
|
|
29
|
+
"npx"
|
|
30
|
+
],
|
|
31
|
+
"author": "AbdoKnbGit",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/AbdoKnbGit/claude-setup.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/AbdoKnbGit/claude-setup#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/AbdoKnbGit/claude-setup/issues"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"commander": "^12.1.0",
|
|
43
|
+
"glob": "^11.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^22.0.0",
|
|
47
|
+
"typescript": "^5.6.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/templates/add.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<!-- Generated by claude-setup add — {{DATE}} -->
|
|
2
|
+
<!-- Run /stack-add in Claude Code -->
|
|
3
|
+
|
|
4
|
+
The developer wants to add this to their Claude Code setup:
|
|
5
|
+
"{{USER_INPUT}}"
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Project context
|
|
10
|
+
|
|
11
|
+
{{CONFIG_FILES}}
|
|
12
|
+
|
|
13
|
+
{{SOURCE_FILES}}
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Current setup — read before writing anything
|
|
18
|
+
|
|
19
|
+
{{#if HAS_CLAUDE_MD}}
|
|
20
|
+
### CLAUDE.md
|
|
21
|
+
{{CLAUDE_MD_CONTENT}}
|
|
22
|
+
{{/if}}
|
|
23
|
+
|
|
24
|
+
{{#if HAS_MCP_JSON}}
|
|
25
|
+
### .mcp.json
|
|
26
|
+
{{MCP_JSON_CONTENT}}
|
|
27
|
+
{{/if}}
|
|
28
|
+
|
|
29
|
+
{{#if HAS_SETTINGS}}
|
|
30
|
+
### .claude/settings.json
|
|
31
|
+
{{SETTINGS_CONTENT}}
|
|
32
|
+
{{/if}}
|
|
33
|
+
|
|
34
|
+
Skills installed: {{SKILLS_LIST}}
|
|
35
|
+
Commands installed: {{COMMANDS_LIST}}
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Your job
|
|
40
|
+
|
|
41
|
+
Figure out which files need to change to fulfill the request.
|
|
42
|
+
Adding a capability to Claude Code is rarely one file — it usually means touching
|
|
43
|
+
CLAUDE.md, possibly an MCP entry, possibly a skill, possibly a hook.
|
|
44
|
+
|
|
45
|
+
For every file you touch:
|
|
46
|
+
- Read its current content above first
|
|
47
|
+
- Merge and append only
|
|
48
|
+
- Do not duplicate what already exists
|
|
49
|
+
- Extend existing files rather than creating parallel ones
|
|
50
|
+
|
|
51
|
+
If the request mentions something not evidenced in the project files: say so and ask
|
|
52
|
+
whether they want to add it generically or need to add the underlying service first.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Output format — strict
|
|
57
|
+
|
|
58
|
+
Updated:
|
|
59
|
+
✅ [path] — [what changed and why]
|
|
60
|
+
|
|
61
|
+
Skipped:
|
|
62
|
+
⏭ [path] — [why not needed for this request]
|
|
63
|
+
|
|
64
|
+
One line per file. Nothing else.
|