abelworkflow 0.6.5 → 0.7.0

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/.gitignore CHANGED
@@ -12,5 +12,5 @@ skills/dev-browser/tmp/
12
12
  .claude/
13
13
  .codex/
14
14
  *pycache*/
15
- .codex
16
15
  openspec/
16
+ package-lock.json
package/README.md CHANGED
@@ -112,7 +112,7 @@ npx abelworkflow --help
112
112
  |---|---|---|
113
113
  | `grok-search` | `~/.agents/skills/grok-search/.env` | `GROK_API_URL` `GROK_API_KEY` `GROK_MODEL` |
114
114
  | `context7-auto-research` | `~/.agents/skills/context7-auto-research/.env` | `CONTEXT7_API_KEY` |
115
- | `prompt-enhancer` | `~/.agents/skills/prompt-enhancer/.env` | `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` `PE_MODEL` |
115
+ | `prompt-enhancer` | `~/.agents/skills/prompt-enhancer/.env` | `PE_API_URL` `PE_API_KEY` `PE_MODEL` |
116
116
 
117
117
  ### 方法二:源码克隆安装
118
118
 
package/lib/cli/logic.mjs CHANGED
@@ -1,23 +1,91 @@
1
+ import * as p from "@clack/prompts";
2
+
1
3
  const interactiveMenuDescriptors = [
2
- { value: "full-init", label: "完整初始化:同步工作流 + 可选安装/配置 Claude Code、Codex、技能环境" },
3
- { value: "install", label: "仅同步/更新工作流到 ~/.agents 并重新链接 Claude/Codex" },
4
- { value: "grok-search", label: "配置 grok-search 环境变量" },
5
- { value: "context7", label: "配置 context7-auto-research 环境变量" },
6
- { value: "prompt-enhancer", label: "配置 prompt-enhancer 环境变量" },
7
- { value: "claude-install", label: "安装或更新 Claude Code CLI" },
8
- { value: "claude-api", label: "配置 Claude Code 第三方 API" },
9
- { value: "codex-install", label: "安装或更新 Codex CLI" },
10
- { value: "codex-api", label: "配置 Codex 第三方 API" },
11
- { value: "exit", label: "退出" }
4
+ { value: "full-init", label: "完整初始化", hint: "同步 + 安装 + 配置", group: "main" },
5
+ { value: "install", label: "仅同步工作流", group: "main" },
6
+ { value: "grok-search", label: "配置 grok-search", hint: "技能", group: "skill" },
7
+ { value: "context7", label: "配置 context7-auto-research", hint: "技能", group: "skill" },
8
+ { value: "prompt-enhancer", label: "配置 prompt-enhancer", hint: "技能", group: "skill" },
9
+ { value: "claude-install", label: "安装/更新 Claude Code", hint: "CLI", group: "cli" },
10
+ { value: "claude-api", label: "配置 Claude API", hint: "CLI", group: "cli" },
11
+ { value: "codex-install", label: "安装/更新 Codex", hint: "CLI", group: "cli" },
12
+ { value: "codex-api", label: "配置 Codex API", hint: "CLI", group: "cli" },
13
+ { value: "exit", label: "退出", group: "exit" }
12
14
  ];
13
15
 
14
16
  const interactiveMenuDefaultValue = "full-init";
15
17
 
