abelworkflow 0.9.2 → 1.0.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.
Files changed (134) hide show
  1. package/README.md +54 -182
  2. package/bin/abelworkflow.mjs +5 -3
  3. package/extensions/pi-gpt-responses-compat/index.ts +1 -1
  4. package/lib/cli/args.mjs +76 -0
  5. package/lib/cli/main.mjs +385 -0
  6. package/lib/cli/prompts.mjs +106 -0
  7. package/lib/config/dotenv.mjs +102 -0
  8. package/lib/config/jsonc.mjs +80 -0
  9. package/lib/config/store.mjs +310 -0
  10. package/lib/config/toml.mjs +638 -0
  11. package/lib/installer/assets.mjs +324 -0
  12. package/lib/installer/install.mjs +153 -0
  13. package/lib/installer/links.mjs +427 -0
  14. package/lib/installer/state.mjs +163 -0
  15. package/lib/paths.mjs +99 -0
  16. package/lib/providers/claude.mjs +197 -0
  17. package/lib/providers/codex.mjs +433 -0
  18. package/lib/providers/pi.mjs +577 -0
  19. package/lib/providers/skills.mjs +105 -0
  20. package/lib/templates/codex/agents/default.toml +7 -0
  21. package/lib/templates/codex/agents/explorer.toml +7 -0
  22. package/lib/templates/codex/agents/planner.toml +7 -0
  23. package/lib/templates/codex/agents/reviewer.toml +7 -0
  24. package/lib/templates/codex/agents/worker.toml +7 -0
  25. package/lib/templates/codex/config-base.toml +11 -3
  26. package/lib/templates/workflow/AGENTS.md +50 -0
  27. package/lib/templates/workflow/commands/abel-design.md +175 -0
  28. package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
  29. package/lib/templates/workflow/commands/abel-implement.md +170 -0
  30. package/{commands → lib/templates/workflow/commands}/abel-init.md +3 -5
  31. package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
  32. package/lib/tools/cli-installer.mjs +277 -0
  33. package/package.json +37 -10
  34. package/skills/context7-auto-research/SKILL.md +5 -5
  35. package/skills/context7-auto-research/{context7-api.js → context7-api.cjs} +5 -8
  36. package/skills/dev-browser/SKILL.md +53 -33
  37. package/skills/dev-browser/dist/scripts/start.d.ts +2 -0
  38. package/skills/dev-browser/dist/scripts/start.d.ts.map +1 -0
  39. package/skills/dev-browser/dist/scripts/start.js +118 -0
  40. package/skills/dev-browser/dist/scripts/start.js.map +1 -0
  41. package/skills/dev-browser/dist/src/client.d.ts +93 -0
  42. package/skills/dev-browser/dist/src/client.d.ts.map +1 -0
  43. package/skills/dev-browser/dist/src/client.js +310 -0
  44. package/skills/dev-browser/dist/src/client.js.map +1 -0
  45. package/skills/dev-browser/dist/src/entrypoint.d.ts +29 -0
  46. package/skills/dev-browser/dist/src/entrypoint.d.ts.map +1 -0
  47. package/skills/dev-browser/dist/src/entrypoint.js +106 -0
  48. package/skills/dev-browser/dist/src/entrypoint.js.map +1 -0
  49. package/skills/dev-browser/dist/src/index.d.ts +4 -0
  50. package/skills/dev-browser/dist/src/index.d.ts.map +1 -0
  51. package/skills/dev-browser/dist/src/index.js +2 -0
  52. package/skills/dev-browser/dist/src/index.js.map +1 -0
  53. package/skills/dev-browser/dist/src/page-api.d.ts +25 -0
  54. package/skills/dev-browser/dist/src/page-api.d.ts.map +1 -0
  55. package/skills/dev-browser/dist/src/page-api.js +104 -0
  56. package/skills/dev-browser/dist/src/page-api.js.map +1 -0
  57. package/skills/dev-browser/dist/src/relay.d.ts +27 -0
  58. package/skills/dev-browser/dist/src/relay.d.ts.map +1 -0
  59. package/skills/dev-browser/dist/src/relay.js +521 -0
  60. package/skills/dev-browser/dist/src/relay.js.map +1 -0
  61. package/skills/dev-browser/dist/src/runtime.d.ts +45 -0
  62. package/skills/dev-browser/dist/src/runtime.d.ts.map +1 -0
  63. package/skills/dev-browser/dist/src/runtime.js +54 -0
  64. package/skills/dev-browser/dist/src/runtime.js.map +1 -0
  65. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +23 -0
  66. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +1 -0
  67. package/skills/dev-browser/{src/snapshot/browser-script.ts → dist/src/snapshot/browser-script.js} +21 -30
  68. package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +1 -0
  69. package/skills/dev-browser/dist/src/snapshot/index.d.ts +14 -0
  70. package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +1 -0
  71. package/skills/dev-browser/{src/snapshot/index.ts → dist/src/snapshot/index.js} +2 -2
  72. package/skills/dev-browser/dist/src/snapshot/index.js.map +1 -0
  73. package/skills/dev-browser/dist/src/snapshot/inject.d.ts +13 -0
  74. package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +1 -0
  75. package/skills/dev-browser/{src/snapshot/inject.ts → dist/src/snapshot/inject.js} +2 -2
  76. package/skills/dev-browser/dist/src/snapshot/inject.js.map +1 -0
  77. package/skills/dev-browser/dist/src/standalone.d.ts +32 -0
  78. package/skills/dev-browser/dist/src/standalone.d.ts.map +1 -0
  79. package/skills/dev-browser/dist/src/standalone.js +174 -0
  80. package/skills/dev-browser/dist/src/standalone.js.map +1 -0
  81. package/skills/dev-browser/dist/src/startup.d.ts +55 -0
  82. package/skills/dev-browser/dist/src/startup.d.ts.map +1 -0
  83. package/skills/dev-browser/dist/src/startup.js +81 -0
  84. package/skills/dev-browser/dist/src/startup.js.map +1 -0
  85. package/skills/dev-browser/dist/src/target-registry.d.ts +29 -0
  86. package/skills/dev-browser/dist/src/target-registry.d.ts.map +1 -0
  87. package/skills/dev-browser/dist/src/target-registry.js +135 -0
  88. package/skills/dev-browser/dist/src/target-registry.js.map +1 -0
  89. package/skills/dev-browser/dist/src/types.d.ts +27 -0
  90. package/skills/dev-browser/dist/src/types.d.ts.map +1 -0
  91. package/skills/dev-browser/dist/src/types.js +2 -0
  92. package/skills/dev-browser/dist/src/types.js.map +1 -0
  93. package/skills/dev-browser/package-lock.json +67 -1510
  94. package/skills/dev-browser/package.json +18 -14
  95. package/skills/dev-browser/references/scraping.md +94 -105
  96. package/skills/grok-search/.env.example +3 -3
  97. package/skills/grok-search/SKILL.md +1 -1
  98. package/skills/grok-search/defaults.json +3 -0
  99. package/skills/grok-search/gitignore.template +4 -0
  100. package/skills/grok-search/scripts/_dotenv.py +5 -1
  101. package/skills/grok-search/scripts/groksearch_cli.py +15 -33
  102. package/skills/prompt-enhancer/SKILL.md +2 -27
  103. package/skills/prompt-enhancer/TEMPLATE.md +0 -4
  104. package/.skill-lock.json +0 -29
  105. package/AGENTS.md +0 -45
  106. package/commands/abel-implement.md +0 -157
  107. package/commands/abel-plan.md +0 -88
  108. package/commands/abel-research.md +0 -126
  109. package/lib/cli/logic.mjs +0 -213
  110. package/lib/cli.mjs +0 -3016
  111. package/skills/confidence-check/SKILL.md +0 -34
  112. package/skills/confidence-check/confidence.ts +0 -276
  113. package/skills/dev-browser/bun.lock +0 -460
  114. package/skills/dev-browser/scripts/start.ts +0 -280
  115. package/skills/dev-browser/src/client.ts +0 -474
  116. package/skills/dev-browser/src/entrypoint.ts +0 -157
  117. package/skills/dev-browser/src/index.ts +0 -289
  118. package/skills/dev-browser/src/relay.ts +0 -731
  119. package/skills/dev-browser/src/runtime.test.ts +0 -60
  120. package/skills/dev-browser/src/runtime.ts +0 -171
  121. package/skills/dev-browser/src/startup.test.ts +0 -95
  122. package/skills/dev-browser/src/startup.ts +0 -153
  123. package/skills/dev-browser/src/types.ts +0 -35
  124. package/skills/dev-browser/tsconfig.json +0 -36
  125. package/skills/dev-browser/vitest.config.ts +0 -12
  126. package/skills/prompt-enhancer/.env.example +0 -12
  127. package/skills/prompt-enhancer/ADVANCED.md +0 -103
  128. package/skills/prompt-enhancer/requirements.txt +0 -2
  129. package/skills/prompt-enhancer/scripts/_dotenv.py +0 -28
  130. package/skills/prompt-enhancer/scripts/enhance.py +0 -191
  131. package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +0 -253
  132. package/skills/sequential-think/SKILL.md +0 -198
  133. package/skills/sequential-think/scripts/.env.example +0 -5
  134. package/skills/sequential-think/scripts/sequential_think_cli.py +0 -253
