joycraft 0.6.13 → 0.6.15
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/chunk-34IWIKXS.js +148 -0
- package/dist/chunk-34IWIKXS.js.map +1 -0
- package/dist/{chunk-GM2T44P6.js → chunk-XOMQIK4U.js} +42 -42
- package/dist/chunk-XOMQIK4U.js.map +1 -0
- package/dist/{chunk-VIVJUY6J.js → chunk-YE4LWG2O.js} +2 -2
- package/dist/cli.js +8 -7
- package/dist/cli.js.map +1 -1
- package/dist/{init-YJYJZUZU.js → init-LXSMLAY5.js} +165 -132
- package/dist/init-LXSMLAY5.js.map +1 -0
- package/dist/{init-autofix-4GUVGDXT.js → init-autofix-OZW5ITFI.js} +3 -3
- package/dist/{upgrade-33NLD24D.js → upgrade-P3JZS7NM.js} +59 -29
- package/dist/upgrade-P3JZS7NM.js.map +1 -0
- package/dist/{version-2FGZETKD.js → version-OTDHPJBE.js} +4 -2
- package/package.json +1 -1
- package/dist/chunk-GM2T44P6.js.map +0 -1
- package/dist/chunk-TD65VH2W.js +0 -75
- package/dist/chunk-TD65VH2W.js.map +0 -1
- package/dist/init-YJYJZUZU.js.map +0 -1
- package/dist/upgrade-33NLD24D.js.map +0 -1
- /package/dist/{chunk-VIVJUY6J.js.map → chunk-YE4LWG2O.js.map} +0 -0
- /package/dist/{init-autofix-4GUVGDXT.js.map → init-autofix-OZW5ITFI.js.map} +0 -0
- /package/dist/{version-2FGZETKD.js.map → version-OTDHPJBE.js.map} +0 -0
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
DEFAULT_GITIGNORE_PROFILE,
|
|
4
4
|
STATE_PATH,
|
|
5
5
|
parseGitignoreProfile
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-34IWIKXS.js";
|
|
7
7
|
|
|
8
8
|
// src/gitignore.ts
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -83,4 +83,4 @@ export {
|
|
|
83
83
|
validateGitignoreFlag,
|
|
84
84
|
resolveGitignoreProfile
|
|
85
85
|
};
|
|
86
|
-
//# sourceMappingURL=chunk-
|
|
86
|
+
//# sourceMappingURL=chunk-YE4LWG2O.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
PRIVATE_DIRS_DISPLAY
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-YE4LWG2O.js";
|
|
5
|
+
import "./chunk-34IWIKXS.js";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
8
|
import { Command } from "commander";
|
|
@@ -15,7 +15,7 @@ var GITIGNORE_OPTION_DESC = `Gitignore profile: 'shared' (commit skills) or 'pri
|
|
|
15
15
|
var program = new Command();
|
|
16
16
|
program.name("joycraft").description("Scaffold and upgrade AI development harnesses").version(pkg.version, "-v, --version");
|
|
17
17
|
program.command("init").description("Scaffold the Joycraft harness into the current project").argument("[dir]", "Target directory", ".").option("--force", "Overwrite existing files").option("--gitignore <profile>", GITIGNORE_OPTION_DESC).action(async (dir, opts) => {
|
|
18
|
-
const { init } = await import("./init-
|
|
18
|
+
const { init } = await import("./init-LXSMLAY5.js");
|
|
19
19
|
try {
|
|
20
20
|
await init(dir, { force: opts.force ?? false, gitignore: opts.gitignore });
|
|
21
21
|
} catch (err) {
|
|
@@ -24,7 +24,7 @@ program.command("init").description("Scaffold the Joycraft harness into the curr
|
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
program.command("upgrade").description("Upgrade installed Joycraft templates and skills to latest").argument("[dir]", "Target directory", ".").option("--yes", "Auto-accept all updates").option("--gitignore <profile>", GITIGNORE_OPTION_DESC).action(async (dir, opts) => {
|
|
27
|
-
const { upgrade } = await import("./upgrade-
|
|
27
|
+
const { upgrade } = await import("./upgrade-P3JZS7NM.js");
|
|
28
28
|
try {
|
|
29
29
|
await upgrade(dir, { yes: opts.yes ?? false, gitignore: opts.gitignore });
|
|
30
30
|
} catch (err) {
|
|
@@ -33,15 +33,16 @@ program.command("upgrade").description("Upgrade installed Joycraft templates and
|
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
35
|
program.command("init-autofix").description("Set up the Level 5 auto-fix loop with holdout scenarios").argument("[dir]", "Target directory", ".").option("--scenarios-repo <name>", "Name for scenarios repo").option("--app-id <id>", "GitHub App ID for Joycraft Autofix").option("--force", "Overwrite existing workflow files").option("--dry-run", "Show what would be created without creating it").action(async (dir, opts) => {
|
|
36
|
-
const { initAutofix } = await import("./init-autofix-
|
|
36
|
+
const { initAutofix } = await import("./init-autofix-OZW5ITFI.js");
|
|
37
37
|
await initAutofix(dir, opts);
|
|
38
38
|
});
|
|
39
39
|
program.command("check-version").description("Check if a newer version of Joycraft is available").action(async () => {
|
|
40
40
|
try {
|
|
41
41
|
const { readFileSync: readFileSync2, existsSync } = await import("fs");
|
|
42
42
|
const { join: join2 } = await import("path");
|
|
43
|
-
const { STATE_PATH, LEGACY_VERSION_FILE } = await import("./version-
|
|
44
|
-
const
|
|
43
|
+
const { STATE_PATH, LEGACY_VERSION_FILE, LEGACY_CLAUDE_STATE_PATH } = await import("./version-OTDHPJBE.js");
|
|
44
|
+
const candidates = [STATE_PATH, LEGACY_CLAUDE_STATE_PATH, LEGACY_VERSION_FILE];
|
|
45
|
+
const statePath = candidates.map((p) => join2(process.cwd(), p)).find((p) => existsSync(p)) ?? join2(process.cwd(), STATE_PATH);
|
|
45
46
|
const data = JSON.parse(readFileSync2(statePath, "utf-8"));
|
|
46
47
|
const res = await fetch("https://registry.npmjs.org/joycraft/latest", { signal: AbortSignal.timeout(3e3) });
|
|
47
48
|
if (res.ok) {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\nimport { PRIVATE_DIRS_DISPLAY } from './gitignore.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));\n\nconst GITIGNORE_OPTION_DESC = `Gitignore profile: 'shared' (commit skills) or 'private' (gitignore ${PRIVATE_DIRS_DISPLAY})`;\n\nconst program = new Command();\n\nprogram\n .name('joycraft')\n .description('Scaffold and upgrade AI development harnesses')\n .version(pkg.version, '-v, --version');\n\nprogram\n .command('init')\n .description('Scaffold the Joycraft harness into the current project')\n .argument('[dir]', 'Target directory', '.')\n .option('--force', 'Overwrite existing files')\n .option('--gitignore <profile>', GITIGNORE_OPTION_DESC)\n .action(async (dir: string, opts: { force?: boolean; gitignore?: string }) => {\n const { init } = await import('./init.js');\n try {\n await init(dir, { force: opts.force ?? false, gitignore: opts.gitignore });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\nprogram\n .command('upgrade')\n .description('Upgrade installed Joycraft templates and skills to latest')\n .argument('[dir]', 'Target directory', '.')\n .option('--yes', 'Auto-accept all updates')\n .option('--gitignore <profile>', GITIGNORE_OPTION_DESC)\n .action(async (dir: string, opts: { yes?: boolean; gitignore?: string }) => {\n const { upgrade } = await import('./upgrade.js');\n try {\n await upgrade(dir, { yes: opts.yes ?? false, gitignore: opts.gitignore });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\nprogram\n .command('init-autofix')\n .description('Set up the Level 5 auto-fix loop with holdout scenarios')\n .argument('[dir]', 'Target directory', '.')\n .option('--scenarios-repo <name>', 'Name for scenarios repo')\n .option('--app-id <id>', 'GitHub App ID for Joycraft Autofix')\n .option('--force', 'Overwrite existing workflow files')\n .option('--dry-run', 'Show what would be created without creating it')\n .action(async (dir: string, opts: { scenariosRepo?: string; appId?: string; force?: boolean; dryRun?: boolean }) => {\n const { initAutofix } = await import('./init-autofix.js');\n await initAutofix(dir, opts);\n });\n\nprogram\n .command('check-version')\n .description('Check if a newer version of Joycraft is available')\n .action(async () => {\n try {\n const { readFileSync, existsSync } = await import('node:fs');\n const { join } = await import('node:path');\n const { STATE_PATH, LEGACY_VERSION_FILE } = await import('./version.js');\n // Prefer the
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\nimport { PRIVATE_DIRS_DISPLAY } from './gitignore.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));\n\nconst GITIGNORE_OPTION_DESC = `Gitignore profile: 'shared' (commit skills) or 'private' (gitignore ${PRIVATE_DIRS_DISPLAY})`;\n\nconst program = new Command();\n\nprogram\n .name('joycraft')\n .description('Scaffold and upgrade AI development harnesses')\n .version(pkg.version, '-v, --version');\n\nprogram\n .command('init')\n .description('Scaffold the Joycraft harness into the current project')\n .argument('[dir]', 'Target directory', '.')\n .option('--force', 'Overwrite existing files')\n .option('--gitignore <profile>', GITIGNORE_OPTION_DESC)\n .action(async (dir: string, opts: { force?: boolean; gitignore?: string }) => {\n const { init } = await import('./init.js');\n try {\n await init(dir, { force: opts.force ?? false, gitignore: opts.gitignore });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\nprogram\n .command('upgrade')\n .description('Upgrade installed Joycraft templates and skills to latest')\n .argument('[dir]', 'Target directory', '.')\n .option('--yes', 'Auto-accept all updates')\n .option('--gitignore <profile>', GITIGNORE_OPTION_DESC)\n .action(async (dir: string, opts: { yes?: boolean; gitignore?: string }) => {\n const { upgrade } = await import('./upgrade.js');\n try {\n await upgrade(dir, { yes: opts.yes ?? false, gitignore: opts.gitignore });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\nprogram\n .command('init-autofix')\n .description('Set up the Level 5 auto-fix loop with holdout scenarios')\n .argument('[dir]', 'Target directory', '.')\n .option('--scenarios-repo <name>', 'Name for scenarios repo')\n .option('--app-id <id>', 'GitHub App ID for Joycraft Autofix')\n .option('--force', 'Overwrite existing workflow files')\n .option('--dry-run', 'Show what would be created without creating it')\n .action(async (dir: string, opts: { scenariosRepo?: string; appId?: string; force?: boolean; dryRun?: boolean }) => {\n const { initAutofix } = await import('./init-autofix.js');\n await initAutofix(dir, opts);\n });\n\nprogram\n .command('check-version')\n .description('Check if a newer version of Joycraft is available')\n .action(async () => {\n try {\n const { readFileSync, existsSync } = await import('node:fs');\n const { join } = await import('node:path');\n const { STATE_PATH, LEGACY_VERSION_FILE, LEGACY_CLAUDE_STATE_PATH } = await import('./version.js');\n // Prefer the current state; fall back through the legacy locations for\n // projects not yet upgraded (upgrade relocates them): the interim\n // .claude/.joycraft/state.json, then the original repo-root file.\n const candidates = [STATE_PATH, LEGACY_CLAUDE_STATE_PATH, LEGACY_VERSION_FILE];\n const statePath =\n candidates.map((p) => join(process.cwd(), p)).find((p) => existsSync(p)) ??\n join(process.cwd(), STATE_PATH);\n const data = JSON.parse(readFileSync(statePath, 'utf-8'));\n const res = await fetch('https://registry.npmjs.org/joycraft/latest', { signal: AbortSignal.timeout(3000) });\n if (res.ok) {\n const latest = ((await res.json()) as { version: string }).version;\n if (data.version !== latest) {\n console.log(`Joycraft ${latest} available (you have ${data.version}). Run: npm install -g joycraft`);\n }\n }\n } catch {\n // Silent — don't block session start\n }\n });\n\n// Start update check immediately so it runs in parallel with the command\nconst updateCheckPromise = (async (): Promise<string | null> => {\n try {\n const res = await fetch('https://registry.npmjs.org/joycraft/latest', {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const latest = ((await res.json()) as { version: string }).version;\n if (latest !== pkg.version) {\n return `\\nJoycraft ${latest} available (you have ${pkg.version}). Run: npm install -g joycraft`;\n }\n }\n } catch {\n // Silent — don't block or error on network issues\n }\n return null;\n})();\n\n// Print update nudge after every command\nprogram.hook('postAction', async () => {\n const message = await updateCheckPromise;\n if (message) {\n console.log(message);\n }\n});\n\n// Show help when no arguments provided\nif (process.argv.length <= 2) {\n program.outputHelp();\n process.exit(0);\n}\n\nprogram.parse();\n"],"mappings":"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","names":["readFileSync","join"]}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
PRIVATE_UNTRACK_COMMAND,
|
|
8
8
|
applyGitignoreProfile,
|
|
9
9
|
resolveGitignoreProfile
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-YE4LWG2O.js";
|
|
11
11
|
import {
|
|
12
12
|
CODEX_SKILLS,
|
|
13
13
|
PI_AGENTS,
|
|
@@ -16,14 +16,15 @@ import {
|
|
|
16
16
|
PI_SKILLS,
|
|
17
17
|
SKILLS,
|
|
18
18
|
TEMPLATES
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-XOMQIK4U.js";
|
|
20
20
|
import {
|
|
21
21
|
DEFAULT_GITIGNORE_PROFILE,
|
|
22
22
|
STATE_PATH,
|
|
23
23
|
hashContent,
|
|
24
24
|
readVersion,
|
|
25
|
+
resolveHarnesses,
|
|
25
26
|
writeVersion
|
|
26
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-34IWIKXS.js";
|
|
27
28
|
|
|
28
29
|
// src/init.ts
|
|
29
30
|
import { mkdirSync as mkdirSync2, existsSync as existsSync4, writeFileSync as writeFileSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync, chmodSync } from "fs";
|
|
@@ -310,6 +311,10 @@ async function detectStack(dir) {
|
|
|
310
311
|
// src/improve-claude-md.ts
|
|
311
312
|
import { existsSync as existsSync2 } from "fs";
|
|
312
313
|
import { join as join2 } from "path";
|
|
314
|
+
var PRIVATE_SETUP_NOTE_MARKER = "After cloning, run";
|
|
315
|
+
function generatePrivateSetupNote() {
|
|
316
|
+
return `> **Private setup:** The harness dirs (\`.claude/\`, \`.agents/\`, \`.pi/\`) are gitignored in this repo, so they aren't committed. ${PRIVATE_SETUP_NOTE_MARKER} \`npx joycraft init\` to regenerate the skill files locally.`;
|
|
317
|
+
}
|
|
313
318
|
function generateCommandsBlock(stack) {
|
|
314
319
|
const lines = ["```bash"];
|
|
315
320
|
if (stack.commands.build) lines.push(`# Build
|
|
@@ -453,6 +458,9 @@ function generateCLAUDEMd(projectName, stack, existingSkills = [], opts) {
|
|
|
453
458
|
generateGettingStartedSection(),
|
|
454
459
|
""
|
|
455
460
|
];
|
|
461
|
+
if (opts?.privateProfile) {
|
|
462
|
+
lines.push(generatePrivateSetupNote(), "");
|
|
463
|
+
}
|
|
456
464
|
if (existingSkills.length > 0) {
|
|
457
465
|
lines.push(generateProjectToolsSection(existingSkills), "");
|
|
458
466
|
}
|
|
@@ -493,7 +501,7 @@ function generateKeyFilesSection2() {
|
|
|
493
501
|
|------|---------|
|
|
494
502
|
| _TODO_ | _Add key files_ |`;
|
|
495
503
|
}
|
|
496
|
-
function generateAgentsMd(projectName, stack) {
|
|
504
|
+
function generateAgentsMd(projectName, stack, privateProfile = false) {
|
|
497
505
|
const frameworkNote = stack.framework ? ` (${stack.framework})` : "";
|
|
498
506
|
const langLabel = stack.language === "unknown" ? "" : ` | **Stack:** ${stack.language}${frameworkNote}`;
|
|
499
507
|
const lines = [
|
|
@@ -515,6 +523,9 @@ function generateAgentsMd(projectName, stack) {
|
|
|
515
523
|
generateDevelopmentSection(stack),
|
|
516
524
|
""
|
|
517
525
|
];
|
|
526
|
+
if (privateProfile) {
|
|
527
|
+
lines.push(generatePrivateSetupNote(), "");
|
|
528
|
+
}
|
|
518
529
|
return lines.join("\n");
|
|
519
530
|
}
|
|
520
531
|
|
|
@@ -721,6 +732,14 @@ async function init(dir, opts) {
|
|
|
721
732
|
const result = { created: [], skipped: [], modified: [], warnings: [] };
|
|
722
733
|
const stack = await detectStack(targetDir);
|
|
723
734
|
const isPi = existsSync4(join4(targetDir, ".pi"));
|
|
735
|
+
const harnesses = await resolveHarnesses(process.stdin.isTTY === true);
|
|
736
|
+
const wants = (h) => harnesses.includes(h);
|
|
737
|
+
if (harnesses.length === 0) {
|
|
738
|
+
console.log(
|
|
739
|
+
"\nNo harness selected \u2014 Joycraft will not install any skills.\nPlease run init again and select at least one harness (claude, codex, pi)."
|
|
740
|
+
);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
724
743
|
const { profile: gitignoreProfile } = await resolveGitignoreProfile({
|
|
725
744
|
flag: opts.gitignore,
|
|
726
745
|
persisted: readVersion(targetDir)?.gitignoreProfile,
|
|
@@ -730,7 +749,7 @@ async function init(dir, opts) {
|
|
|
730
749
|
ensureDir(join4(targetDir, "docs", "context"));
|
|
731
750
|
const skillsDir = join4(targetDir, ".claude", "skills");
|
|
732
751
|
let existingSkills = [];
|
|
733
|
-
if (existsSync4(skillsDir)) {
|
|
752
|
+
if (wants("claude") && existsSync4(skillsDir)) {
|
|
734
753
|
existingSkills = readdirSync2(skillsDir).filter((name) => {
|
|
735
754
|
if (name.startsWith("joycraft-")) return false;
|
|
736
755
|
if (name.startsWith(".")) return false;
|
|
@@ -742,60 +761,53 @@ async function init(dir, opts) {
|
|
|
742
761
|
}
|
|
743
762
|
});
|
|
744
763
|
}
|
|
745
|
-
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
764
|
+
if (wants("claude")) {
|
|
765
|
+
for (const [filename, content] of Object.entries(SKILLS)) {
|
|
766
|
+
const skillName = filename.replace(/\.md$/, "");
|
|
767
|
+
const skillDir = join4(skillsDir, skillName);
|
|
768
|
+
ensureDir(skillDir);
|
|
769
|
+
writeFile(join4(skillDir, "SKILL.md"), content, opts.force, result);
|
|
770
|
+
}
|
|
750
771
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
return statSync(fullPath).isDirectory();
|
|
760
|
-
} catch {
|
|
761
|
-
return false;
|
|
762
|
-
}
|
|
763
|
-
});
|
|
772
|
+
if (wants("codex")) {
|
|
773
|
+
const codexSkillsDir = join4(targetDir, ".agents", "skills");
|
|
774
|
+
for (const [filename, content] of Object.entries(CODEX_SKILLS)) {
|
|
775
|
+
const skillName = filename.replace(/\.md$/, "");
|
|
776
|
+
const skillDir = join4(codexSkillsDir, skillName);
|
|
777
|
+
ensureDir(skillDir);
|
|
778
|
+
writeFile(join4(skillDir, "SKILL.md"), content, opts.force, result);
|
|
779
|
+
}
|
|
764
780
|
}
|
|
765
|
-
|
|
766
|
-
const
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
if (name !== "README.md") {
|
|
784
|
-
try {
|
|
785
|
-
chmodSync(scriptPath, 493);
|
|
786
|
-
} catch {
|
|
781
|
+
if (wants("pi")) {
|
|
782
|
+
const piSkillsDir = join4(targetDir, ".pi", "skills");
|
|
783
|
+
for (const [filename, content] of Object.entries(PI_SKILLS)) {
|
|
784
|
+
const skillName = filename.replace(/\.md$/, "");
|
|
785
|
+
const skillDir = join4(piSkillsDir, skillName);
|
|
786
|
+
ensureDir(skillDir);
|
|
787
|
+
writeFile(join4(skillDir, "SKILL.md"), content, opts.force, result);
|
|
788
|
+
}
|
|
789
|
+
const piScriptsDir = join4(targetDir, ".pi", "scripts", "joycraft");
|
|
790
|
+
ensureDir(piScriptsDir);
|
|
791
|
+
for (const [name, content] of Object.entries(PI_SCRIPTS)) {
|
|
792
|
+
const scriptPath = join4(piScriptsDir, name);
|
|
793
|
+
writeFile(scriptPath, content, opts.force, result);
|
|
794
|
+
if (name !== "README.md") {
|
|
795
|
+
try {
|
|
796
|
+
chmodSync(scriptPath, 493);
|
|
797
|
+
} catch {
|
|
798
|
+
}
|
|
787
799
|
}
|
|
788
800
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
801
|
+
const piExtDir = join4(targetDir, ".pi", "extensions");
|
|
802
|
+
ensureDir(piExtDir);
|
|
803
|
+
for (const [name, content] of Object.entries(PI_EXTENSIONS)) {
|
|
804
|
+
writeFile(join4(piExtDir, name), content, opts.force, result);
|
|
805
|
+
}
|
|
806
|
+
const piAgentsDir = join4(targetDir, ".pi", "agents");
|
|
807
|
+
ensureDir(piAgentsDir);
|
|
808
|
+
for (const [name, content] of Object.entries(PI_AGENTS)) {
|
|
809
|
+
writeFile(join4(piAgentsDir, name), content, opts.force, result);
|
|
810
|
+
}
|
|
799
811
|
}
|
|
800
812
|
const templatesDir = join4(targetDir, "docs", "templates");
|
|
801
813
|
ensureDir(templatesDir);
|
|
@@ -808,7 +820,9 @@ async function init(dir, opts) {
|
|
|
808
820
|
result.skipped.push(claudeMdPath);
|
|
809
821
|
} else {
|
|
810
822
|
const projectName = basename(targetDir);
|
|
811
|
-
const content = generateCLAUDEMd(projectName, stack, existingSkills
|
|
823
|
+
const content = generateCLAUDEMd(projectName, stack, existingSkills, {
|
|
824
|
+
privateProfile: gitignoreProfile === "private"
|
|
825
|
+
});
|
|
812
826
|
writeFileSync2(claudeMdPath, content, "utf-8");
|
|
813
827
|
result.created.push(claudeMdPath);
|
|
814
828
|
}
|
|
@@ -817,40 +831,47 @@ async function init(dir, opts) {
|
|
|
817
831
|
result.skipped.push(agentsMdPath);
|
|
818
832
|
} else {
|
|
819
833
|
const projectName = basename(targetDir);
|
|
820
|
-
const content = generateAgentsMd(projectName, stack);
|
|
834
|
+
const content = generateAgentsMd(projectName, stack, gitignoreProfile === "private");
|
|
821
835
|
writeFileSync2(agentsMdPath, content, "utf-8");
|
|
822
836
|
result.created.push(agentsMdPath);
|
|
823
837
|
}
|
|
824
838
|
const fileHashes = {};
|
|
825
|
-
|
|
826
|
-
const
|
|
827
|
-
|
|
839
|
+
if (wants("claude")) {
|
|
840
|
+
for (const [filename, content] of Object.entries(SKILLS)) {
|
|
841
|
+
const skillName = filename.replace(/\.md$/, "");
|
|
842
|
+
fileHashes[join4(".claude", "skills", skillName, "SKILL.md")] = hashContent(content);
|
|
843
|
+
}
|
|
828
844
|
}
|
|
829
|
-
|
|
830
|
-
const
|
|
831
|
-
|
|
845
|
+
if (wants("codex")) {
|
|
846
|
+
for (const [filename, content] of Object.entries(CODEX_SKILLS)) {
|
|
847
|
+
const skillName = filename.replace(/\.md$/, "");
|
|
848
|
+
fileHashes[join4(".agents", "skills", skillName, "SKILL.md")] = hashContent(content);
|
|
849
|
+
}
|
|
832
850
|
}
|
|
833
851
|
for (const [filename, content] of Object.entries(TEMPLATES)) {
|
|
834
852
|
fileHashes[join4("docs", "templates", filename)] = hashContent(content);
|
|
835
853
|
}
|
|
836
|
-
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
854
|
+
if (wants("pi")) {
|
|
855
|
+
for (const [filename, content] of Object.entries(PI_SKILLS)) {
|
|
856
|
+
const skillName = filename.replace(/\.md$/, "");
|
|
857
|
+
fileHashes[join4(".pi", "skills", skillName, "SKILL.md")] = hashContent(content);
|
|
858
|
+
}
|
|
859
|
+
for (const [name, content] of Object.entries(PI_SCRIPTS)) {
|
|
860
|
+
fileHashes[join4(".pi", "scripts", "joycraft", name)] = hashContent(content);
|
|
861
|
+
}
|
|
862
|
+
for (const [name, content] of Object.entries(PI_EXTENSIONS)) {
|
|
863
|
+
fileHashes[join4(".pi", "extensions", name)] = hashContent(content);
|
|
864
|
+
}
|
|
865
|
+
for (const [name, content] of Object.entries(PI_AGENTS)) {
|
|
866
|
+
fileHashes[join4(".pi", "agents", name)] = hashContent(content);
|
|
867
|
+
}
|
|
848
868
|
}
|
|
849
|
-
writeVersion(targetDir, getPackageVersion(), fileHashes, gitignoreProfile);
|
|
869
|
+
writeVersion(targetDir, getPackageVersion(), fileHashes, gitignoreProfile, harnesses);
|
|
850
870
|
applyGitignoreProfile(targetDir, gitignoreProfile);
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
871
|
+
if (wants("claude")) {
|
|
872
|
+
const hooksDir = join4(targetDir, ".claude", "hooks");
|
|
873
|
+
ensureDir(hooksDir);
|
|
874
|
+
const hookScript = `// Joycraft version check \u2014 runs on Claude Code session start
|
|
854
875
|
import { readFileSync } from 'node:fs';
|
|
855
876
|
import { join } from 'node:path';
|
|
856
877
|
try {
|
|
@@ -862,69 +883,78 @@ try {
|
|
|
862
883
|
}
|
|
863
884
|
} catch {}
|
|
864
885
|
`;
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
if (existsSync4(settingsPath)) {
|
|
870
|
-
try {
|
|
871
|
-
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
872
|
-
} catch {
|
|
873
|
-
settingsMalformed = true;
|
|
874
|
-
result.warnings.push(
|
|
875
|
-
"settings.json exists but is malformed \u2014 skipping settings merge to protect your config.\n Fix the JSON in .claude/settings.json and re-run init."
|
|
876
|
-
);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
if (!settingsMalformed) {
|
|
880
|
-
if (!settings.hooks) settings.hooks = {};
|
|
881
|
-
const hooksConfig = settings.hooks;
|
|
882
|
-
if (!hooksConfig.SessionStart) hooksConfig.SessionStart = [];
|
|
883
|
-
const sessionStartHooks = hooksConfig.SessionStart;
|
|
884
|
-
const hasJoycraftHook = sessionStartHooks.some((h) => {
|
|
885
|
-
const innerHooks = h.hooks;
|
|
886
|
-
return innerHooks?.some((ih) => typeof ih.command === "string" && ih.command.includes("joycraft"));
|
|
887
|
-
});
|
|
888
|
-
if (!hasJoycraftHook) {
|
|
889
|
-
sessionStartHooks.push({
|
|
890
|
-
matcher: "",
|
|
891
|
-
hooks: [{
|
|
892
|
-
type: "command",
|
|
893
|
-
command: "node .claude/hooks/joycraft-version-check.mjs"
|
|
894
|
-
}]
|
|
895
|
-
});
|
|
896
|
-
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
897
|
-
result.created.push(settingsPath);
|
|
898
|
-
}
|
|
899
|
-
const permissions = generatePermissions(stack);
|
|
886
|
+
writeFile(join4(hooksDir, "joycraft-version-check.mjs"), hookScript, opts.force, result);
|
|
887
|
+
const settingsPath = join4(targetDir, ".claude", "settings.json");
|
|
888
|
+
let settings = {};
|
|
889
|
+
let settingsMalformed = false;
|
|
900
890
|
if (existsSync4(settingsPath)) {
|
|
901
891
|
try {
|
|
902
892
|
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
903
893
|
} catch {
|
|
894
|
+
settingsMalformed = true;
|
|
904
895
|
result.warnings.push(
|
|
905
|
-
"settings.json
|
|
896
|
+
"settings.json exists but is malformed \u2014 skipping settings merge to protect your config.\n Fix the JSON in .claude/settings.json and re-run init."
|
|
906
897
|
);
|
|
907
|
-
settingsMalformed = true;
|
|
908
898
|
}
|
|
909
899
|
}
|
|
910
900
|
if (!settingsMalformed) {
|
|
911
|
-
if (!settings.
|
|
912
|
-
const
|
|
913
|
-
if (!
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
901
|
+
if (!settings.hooks) settings.hooks = {};
|
|
902
|
+
const hooksConfig = settings.hooks;
|
|
903
|
+
if (!hooksConfig.SessionStart) hooksConfig.SessionStart = [];
|
|
904
|
+
const sessionStartHooks = hooksConfig.SessionStart;
|
|
905
|
+
const hasJoycraftHook = sessionStartHooks.some((h) => {
|
|
906
|
+
const innerHooks = h.hooks;
|
|
907
|
+
return innerHooks?.some((ih) => typeof ih.command === "string" && ih.command.includes("joycraft"));
|
|
908
|
+
});
|
|
909
|
+
if (!settings.env) settings.env = {};
|
|
910
|
+
const env = settings.env;
|
|
911
|
+
const envMissing = !("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS" in env);
|
|
912
|
+
if (envMissing) {
|
|
913
|
+
env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
|
|
914
|
+
}
|
|
915
|
+
if (!hasJoycraftHook) {
|
|
916
|
+
sessionStartHooks.push({
|
|
917
|
+
matcher: "",
|
|
918
|
+
hooks: [{
|
|
919
|
+
type: "command",
|
|
920
|
+
command: "node .claude/hooks/joycraft-version-check.mjs"
|
|
921
|
+
}]
|
|
922
|
+
});
|
|
917
923
|
}
|
|
918
|
-
|
|
919
|
-
|
|
924
|
+
if (!hasJoycraftHook || envMissing) {
|
|
925
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
926
|
+
if (!result.created.includes(settingsPath)) result.created.push(settingsPath);
|
|
927
|
+
}
|
|
928
|
+
const permissions = generatePermissions(stack);
|
|
929
|
+
if (existsSync4(settingsPath)) {
|
|
930
|
+
try {
|
|
931
|
+
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
932
|
+
} catch {
|
|
933
|
+
result.warnings.push(
|
|
934
|
+
"settings.json became unreadable after hook merge \u2014 skipping permissions merge.\n Fix the JSON in .claude/settings.json and re-run init."
|
|
935
|
+
);
|
|
936
|
+
settingsMalformed = true;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (!settingsMalformed) {
|
|
940
|
+
if (!settings.permissions) settings.permissions = {};
|
|
941
|
+
const perms = settings.permissions;
|
|
942
|
+
if (!perms.allow) perms.allow = [];
|
|
943
|
+
if (!perms.deny) perms.deny = [];
|
|
944
|
+
for (const rule of permissions.allow) {
|
|
945
|
+
if (!perms.allow.includes(rule)) perms.allow.push(rule);
|
|
946
|
+
}
|
|
947
|
+
for (const rule of permissions.deny) {
|
|
948
|
+
if (!perms.deny.includes(rule)) perms.deny.push(rule);
|
|
949
|
+
}
|
|
950
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
920
951
|
}
|
|
921
|
-
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
922
952
|
}
|
|
953
|
+
const hookResult = installSafeguardHooks(targetDir, [], opts.force, settingsMalformed);
|
|
954
|
+
result.created.push(...hookResult.created);
|
|
955
|
+
result.skipped.push(...hookResult.skipped);
|
|
923
956
|
}
|
|
924
|
-
|
|
925
|
-
result.created.push(...hookResult.created);
|
|
926
|
-
result.skipped.push(...hookResult.skipped);
|
|
927
|
-
if (gitignoreProfile === "shared") {
|
|
957
|
+
if (gitignoreProfile === "shared" && wants("claude")) {
|
|
928
958
|
const gitignorePath = join4(targetDir, ".gitignore");
|
|
929
959
|
if (existsSync4(gitignorePath)) {
|
|
930
960
|
const gitignore = readFileSync3(gitignorePath, "utf-8");
|
|
@@ -935,10 +965,13 @@ try {
|
|
|
935
965
|
}
|
|
936
966
|
}
|
|
937
967
|
}
|
|
938
|
-
printSummary(result, stack, existingSkills, isPi, gitignoreProfile);
|
|
968
|
+
printSummary(result, stack, existingSkills, isPi, gitignoreProfile, harnesses);
|
|
939
969
|
}
|
|
940
|
-
function printSummary(result, stack, existingSkills = [], isPi = false, gitignoreProfile = DEFAULT_GITIGNORE_PROFILE) {
|
|
970
|
+
function printSummary(result, stack, existingSkills = [], isPi = false, gitignoreProfile = DEFAULT_GITIGNORE_PROFILE, harnesses = []) {
|
|
941
971
|
console.log("\nJoycraft initialized!\n");
|
|
972
|
+
if (harnesses.length > 0) {
|
|
973
|
+
console.log(` Installed harnesses: ${harnesses.join(", ")}`);
|
|
974
|
+
}
|
|
942
975
|
if (stack.language !== "unknown") {
|
|
943
976
|
const fw = stack.framework ? ` + ${stack.framework}` : "";
|
|
944
977
|
console.log(` Detected stack: ${stack.language}${fw} (${stack.packageManager})`);
|
|
@@ -1008,4 +1041,4 @@ function printSummary(result, stack, existingSkills = [], isPi = false, gitignor
|
|
|
1008
1041
|
export {
|
|
1009
1042
|
init
|
|
1010
1043
|
};
|
|
1011
|
-
//# sourceMappingURL=init-
|
|
1044
|
+
//# sourceMappingURL=init-LXSMLAY5.js.map
|