joycraft 0.3.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/README.md +120 -0
- package/dist/chunk-IJ7SLXOI.js +1088 -0
- package/dist/chunk-IJ7SLXOI.js.map +1 -0
- package/dist/cli.js +31 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +210 -0
- package/dist/index.js.map +1 -0
- package/dist/init-ZFFV4NO3.js +552 -0
- package/dist/init-ZFFV4NO3.js.map +1 -0
- package/dist/upgrade-FHBKUDAL.js +141 -0
- package/dist/upgrade-FHBKUDAL.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
SKILLS,
|
|
4
|
+
TEMPLATES,
|
|
5
|
+
hashContent,
|
|
6
|
+
readVersion,
|
|
7
|
+
writeVersion
|
|
8
|
+
} from "./chunk-IJ7SLXOI.js";
|
|
9
|
+
|
|
10
|
+
// src/upgrade.ts
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
12
|
+
import { join, dirname, resolve } from "path";
|
|
13
|
+
import { createInterface } from "readline";
|
|
14
|
+
function getManagedFiles() {
|
|
15
|
+
const files = {};
|
|
16
|
+
for (const [name, content] of Object.entries(SKILLS)) {
|
|
17
|
+
const skillName = name.replace(/\.md$/, "");
|
|
18
|
+
files[join(".claude", "skills", skillName, "SKILL.md")] = content;
|
|
19
|
+
}
|
|
20
|
+
for (const [name, content] of Object.entries(TEMPLATES)) {
|
|
21
|
+
files[join("docs", "templates", name)] = content;
|
|
22
|
+
}
|
|
23
|
+
return files;
|
|
24
|
+
}
|
|
25
|
+
function countLines(content) {
|
|
26
|
+
return content.split("\n").length;
|
|
27
|
+
}
|
|
28
|
+
async function askUser(question) {
|
|
29
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
30
|
+
return new Promise((resolve2) => {
|
|
31
|
+
rl.question(`${question} [y/N] `, (answer) => {
|
|
32
|
+
rl.close();
|
|
33
|
+
resolve2(answer.trim().toLowerCase() === "y");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function upgrade(dir, opts) {
|
|
38
|
+
const targetDir = resolve(dir);
|
|
39
|
+
const versionInfo = readVersion(targetDir);
|
|
40
|
+
const hasSkill = existsSync(join(targetDir, ".claude", "skills", "tune", "SKILL.md")) || existsSync(join(targetDir, ".claude", "skills", "joy", "SKILL.md")) || existsSync(join(targetDir, ".claude", "skills", "joysmith", "SKILL.md"));
|
|
41
|
+
if (!versionInfo && !hasSkill) {
|
|
42
|
+
console.log("This project has not been initialized with Joycraft.");
|
|
43
|
+
console.log("Run `npx joycraft init` first.");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const pkgVersion = getPackageVersion();
|
|
47
|
+
const managedFiles = getManagedFiles();
|
|
48
|
+
const installedHashes = versionInfo?.files ?? {};
|
|
49
|
+
const changes = [];
|
|
50
|
+
let upToDate = 0;
|
|
51
|
+
for (const [relPath, newContent] of Object.entries(managedFiles)) {
|
|
52
|
+
const absPath = join(targetDir, relPath);
|
|
53
|
+
const newHash = hashContent(newContent);
|
|
54
|
+
if (!existsSync(absPath)) {
|
|
55
|
+
changes.push({ relativePath: relPath, absolutePath: absPath, newContent, kind: "new" });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const currentContent = readFileSync(absPath, "utf-8");
|
|
59
|
+
const currentHash = hashContent(currentContent);
|
|
60
|
+
if (currentHash === newHash) {
|
|
61
|
+
upToDate++;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const originalHash = installedHashes[relPath];
|
|
65
|
+
if (originalHash && currentHash === originalHash) {
|
|
66
|
+
changes.push({ relativePath: relPath, absolutePath: absPath, newContent, kind: "updated" });
|
|
67
|
+
} else {
|
|
68
|
+
changes.push({ relativePath: relPath, absolutePath: absPath, newContent, kind: "customized" });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (changes.length === 0) {
|
|
72
|
+
console.log("Already up to date.");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let updated = 0;
|
|
76
|
+
let skipped = 0;
|
|
77
|
+
let added = 0;
|
|
78
|
+
for (const change of changes) {
|
|
79
|
+
if (change.kind === "new") {
|
|
80
|
+
const label = `New file: ${change.relativePath}`;
|
|
81
|
+
const accept = opts.yes || await askUser(`${label} \u2014 add it?`);
|
|
82
|
+
if (accept) {
|
|
83
|
+
mkdirSync(dirname(change.absolutePath), { recursive: true });
|
|
84
|
+
writeFileSync(change.absolutePath, change.newContent, "utf-8");
|
|
85
|
+
added++;
|
|
86
|
+
if (!opts.yes) console.log(` + ${change.relativePath}`);
|
|
87
|
+
} else {
|
|
88
|
+
skipped++;
|
|
89
|
+
}
|
|
90
|
+
} else if (change.kind === "updated") {
|
|
91
|
+
writeFileSync(change.absolutePath, change.newContent, "utf-8");
|
|
92
|
+
updated++;
|
|
93
|
+
} else if (change.kind === "customized") {
|
|
94
|
+
const currentContent = readFileSync(change.absolutePath, "utf-8");
|
|
95
|
+
const currentLines = countLines(currentContent);
|
|
96
|
+
const newLines = countLines(change.newContent);
|
|
97
|
+
const diff = newLines - currentLines;
|
|
98
|
+
const diffLabel = diff > 0 ? `+${diff} lines` : diff < 0 ? `${diff} lines` : "same length";
|
|
99
|
+
const label = `Customized: ${change.relativePath} (local: ${currentLines} lines, latest: ${newLines} lines, ${diffLabel})`;
|
|
100
|
+
if (opts.yes) {
|
|
101
|
+
writeFileSync(change.absolutePath, change.newContent, "utf-8");
|
|
102
|
+
updated++;
|
|
103
|
+
} else {
|
|
104
|
+
const accept = await askUser(`${label} \u2014 overwrite with latest?`);
|
|
105
|
+
if (accept) {
|
|
106
|
+
writeFileSync(change.absolutePath, change.newContent, "utf-8");
|
|
107
|
+
updated++;
|
|
108
|
+
} else {
|
|
109
|
+
skipped++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const newHashes = {};
|
|
115
|
+
for (const [relPath, content] of Object.entries(managedFiles)) {
|
|
116
|
+
const absPath = join(targetDir, relPath);
|
|
117
|
+
if (existsSync(absPath)) {
|
|
118
|
+
const current = readFileSync(absPath, "utf-8");
|
|
119
|
+
newHashes[relPath] = hashContent(current);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
writeVersion(targetDir, pkgVersion, newHashes);
|
|
123
|
+
const parts = [];
|
|
124
|
+
if (updated > 0) parts.push(`Updated ${updated}`);
|
|
125
|
+
if (skipped > 0) parts.push(`skipped ${skipped} (customized)`);
|
|
126
|
+
if (added > 0) parts.push(`added ${added} new`);
|
|
127
|
+
if (upToDate > 0) parts.push(`${upToDate} already up to date`);
|
|
128
|
+
console.log(`
|
|
129
|
+
Upgrade complete: ${parts.join(", ")}.`);
|
|
130
|
+
}
|
|
131
|
+
function getPackageVersion() {
|
|
132
|
+
try {
|
|
133
|
+
return "0.1.0";
|
|
134
|
+
} catch {
|
|
135
|
+
return "0.0.0";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export {
|
|
139
|
+
upgrade
|
|
140
|
+
};
|
|
141
|
+
//# sourceMappingURL=upgrade-FHBKUDAL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/upgrade.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { join, dirname, resolve } from 'node:path';\nimport { createInterface } from 'node:readline';\nimport { readVersion, writeVersion, hashContent } from './version.js';\nimport { SKILLS, TEMPLATES } from './bundled-files.js';\n\nexport interface UpgradeOptions {\n yes: boolean;\n}\n\ninterface FileChange {\n relativePath: string;\n absolutePath: string;\n newContent: string;\n kind: 'new' | 'updated' | 'customized';\n}\n\nfunction getManagedFiles(): Record<string, string> {\n const files: Record<string, string> = {};\n for (const [name, content] of Object.entries(SKILLS)) {\n const skillName = name.replace(/\\.md$/, '');\n files[join('.claude', 'skills', skillName, 'SKILL.md')] = content;\n }\n for (const [name, content] of Object.entries(TEMPLATES)) {\n files[join('docs', 'templates', name)] = content;\n }\n return files;\n}\n\nfunction countLines(content: string): number {\n return content.split('\\n').length;\n}\n\nasync function askUser(question: string): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(`${question} [y/N] `, (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'y');\n });\n });\n}\n\nexport async function upgrade(dir: string, opts: UpgradeOptions): Promise<void> {\n const targetDir = resolve(dir);\n\n // Check if project was initialized\n const versionInfo = readVersion(targetDir);\n const hasSkill = existsSync(join(targetDir, '.claude', 'skills', 'tune', 'SKILL.md'))\n || existsSync(join(targetDir, '.claude', 'skills', 'joy', 'SKILL.md'))\n || existsSync(join(targetDir, '.claude', 'skills', 'joysmith', 'SKILL.md'));\n\n if (!versionInfo && !hasSkill) {\n console.log('This project has not been initialized with Joycraft.');\n console.log('Run `npx joycraft init` first.');\n return;\n }\n\n // Get current package version\n const pkgVersion = getPackageVersion();\n\n // If version matches exactly, check if any file content actually changed\n const managedFiles = getManagedFiles();\n const installedHashes = versionInfo?.files ?? {};\n\n const changes: FileChange[] = [];\n let upToDate = 0;\n\n for (const [relPath, newContent] of Object.entries(managedFiles)) {\n const absPath = join(targetDir, relPath);\n const newHash = hashContent(newContent);\n\n if (!existsSync(absPath)) {\n // File doesn't exist locally — new file\n changes.push({ relativePath: relPath, absolutePath: absPath, newContent, kind: 'new' });\n continue;\n }\n\n const currentContent = readFileSync(absPath, 'utf-8');\n const currentHash = hashContent(currentContent);\n\n if (currentHash === newHash) {\n // Already matches the latest version\n upToDate++;\n continue;\n }\n\n const originalHash = installedHashes[relPath];\n\n if (originalHash && currentHash === originalHash) {\n // User hasn't modified the file — safe to auto-update\n changes.push({ relativePath: relPath, absolutePath: absPath, newContent, kind: 'updated' });\n } else {\n // User has customized this file (or no original hash recorded)\n changes.push({ relativePath: relPath, absolutePath: absPath, newContent, kind: 'customized' });\n }\n }\n\n if (changes.length === 0) {\n console.log('Already up to date.');\n return;\n }\n\n // Process changes\n let updated = 0;\n let skipped = 0;\n let added = 0;\n\n for (const change of changes) {\n if (change.kind === 'new') {\n const label = `New file: ${change.relativePath}`;\n const accept = opts.yes || await askUser(`${label} — add it?`);\n if (accept) {\n mkdirSync(dirname(change.absolutePath), { recursive: true });\n writeFileSync(change.absolutePath, change.newContent, 'utf-8');\n added++;\n if (!opts.yes) console.log(` + ${change.relativePath}`);\n } else {\n skipped++;\n }\n } else if (change.kind === 'updated') {\n // Safe to auto-update — user hasn't touched the file\n writeFileSync(change.absolutePath, change.newContent, 'utf-8');\n updated++;\n } else if (change.kind === 'customized') {\n const currentContent = readFileSync(change.absolutePath, 'utf-8');\n const currentLines = countLines(currentContent);\n const newLines = countLines(change.newContent);\n const diff = newLines - currentLines;\n const diffLabel = diff > 0 ? `+${diff} lines` : diff < 0 ? `${diff} lines` : 'same length';\n const label = `Customized: ${change.relativePath} (local: ${currentLines} lines, latest: ${newLines} lines, ${diffLabel})`;\n\n if (opts.yes) {\n writeFileSync(change.absolutePath, change.newContent, 'utf-8');\n updated++;\n } else {\n const accept = await askUser(`${label} — overwrite with latest?`);\n if (accept) {\n writeFileSync(change.absolutePath, change.newContent, 'utf-8');\n updated++;\n } else {\n skipped++;\n }\n }\n }\n }\n\n // Write new version file with updated hashes\n const newHashes: Record<string, string> = {};\n for (const [relPath, content] of Object.entries(managedFiles)) {\n const absPath = join(targetDir, relPath);\n if (existsSync(absPath)) {\n const current = readFileSync(absPath, 'utf-8');\n newHashes[relPath] = hashContent(current);\n }\n }\n writeVersion(targetDir, pkgVersion, newHashes);\n\n // Print summary\n const parts: string[] = [];\n if (updated > 0) parts.push(`Updated ${updated}`);\n if (skipped > 0) parts.push(`skipped ${skipped} (customized)`);\n if (added > 0) parts.push(`added ${added} new`);\n if (upToDate > 0) parts.push(`${upToDate} already up to date`);\n console.log(`\\nUpgrade complete: ${parts.join(', ')}.`);\n}\n\nfunction getPackageVersion(): string {\n try {\n // In bundled CLI, __dirname won't help — use a hardcoded fallback\n // The version is set in package.json and read at build time\n return '0.1.0';\n } catch {\n return '0.0.0';\n }\n}\n"],"mappings":"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","names":["resolve"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "joycraft",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "CLI + Claude Code plugin that scaffolds and upgrades AI development harnesses",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"joycraft": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"test": "vitest",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"prepublishOnly": "pnpm build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"claude-code",
|
|
22
|
+
"ai",
|
|
23
|
+
"development",
|
|
24
|
+
"harness",
|
|
25
|
+
"scaffolding"
|
|
26
|
+
],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"commander": "^13.1.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.13.0",
|
|
34
|
+
"tsup": "^8.4.0",
|
|
35
|
+
"typescript": "^5.7.0",
|
|
36
|
+
"vitest": "^3.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|