@@ -0,0 +1,577 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import * as p from "@clack/prompts";
4
+ import { piInsecureTlsHeader } from "../../extensions/pi-gpt-responses-compat/tls-fetch.mjs";
5
+ import {
6
+ readJsonFileSafe,
7
+ readJsoncFileSafe,
8
+ updateLockedJson,
9
+ writeJsonFileWithBackup,
10
+ writeText
11
+ } from "../config/store.mjs";
12
+ import { defaultPaths, maskSecret, pathToLabel } from "../paths.mjs";
13
+
14
+ const minimumPiVersion = [0, 80, 0];
15
+ const piRpcRequestId = "abelworkflow-provider";
16
+ const piRpcArgs = [
17
+ "--mode", "rpc",
18
+ "--no-session",
19
+ "--offline",
20
+ "--no-context-files",
21
+ "--no-skills",
22
+ "--no-prompt-templates",
23
+ "--no-themes"
24
+ ];
25
+
26
+ function requirePiProviderId(value) {
27
+ const providerId = typeof value === "string" ? value.trim() : "";
28
+ if (!providerId) {
29
+ throw new Error("未检测到 Pi 当前有效 Provider;请先在 Pi 中配置可用模型。");
30
+ }
31
+ return providerId;
32
+ }
33
+
34
+ async function updatePiAuthFile(path, providerId, apiKey) {
35
+ const targetProviderId = requirePiProviderId(providerId);
36
+ return updateLockedJson(path, (auth) => {
37
+ if (!auth || typeof auth !== "object" || Array.isArray(auth)) {
38
+ throw new TypeError("Pi auth.json must contain a JSON object");
39
+ }
40
+ return buildPiAuthConfig(auth, targetProviderId, apiKey);
41
+ }, {
42
+ sensitive: true,
43
+ retries: {
44
+ retries: 10,
45
+ factor: 2,
46
+ minTimeout: 100,
47
+ maxTimeout: 10000,
48
+ randomize: true
49
+ }
50
+ });
51
+ }
52
+
53
+ function parsePiVersion(value) {
54
+ const match = String(value || "").match(/(?:^|\D)v?(\d+)\.(\d+)\.(\d+)(?:\D|$)/u);
55
+ return match ? match.slice(1).map(Number) : null;
56
+ }
57
+
58
+ function assertSupportedPiVersion(value) {
59
+ const version = parsePiVersion(value);
60
+ if (!version) {
61
+ throw new Error("无法检测 Pi 版本(可能尚未安装);请先安装 Pi 0.80.0 或更高版本。");
62
+ }
63
+ for (let index = 0; index < minimumPiVersion.length; index += 1) {
64
+ if (version[index] > minimumPiVersion[index]) return version;
65
+ if (version[index] < minimumPiVersion[index]) {
66
+ throw new Error(`当前 Pi ${version.join(".")} 不支持 auth-only 自定义 Provider;请先升级到 Pi 0.80.0 或更高版本。`);
67
+ }
68
+ }
69
+ return version;
70
+ }
71
+
72
+ function detectPiVersion() {
73
+ const result = spawnSync("pi", ["--version"], {
74
+ encoding: "utf8",
75
+ shell: process.platform === "win32",
76
+ stdio: ["ignore", "pipe", "pipe"]
77
+ });
78
+ return result.status === 0 ? `${result.stdout || ""} ${result.stderr || ""}`.trim() : undefined;
79
+ }
80
+
81
+ function parsePiRpcEffectiveModel(value) {
82
+ for (const line of String(value || "").split(/\r?\n/u)) {
83
+ let payload;
84
+ try {
85
+ payload = JSON.parse(line);
86
+ } catch {
87
+ continue;
88
+ }
89
+ const model = payload?.type === "response"
90
+ && payload.command === "get_state"
91
+ && payload.success === true
92
+ ? payload.data?.model
93
+ : undefined;
94
+ const provider = typeof model?.provider === "string" ? model.provider.trim() : "";
95
+ const id = typeof model?.id === "string" ? model.id.trim() : "";
96
+ if (!provider || !id) continue;
97
+ return {
98
+ provider,
99
+ id,
100
+ api: typeof model.api === "string" ? model.api.trim() : "",
101
+ baseUrl: typeof model.baseUrl === "string" ? model.baseUrl.trim() : ""
102
+ };
103
+ }
104
+ }
105
+
106
+ function runPiRpcCommand(command, args, {
107
+ input = "",
108
+ maxBuffer = 1024 * 1024,
109
+ shell = process.platform === "win32",
110
+ start = spawn,
111
+ timeout = 20000
112
+ } = {}) {
113
+ return new Promise((resolve) => {
114
+ let child;
115
+ let stdout = "";
116
+ let settled = false;
117
+ let timer;
118
+ const finish = (status) => {
119
+ if (settled) return;
120
+ settled = true;
121
+ clearTimeout(timer);
122
+ child?.stdin?.destroy();
123
+ child?.stdout?.destroy();
124
+ child?.stderr?.destroy();
125
+ if (child?.exitCode === null && child.signalCode === null && !child.killed) child.kill();
126
+ child?.unref();
127
+ resolve({ status, stdout });
128
+ };
129
+
130
+ try {
131
+ const env = { ...process.env };
132
+ delete env.NODE_TEST_CONTEXT;
133
+ child = start(command, args, {
134
+ env,
135
+ shell,
136
+ stdio: ["pipe", "pipe", "ignore"],
137
+ windowsHide: true
138
+ });
139
+ } catch {
140
+ finish(null);
141
+ return;
142
+ }
143
+
144
+ timer = setTimeout(() => finish(null), timeout);
145
+ child.on("error", () => finish(null));
146
+ child.on("exit", (code) => finish(code));
147
+ child.stdout.setEncoding("utf8");
148
+ child.stdout.on("data", (chunk) => {
149
+ stdout += chunk;
150
+ if (Buffer.byteLength(stdout, "utf8") > maxBuffer) {
151
+ finish(null);
152
+ } else if (parsePiRpcEffectiveModel(stdout)) {
153
+ finish(0);
154
+ }
155
+ });
156
+ child.stdin.on("error", () => finish(null));
157
+ child.stdin.write(input, (error) => {
158
+ if (error) finish(null);
159
+ });
160
+ });
161
+ }
162
+
163
+ async function detectPiEffectiveModel(run = runPiRpcCommand) {
164
+ let result;
165
+ try {
166
+ result = await run("pi", piRpcArgs, {
167
+ encoding: "utf8",
168
+ input: `${JSON.stringify({ id: piRpcRequestId, type: "get_state" })}\n`,
169
+ maxBuffer: 1024 * 1024,
170
+ shell: process.platform === "win32",
171
+ timeout: 20000
172
+ });
173
+ } catch {
174
+ return undefined;
175
+ }
176
+ if (result?.error || result?.status !== 0) return undefined;
177
+ return parsePiRpcEffectiveModel(result.stdout);
178
+ }
179
+
180
+ function parsePiModelIds(value) {
181
+ return [...new Set(String(value || "")
182
+ .split(/[\n,]+/u)
183
+ .map((item) => item.trim())
184
+ .filter(Boolean))];
185
+ }
186
+
187
+ function getOpenAiUrlPathname(value) {
188
+ const input = String(value || "").trim();
189
+ try {
190
+ return new URL(input).pathname.replace(/\/+$/u, "");
191
+ } catch {
192
+ return input.split(/[?#]/u)[0].replace(/\/+$/u, "");
193
+ }
194
+ }
195
+
196
+ function inferPiApiFromBaseUrl(value) {
197
+ const pathname = getOpenAiUrlPathname(value);
198
+ if (pathname.endsWith("/v1/chat/completions")) {
199
+ return "openai-completions";
200
+ }
201
+ if (pathname.endsWith("/v1/responses")) {
202
+ return "openai-responses";
203
+ }
204
+ return null;
205
+ }
206
+
207
+ function normalizeOpenAiBaseUrl(value) {
208
+ const input = String(value || "").trim();
209
+ try {
210
+ const url = new URL(input);
211
+ let pathname = url.pathname.replace(/\/+$/u, "")
212
+ .replace(/\/v1\/(?:chat\/completions|responses)$/u, "/v1");
213
+ if (!pathname.endsWith("/v1")) {
214
+ pathname = `${pathname}/v1`;
215
+ }
216
+ url.pathname = pathname;
217
+ return url.toString();
218
+ } catch {
219
+ const baseUrl = input.replace(/\/+$/u, "")
220
+ .replace(/\/v1\/(?:chat\/completions|responses)$/u, "/v1");
221
+ return baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
222
+ }
223
+ }
224
+
225
+ function getPiApiPromptOptions() {
226
+ return [
227
+ { value: "openai-completions", label: "OpenAI Chat Completions(推荐)" },
228
+ { value: "openai-responses", label: "OpenAI Responses API" }
229
+ ];
230
+ }
231
+
232
+ function resolveExistingPiApiConfig(modelsConfig = {}, settings = {}, auth = {}, effectiveModel) {
233
+ const savedProviderId = typeof settings.defaultProvider === "string" ? settings.defaultProvider.trim() : "";
234
+ const savedModelId = typeof settings.defaultModel === "string" ? settings.defaultModel.trim() : "";
235
+ const effectiveProviderId = typeof effectiveModel?.provider === "string" ? effectiveModel.provider.trim() : "";
236
+ const effectiveModelId = typeof effectiveModel?.id === "string" ? effectiveModel.id.trim() : "";
237
+ const savedProvider = savedProviderId
238
+ && modelsConfig.providers?.[savedProviderId]
239
+ && typeof modelsConfig.providers[savedProviderId] === "object"
240
+ ? modelsConfig.providers[savedProviderId]
241
+ : undefined;
242
+ const savedModels = Array.isArray(savedProvider?.models) ? savedProvider.models : [];
243
+ const savedTargetExists = savedProviderId
244
+ && savedModelId
245
+ && savedModels.some((model) => model?.id === savedModelId);
246
+ const providerId = effectiveProviderId && effectiveModelId
247
+ ? effectiveProviderId
248
+ : savedTargetExists ? savedProviderId : "";
249
+ const defaultModel = effectiveProviderId && effectiveModelId
250
+ ? effectiveModelId
251
+ : savedTargetExists ? savedModelId : "";
252
+ const provider = providerId
253
+ && modelsConfig.providers?.[providerId]
254
+ && typeof modelsConfig.providers[providerId] === "object"
255
+ ? modelsConfig.providers[providerId]
256
+ : {};
257
+ const credential = providerId && auth[providerId] && typeof auth[providerId] === "object"
258
+ ? auth[providerId]
259
+ : {};
260
+ const authApiKey = credential.type === "api_key" && typeof credential.key === "string"
261
+ ? credential.key
262
+ : "";
263
+ const models = Array.isArray(provider.models) ? provider.models.filter((model) => model?.id) : [];
264
+ const model = models.find((item) => item.id === defaultModel) || {};
265
+ const runtimeMatches = Boolean(effectiveProviderId
266
+ && effectiveModelId
267
+ && effectiveProviderId === providerId
268
+ && effectiveModelId === defaultModel);
269
+ return {
270
+ providerId,
271
+ baseUrl: runtimeMatches && typeof effectiveModel.baseUrl === "string"
272
+ ? effectiveModel.baseUrl
273
+ : typeof model.baseUrl === "string" ? model.baseUrl
274
+ : typeof provider.baseUrl === "string" ? provider.baseUrl : "",
275
+ api: runtimeMatches && typeof effectiveModel.api === "string"
276
+ ? effectiveModel.api
277
+ : typeof model.api === "string" ? model.api
278
+ : typeof provider.api === "string" ? provider.api : "",
279
+ apiKey: authApiKey || (typeof provider.apiKey === "string" ? provider.apiKey : ""),
280
+ modelIds: models.map((model) => model.id),
281
+ defaultModel
282
+ };
283
+ }
284
+
285
+ function assertConfigurablePiProvider(modelsConfig = {}, configuration = {}) {
286
+ const { providerId, defaultModel, api } = configuration;
287
+ const provider = modelsConfig.providers?.[providerId];
288
+ const models = Array.isArray(provider?.models) ? provider.models : [];
289
+ if (!provider || typeof provider !== "object" || !models.some((model) => model?.id === defaultModel)) {
290
+ throw new Error(`Pi 当前有效 Provider ${providerId || "未知"} 不是 models.json 中的自定义 Provider;为避免覆盖内置模型,已停止配置。`);
291
+ }
292
+ const supportedApis = new Set(getPiApiPromptOptions().map((option) => option.value));
293
+ if (api && !supportedApis.has(api)) {
294
+ throw new Error(`Pi 当前有效 Provider ${providerId} 使用不受支持的 API 类型 ${api};此配置器仅支持 OpenAI-compatible API。`);
295
+ }
296
+ }
297
+
298
+ function buildPiModelConfig(modelId, existingModel = {}) {
299
+ const model = { ...existingModel };
300
+ delete model.baseUrl;
301
+ delete model.api;
302
+ return {
303
+ ...model,
304
+ id: modelId,
305
+ name: model.name || modelId,
306
+ reasoning: model.reasoning ?? true,
307
+ input: Array.isArray(model.input) ? model.input : ["text", "image"],
308
+ contextWindow: model.contextWindow ?? 262144,
309
+ maxTokens: model.maxTokens ?? 64000
310
+ };
311
+ }
312
+
313
+ function hasPiInsecureTlsSetting(modelsConfig = {}, providerId) {
314
+ const headers = modelsConfig.providers?.[requirePiProviderId(providerId)]?.headers;
315
+ return headers && typeof headers === "object"
316
+ ? Object.keys(headers).some((key) => key.toLowerCase() === piInsecureTlsHeader)
317
+ : false;
318
+ }
319
+
320
+ function buildPiModelsConfig(modelsConfig = {}, {
321
+ providerId,
322
+ baseUrl,
323
+ api,
324
+ modelIds,
325
+ insecureTls = false
326
+ }) {
327
+ const targetProviderId = requirePiProviderId(providerId);
328
+ const providers = modelsConfig.providers && typeof modelsConfig.providers === "object" ? modelsConfig.providers : {};
329
+ const currentProvider = providers[targetProviderId] && typeof providers[targetProviderId] === "object"
330
+ ? providers[targetProviderId]
331
+ : {};
332
+ const headers = currentProvider.headers && typeof currentProvider.headers === "object"
333
+ ? { ...currentProvider.headers }
334
+ : {};
335
+ for (const key of Object.keys(headers)) {
336
+ if (key.toLowerCase() === piInsecureTlsHeader) {
337
+ delete headers[key];
338
+ }
339
+ }
340
+ if (insecureTls) {
341
+ headers[piInsecureTlsHeader] = new URL(baseUrl).origin;
342
+ }
343
+ const existingModels = new Map(
344
+ (Array.isArray(currentProvider.models) ? currentProvider.models : [])
345
+ .filter((model) => model?.id)
346
+ .map((model) => [model.id, model])
347
+ );
348
+
349
+ const provider = {
350
+ ...currentProvider,
351
+ baseUrl,
352
+ api,
353
+ compat: {
354
+ ...(currentProvider.compat && typeof currentProvider.compat === "object" ? currentProvider.compat : {}),
355
+ supportsDeveloperRole: false
356
+ },
357
+ models: modelIds.map((modelId) => buildPiModelConfig(modelId, existingModels.get(modelId)))
358
+ };
359
+ delete provider.apiKey;
360
+ if (Object.keys(headers).length) {
361
+ provider.headers = headers;
362
+ } else {
363
+ delete provider.headers;
364
+ }
365
+
366
+ return {
367
+ ...modelsConfig,
368
+ providers: {
369
+ ...providers,
370
+ [targetProviderId]: provider
371
+ }
372
+ };
373
+ }
374
+
375
+ function buildPiAuthConfig(auth = {}, providerId, apiKey) {
376
+ const targetProviderId = requirePiProviderId(providerId);
377
+ const credential = auth[targetProviderId] && typeof auth[targetProviderId] === "object"
378
+ ? auth[targetProviderId]
379
+ : {};
380
+ return {
381
+ ...auth,
382
+ [targetProviderId]: {
383
+ ...credential,
384
+ type: "api_key",
385
+ key: apiKey
386
+ }
387
+ };
388
+ }
389
+
390
+ function buildPiSettingsConfig(settings = {}, providerId, defaultModel) {
391
+ return {
392
+ ...settings,
393
+ defaultProvider: requirePiProviderId(providerId),
394
+ defaultModel,
395
+ defaultThinkingLevel: settings.defaultThinkingLevel || "high",
396
+ enableSkillCommands: settings.enableSkillCommands ?? true
397
+ };
398
+ }
399
+
400
+ async function readExistingPiConfiguration(paths, operations = {}) {
401
+ const readAuth = operations.readAuth ?? ((path) => readJsonFileSafe(path, {}, { sensitive: true }));
402
+ const readModels = operations.readModels ?? ((path) => readJsoncFileSafe(path, {}, { sensitive: true }));
403
+ const readSettings = operations.readSettings ?? ((path) => readJsonFileSafe(path, {}));
404
+ const auth = await readAuth(paths.piAuthPath);
405
+ const models = await readModels(paths.piModelsPath);
406
+ const settings = await readSettings(paths.piSettingsPath);
407
+ return { auth, models, settings };
408
+ }
409
+
410
+ function buildPiConfiguration({ auth, models, settings }, {
411
+ providerId,
412
+ apiKey,
413
+ baseUrl,
414
+ api,
415
+ modelIds,
416
+ insecureTls,
417
+ defaultModel
418
+ }) {
419
+ const targetProviderId = requirePiProviderId(providerId);
420
+ return {
421
+ providerId: targetProviderId,
422
+ apiKey,
423
+ auth: buildPiAuthConfig(auth, targetProviderId, apiKey),
424
+ models: buildPiModelsConfig(models, {
425
+ providerId: targetProviderId,
426
+ baseUrl,
427
+ api,
428
+ modelIds,
429
+ insecureTls
430
+ }),
431
+ settings: buildPiSettingsConfig(settings, targetProviderId, defaultModel)
432
+ };
433
+ }
434
+
435
+ async function persistPiConfiguration(paths, configuration, operations = {}) {
436
+ const updateAuth = operations.updateAuth ?? updatePiAuthFile;
437
+ const writeModels = operations.writeModels ?? ((path, value) => writeText(
438
+ path,
439
+ `${JSON.stringify(value, null, 2)}\n`,
440
+ { sensitive: true }
441
+ ));
442
+ const writeSettings = operations.writeSettings ?? ((path, value) => writeJsonFileWithBackup(path, value));
443
+ await updateAuth(paths.piAuthPath, configuration.providerId, configuration.apiKey);
444
+ await writeModels(paths.piModelsPath, configuration.models);
445
+ await writeSettings(paths.piSettingsPath, configuration.settings);
446
+ }
447
+
448
+ async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = async () => {}, promptApi, runtime = {}) {
449
+ const piVersion = await (runtime.getPiVersion ?? detectPiVersion)();
450
+ try {
451
+ assertSupportedPiVersion(piVersion);
452
+ } catch (error) {
453
+ (runtime.log ?? p.log).warn(error.message || String(error));
454
+ return;
455
+ }
456
+ const {
457
+ assertNotCancelled,
458
+ confirmOrCancel,
459
+ passwordPromptOptions,
460
+ required,
461
+ requiredUnlessExisting,
462
+ resolvePasswordValue,
463
+ selectOrCancel
464
+ } = promptApi;
465
+ const {
466
+ auth,
467
+ models: modelsConfig,
468
+ settings
469
+ } = await readExistingPiConfiguration(paths);
470
+ const detectionSpinner = p.spinner();
471
+ detectionSpinner.start("正在识别 Pi 当前有效模型");
472
+ let effectiveModel;
473
+ try {
474
+ effectiveModel = await (runtime.getPiEffectiveModel ?? detectPiEffectiveModel)();
475
+ } finally {
476
+ detectionSpinner.stop(effectiveModel
477
+ ? `已识别 ${effectiveModel.provider}/${effectiveModel.id}`
478
+ : "未识别到 Pi 当前有效模型");
479
+ }
480
+ const existing = resolveExistingPiApiConfig(modelsConfig, settings, auth, effectiveModel);
481
+ const providerId = requirePiProviderId(existing.providerId);
482
+ assertConfigurablePiProvider(modelsConfig, existing);
483
+ if (effectiveModel
484
+ && (settings.defaultProvider !== existing.providerId || settings.defaultModel !== existing.defaultModel)) {
485
+ p.log.warn(`Pi 保存的默认模型 ${settings.defaultProvider || "未知"}/${settings.defaultModel || "未知"} 与当前有效模型 ${existing.providerId}/${existing.defaultModel} 不同;将配置当前有效模型。`);
486
+ }
487
+ const providerLabel = `Pi ${providerId}`;
488
+
489
+ const baseUrlInput = await p.text({
490
+ message: `${providerLabel} Base URL`,
491
+ initialValue: existing.baseUrl,
492
+ validate: required()
493
+ });
494
+ assertNotCancelled(baseUrlInput);
495
+ const inferredApi = inferPiApiFromBaseUrl(baseUrlInput);
496
+ const baseUrl = normalizeOpenAiBaseUrl(baseUrlInput);
497
+
498
+ const insecureTls = await confirmOrCancel({
499
+ message: `是否仅为 ${providerLabel} 中转请求跳过 TLS 证书校验?仅证书无法修复时启用`,
500
+ initialValue: hasPiInsecureTlsSetting(modelsConfig, providerId)
501
+ });
502
+
503
+ const piApiOptions = getPiApiPromptOptions();
504
+ const initialApi = piApiOptions.some((option) => option.value === existing.api)
505
+ ? existing.api
506
+ : undefined;
507
+ const api = inferredApi || await selectOrCancel({
508
+ message: `${providerLabel} API 类型`,
509
+ options: piApiOptions,
510
+ ...(initialApi ? { initialValue: initialApi } : {})
511
+ });
512
+
513
+ const apiKey = await p.password(passwordPromptOptions(
514
+ `${providerLabel} API Key`,
515
+ existing.apiKey,
516
+ requiredUnlessExisting(existing.apiKey, "API Key 不能为空")
517
+ ));
518
+ assertNotCancelled(apiKey);
519
+ const finalApiKey = resolvePasswordValue(apiKey, existing.apiKey);
520
+
521
+ const modelIdsText = await p.text({
522
+ message: `${providerLabel} 模型 ID(多个用逗号分隔)`,
523
+ initialValue: (existing.modelIds.length
524
+ ? existing.modelIds
525
+ : existing.defaultModel ? [existing.defaultModel] : []).join(","),
526
+ validate: (value) => parsePiModelIds(value).length ? undefined : "至少需要一个模型 ID"
527
+ });
528
+ assertNotCancelled(modelIdsText);
529
+ const modelIds = parsePiModelIds(modelIdsText);
530
+
531
+ const defaultModel = await p.text({
532
+ message: "Pi 默认模型",
533
+ initialValue: modelIds.includes(existing.defaultModel) ? existing.defaultModel : modelIds[0],
534
+ validate: (value) => modelIds.includes(String(value || "").trim()) ? undefined : "默认模型必须在模型 ID 列表中"
535
+ });
536
+ assertNotCancelled(defaultModel);
537
+
538
+ const finalDefaultModel = String(defaultModel).trim();
539
+ const configuration = buildPiConfiguration({ auth, models: modelsConfig, settings }, {
540
+ providerId,
541
+ apiKey: finalApiKey,
542
+ baseUrl,
543
+ api,
544
+ modelIds,
545
+ insecureTls,
546
+ defaultModel: finalDefaultModel
547
+ });
548
+ await ensurePiResourcesLinked(paths);
549
+ await persistPiConfiguration(paths, configuration);
550
+
551
+ p.log.step(`已更新 ${pathToLabel(paths.piModelsPath, paths.homeDir)} (${providerId}, ${baseUrl})`);
552
+ p.log.step(`已更新 ${pathToLabel(paths.piSettingsPath, paths.homeDir)} (默认模型: ${finalDefaultModel})`);
553
+ p.log.step(`已更新 ${pathToLabel(paths.piAuthPath, paths.homeDir)} (${maskSecret(finalApiKey)})`);
554
+ p.log.step(`已链接 Pi 扩展到 ${pathToLabel(join(paths.piAgentDir, "extensions"), paths.homeDir)}`);
555
+ }
556
+
557
+ export {
558
+ assertConfigurablePiProvider,
559
+ assertSupportedPiVersion,
560
+ buildPiAuthConfig,
561
+ buildPiConfiguration,
562
+ buildPiModelsConfig,
563
+ buildPiSettingsConfig,
564
+ configurePiApi,
565
+ detectPiEffectiveModel,
566
+ getPiApiPromptOptions,
567
+ hasPiInsecureTlsSetting,
568
+ inferPiApiFromBaseUrl,
569
+ normalizeOpenAiBaseUrl,
570
+ parsePiRpcEffectiveModel,
571
+ parsePiModelIds,
572
+ persistPiConfiguration,
573
+ readExistingPiConfiguration,
574
+ resolveExistingPiApiConfig,
575
+ runPiRpcCommand,
576
+ updatePiAuthFile
577
+ };
@@ -0,0 +1,105 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import * as p from "@clack/prompts";
4
+ import { readDotenvFile, updateDotenvFile } from "../config/store.mjs";
5
+ import { pathToLabel } from "../paths.mjs";
6
+
7
+ const grokDefaults = Object.freeze(JSON.parse(readFileSync(
8
+ new URL("../../skills/grok-search/defaults.json", import.meta.url),
9
+ "utf8"
10
+ )));
11
+
12
+ function readSkillEnvFile(path) {
13
+ return readDotenvFile(path, { sensitive: true });
14
+ }
15
+
16
+ async function updateSkillEnvFile(path, updates) {
17
+ return updateDotenvFile(path, updates, { sensitive: true });
18
+ }
19
+
20
+ async function configureGrokSearchEnv(paths, ensureSkillPresent = async () => {}, promptApi) {
21
+ const {
22
+ assertNotCancelled,
23
+ confirmOrCancel,
24
+ passwordPromptOptions,
25
+ required,
26
+ requiredUnlessExisting,
27
+ resolvePasswordValue
28
+ } = promptApi;
29
+ await ensureSkillPresent(paths);
30
+ const envPath = join(paths.agentsDir, "skills", "grok-search", ".env");
31
+ const existing = await readSkillEnvFile(envPath);
32
+ const baseUrl = await p.text({
33
+ message: "Grok API URL",
34
+ initialValue: existing.GROK_API_URL || "https://api.x.ai/v1",
35
+ validate: required()
36
+ });
37
+ assertNotCancelled(baseUrl);
38
+
39
+ const apiKey = await p.password(passwordPromptOptions(
40
+ "Grok API Key",
41
+ existing.GROK_API_KEY,
42
+ requiredUnlessExisting(existing.GROK_API_KEY, "Grok API Key 不能为空")
43
+ ));
44
+ assertNotCancelled(apiKey);
45
+ const finalApiKey = resolvePasswordValue(apiKey, existing.GROK_API_KEY);
46
+
47
+ const model = await p.text({
48
+ message: "Grok 默认模型",
49
+ initialValue: existing.GROK_MODEL || grokDefaults.model,
50
+ validate: required()
51
+ });
52
+ assertNotCancelled(model);
53
+
54
+ const useTavily = await confirmOrCancel({
55
+ message: "是否同时配置 Tavily 作为额外搜索源?",
56
+ initialValue: Boolean(existing.TAVILY_API_KEY)
57
+ });
58
+
59
+ const tavilyKey = useTavily
60
+ ? await p.password(passwordPromptOptions(
61
+ "Tavily API Key",
62
+ existing.TAVILY_API_KEY,
63
+ requiredUnlessExisting(existing.TAVILY_API_KEY, "Tavily API Key 不能为空")
64
+ ))
65
+ : "";
66
+ if (useTavily) assertNotCancelled(tavilyKey);
67
+ const finalTavilyKey = useTavily ? resolvePasswordValue(tavilyKey, existing.TAVILY_API_KEY) : null;
68
+
69
+ await updateSkillEnvFile(envPath, {
70
+ GROK_API_URL: baseUrl,
71
+ GROK_API_KEY: finalApiKey,
72
+ GROK_MODEL: model,
73
+ TAVILY_API_KEY: finalTavilyKey,
74
+ TAVILY_ENABLED: useTavily ? "true" : null
75
+ });
76
+
77
+ p.log.step(`已写入 ${pathToLabel(envPath)}`);
78
+ }
79
+
80
+ async function configureContext7Env(paths, ensureSkillPresent = async () => {}, promptApi) {
81
+ const { assertNotCancelled, passwordPromptOptions, resolvePasswordValue } = promptApi;
82
+ await ensureSkillPresent(paths);
83
+ const envPath = join(paths.agentsDir, "skills", "context7-auto-research", ".env");
84
+ const existing = await readSkillEnvFile(envPath);
85
+ const apiKey = await p.password(passwordPromptOptions(
86
+ "Context7 API Key(可选)",
87
+ existing.CONTEXT7_API_KEY
88
+ ));
89
+ assertNotCancelled(apiKey);
90
+ const finalApiKey = resolvePasswordValue(apiKey, existing.CONTEXT7_API_KEY);
91
+
92
+ await updateSkillEnvFile(envPath, {
93
+ CONTEXT7_API_KEY: finalApiKey
94
+ });
95
+
96
+ p.log.step(`已写入 ${pathToLabel(envPath)}`);
97
+ }
98
+
99
+ export {
100
+ configureContext7Env,
101
+ configureGrokSearchEnv,
102
+ grokDefaults,
103
+ readSkillEnvFile,
104
+ updateSkillEnvFile
105
+ };
@@ -21,6 +21,13 @@ You are the general-purpose fallback sub-agent. Be useful quickly, stay within
21
21
  scope, and avoid impersonating a specialist role when the task clearly belongs
22
22
  to one.
23
23
 
24
+ ## Development Context
25
+
26
+ Development context: work is currently in the development phase, and this is a development repository.
27
+ Do not retain runtime user state in the repository or shipped artifacts, and do not preserve compatibility layers for unreleased behavior.
28
+ Prefer deleting obsolete code paths; keep code and prompts concise and avoid over-engineering.
29
+ This context does not by itself authorize destructive changes to user files or credentials outside the repository.
30
+
24
31
  ## Priorities (in order)
25
32
 
26
33
  1. Role fit — identify whether the request truly belongs to default