abelworkflow 0.6.4 → 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
 
@@ -0,0 +1,188 @@
1
+ import * as p from "@clack/prompts";
2
+
3
+ const interactiveMenuDescriptors = [
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" }
14
+ ];
15
+
16
+ const interactiveMenuDefaultValue = "full-init";
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
+
83
+ function parseArgs(argv, { defaultAgentsDir, resolvePath }) {
84
+ const options = {
85
+ agentsDir: defaultAgentsDir,
86
+ force: false,
87
+ relinkOnly: false,
88
+ nonInteractive: false,
89
+ command: "menu"
90
+ };
91
+ const positional = [];
92
+ let helpRequested = false;
93
+
94
+ for (let i = 0; i < argv.length; i += 1) {
95
+ const arg = argv[i];
96
+ if (arg === "--force" || arg === "-f") {
97
+ options.force = true;
98
+ continue;
99
+ }
100
+ if (arg === "--link-only") {
101
+ options.relinkOnly = true;
102
+ continue;
103
+ }
104
+ if (arg === "--agents-dir") {
105
+ const value = argv[i + 1];
106
+ if (!value) {
107
+ throw new Error("--agents-dir requires a path");
108
+ }
109
+ options.agentsDir = resolvePath(value);
110
+ i += 1;
111
+ continue;
112
+ }
113
+ if (arg === "--non-interactive") {
114
+ options.nonInteractive = true;
115
+ continue;
116
+ }
117
+ if (arg === "--help" || arg === "-h" || arg === "help") {
118
+ helpRequested = true;
119
+ options.command = "help";
120
+ continue;
121
+ }
122
+ if (arg.startsWith("-")) {
123
+ throw new Error(`Unknown argument: ${arg}`);
124
+ }
125
+ positional.push(arg);
126
+ }
127
+
128
+ if (positional.length > 1) {
129
+ throw new Error(`Unknown argument: ${positional.slice(1).join(" ")}`);
130
+ }
131
+
132
+ if (positional[0]) {
133
+ if (["menu", "init"].includes(positional[0])) {
134
+ if (!helpRequested) {
135
+ options.command = "menu";
136
+ }
137
+ } else if (["install", "sync"].includes(positional[0])) {
138
+ if (!helpRequested) {
139
+ options.command = "install";
140
+ }
141
+ } else {
142
+ throw new Error(`Unknown command: ${positional[0]}`);
143
+ }
144
+ }
145
+
146
+ if (options.command !== "install" && (options.force || options.relinkOnly || options.agentsDir !== defaultAgentsDir)) {
147
+ throw new Error("`--force`、`--link-only`、`--agents-dir` 仅能与 `install` 命令一起使用");
148
+ }
149
+
150
+ if (!options.nonInteractive && process.env.CI) {
151
+ options.nonInteractive = true;
152
+ }
153
+
154
+ return options;
155
+ }
156
+
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` 进行安装");
162
+ }
163
+ if (command === "menu" && (!inputIsTTY || !outputIsTTY)) {
164
+ throw new Error("交互式菜单需要 TTY 终端;非交互场景请显式使用 `npx abelworkflow install`");
165
+ }
166
+ }
167
+
168
+ function getRunCommandSpawnOptions(platform = process.env.ABELWORKFLOW_TEST_PLATFORM || process.platform) {
169
+ return {
170
+ stdio: "inherit",
171
+ shell: platform === "win32"
172
+ };
173
+ }
174
+
175
+ export {
176
+ assertInteractiveMenuSupported,
177
+ assertNotCancelled,
178
+ CancelledError,
179
+ confirmOrCancel,
180
+ getRunCommandSpawnOptions,
181
+ interactiveMenuDefaultValue,
182
+ interactiveMenuDescriptors,
183
+ parseArgs,
184
+ required,
185
+ requiredUnlessExisting,
186
+ resolvePasswordValue,
187
+ selectOrCancel
188
+ };