18
+ class CancelledError extends Error {
19
+ constructor(message = "用户取消") {
20
+ super(message);
21
+ this.name = "CancelledError";
22
+ }
23
+ }
24
+
25
+ function required(message = "此项不能为空") {
26
+ return (value) => {
27
+ if (value === undefined || value === null || (typeof value === "string" && value.trim() === "")) {
28
+ return message;
29
+ }
30
+ };
31
+ }
32
+
33
+ function requiredUnlessExisting(existingValue, message = "此项不能为空") {
34
+ return (value) => {
35
+ if ((value === undefined || value === null || (typeof value === "string" && value.trim() === "")) && !existingValue) {
36
+ return message;
37
+ }
38
+ };
39
+ }
40
+
41
+ /**
42
+ * 回退逻辑:用户输入非空则用新值,否则用旧值。
43
+ * 输入 "-" 表示清除已有值(返回 undefined)。
44
+ * existingValue 可能来自 dotenv / JSON 读取,会传入空字符串;
45
+ * 空字符串将被净化为 undefined,避免写入无意义的空配置。
46
+ * 注意:本函数仅适用于字符串类型的配置值,不适用于可能为 0/false 的数字或布尔值。
47
+ */
48
+ function resolvePasswordValue(userInput, existingValue) {
49
+ if (typeof userInput === "string") {
50
+ const trimmed = userInput.trim();
51
+ if (trimmed === "") {
52
+ if (typeof existingValue === "string" && existingValue.trim() === "") {
53
+ return undefined;
54
+ }
55
+ return existingValue || undefined;
56
+ }
57
+ return trimmed === "-" ? undefined : trimmed;
58
+ }
59
+ if (typeof existingValue === "string" && existingValue.trim() === "") {
60
+ return undefined;
61
+ }
62
+ return existingValue || undefined;
63
+ }
64
+
65
+ function assertNotCancelled(value) {
66
+ if (p.isCancel(value)) {
67
+ throw new CancelledError();
68
+ }
69
+ }
70
+
71
+ async function confirmOrCancel({ message, initialValue = false }) {
72
+ const value = await p.confirm({ message, initialValue, active: "是", inactive: "否" });
73
+ assertNotCancelled(value);
74
+ return value;
75
+ }
76
+
77
+ async function selectOrCancel(options) {
78
+ const value = await p.select(options);
79
+ assertNotCancelled(value);
80
+ return value;
81
+ }
82
+
16
83
  function parseArgs(argv, { defaultAgentsDir, resolvePath }) {
17
84
  const options = {
18
85
  agentsDir: defaultAgentsDir,
19
86
  force: false,
20
87
  relinkOnly: false,
88
+ nonInteractive: false,
21
89
  command: "menu"
22
90
  };
23
91
  const positional = [];
@@ -42,6 +110,10 @@ function parseArgs(argv, { defaultAgentsDir, resolvePath }) {
42
110
  i += 1;
43
111
  continue;
44
112
  }
113
+ if (arg === "--non-interactive") {
114
+ options.nonInteractive = true;
115
+ continue;
116
+ }
45
117
  if (arg === "--help" || arg === "-h" || arg === "help") {
46
118
  helpRequested = true;
47
119
  options.command = "help";
@@ -75,40 +147,22 @@ function parseArgs(argv, { defaultAgentsDir, resolvePath }) {
75
147
  throw new Error("`--force`、`--link-only`、`--agents-dir` 仅能与 `install` 命令一起使用");
76
148
  }
77
149
 
78
- return options;
79
- }
80
-
81
- function assertInteractiveMenuSupported({ command, inputIsTTY, outputIsTTY }) {
82
- if (command === "menu" && (!inputIsTTY || !outputIsTTY)) {
83
- throw new Error("交互式菜单需要 TTY 终端;非交互场景请显式使用 `npx abelworkflow install`");
150
+ if (!options.nonInteractive && process.env.CI) {
151
+ options.nonInteractive = true;
84
152
  }
85
- }
86
153
 
87
- function resolvePromptValue(answer, { defaultValue, allowEmpty = false } = {}) {
88
- const value = String(answer).trim();
89
- if (!value && defaultValue !== undefined) {
90
- return { ok: true, value: defaultValue };
91
- }
92
- if (!value && !allowEmpty) {
93
- return { ok: false, error: "此项不能为空。" };
94
- }
95
- return { ok: true, value };
154
+ return options;
96
155
  }
97
156
 
98
- function resolveSelectValue(answer, choices) {
99
- const index = Number(answer) - 1;
100
- if (Number.isInteger(index) && index >= 0 && index < choices.length) {
101
- return { ok: true, value: choices[index].value };
157
+ function assertInteractiveMenuSupported({ command, inputIsTTY, outputIsTTY, nonInteractive }) {
158
+ // 防御性检查(死代码守护):main() 已在调用前将非交互 menu 降级为 install,
159
+ // 此处仅防止外部直接调用 assertInteractiveMenuSupported 时漏掉判断。
160
+ if (command === "menu" && nonInteractive) {
161
+ throw new Error("非交互模式已启用;请显式使用 `npx abelworkflow install` 进行安装");
102
162
  }
103
- const direct = choices.find((choice) => choice.value === answer);
104
- if (direct) {
105
- return { ok: true, value: direct.value };
163
+ if (command === "menu" && (!inputIsTTY || !outputIsTTY)) {
164
+ throw new Error("交互式菜单需要 TTY 终端;非交互场景请显式使用 `npx abelworkflow install`");
106
165
  }
107
- return { ok: false, error: "无效选择,请重新输入。" };
108
- }
109
-
110
- function shouldUseVisibleSecretFallback({ inputIsTTY, platform }) {
111
- return !inputIsTTY || platform === "win32";
112
166
  }
113
167
 
114
168
  function getRunCommandSpawnOptions(platform = process.env.ABELWORKFLOW_TEST_PLATFORM || process.platform) {
@@ -120,11 +174,15 @@ function getRunCommandSpawnOptions(platform = process.env.ABELWORKFLOW_TEST_PLAT
120
174
 
121
175
  export {
122
176
  assertInteractiveMenuSupported,
177
+ assertNotCancelled,
178
+ CancelledError,
179
+ confirmOrCancel,
123
180
  getRunCommandSpawnOptions,
124
181
  interactiveMenuDefaultValue,
125
182
  interactiveMenuDescriptors,
126
183
  parseArgs,
127
- resolvePromptValue,
128
- resolveSelectValue,
129
- shouldUseVisibleSecretFallback
184
+ required,
185
+ requiredUnlessExisting,
186
+ resolvePasswordValue,
187
+ selectOrCancel
130
188
  };