pm-workflow-studio 0.1.2 → 0.1.3
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 +147 -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,14 @@ 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
|
+
|
|
57
72
|
function exists(target) {
|
|
58
73
|
return fs.existsSync(target);
|
|
59
74
|
}
|
|
@@ -89,6 +104,7 @@ function parseInitArgs(argv) {
|
|
|
89
104
|
ai: "auto",
|
|
90
105
|
root: ".",
|
|
91
106
|
name: "My Product",
|
|
107
|
+
interactive: false,
|
|
92
108
|
};
|
|
93
109
|
|
|
94
110
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -97,6 +113,10 @@ function parseInitArgs(argv) {
|
|
|
97
113
|
printHelp();
|
|
98
114
|
process.exit(0);
|
|
99
115
|
}
|
|
116
|
+
if (arg === "-i" || arg === "--interactive") {
|
|
117
|
+
options.interactive = true;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
100
120
|
if (arg === "--ai" || arg === "--cli") {
|
|
101
121
|
const value = argv[index + 1];
|
|
102
122
|
if (!value) fail(`${arg} requires a value.`);
|
|
@@ -140,6 +160,110 @@ function parseInitArgs(argv) {
|
|
|
140
160
|
return options;
|
|
141
161
|
}
|
|
142
162
|
|
|
163
|
+
function createPrompt() {
|
|
164
|
+
const rl = readline.createInterface({
|
|
165
|
+
input: process.stdin,
|
|
166
|
+
output: process.stdout,
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
ask(question) {
|
|
170
|
+
return new Promise((resolve) => {
|
|
171
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
close() {
|
|
175
|
+
rl.close();
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function printInteractiveHeader() {
|
|
181
|
+
console.log("");
|
|
182
|
+
console.log(color.cyan("┌────────────────────────────────────────────┐"));
|
|
183
|
+
console.log(color.cyan("│") + ` ${color.bold("PM Workflow Studio")} ` + color.cyan("│"));
|
|
184
|
+
console.log(color.cyan("│") + " 交互式创建产品工作室,命令参数仍然可用 " + color.cyan("│"));
|
|
185
|
+
console.log(color.cyan("└────────────────────────────────────────────┘"));
|
|
186
|
+
console.log("");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function printStep(index, title) {
|
|
190
|
+
console.log(color.dim(`\nStep ${index}`));
|
|
191
|
+
console.log(color.bold(title));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function formatDefault(value) {
|
|
195
|
+
return color.dim(`(${value})`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function askText(prompt, label, defaultValue) {
|
|
199
|
+
const answer = await prompt.ask(`${label} ${formatDefault(defaultValue)}: `);
|
|
200
|
+
return answer || defaultValue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function askAi(prompt, defaultValue) {
|
|
204
|
+
const choices = [
|
|
205
|
+
["1", "auto", "自动判断,空目录默认 Codex"],
|
|
206
|
+
["2", "codex", "生成 .codex + .agents 结构"],
|
|
207
|
+
["3", "claude", "生成 .claude 结构"],
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
while (true) {
|
|
211
|
+
for (const [key, value, description] of choices) {
|
|
212
|
+
const marker = value === defaultValue ? color.green(" *") : " ";
|
|
213
|
+
console.log(` ${key}. ${value.padEnd(6)} ${color.dim(description)}${marker}`);
|
|
214
|
+
}
|
|
215
|
+
const answer = await prompt.ask(`选择 AI CLI ${formatDefault(defaultValue)}: `);
|
|
216
|
+
const normalized = answer ? answer.toLowerCase() : defaultValue;
|
|
217
|
+
const matched = choices.find(([key, value]) => normalized === key || normalized === value);
|
|
218
|
+
if (matched) return matched[1];
|
|
219
|
+
console.log("请输入 1/2/3,或 auto/codex/claude。");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function askConfirm(prompt, label, defaultValue = true) {
|
|
224
|
+
const suffix = defaultValue ? "Y/n" : "y/N";
|
|
225
|
+
while (true) {
|
|
226
|
+
const answer = (await prompt.ask(`${label} ${formatDefault(suffix)}: `)).toLowerCase();
|
|
227
|
+
if (!answer) return defaultValue;
|
|
228
|
+
if (["y", "yes", "是", "确认"].includes(answer)) return true;
|
|
229
|
+
if (["n", "no", "否", "取消"].includes(answer)) return false;
|
|
230
|
+
console.log("请输入 y 或 n。");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function runInteractiveInit(seedOptions = {}) {
|
|
235
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
236
|
+
fail("interactive mode requires a TTY. Use `pmflow init --ai auto --root . --name \"My Product\"` in non-interactive environments.");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const prompt = createPrompt();
|
|
240
|
+
try {
|
|
241
|
+
printInteractiveHeader();
|
|
242
|
+
|
|
243
|
+
printStep(1, "产品信息");
|
|
244
|
+
const name = await askText(prompt, "产品名称", seedOptions.name || "My Product");
|
|
245
|
+
const root = await askText(prompt, "项目目录", seedOptions.root || ".");
|
|
246
|
+
|
|
247
|
+
printStep(2, "AI 工作区结构");
|
|
248
|
+
const ai = await askAi(prompt, seedOptions.ai || "auto");
|
|
249
|
+
|
|
250
|
+
printStep(3, "确认创建");
|
|
251
|
+
console.log(` 产品名称: ${color.green(name)}`);
|
|
252
|
+
console.log(` 项目目录: ${color.green(path.resolve(root))}`);
|
|
253
|
+
console.log(` AI 结构: ${color.green(ai)}`);
|
|
254
|
+
const confirmed = await askConfirm(prompt, "开始创建?", true);
|
|
255
|
+
if (!confirmed) {
|
|
256
|
+
console.log("已取消。");
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log("");
|
|
261
|
+
createStructure(root, name, normalizeAi(ai));
|
|
262
|
+
} finally {
|
|
263
|
+
prompt.close();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
143
267
|
function templatePath(name) {
|
|
144
268
|
const centralPath = path.join(CODEX_TEMPLATES, name);
|
|
145
269
|
if (exists(centralPath)) return centralPath;
|
|
@@ -442,22 +566,41 @@ function createStructure(rootInput, productName, cli) {
|
|
|
442
566
|
console.log(nextStep);
|
|
443
567
|
}
|
|
444
568
|
|
|
445
|
-
function runInit(argv) {
|
|
569
|
+
async function runInit(argv) {
|
|
446
570
|
const options = parseInitArgs(argv);
|
|
571
|
+
if (options.interactive || (argv.length === 0 && process.stdin.isTTY && process.stdout.isTTY)) {
|
|
572
|
+
await runInteractiveInit(options);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
447
575
|
createStructure(options.root, options.name, options.ai);
|
|
448
576
|
}
|
|
449
577
|
|
|
450
|
-
function main() {
|
|
578
|
+
async function main() {
|
|
451
579
|
const [command, ...rest] = process.argv.slice(2);
|
|
452
580
|
if (!command || command === "-h" || command === "--help") {
|
|
581
|
+
if (!command) {
|
|
582
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
583
|
+
await runInteractiveInit();
|
|
584
|
+
} else {
|
|
585
|
+
printHelp();
|
|
586
|
+
}
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
453
589
|
printHelp();
|
|
454
590
|
return;
|
|
455
591
|
}
|
|
456
592
|
if (command === "init") {
|
|
457
|
-
runInit(rest);
|
|
593
|
+
await runInit(rest);
|
|
458
594
|
return;
|
|
459
595
|
}
|
|
460
596
|
fail(`unknown command "${command}".`);
|
|
461
597
|
}
|
|
462
598
|
|
|
463
|
-
main()
|
|
599
|
+
main().catch((error) => {
|
|
600
|
+
if (error && error.message) {
|
|
601
|
+
console.error(`pmflow: ${error.message}`);
|
|
602
|
+
} else {
|
|
603
|
+
console.error(error);
|
|
604
|
+
}
|
|
605
|
+
process.exit(1);
|
|
606
|
+
});
|