pm-workflow-studio 0.1.2 → 0.1.4
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 +16 -0
- package/bin/pmflow.js +167 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,20 @@ pm-workflow --help
|
|
|
17
17
|
|
|
18
18
|
## 快速开始
|
|
19
19
|
|
|
20
|
+
交互式:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pmflow
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
或:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pmflow init
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
CLI 会逐步询问产品名称、项目目录和 AI 工作区结构。适合不想记参数的日常使用。
|
|
33
|
+
|
|
20
34
|
Codex:
|
|
21
35
|
|
|
22
36
|
```bash
|
|
@@ -49,6 +63,7 @@ claude
|
|
|
49
63
|
|
|
50
64
|
```bash
|
|
51
65
|
pmflow init --ai auto --name "习惯打卡"
|
|
66
|
+
pmflow init --interactive
|
|
52
67
|
pmflow init --ai codex --root ./pm-workflow-demo --name "习惯打卡"
|
|
53
68
|
pmflow init --ai claude --root ./pm-workflow-claude-demo --name "习惯打卡"
|
|
54
69
|
```
|
|
@@ -56,6 +71,7 @@ pmflow init --ai claude --root ./pm-workflow-claude-demo --name "习惯打卡"
|
|
|
56
71
|
- `--ai auto|codex|claude`:选择生成结构。默认 `auto`;空目录默认 Codex;目录已有 `.claude/` 时选择 Claude Code。
|
|
57
72
|
- `--root <dir>`:目标项目目录,默认当前目录。
|
|
58
73
|
- `--name <product name>`:产品名称,默认 `My Product`。
|
|
74
|
+
- `--interactive` / `-i`:进入交互式创建向导。
|
|
59
75
|
- `--cli` 是 `--ai` 的别名。
|
|
60
76
|
|
|
61
77
|
当前支持 Codex 和 Claude Code;`kiro` 暂未支持。
|
package/bin/pmflow.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("node:fs");
|
|
4
4
|
const path = require("node:path");
|
|
5
|
+
const readline = require("node:readline");
|
|
5
6
|
|
|
6
7
|
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
7
8
|
const CODEX_MIRROR = path.join(PACKAGE_ROOT, ".codex");
|
|
@@ -26,6 +27,8 @@ function printHelp() {
|
|
|
26
27
|
console.log(`PM Workflow Studio
|
|
27
28
|
|
|
28
29
|
Usage:
|
|
30
|
+
pmflow
|
|
31
|
+
pmflow init
|
|
29
32
|
pmflow init [--ai auto|codex|claude] [--root <dir>] [--name <product name>]
|
|
30
33
|
pm-workflow init [--ai auto|codex|claude] [--root <dir>] [--name <product name>]
|
|
31
34
|
|
|
@@ -36,9 +39,13 @@ Options:
|
|
|
36
39
|
claude: generate .claude structure for Claude Code.
|
|
37
40
|
--root Target workspace directory. Defaults to current directory.
|
|
38
41
|
--name Product name for generated templates. Defaults to "My Product".
|
|
42
|
+
-i, --interactive
|
|
43
|
+
Start the interactive setup wizard.
|
|
39
44
|
-h, --help Show help.
|
|
40
45
|
|
|
41
46
|
Examples:
|
|
47
|
+
pmflow
|
|
48
|
+
pmflow init
|
|
42
49
|
pmflow init --ai codex --root ./pm-workflow-demo --name "习惯打卡"
|
|
43
50
|
pmflow init --ai claude --root ./pm-workflow-claude-demo --name "习惯打卡"
|
|
44
51
|
pmflow init --ai auto --name "习惯打卡"
|
|
@@ -54,6 +61,20 @@ function fail(message) {
|
|
|
54
61
|
process.exit(1);
|
|
55
62
|
}
|
|
56
63
|
|
|
64
|
+
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
65
|
+
const color = {
|
|
66
|
+
cyan: (value) => (useColor ? `\u001b[36m${value}\u001b[0m` : value),
|
|
67
|
+
dim: (value) => (useColor ? `\u001b[2m${value}\u001b[0m` : value),
|
|
68
|
+
green: (value) => (useColor ? `\u001b[32m${value}\u001b[0m` : value),
|
|
69
|
+
bold: (value) => (useColor ? `\u001b[1m${value}\u001b[0m` : value),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const symbol = {
|
|
73
|
+
pointer: useColor ? color.cyan("?") : "?",
|
|
74
|
+
done: useColor ? color.green("✔") : "✔",
|
|
75
|
+
arrow: useColor ? color.cyan("›") : "›",
|
|
76
|
+
};
|
|
77
|
+
|
|
57
78
|
function exists(target) {
|
|
58
79
|
return fs.existsSync(target);
|
|
59
80
|
}
|
|
@@ -89,6 +110,7 @@ function parseInitArgs(argv) {
|
|
|
89
110
|
ai: "auto",
|
|
90
111
|
root: ".",
|
|
91
112
|
name: "My Product",
|
|
113
|
+
interactive: false,
|
|
92
114
|
};
|
|
93
115
|
|
|
94
116
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -97,6 +119,10 @@ function parseInitArgs(argv) {
|
|
|
97
119
|
printHelp();
|
|
98
120
|
process.exit(0);
|
|
99
121
|
}
|
|
122
|
+
if (arg === "-i" || arg === "--interactive") {
|
|
123
|
+
options.interactive = true;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
100
126
|
if (arg === "--ai" || arg === "--cli") {
|
|
101
127
|
const value = argv[index + 1];
|
|
102
128
|
if (!value) fail(`${arg} requires a value.`);
|
|
@@ -140,6 +166,124 @@ function parseInitArgs(argv) {
|
|
|
140
166
|
return options;
|
|
141
167
|
}
|
|
142
168
|
|
|
169
|
+
function createPrompt() {
|
|
170
|
+
const rl = readline.createInterface({
|
|
171
|
+
input: process.stdin,
|
|
172
|
+
output: process.stdout,
|
|
173
|
+
});
|
|
174
|
+
return {
|
|
175
|
+
ask(question) {
|
|
176
|
+
return new Promise((resolve) => {
|
|
177
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
close() {
|
|
181
|
+
rl.close();
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function printInteractiveHeader() {
|
|
187
|
+
console.log("");
|
|
188
|
+
console.log(color.bold("PM Workflow Studio"));
|
|
189
|
+
console.log(color.dim("Create a Codex or Claude Code product workspace."));
|
|
190
|
+
console.log("");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function promptLine(label, defaultValue) {
|
|
194
|
+
const suffix = defaultValue ? ` ${color.dim(`(${defaultValue})`)}` : "";
|
|
195
|
+
return `${symbol.pointer} ${label}${suffix} ${symbol.arrow} `;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function printAnswer(label, value) {
|
|
199
|
+
console.log(`${symbol.done} ${label}: ${color.green(value)}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function askText(prompt, label, defaultValue) {
|
|
203
|
+
const answer = await prompt.ask(promptLine(label, defaultValue));
|
|
204
|
+
const value = answer || defaultValue;
|
|
205
|
+
printAnswer(label, value);
|
|
206
|
+
return value;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function askAi(prompt, defaultValue) {
|
|
210
|
+
const choices = [
|
|
211
|
+
["1", "auto", "Auto, choose by target directory"],
|
|
212
|
+
["2", "codex", "Codex, generate .codex + .agents"],
|
|
213
|
+
["3", "claude", "Claude Code, generate .claude"],
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
while (true) {
|
|
217
|
+
console.log(`${symbol.pointer} Select AI workspace ${color.dim(`(${defaultValue})`)}`);
|
|
218
|
+
for (const [key, value, description] of choices) {
|
|
219
|
+
const marker = value === defaultValue ? color.dim(" (default)") : "";
|
|
220
|
+
console.log(` ${key}. ${value.padEnd(6)} ${color.dim(description)}${marker}`);
|
|
221
|
+
}
|
|
222
|
+
const answer = await prompt.ask(` ${symbol.arrow} `);
|
|
223
|
+
const normalized = answer ? answer.toLowerCase() : defaultValue;
|
|
224
|
+
const matched = choices.find(([key, value]) => normalized === key || normalized === value);
|
|
225
|
+
if (matched) {
|
|
226
|
+
printAnswer("AI workspace", matched[1]);
|
|
227
|
+
return matched[1];
|
|
228
|
+
}
|
|
229
|
+
console.log(color.dim(" Enter 1/2/3, or auto/codex/claude."));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function askConfirm(prompt, label, defaultValue = true) {
|
|
234
|
+
const suffix = defaultValue ? "Y/n" : "y/N";
|
|
235
|
+
while (true) {
|
|
236
|
+
const answer = (await prompt.ask(promptLine(label, suffix))).toLowerCase();
|
|
237
|
+
if (!answer) {
|
|
238
|
+
printAnswer(label, defaultValue ? "yes" : "no");
|
|
239
|
+
return defaultValue;
|
|
240
|
+
}
|
|
241
|
+
if (["y", "yes", "是", "确认"].includes(answer)) {
|
|
242
|
+
printAnswer(label, "yes");
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
if (["n", "no", "否", "取消"].includes(answer)) {
|
|
246
|
+
printAnswer(label, "no");
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
console.log(color.dim(" Enter y or n."));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function runInteractiveInit(seedOptions = {}) {
|
|
254
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
255
|
+
fail("interactive mode requires a TTY. Use `pmflow init --ai auto --root . --name \"My Product\"` in non-interactive environments.");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const prompt = createPrompt();
|
|
259
|
+
try {
|
|
260
|
+
printInteractiveHeader();
|
|
261
|
+
|
|
262
|
+
const name = await askText(prompt, "What is your product named?", seedOptions.name || "My Product");
|
|
263
|
+
const root = await askText(prompt, "Where should the project be created?", seedOptions.root || ".");
|
|
264
|
+
|
|
265
|
+
const ai = await askAi(prompt, seedOptions.ai || "auto");
|
|
266
|
+
|
|
267
|
+
console.log("");
|
|
268
|
+
console.log(color.bold("Summary"));
|
|
269
|
+
console.log(` Product: ${color.green(name)}`);
|
|
270
|
+
console.log(` Directory: ${color.green(path.resolve(root))}`);
|
|
271
|
+
console.log(` Workspace: ${color.green(ai)}`);
|
|
272
|
+
console.log("");
|
|
273
|
+
|
|
274
|
+
const confirmed = await askConfirm(prompt, "Create this workspace?", true);
|
|
275
|
+
if (!confirmed) {
|
|
276
|
+
console.log(color.dim("Canceled."));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
console.log("");
|
|
281
|
+
createStructure(root, name, normalizeAi(ai));
|
|
282
|
+
} finally {
|
|
283
|
+
prompt.close();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
143
287
|
function templatePath(name) {
|
|
144
288
|
const centralPath = path.join(CODEX_TEMPLATES, name);
|
|
145
289
|
if (exists(centralPath)) return centralPath;
|
|
@@ -442,22 +586,41 @@ function createStructure(rootInput, productName, cli) {
|
|
|
442
586
|
console.log(nextStep);
|
|
443
587
|
}
|
|
444
588
|
|
|
445
|
-
function runInit(argv) {
|
|
589
|
+
async function runInit(argv) {
|
|
446
590
|
const options = parseInitArgs(argv);
|
|
591
|
+
if (options.interactive || (argv.length === 0 && process.stdin.isTTY && process.stdout.isTTY)) {
|
|
592
|
+
await runInteractiveInit(options);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
447
595
|
createStructure(options.root, options.name, options.ai);
|
|
448
596
|
}
|
|
449
597
|
|
|
450
|
-
function main() {
|
|
598
|
+
async function main() {
|
|
451
599
|
const [command, ...rest] = process.argv.slice(2);
|
|
452
600
|
if (!command || command === "-h" || command === "--help") {
|
|
601
|
+
if (!command) {
|
|
602
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
603
|
+
await runInteractiveInit();
|
|
604
|
+
} else {
|
|
605
|
+
printHelp();
|
|
606
|
+
}
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
453
609
|
printHelp();
|
|
454
610
|
return;
|
|
455
611
|
}
|
|
456
612
|
if (command === "init") {
|
|
457
|
-
runInit(rest);
|
|
613
|
+
await runInit(rest);
|
|
458
614
|
return;
|
|
459
615
|
}
|
|
460
616
|
fail(`unknown command "${command}".`);
|
|
461
617
|
}
|
|
462
618
|
|
|
463
|
-
main()
|
|
619
|
+
main().catch((error) => {
|
|
620
|
+
if (error && error.message) {
|
|
621
|
+
console.error(`pmflow: ${error.message}`);
|
|
622
|
+
} else {
|
|
623
|
+
console.error(error);
|
|
624
|
+
}
|
|
625
|
+
process.exit(1);
|
|
626
|
+
});
|