@puzzmo/cli 1.0.8 → 1.0.9
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.
|
@@ -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
|
-
|
|
112
|
+
p.log.step("Installing Puzzmo skills...");
|
|
132
113
|
const count = installSkills(selectedAgent, gameDir, opts.pm);
|
|
133
|
-
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@puzzmo/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
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
|
-
|
|
129
|
+
p.log.step("Installing Puzzmo skills...")
|
|
157
130
|
const count = installSkills(selectedAgent, gameDir, opts.pm)
|
|
158
|
-
|
|
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
|
}
|