joycraft 0.6.14 → 0.6.16
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 +31 -13
- package/dist/chunk-34IWIKXS.js +148 -0
- package/dist/chunk-34IWIKXS.js.map +1 -0
- package/dist/{chunk-VIVJUY6J.js → chunk-G6WSFZQG.js} +3 -3
- package/dist/chunk-G6WSFZQG.js.map +1 -0
- package/dist/{chunk-VCLRPD62.js → chunk-UEG5IO6Q.js} +9 -187
- package/dist/chunk-UEG5IO6Q.js.map +1 -0
- package/dist/cli.js +8 -7
- package/dist/cli.js.map +1 -1
- package/dist/{init-OUKVQXNY.js → init-WPKDBQDN.js} +275 -156
- package/dist/init-WPKDBQDN.js.map +1 -0
- package/dist/{init-autofix-YAI6E4VJ.js → init-autofix-ESN27L3W.js} +6 -4
- package/dist/{init-autofix-YAI6E4VJ.js.map → init-autofix-ESN27L3W.js.map} +1 -1
- package/dist/{upgrade-E3VXHORR.js → upgrade-LKX25GTT.js} +59 -29
- package/dist/upgrade-LKX25GTT.js.map +1 -0
- package/dist/{version-2FGZETKD.js → version-OTDHPJBE.js} +4 -2
- package/package.json +1 -1
- package/dist/chunk-TD65VH2W.js +0 -75
- package/dist/chunk-TD65VH2W.js.map +0 -1
- package/dist/chunk-VCLRPD62.js.map +0 -1
- package/dist/chunk-VIVJUY6J.js.map +0 -1
- package/dist/init-OUKVQXNY.js.map +0 -1
- package/dist/upgrade-E3VXHORR.js.map +0 -1
- /package/dist/{version-2FGZETKD.js.map → version-OTDHPJBE.js.map} +0 -0
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
PRIVATE_UNTRACK_COMMAND,
|
|
8
8
|
applyGitignoreProfile,
|
|
9
9
|
resolveGitignoreProfile
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-G6WSFZQG.js";
|
|
11
11
|
import {
|
|
12
12
|
CODEX_SKILLS,
|
|
13
13
|
PI_AGENTS,
|
|
@@ -16,18 +16,19 @@ import {
|
|
|
16
16
|
PI_SKILLS,
|
|
17
17
|
SKILLS,
|
|
18
18
|
TEMPLATES
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-UEG5IO6Q.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
|
-
import { mkdirSync as mkdirSync2, existsSync as
|
|
30
|
-
import { join as
|
|
30
|
+
import { mkdirSync as mkdirSync2, existsSync as existsSync5, writeFileSync as writeFileSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync, chmodSync } from "fs";
|
|
31
|
+
import { join as join5, basename, resolve, dirname } from "path";
|
|
31
32
|
|
|
32
33
|
// src/detect.ts
|
|
33
34
|
import { readFileSync, existsSync, readdirSync } 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
|
|
|
@@ -702,39 +713,122 @@ function installSafeguardHooks(targetDir, customPatterns = [], force = false, sk
|
|
|
702
713
|
return result;
|
|
703
714
|
}
|
|
704
715
|
|
|
716
|
+
// src/tsconfig.ts
|
|
717
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
718
|
+
import { join as join4 } from "path";
|
|
719
|
+
var PI_EXCLUDE = ".pi";
|
|
720
|
+
function stripJsonComments(text) {
|
|
721
|
+
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
722
|
+
}
|
|
723
|
+
function alreadyExcludesPi(rawText) {
|
|
724
|
+
try {
|
|
725
|
+
const parsed = JSON.parse(stripJsonComments(rawText));
|
|
726
|
+
if (!Array.isArray(parsed.exclude)) return false;
|
|
727
|
+
return parsed.exclude.some(
|
|
728
|
+
(e) => typeof e === "string" && (e === PI_EXCLUDE || e === "./.pi" || e === ".pi/**")
|
|
729
|
+
);
|
|
730
|
+
} catch {
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function insertPiExclude(rawText) {
|
|
735
|
+
const excludeArrayRe = /("exclude"\s*:\s*\[)(\s*)/;
|
|
736
|
+
if (excludeArrayRe.test(rawText)) {
|
|
737
|
+
return rawText.replace(excludeArrayRe, (_m, open, ws) => {
|
|
738
|
+
const sep = ws.includes("\n") ? ws : " ";
|
|
739
|
+
return `${open}${sep}"${PI_EXCLUDE}",${ws.includes("\n") ? "" : " "}`;
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
const includeArrayRe = /("include"\s*:\s*\[[^\]]*\])/;
|
|
743
|
+
if (includeArrayRe.test(rawText)) {
|
|
744
|
+
return rawText.replace(includeArrayRe, (m) => `${m},
|
|
745
|
+
"exclude": ["${PI_EXCLUDE}"]`);
|
|
746
|
+
}
|
|
747
|
+
const lastBrace = rawText.lastIndexOf("}");
|
|
748
|
+
if (lastBrace > 0 && rawText.slice(0, lastBrace).includes(":")) {
|
|
749
|
+
const before = rawText.slice(0, lastBrace).replace(/\s*$/, "");
|
|
750
|
+
const after = rawText.slice(lastBrace);
|
|
751
|
+
const needsComma = !before.endsWith(",") && !before.endsWith("{");
|
|
752
|
+
return `${before}${needsComma ? "," : ""}
|
|
753
|
+
"exclude": ["${PI_EXCLUDE}"]
|
|
754
|
+
${after}`;
|
|
755
|
+
}
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
function ensurePiExcludedFromTsconfig(targetDir) {
|
|
759
|
+
const path = join4(targetDir, "tsconfig.json");
|
|
760
|
+
if (!existsSync4(path)) return { status: "no-tsconfig" };
|
|
761
|
+
let rawText;
|
|
762
|
+
try {
|
|
763
|
+
rawText = readFileSync3(path, "utf-8");
|
|
764
|
+
} catch {
|
|
765
|
+
return { status: "skipped", reason: "tsconfig.json could not be read" };
|
|
766
|
+
}
|
|
767
|
+
if (alreadyExcludesPi(rawText)) {
|
|
768
|
+
return { status: "already-present", path };
|
|
769
|
+
}
|
|
770
|
+
const updated = insertPiExclude(rawText);
|
|
771
|
+
if (updated === null || updated === rawText) {
|
|
772
|
+
return {
|
|
773
|
+
status: "skipped",
|
|
774
|
+
reason: 'could not safely edit tsconfig.json \u2014 add ".pi" to its "exclude" array manually so the Pi extension stays out of your TypeScript build'
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
if (!alreadyExcludesPi(updated)) {
|
|
778
|
+
return {
|
|
779
|
+
status: "skipped",
|
|
780
|
+
reason: 'tsconfig.json edit could not be verified \u2014 add ".pi" to its "exclude" array manually'
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
try {
|
|
784
|
+
writeFileSync2(path, updated, "utf-8");
|
|
785
|
+
} catch {
|
|
786
|
+
return { status: "skipped", reason: "tsconfig.json could not be written" };
|
|
787
|
+
}
|
|
788
|
+
return { status: "added", path };
|
|
789
|
+
}
|
|
790
|
+
|
|
705
791
|
// src/init.ts
|
|
706
792
|
function ensureDir(dir) {
|
|
707
|
-
if (!
|
|
793
|
+
if (!existsSync5(dir)) {
|
|
708
794
|
mkdirSync2(dir, { recursive: true });
|
|
709
795
|
}
|
|
710
796
|
}
|
|
711
797
|
function writeFile(path, content, force, result) {
|
|
712
|
-
if (
|
|
798
|
+
if (existsSync5(path) && !force) {
|
|
713
799
|
result.skipped.push(path);
|
|
714
800
|
return;
|
|
715
801
|
}
|
|
716
|
-
|
|
802
|
+
writeFileSync3(path, content, "utf-8");
|
|
717
803
|
result.created.push(path);
|
|
718
804
|
}
|
|
719
805
|
async function init(dir, opts) {
|
|
720
806
|
const targetDir = resolve(dir);
|
|
721
807
|
const result = { created: [], skipped: [], modified: [], warnings: [] };
|
|
722
808
|
const stack = await detectStack(targetDir);
|
|
723
|
-
const isPi =
|
|
809
|
+
const isPi = existsSync5(join5(targetDir, ".pi"));
|
|
810
|
+
const harnesses = await resolveHarnesses(process.stdin.isTTY === true);
|
|
811
|
+
const wants = (h) => harnesses.includes(h);
|
|
812
|
+
if (harnesses.length === 0) {
|
|
813
|
+
console.log(
|
|
814
|
+
"\nNo harness selected \u2014 Joycraft will not install any skills.\nPlease run init again and select at least one harness (claude, codex, pi)."
|
|
815
|
+
);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
724
818
|
const { profile: gitignoreProfile } = await resolveGitignoreProfile({
|
|
725
819
|
flag: opts.gitignore,
|
|
726
820
|
persisted: readVersion(targetDir)?.gitignoreProfile,
|
|
727
821
|
interactive: process.stdin.isTTY === true,
|
|
728
822
|
promptIntro: "\nHow should Joycraft files be tracked in git?"
|
|
729
823
|
});
|
|
730
|
-
ensureDir(
|
|
731
|
-
const skillsDir =
|
|
824
|
+
ensureDir(join5(targetDir, "docs", "context"));
|
|
825
|
+
const skillsDir = join5(targetDir, ".claude", "skills");
|
|
732
826
|
let existingSkills = [];
|
|
733
|
-
if (
|
|
827
|
+
if (wants("claude") && existsSync5(skillsDir)) {
|
|
734
828
|
existingSkills = readdirSync2(skillsDir).filter((name) => {
|
|
735
829
|
if (name.startsWith("joycraft-")) return false;
|
|
736
830
|
if (name.startsWith(".")) return false;
|
|
737
|
-
const fullPath =
|
|
831
|
+
const fullPath = join5(skillsDir, name);
|
|
738
832
|
try {
|
|
739
833
|
return statSync(fullPath).isDirectory();
|
|
740
834
|
} catch {
|
|
@@ -742,115 +836,128 @@ async function init(dir, opts) {
|
|
|
742
836
|
}
|
|
743
837
|
});
|
|
744
838
|
}
|
|
745
|
-
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
839
|
+
if (wants("claude")) {
|
|
840
|
+
for (const [filename, content] of Object.entries(SKILLS)) {
|
|
841
|
+
const skillName = filename.replace(/\.md$/, "");
|
|
842
|
+
const skillDir = join5(skillsDir, skillName);
|
|
843
|
+
ensureDir(skillDir);
|
|
844
|
+
writeFile(join5(skillDir, "SKILL.md"), content, opts.force, result);
|
|
845
|
+
}
|
|
750
846
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
return statSync(fullPath).isDirectory();
|
|
760
|
-
} catch {
|
|
761
|
-
return false;
|
|
762
|
-
}
|
|
763
|
-
});
|
|
847
|
+
if (wants("codex")) {
|
|
848
|
+
const codexSkillsDir = join5(targetDir, ".agents", "skills");
|
|
849
|
+
for (const [filename, content] of Object.entries(CODEX_SKILLS)) {
|
|
850
|
+
const skillName = filename.replace(/\.md$/, "");
|
|
851
|
+
const skillDir = join5(codexSkillsDir, skillName);
|
|
852
|
+
ensureDir(skillDir);
|
|
853
|
+
writeFile(join5(skillDir, "SKILL.md"), content, opts.force, result);
|
|
854
|
+
}
|
|
764
855
|
}
|
|
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 {
|
|
856
|
+
if (wants("pi")) {
|
|
857
|
+
const piSkillsDir = join5(targetDir, ".pi", "skills");
|
|
858
|
+
for (const [filename, content] of Object.entries(PI_SKILLS)) {
|
|
859
|
+
const skillName = filename.replace(/\.md$/, "");
|
|
860
|
+
const skillDir = join5(piSkillsDir, skillName);
|
|
861
|
+
ensureDir(skillDir);
|
|
862
|
+
writeFile(join5(skillDir, "SKILL.md"), content, opts.force, result);
|
|
863
|
+
}
|
|
864
|
+
const piScriptsDir = join5(targetDir, ".pi", "scripts", "joycraft");
|
|
865
|
+
ensureDir(piScriptsDir);
|
|
866
|
+
for (const [name, content] of Object.entries(PI_SCRIPTS)) {
|
|
867
|
+
const scriptPath = join5(piScriptsDir, name);
|
|
868
|
+
writeFile(scriptPath, content, opts.force, result);
|
|
869
|
+
if (name !== "README.md") {
|
|
870
|
+
try {
|
|
871
|
+
chmodSync(scriptPath, 493);
|
|
872
|
+
} catch {
|
|
873
|
+
}
|
|
787
874
|
}
|
|
788
875
|
}
|
|
876
|
+
const piExtDir = join5(targetDir, ".pi", "extensions");
|
|
877
|
+
ensureDir(piExtDir);
|
|
878
|
+
for (const [name, content] of Object.entries(PI_EXTENSIONS)) {
|
|
879
|
+
writeFile(join5(piExtDir, name), content, opts.force, result);
|
|
880
|
+
}
|
|
881
|
+
const piAgentsDir = join5(targetDir, ".pi", "agents");
|
|
882
|
+
ensureDir(piAgentsDir);
|
|
883
|
+
for (const [name, content] of Object.entries(PI_AGENTS)) {
|
|
884
|
+
writeFile(join5(piAgentsDir, name), content, opts.force, result);
|
|
885
|
+
}
|
|
789
886
|
}
|
|
790
|
-
const
|
|
791
|
-
ensureDir(piExtDir);
|
|
792
|
-
for (const [name, content] of Object.entries(PI_EXTENSIONS)) {
|
|
793
|
-
writeFile(join4(piExtDir, name), content, opts.force, result);
|
|
794
|
-
}
|
|
795
|
-
const piAgentsDir = join4(targetDir, ".pi", "agents");
|
|
796
|
-
ensureDir(piAgentsDir);
|
|
797
|
-
for (const [name, content] of Object.entries(PI_AGENTS)) {
|
|
798
|
-
writeFile(join4(piAgentsDir, name), content, opts.force, result);
|
|
799
|
-
}
|
|
800
|
-
const templatesDir = join4(targetDir, "docs", "templates");
|
|
887
|
+
const templatesDir = join5(targetDir, "docs", "templates");
|
|
801
888
|
ensureDir(templatesDir);
|
|
802
889
|
for (const [filename, content] of Object.entries(TEMPLATES)) {
|
|
803
|
-
ensureDir(dirname(
|
|
804
|
-
writeFile(
|
|
890
|
+
ensureDir(dirname(join5(templatesDir, filename)));
|
|
891
|
+
writeFile(join5(templatesDir, filename), content, opts.force, result);
|
|
805
892
|
}
|
|
806
|
-
const claudeMdPath =
|
|
807
|
-
if (
|
|
893
|
+
const claudeMdPath = join5(targetDir, "CLAUDE.md");
|
|
894
|
+
if (existsSync5(claudeMdPath) && !opts.force) {
|
|
808
895
|
result.skipped.push(claudeMdPath);
|
|
809
896
|
} else {
|
|
810
897
|
const projectName = basename(targetDir);
|
|
811
|
-
const content = generateCLAUDEMd(projectName, stack, existingSkills
|
|
812
|
-
|
|
898
|
+
const content = generateCLAUDEMd(projectName, stack, existingSkills, {
|
|
899
|
+
privateProfile: gitignoreProfile === "private"
|
|
900
|
+
});
|
|
901
|
+
writeFileSync3(claudeMdPath, content, "utf-8");
|
|
813
902
|
result.created.push(claudeMdPath);
|
|
814
903
|
}
|
|
815
|
-
const agentsMdPath =
|
|
816
|
-
if (
|
|
904
|
+
const agentsMdPath = join5(targetDir, "AGENTS.md");
|
|
905
|
+
if (existsSync5(agentsMdPath) && !opts.force) {
|
|
817
906
|
result.skipped.push(agentsMdPath);
|
|
818
907
|
} else {
|
|
819
908
|
const projectName = basename(targetDir);
|
|
820
|
-
const content = generateAgentsMd(projectName, stack);
|
|
821
|
-
|
|
909
|
+
const content = generateAgentsMd(projectName, stack, gitignoreProfile === "private");
|
|
910
|
+
writeFileSync3(agentsMdPath, content, "utf-8");
|
|
822
911
|
result.created.push(agentsMdPath);
|
|
823
912
|
}
|
|
824
913
|
const fileHashes = {};
|
|
825
|
-
|
|
826
|
-
const
|
|
827
|
-
|
|
914
|
+
if (wants("claude")) {
|
|
915
|
+
for (const [filename, content] of Object.entries(SKILLS)) {
|
|
916
|
+
const skillName = filename.replace(/\.md$/, "");
|
|
917
|
+
fileHashes[join5(".claude", "skills", skillName, "SKILL.md")] = hashContent(content);
|
|
918
|
+
}
|
|
828
919
|
}
|
|
829
|
-
|
|
830
|
-
const
|
|
831
|
-
|
|
920
|
+
if (wants("codex")) {
|
|
921
|
+
for (const [filename, content] of Object.entries(CODEX_SKILLS)) {
|
|
922
|
+
const skillName = filename.replace(/\.md$/, "");
|
|
923
|
+
fileHashes[join5(".agents", "skills", skillName, "SKILL.md")] = hashContent(content);
|
|
924
|
+
}
|
|
832
925
|
}
|
|
833
926
|
for (const [filename, content] of Object.entries(TEMPLATES)) {
|
|
834
|
-
fileHashes[
|
|
835
|
-
}
|
|
836
|
-
for (const [filename, content] of Object.entries(PI_SKILLS)) {
|
|
837
|
-
const skillName = filename.replace(/\.md$/, "");
|
|
838
|
-
fileHashes[join4(".pi", "skills", skillName, "SKILL.md")] = hashContent(content);
|
|
839
|
-
}
|
|
840
|
-
for (const [name, content] of Object.entries(PI_SCRIPTS)) {
|
|
841
|
-
fileHashes[join4(".pi", "scripts", "joycraft", name)] = hashContent(content);
|
|
927
|
+
fileHashes[join5("docs", "templates", filename)] = hashContent(content);
|
|
842
928
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
929
|
+
if (wants("pi")) {
|
|
930
|
+
for (const [filename, content] of Object.entries(PI_SKILLS)) {
|
|
931
|
+
const skillName = filename.replace(/\.md$/, "");
|
|
932
|
+
fileHashes[join5(".pi", "skills", skillName, "SKILL.md")] = hashContent(content);
|
|
933
|
+
}
|
|
934
|
+
for (const [name, content] of Object.entries(PI_SCRIPTS)) {
|
|
935
|
+
fileHashes[join5(".pi", "scripts", "joycraft", name)] = hashContent(content);
|
|
936
|
+
}
|
|
937
|
+
for (const [name, content] of Object.entries(PI_EXTENSIONS)) {
|
|
938
|
+
fileHashes[join5(".pi", "extensions", name)] = hashContent(content);
|
|
939
|
+
}
|
|
940
|
+
for (const [name, content] of Object.entries(PI_AGENTS)) {
|
|
941
|
+
fileHashes[join5(".pi", "agents", name)] = hashContent(content);
|
|
942
|
+
}
|
|
848
943
|
}
|
|
849
|
-
writeVersion(targetDir, getPackageVersion(), fileHashes, gitignoreProfile);
|
|
944
|
+
writeVersion(targetDir, getPackageVersion(), fileHashes, gitignoreProfile, harnesses);
|
|
850
945
|
applyGitignoreProfile(targetDir, gitignoreProfile);
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
946
|
+
if (wants("pi")) {
|
|
947
|
+
const outcome = ensurePiExcludedFromTsconfig(targetDir);
|
|
948
|
+
if (outcome.status === "added") {
|
|
949
|
+
result.modified.push(outcome.path);
|
|
950
|
+
result.warnings.push(
|
|
951
|
+
'Added ".pi" to tsconfig.json "exclude" so the Pi extension stays out of your TypeScript build.'
|
|
952
|
+
);
|
|
953
|
+
} else if (outcome.status === "skipped") {
|
|
954
|
+
result.warnings.push(outcome.reason);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (wants("claude")) {
|
|
958
|
+
const hooksDir = join5(targetDir, ".claude", "hooks");
|
|
959
|
+
ensureDir(hooksDir);
|
|
960
|
+
const hookScript = `// Joycraft version check \u2014 runs on Claude Code session start
|
|
854
961
|
import { readFileSync } from 'node:fs';
|
|
855
962
|
import { join } from 'node:path';
|
|
856
963
|
try {
|
|
@@ -862,72 +969,81 @@ try {
|
|
|
862
969
|
}
|
|
863
970
|
} catch {}
|
|
864
971
|
`;
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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);
|
|
900
|
-
if (existsSync4(settingsPath)) {
|
|
972
|
+
writeFile(join5(hooksDir, "joycraft-version-check.mjs"), hookScript, opts.force, result);
|
|
973
|
+
const settingsPath = join5(targetDir, ".claude", "settings.json");
|
|
974
|
+
let settings = {};
|
|
975
|
+
let settingsMalformed = false;
|
|
976
|
+
if (existsSync5(settingsPath)) {
|
|
901
977
|
try {
|
|
902
|
-
settings = JSON.parse(
|
|
978
|
+
settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
903
979
|
} catch {
|
|
980
|
+
settingsMalformed = true;
|
|
904
981
|
result.warnings.push(
|
|
905
|
-
"settings.json
|
|
982
|
+
"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
983
|
);
|
|
907
|
-
settingsMalformed = true;
|
|
908
984
|
}
|
|
909
985
|
}
|
|
910
986
|
if (!settingsMalformed) {
|
|
911
|
-
if (!settings.
|
|
912
|
-
const
|
|
913
|
-
if (!
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
987
|
+
if (!settings.hooks) settings.hooks = {};
|
|
988
|
+
const hooksConfig = settings.hooks;
|
|
989
|
+
if (!hooksConfig.SessionStart) hooksConfig.SessionStart = [];
|
|
990
|
+
const sessionStartHooks = hooksConfig.SessionStart;
|
|
991
|
+
const hasJoycraftHook = sessionStartHooks.some((h) => {
|
|
992
|
+
const innerHooks = h.hooks;
|
|
993
|
+
return innerHooks?.some((ih) => typeof ih.command === "string" && ih.command.includes("joycraft"));
|
|
994
|
+
});
|
|
995
|
+
if (!settings.env) settings.env = {};
|
|
996
|
+
const env = settings.env;
|
|
997
|
+
const envMissing = !("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS" in env);
|
|
998
|
+
if (envMissing) {
|
|
999
|
+
env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
|
|
1000
|
+
}
|
|
1001
|
+
if (!hasJoycraftHook) {
|
|
1002
|
+
sessionStartHooks.push({
|
|
1003
|
+
matcher: "",
|
|
1004
|
+
hooks: [{
|
|
1005
|
+
type: "command",
|
|
1006
|
+
command: "node .claude/hooks/joycraft-version-check.mjs"
|
|
1007
|
+
}]
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
if (!hasJoycraftHook || envMissing) {
|
|
1011
|
+
writeFileSync3(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
1012
|
+
if (!result.created.includes(settingsPath)) result.created.push(settingsPath);
|
|
917
1013
|
}
|
|
918
|
-
|
|
919
|
-
|
|
1014
|
+
const permissions = generatePermissions(stack);
|
|
1015
|
+
if (existsSync5(settingsPath)) {
|
|
1016
|
+
try {
|
|
1017
|
+
settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
1018
|
+
} catch {
|
|
1019
|
+
result.warnings.push(
|
|
1020
|
+
"settings.json became unreadable after hook merge \u2014 skipping permissions merge.\n Fix the JSON in .claude/settings.json and re-run init."
|
|
1021
|
+
);
|
|
1022
|
+
settingsMalformed = true;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if (!settingsMalformed) {
|
|
1026
|
+
if (!settings.permissions) settings.permissions = {};
|
|
1027
|
+
const perms = settings.permissions;
|
|
1028
|
+
if (!perms.allow) perms.allow = [];
|
|
1029
|
+
if (!perms.deny) perms.deny = [];
|
|
1030
|
+
for (const rule of permissions.allow) {
|
|
1031
|
+
if (!perms.allow.includes(rule)) perms.allow.push(rule);
|
|
1032
|
+
}
|
|
1033
|
+
for (const rule of permissions.deny) {
|
|
1034
|
+
if (!perms.deny.includes(rule)) perms.deny.push(rule);
|
|
1035
|
+
}
|
|
1036
|
+
writeFileSync3(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
920
1037
|
}
|
|
921
|
-
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
922
1038
|
}
|
|
1039
|
+
const hookResult = installSafeguardHooks(targetDir, [], opts.force, settingsMalformed);
|
|
1040
|
+
result.created.push(...hookResult.created);
|
|
1041
|
+
result.skipped.push(...hookResult.skipped);
|
|
923
1042
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const gitignorePath = join4(targetDir, ".gitignore");
|
|
929
|
-
if (existsSync4(gitignorePath)) {
|
|
930
|
-
const gitignore = readFileSync3(gitignorePath, "utf-8");
|
|
1043
|
+
if (gitignoreProfile === "shared" && wants("claude")) {
|
|
1044
|
+
const gitignorePath = join5(targetDir, ".gitignore");
|
|
1045
|
+
if (existsSync5(gitignorePath)) {
|
|
1046
|
+
const gitignore = readFileSync4(gitignorePath, "utf-8");
|
|
931
1047
|
if (/^\.claude\/?$/m.test(gitignore) || /^\.claude\/\*$/m.test(gitignore)) {
|
|
932
1048
|
result.warnings.push(
|
|
933
1049
|
".claude/ is in your .gitignore \u2014 teammates won't get Joycraft skills.\n Add this line to .gitignore to fix: !.claude/skills/"
|
|
@@ -935,10 +1051,13 @@ try {
|
|
|
935
1051
|
}
|
|
936
1052
|
}
|
|
937
1053
|
}
|
|
938
|
-
printSummary(result, stack, existingSkills, isPi, gitignoreProfile);
|
|
1054
|
+
printSummary(result, stack, existingSkills, isPi, gitignoreProfile, harnesses);
|
|
939
1055
|
}
|
|
940
|
-
function printSummary(result, stack, existingSkills = [], isPi = false, gitignoreProfile = DEFAULT_GITIGNORE_PROFILE) {
|
|
1056
|
+
function printSummary(result, stack, existingSkills = [], isPi = false, gitignoreProfile = DEFAULT_GITIGNORE_PROFILE, harnesses = []) {
|
|
941
1057
|
console.log("\nJoycraft initialized!\n");
|
|
1058
|
+
if (harnesses.length > 0) {
|
|
1059
|
+
console.log(` Installed harnesses: ${harnesses.join(", ")}`);
|
|
1060
|
+
}
|
|
942
1061
|
if (stack.language !== "unknown") {
|
|
943
1062
|
const fw = stack.framework ? ` + ${stack.framework}` : "";
|
|
944
1063
|
console.log(` Detected stack: ${stack.language}${fw} (${stack.packageManager})`);
|
|
@@ -1008,4 +1127,4 @@ function printSummary(result, stack, existingSkills = [], isPi = false, gitignor
|
|
|
1008
1127
|
export {
|
|
1009
1128
|
init
|
|
1010
1129
|
};
|
|
1011
|
-
//# sourceMappingURL=init-
|
|
1130
|
+
//# sourceMappingURL=init-WPKDBQDN.js.map
|