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.
Files changed (3) hide show
  1. package/README.md +16 -0
  2. package/bin/pmflow.js +167 -4
  3. 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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pm-workflow-studio",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "AI product development studio CLI for Codex and Claude Code.",
5
5
  "type": "commonjs",
6
6
  "bin": {