@puzzmo/cli 1.0.8 → 1.0.10
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/lib/commands/game/create.js +45 -67
- package/lib/commands/game/create.js.map +1 -1
- package/lib/skills/registry.d.ts +2 -1
- package/lib/skills/registry.js +12 -51
- package/lib/skills/registry.js.map +1 -1
- package/package.json +2 -1
- package/src/commands/game/create.ts +48 -72
- package/src/skills/registry.ts +13 -54
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
3
4
|
import { detectAgent } from "../../wizard/agent-detect.js";
|
|
4
5
|
import { downloadPage } from "../../download/page-downloader.js";
|
|
5
6
|
import { runCommand, gitCommit } from "../../util/exec.js";
|
|
@@ -31,37 +32,6 @@ const parseArgs = (args) => {
|
|
|
31
32
|
}
|
|
32
33
|
return opts;
|
|
33
34
|
};
|
|
34
|
-
/** Interactive text prompt (simple readline-based fallback) */
|
|
35
|
-
const askText = async (message, defaultValue) => {
|
|
36
|
-
const { createInterface } = await import("node:readline");
|
|
37
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
38
|
-
const prompt = defaultValue ? `${message} (${defaultValue}): ` : `${message}: `;
|
|
39
|
-
return new Promise((resolve) => {
|
|
40
|
-
rl.question(prompt, (answer) => {
|
|
41
|
-
rl.close();
|
|
42
|
-
resolve(answer.trim() || defaultValue || "");
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
/** Interactive select prompt */
|
|
47
|
-
const askSelect = async (message, options) => {
|
|
48
|
-
console.log(`\n${message}`);
|
|
49
|
-
const enabled = options.filter((o) => !o.disabled);
|
|
50
|
-
options.forEach((opt) => {
|
|
51
|
-
const prefix = opt.disabled ? " " : ` ${enabled.indexOf(opt) + 1}.`;
|
|
52
|
-
const suffix = opt.disabled ? " (Coming soon)" : "";
|
|
53
|
-
console.log(`${prefix} ${opt.label}${suffix}`);
|
|
54
|
-
});
|
|
55
|
-
const { createInterface } = await import("node:readline");
|
|
56
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
57
|
-
return new Promise((resolve) => {
|
|
58
|
-
rl.question(`\nChoice (1-${enabled.length}): `, (answer) => {
|
|
59
|
-
rl.close();
|
|
60
|
-
const idx = parseInt(answer.trim(), 10) - 1;
|
|
61
|
-
resolve(enabled[idx]?.value ?? enabled[0].value);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
};
|
|
65
35
|
/** Converts a string to a URL-friendly slug */
|
|
66
36
|
const slugify = (str) => str
|
|
67
37
|
.toLowerCase()
|
|
@@ -70,89 +40,97 @@ const slugify = (str) => str
|
|
|
70
40
|
/** Main game create wizard */
|
|
71
41
|
export const gameCreate = async (args) => {
|
|
72
42
|
const opts = parseArgs(args);
|
|
73
|
-
|
|
43
|
+
p.intro("Puzzmo Game Creator");
|
|
74
44
|
// Step 1: Mode selection
|
|
75
|
-
const mode = await
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
45
|
+
const mode = await p.select({
|
|
46
|
+
message: "How would you like to create your game?",
|
|
47
|
+
options: [
|
|
48
|
+
{ value: "import", label: "Import an HTML game from a web page" },
|
|
49
|
+
{ value: "new-repo", label: "Create game in a new repo", hint: "coming soon" },
|
|
50
|
+
{ value: "add-to-repo", label: "Add a game to this repo", hint: "coming soon" },
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
if (p.isCancel(mode))
|
|
54
|
+
process.exit(0);
|
|
80
55
|
if (mode !== "import") {
|
|
81
|
-
|
|
56
|
+
p.log.warn("This mode is not yet supported.");
|
|
82
57
|
process.exit(0);
|
|
83
58
|
}
|
|
84
59
|
// Step 2: Source URL
|
|
85
|
-
|
|
60
|
+
let url = opts.url;
|
|
86
61
|
if (!url) {
|
|
87
|
-
|
|
88
|
-
|
|
62
|
+
url = (await p.text({ message: "Source URL to import", validate: (v) => (!v ? "URL is required" : undefined) }));
|
|
63
|
+
if (p.isCancel(url))
|
|
64
|
+
process.exit(0);
|
|
89
65
|
}
|
|
90
66
|
// Step 3: Download page to a temp dir, extract title
|
|
91
67
|
const tmpDir = path.resolve(".puzzmo-import-tmp");
|
|
92
|
-
|
|
68
|
+
const s = p.spinner();
|
|
69
|
+
s.start(`Downloading ${url}`);
|
|
93
70
|
const { title } = await downloadPage(url, tmpDir);
|
|
94
|
-
|
|
71
|
+
s.stop("Download complete.");
|
|
95
72
|
// Step 4: Game name (default to HTML title if available)
|
|
96
73
|
const defaultName = opts.name || title;
|
|
97
|
-
const name = await
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
74
|
+
const name = (await p.text({
|
|
75
|
+
message: "Game name",
|
|
76
|
+
initialValue: defaultName,
|
|
77
|
+
validate: (v) => (!v ? "Game name is required" : undefined),
|
|
78
|
+
}));
|
|
79
|
+
if (p.isCancel(name))
|
|
80
|
+
process.exit(0);
|
|
102
81
|
// Step 5: Move downloaded files to slugified directory
|
|
103
82
|
const slug = slugify(name);
|
|
104
83
|
const gameDir = path.resolve(slug);
|
|
105
84
|
fs.renameSync(tmpDir, gameDir);
|
|
106
|
-
// Step
|
|
85
|
+
// Step 6: Detect agents
|
|
107
86
|
const agents = detectAgent();
|
|
108
87
|
const agentChoices = [
|
|
109
88
|
...agents.map((a) => ({ value: a.binary, label: `${a.displayName} (${a.path})` })),
|
|
110
89
|
{ value: "none", label: "None - I'll run the skills manually" },
|
|
111
90
|
];
|
|
112
|
-
// Step
|
|
91
|
+
// Step 7: Ask about agent
|
|
113
92
|
let selectedAgent;
|
|
114
93
|
if (opts.agent) {
|
|
115
94
|
selectedAgent = opts.agent;
|
|
116
95
|
}
|
|
117
96
|
else if (agents.length > 0) {
|
|
118
|
-
selectedAgent = await
|
|
97
|
+
selectedAgent = (await p.select({ message: "Which LLM agent do you use?", options: agentChoices }));
|
|
98
|
+
if (p.isCancel(selectedAgent))
|
|
99
|
+
process.exit(0);
|
|
119
100
|
}
|
|
120
101
|
else {
|
|
121
|
-
|
|
102
|
+
p.log.info("No LLM agents detected (checked: claude, codex)");
|
|
122
103
|
selectedAgent = "none";
|
|
123
104
|
}
|
|
124
|
-
// Step
|
|
105
|
+
// Step 8: Login if token provided
|
|
125
106
|
if (opts.accessToken) {
|
|
126
|
-
|
|
107
|
+
p.log.step("Logging in...");
|
|
127
108
|
login(opts.accessToken);
|
|
128
109
|
}
|
|
129
|
-
// Step
|
|
110
|
+
// Step 9: Install skills into the game directory
|
|
130
111
|
if (selectedAgent !== "none") {
|
|
131
|
-
|
|
132
|
-
const count = installSkills(selectedAgent, gameDir
|
|
133
|
-
|
|
112
|
+
p.log.step("Installing Puzzmo skills...");
|
|
113
|
+
const count = installSkills(selectedAgent, gameDir);
|
|
114
|
+
p.log.success(`Installed ${count} skill(s).`);
|
|
134
115
|
}
|
|
135
|
-
// Step
|
|
116
|
+
// Step 10: Initialize git
|
|
136
117
|
if (!fs.existsSync(path.join(gameDir, ".git"))) {
|
|
137
118
|
runCommand("git init", { cwd: gameDir });
|
|
138
119
|
runCommand("git add -A", { cwd: gameDir });
|
|
139
120
|
gitCommit("Initial game import", { cwd: gameDir });
|
|
140
121
|
}
|
|
141
|
-
// Step
|
|
122
|
+
// Step 11: Run skills pipeline
|
|
142
123
|
if (selectedAgent !== "none") {
|
|
143
|
-
|
|
124
|
+
p.log.step("Starting migration pipeline...");
|
|
144
125
|
await runSkillsPipelineTUI(selectedAgent, gameDir);
|
|
145
126
|
}
|
|
146
127
|
// Done
|
|
147
128
|
const pm = opts.pm || "npm";
|
|
148
129
|
const runCmd = pm === "npm" ? "npx" : pm === "yarn" ? "yarn dlx" : "pnpm dlx";
|
|
149
|
-
|
|
150
|
-
console.log(`\nNext steps:`);
|
|
151
|
-
console.log(` cd ${slug}`);
|
|
152
|
-
console.log(` ${runCmd} vite # Start development server`);
|
|
153
|
-
console.log(` ${runCmd} vite build # Build for production`);
|
|
130
|
+
p.note([`cd ${slug}`, `${runCmd} vite # Start development server`, `${runCmd} vite build # Build for production`].join("\n"), "Next steps");
|
|
154
131
|
if (selectedAgent === "none") {
|
|
155
|
-
|
|
132
|
+
p.log.info("To run migration skills manually, see packages/skills/ for SKILL.md files.");
|
|
156
133
|
}
|
|
134
|
+
p.outro(`Done! Your game is in ./${slug}/`);
|
|
157
135
|
};
|
|
158
136
|
//# sourceMappingURL=create.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/commands/game/create.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/commands/game/create.ts"],"names":[],"mappings":"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"}
|
package/lib/skills/registry.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ export type SkillDefinition = {
|
|
|
6
6
|
export declare const skillsPipeline: SkillDefinition[];
|
|
7
7
|
/** Returns the skills directory for a given agent inside the game dir */
|
|
8
8
|
export declare const agentSkillsDir: (agent: string) => string;
|
|
9
|
-
|
|
9
|
+
/** Installs all Puzzmo skills from the OSS repo using `npx skills add` */
|
|
10
|
+
export declare const installSkills: (agent: string, gameDir: string) => number;
|
package/lib/skills/registry.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
4
2
|
/** Ordered list of skills for the migration pipeline */
|
|
5
3
|
export const skillsPipeline = [
|
|
6
4
|
{ name: "convert-to-vite", optional: false },
|
|
@@ -22,54 +20,17 @@ export const agentSkillsDir = (agent) => {
|
|
|
22
20
|
return ".gemini/skills";
|
|
23
21
|
return `.${agent}/skills`;
|
|
24
22
|
};
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
.replace(/npm install --save-dev /g, "yarn add --dev ")
|
|
36
|
-
.replace(/npm install /g, "yarn add ")
|
|
37
|
-
.replace(/npm run /g, "yarn ")
|
|
38
|
-
.replace(/npx /g, "yarn dlx ");
|
|
23
|
+
/** Installs all Puzzmo skills from the OSS repo using `npx skills add` */
|
|
24
|
+
export const installSkills = (agent, gameDir) => {
|
|
25
|
+
const result = spawnSync("npx", ["skills", "add", "puzzmo-com/oss", "--agent", agent, "--skill", "*", "--copy", "-y"], {
|
|
26
|
+
cwd: gameDir,
|
|
27
|
+
stdio: "inherit",
|
|
28
|
+
encoding: "utf-8",
|
|
29
|
+
});
|
|
30
|
+
if (result.status !== 0) {
|
|
31
|
+
console.error("Failed to install skills from puzzmo-com/oss");
|
|
32
|
+
process.exit(1);
|
|
39
33
|
}
|
|
40
|
-
|
|
41
|
-
return content
|
|
42
|
-
.replace(/npm install --save-dev /g, "pnpm add --save-dev ")
|
|
43
|
-
.replace(/npm install /g, "pnpm add ")
|
|
44
|
-
.replace(/npm run /g, "pnpm ")
|
|
45
|
-
.replace(/npx /g, "pnpm dlx ");
|
|
46
|
-
}
|
|
47
|
-
return content;
|
|
48
|
-
};
|
|
49
|
-
export const installSkills = (agent, gameDir, pm) => {
|
|
50
|
-
const skillsRoot = agentSkillsDir(agent);
|
|
51
|
-
// Locate the bundled skills source (packages/skills/ relative to this file)
|
|
52
|
-
// This file is at packages/cli/src/skills/registry.ts (or lib/skills/registry.js when built)
|
|
53
|
-
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
54
|
-
// Walk up to packages/cli, then over to packages/skills
|
|
55
|
-
const packagesDir = path.resolve(thisDir, "..", "..", "..");
|
|
56
|
-
const skillsSrcDir = path.join(packagesDir, "skills");
|
|
57
|
-
let installed = 0;
|
|
58
|
-
for (const skill of skillsPipeline) {
|
|
59
|
-
const srcFile = path.join(skillsSrcDir, skill.name, "SKILL.md");
|
|
60
|
-
if (!fs.existsSync(srcFile))
|
|
61
|
-
continue;
|
|
62
|
-
const destDir = path.join(gameDir, skillsRoot, skill.name);
|
|
63
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
64
|
-
if (pm && pm !== "npm") {
|
|
65
|
-
const content = fs.readFileSync(srcFile, "utf-8");
|
|
66
|
-
fs.writeFileSync(path.join(destDir, "SKILL.md"), rewriteForPM(content, pm));
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
fs.copyFileSync(srcFile, path.join(destDir, "SKILL.md"));
|
|
70
|
-
}
|
|
71
|
-
installed++;
|
|
72
|
-
}
|
|
73
|
-
return installed;
|
|
34
|
+
return skillsPipeline.length;
|
|
74
35
|
};
|
|
75
36
|
//# sourceMappingURL=registry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/skills/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/skills/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAO9C,wDAAwD;AACxD,MAAM,CAAC,MAAM,cAAc,GAAsB;IAC/C,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,KAAK,EAAE;IAC5C,EAAE,IAAI,EAAE,sBAAsB,EAAE,QAAQ,EAAE,KAAK,EAAE;IACjD,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,KAAK,EAAE;IAC5C,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE;IACxC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE;IACtC,EAAE,IAAI,EAAE,qBAAqB,EAAE,QAAQ,EAAE,KAAK,EAAE;IAChD,EAAE,IAAI,EAAE,mBAAmB,EAAE,QAAQ,EAAE,KAAK,EAAE;IAC9C,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,KAAK,EAAE;CAC1C,CAAA;AAED,yEAAyE;AACzE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAa,EAAU,EAAE;IACtD,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAA;IAC/C,IAAI,KAAK,KAAK,OAAO;QAAE,OAAO,eAAe,CAAA;IAC7C,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAA;IAC/C,OAAO,IAAI,KAAK,SAAS,CAAA;AAC3B,CAAC,CAAA;AAED,0EAA0E;AAC1E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAa,EAAE,OAAe,EAAU,EAAE;IACtE,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE;QACrH,GAAG,EAAE,OAAO;QACZ,KAAK,EAAE,SAAS;QAChB,QAAQ,EAAE,OAAO;KAClB,CAAC,CAAA;IAEF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAA;QAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,OAAO,cAAc,CAAC,MAAM,CAAA;AAC9B,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@puzzmo/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"puzzmo": "./lib/index.js"
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"type-check": "tsc --noEmit"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
+
"@clack/prompts": "^1.1.0",
|
|
24
25
|
"@puzzmo/agent-cli-detect": "*"
|
|
25
26
|
}
|
|
26
27
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs"
|
|
2
2
|
import path from "node:path"
|
|
3
|
+
import * as p from "@clack/prompts"
|
|
3
4
|
|
|
4
5
|
import { detectAgent } from "../../wizard/agent-detect.js"
|
|
5
6
|
import { downloadPage } from "../../download/page-downloader.js"
|
|
@@ -40,42 +41,6 @@ const parseArgs = (args: string[]): CreateOptions => {
|
|
|
40
41
|
return opts
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
/** Interactive text prompt (simple readline-based fallback) */
|
|
44
|
-
const askText = async (message: string, defaultValue?: string): Promise<string> => {
|
|
45
|
-
const { createInterface } = await import("node:readline")
|
|
46
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
47
|
-
const prompt = defaultValue ? `${message} (${defaultValue}): ` : `${message}: `
|
|
48
|
-
|
|
49
|
-
return new Promise((resolve) => {
|
|
50
|
-
rl.question(prompt, (answer) => {
|
|
51
|
-
rl.close()
|
|
52
|
-
resolve(answer.trim() || defaultValue || "")
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Interactive select prompt */
|
|
58
|
-
const askSelect = async (message: string, options: { value: string; label: string; disabled?: boolean }[]): Promise<string> => {
|
|
59
|
-
console.log(`\n${message}`)
|
|
60
|
-
const enabled = options.filter((o) => !o.disabled)
|
|
61
|
-
options.forEach((opt) => {
|
|
62
|
-
const prefix = opt.disabled ? " " : ` ${enabled.indexOf(opt) + 1}.`
|
|
63
|
-
const suffix = opt.disabled ? " (Coming soon)" : ""
|
|
64
|
-
console.log(`${prefix} ${opt.label}${suffix}`)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const { createInterface } = await import("node:readline")
|
|
68
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
69
|
-
|
|
70
|
-
return new Promise((resolve) => {
|
|
71
|
-
rl.question(`\nChoice (1-${enabled.length}): `, (answer) => {
|
|
72
|
-
rl.close()
|
|
73
|
-
const idx = parseInt(answer.trim(), 10) - 1
|
|
74
|
-
resolve(enabled[idx]?.value ?? enabled[0].value)
|
|
75
|
-
})
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
|
|
79
44
|
/** Converts a string to a URL-friendly slug */
|
|
80
45
|
const slugify = (str: string): string =>
|
|
81
46
|
str
|
|
@@ -87,99 +52,110 @@ const slugify = (str: string): string =>
|
|
|
87
52
|
export const gameCreate = async (args: string[]) => {
|
|
88
53
|
const opts = parseArgs(args)
|
|
89
54
|
|
|
90
|
-
|
|
55
|
+
p.intro("Puzzmo Game Creator")
|
|
91
56
|
|
|
92
57
|
// Step 1: Mode selection
|
|
93
|
-
const mode = await
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
58
|
+
const mode = await p.select({
|
|
59
|
+
message: "How would you like to create your game?",
|
|
60
|
+
options: [
|
|
61
|
+
{ value: "import", label: "Import an HTML game from a web page" },
|
|
62
|
+
{ value: "new-repo", label: "Create game in a new repo", hint: "coming soon" },
|
|
63
|
+
{ value: "add-to-repo", label: "Add a game to this repo", hint: "coming soon" },
|
|
64
|
+
],
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
if (p.isCancel(mode)) process.exit(0)
|
|
98
68
|
|
|
99
69
|
if (mode !== "import") {
|
|
100
|
-
|
|
70
|
+
p.log.warn("This mode is not yet supported.")
|
|
101
71
|
process.exit(0)
|
|
102
72
|
}
|
|
103
73
|
|
|
104
74
|
// Step 2: Source URL
|
|
105
|
-
|
|
75
|
+
let url = opts.url
|
|
106
76
|
if (!url) {
|
|
107
|
-
|
|
108
|
-
process.exit(
|
|
77
|
+
url = (await p.text({ message: "Source URL to import", validate: (v) => (!v ? "URL is required" : undefined) })) as string
|
|
78
|
+
if (p.isCancel(url)) process.exit(0)
|
|
109
79
|
}
|
|
110
80
|
|
|
111
81
|
// Step 3: Download page to a temp dir, extract title
|
|
112
82
|
const tmpDir = path.resolve(".puzzmo-import-tmp")
|
|
113
|
-
|
|
83
|
+
const s = p.spinner()
|
|
84
|
+
s.start(`Downloading ${url}`)
|
|
114
85
|
const { title } = await downloadPage(url, tmpDir)
|
|
115
|
-
|
|
86
|
+
s.stop("Download complete.")
|
|
116
87
|
|
|
117
88
|
// Step 4: Game name (default to HTML title if available)
|
|
118
89
|
const defaultName = opts.name || title
|
|
119
|
-
const name = await
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
90
|
+
const name = (await p.text({
|
|
91
|
+
message: "Game name",
|
|
92
|
+
initialValue: defaultName,
|
|
93
|
+
validate: (v) => (!v ? "Game name is required" : undefined),
|
|
94
|
+
})) as string
|
|
95
|
+
if (p.isCancel(name)) process.exit(0)
|
|
124
96
|
|
|
125
97
|
// Step 5: Move downloaded files to slugified directory
|
|
126
98
|
const slug = slugify(name)
|
|
127
99
|
const gameDir = path.resolve(slug)
|
|
128
100
|
fs.renameSync(tmpDir, gameDir)
|
|
129
101
|
|
|
130
|
-
// Step
|
|
102
|
+
// Step 6: Detect agents
|
|
131
103
|
const agents = detectAgent()
|
|
132
104
|
const agentChoices = [
|
|
133
105
|
...agents.map((a) => ({ value: a.binary, label: `${a.displayName} (${a.path})` })),
|
|
134
106
|
{ value: "none", label: "None - I'll run the skills manually" },
|
|
135
107
|
]
|
|
136
108
|
|
|
137
|
-
// Step
|
|
109
|
+
// Step 7: Ask about agent
|
|
138
110
|
let selectedAgent: string
|
|
139
111
|
if (opts.agent) {
|
|
140
112
|
selectedAgent = opts.agent
|
|
141
113
|
} else if (agents.length > 0) {
|
|
142
|
-
selectedAgent = await
|
|
114
|
+
selectedAgent = (await p.select({ message: "Which LLM agent do you use?", options: agentChoices })) as string
|
|
115
|
+
if (p.isCancel(selectedAgent)) process.exit(0)
|
|
143
116
|
} else {
|
|
144
|
-
|
|
117
|
+
p.log.info("No LLM agents detected (checked: claude, codex)")
|
|
145
118
|
selectedAgent = "none"
|
|
146
119
|
}
|
|
147
120
|
|
|
148
|
-
// Step
|
|
121
|
+
// Step 8: Login if token provided
|
|
149
122
|
if (opts.accessToken) {
|
|
150
|
-
|
|
123
|
+
p.log.step("Logging in...")
|
|
151
124
|
login(opts.accessToken)
|
|
152
125
|
}
|
|
153
126
|
|
|
154
|
-
// Step
|
|
127
|
+
// Step 9: Install skills into the game directory
|
|
155
128
|
if (selectedAgent !== "none") {
|
|
156
|
-
|
|
157
|
-
const count = installSkills(selectedAgent, gameDir
|
|
158
|
-
|
|
129
|
+
p.log.step("Installing Puzzmo skills...")
|
|
130
|
+
const count = installSkills(selectedAgent, gameDir)
|
|
131
|
+
p.log.success(`Installed ${count} skill(s).`)
|
|
159
132
|
}
|
|
160
133
|
|
|
161
|
-
// Step
|
|
134
|
+
// Step 10: Initialize git
|
|
162
135
|
if (!fs.existsSync(path.join(gameDir, ".git"))) {
|
|
163
136
|
runCommand("git init", { cwd: gameDir })
|
|
164
137
|
runCommand("git add -A", { cwd: gameDir })
|
|
165
138
|
gitCommit("Initial game import", { cwd: gameDir })
|
|
166
139
|
}
|
|
167
140
|
|
|
168
|
-
// Step
|
|
141
|
+
// Step 11: Run skills pipeline
|
|
169
142
|
if (selectedAgent !== "none") {
|
|
170
|
-
|
|
143
|
+
p.log.step("Starting migration pipeline...")
|
|
171
144
|
await runSkillsPipelineTUI(selectedAgent, gameDir)
|
|
172
145
|
}
|
|
173
146
|
|
|
174
147
|
// Done
|
|
175
148
|
const pm = opts.pm || "npm"
|
|
176
149
|
const runCmd = pm === "npm" ? "npx" : pm === "yarn" ? "yarn dlx" : "pnpm dlx"
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
150
|
+
|
|
151
|
+
p.note(
|
|
152
|
+
[`cd ${slug}`, `${runCmd} vite # Start development server`, `${runCmd} vite build # Build for production`].join("\n"),
|
|
153
|
+
"Next steps",
|
|
154
|
+
)
|
|
155
|
+
|
|
182
156
|
if (selectedAgent === "none") {
|
|
183
|
-
|
|
157
|
+
p.log.info("To run migration skills manually, see packages/skills/ for SKILL.md files.")
|
|
184
158
|
}
|
|
159
|
+
|
|
160
|
+
p.outro(`Done! Your game is in ./${slug}/`)
|
|
185
161
|
}
|
package/src/skills/registry.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from "node:path"
|
|
3
|
-
import { fileURLToPath } from "node:url"
|
|
1
|
+
import { spawnSync } from "node:child_process"
|
|
4
2
|
|
|
5
3
|
export type SkillDefinition = {
|
|
6
4
|
name: string
|
|
@@ -27,57 +25,18 @@ export const agentSkillsDir = (agent: string): string => {
|
|
|
27
25
|
return `.${agent}/skills`
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
.replace(/npm run /g, "yarn ")
|
|
42
|
-
.replace(/npx /g, "yarn dlx ")
|
|
43
|
-
}
|
|
44
|
-
if (pm === "pnpm") {
|
|
45
|
-
return content
|
|
46
|
-
.replace(/npm install --save-dev /g, "pnpm add --save-dev ")
|
|
47
|
-
.replace(/npm install /g, "pnpm add ")
|
|
48
|
-
.replace(/npm run /g, "pnpm ")
|
|
49
|
-
.replace(/npx /g, "pnpm dlx ")
|
|
50
|
-
}
|
|
51
|
-
return content
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const installSkills = (agent: string, gameDir: string, pm?: string): number => {
|
|
55
|
-
const skillsRoot = agentSkillsDir(agent)
|
|
56
|
-
|
|
57
|
-
// Locate the bundled skills source (packages/skills/ relative to this file)
|
|
58
|
-
// This file is at packages/cli/src/skills/registry.ts (or lib/skills/registry.js when built)
|
|
59
|
-
const thisDir = path.dirname(fileURLToPath(import.meta.url))
|
|
60
|
-
// Walk up to packages/cli, then over to packages/skills
|
|
61
|
-
const packagesDir = path.resolve(thisDir, "..", "..", "..")
|
|
62
|
-
const skillsSrcDir = path.join(packagesDir, "skills")
|
|
63
|
-
|
|
64
|
-
let installed = 0
|
|
65
|
-
|
|
66
|
-
for (const skill of skillsPipeline) {
|
|
67
|
-
const srcFile = path.join(skillsSrcDir, skill.name, "SKILL.md")
|
|
68
|
-
if (!fs.existsSync(srcFile)) continue
|
|
69
|
-
|
|
70
|
-
const destDir = path.join(gameDir, skillsRoot, skill.name)
|
|
71
|
-
fs.mkdirSync(destDir, { recursive: true })
|
|
72
|
-
|
|
73
|
-
if (pm && pm !== "npm") {
|
|
74
|
-
const content = fs.readFileSync(srcFile, "utf-8")
|
|
75
|
-
fs.writeFileSync(path.join(destDir, "SKILL.md"), rewriteForPM(content, pm))
|
|
76
|
-
} else {
|
|
77
|
-
fs.copyFileSync(srcFile, path.join(destDir, "SKILL.md"))
|
|
78
|
-
}
|
|
79
|
-
installed++
|
|
28
|
+
/** Installs all Puzzmo skills from the OSS repo using `npx skills add` */
|
|
29
|
+
export const installSkills = (agent: string, gameDir: string): number => {
|
|
30
|
+
const result = spawnSync("npx", ["skills", "add", "puzzmo-com/oss", "--agent", agent, "--skill", "*", "--copy", "-y"], {
|
|
31
|
+
cwd: gameDir,
|
|
32
|
+
stdio: "inherit",
|
|
33
|
+
encoding: "utf-8",
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
if (result.status !== 0) {
|
|
37
|
+
console.error("Failed to install skills from puzzmo-com/oss")
|
|
38
|
+
process.exit(1)
|
|
80
39
|
}
|
|
81
40
|
|
|
82
|
-
return
|
|
41
|
+
return skillsPipeline.length
|
|
83
42
|
}
|