abelworkflow 0.9.2 → 1.0.0-rc.1

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 (126) hide show
  1. package/README.md +55 -182
  2. package/bin/abelworkflow.mjs +5 -3
  3. package/lib/cli/args.mjs +76 -0
  4. package/lib/cli/main.mjs +387 -0
  5. package/lib/cli/prompts.mjs +119 -0
  6. package/lib/config/dotenv.mjs +102 -0
  7. package/lib/config/jsonc.mjs +80 -0
  8. package/lib/config/store.mjs +310 -0
  9. package/lib/config/toml.mjs +638 -0
  10. package/lib/installer/assets.mjs +326 -0
  11. package/lib/installer/install.mjs +167 -0
  12. package/lib/installer/links.mjs +427 -0
  13. package/lib/installer/render.mjs +46 -0
  14. package/lib/installer/state.mjs +182 -0
  15. package/lib/paths.mjs +99 -0
  16. package/lib/providers/claude.mjs +388 -0
  17. package/lib/providers/codex.mjs +433 -0
  18. package/lib/providers/pi.mjs +362 -0
  19. package/lib/providers/skills.mjs +164 -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/{AGENTS.md → lib/templates/workflow/AGENTS.md} +8 -3
  27. package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
  28. package/{commands → lib/templates/workflow/commands}/abel-implement.md +3 -8
  29. package/{commands → lib/templates/workflow/commands}/abel-init.md +1 -1
  30. package/{commands → lib/templates/workflow/commands}/abel-plan.md +1 -7
  31. package/{commands → lib/templates/workflow/commands}/abel-research.md +1 -1
  32. package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
  33. package/lib/tools/cli-installer.mjs +277 -0
  34. package/package.json +43 -10
  35. package/skills/context7-auto-research/SKILL.md +5 -5
  36. package/skills/context7-auto-research/{context7-api.js → context7-api.cjs} +5 -8
  37. package/skills/dev-browser/SKILL.md +53 -33
  38. package/skills/dev-browser/dist/scripts/start.d.ts +2 -0
  39. package/skills/dev-browser/dist/scripts/start.d.ts.map +1 -0
  40. package/skills/dev-browser/dist/scripts/start.js +118 -0
  41. package/skills/dev-browser/dist/scripts/start.js.map +1 -0
  42. package/skills/dev-browser/dist/src/client.d.ts +93 -0
  43. package/skills/dev-browser/dist/src/client.d.ts.map +1 -0
  44. package/skills/dev-browser/dist/src/client.js +310 -0
  45. package/skills/dev-browser/dist/src/client.js.map +1 -0
  46. package/skills/dev-browser/dist/src/entrypoint.d.ts +29 -0
  47. package/skills/dev-browser/dist/src/entrypoint.d.ts.map +1 -0
  48. package/skills/dev-browser/dist/src/entrypoint.js +106 -0
  49. package/skills/dev-browser/dist/src/entrypoint.js.map +1 -0
  50. package/skills/dev-browser/dist/src/index.d.ts +4 -0
  51. package/skills/dev-browser/dist/src/index.d.ts.map +1 -0
  52. package/skills/dev-browser/dist/src/index.js +2 -0
  53. package/skills/dev-browser/dist/src/index.js.map +1 -0
  54. package/skills/dev-browser/dist/src/page-api.d.ts +25 -0
  55. package/skills/dev-browser/dist/src/page-api.d.ts.map +1 -0
  56. package/skills/dev-browser/dist/src/page-api.js +104 -0
  57. package/skills/dev-browser/dist/src/page-api.js.map +1 -0
  58. package/skills/dev-browser/dist/src/relay.d.ts +27 -0
  59. package/skills/dev-browser/dist/src/relay.d.ts.map +1 -0
  60. package/skills/dev-browser/dist/src/relay.js +521 -0
  61. package/skills/dev-browser/dist/src/relay.js.map +1 -0
  62. package/skills/dev-browser/dist/src/runtime.d.ts +45 -0
  63. package/skills/dev-browser/dist/src/runtime.d.ts.map +1 -0
  64. package/skills/dev-browser/dist/src/runtime.js +54 -0
  65. package/skills/dev-browser/dist/src/runtime.js.map +1 -0
  66. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +23 -0
  67. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +1 -0
  68. package/skills/dev-browser/{src/snapshot/browser-script.ts → dist/src/snapshot/browser-script.js} +21 -30
  69. package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +1 -0
  70. package/skills/dev-browser/dist/src/snapshot/index.d.ts +14 -0
  71. package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +1 -0
  72. package/skills/dev-browser/{src/snapshot/index.ts → dist/src/snapshot/index.js} +2 -2
  73. package/skills/dev-browser/dist/src/snapshot/index.js.map +1 -0
  74. package/skills/dev-browser/dist/src/snapshot/inject.d.ts +13 -0
  75. package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +1 -0
  76. package/skills/dev-browser/{src/snapshot/inject.ts → dist/src/snapshot/inject.js} +2 -2
  77. package/skills/dev-browser/dist/src/snapshot/inject.js.map +1 -0
  78. package/skills/dev-browser/dist/src/standalone.d.ts +32 -0
  79. package/skills/dev-browser/dist/src/standalone.d.ts.map +1 -0
  80. package/skills/dev-browser/dist/src/standalone.js +174 -0
  81. package/skills/dev-browser/dist/src/standalone.js.map +1 -0
  82. package/skills/dev-browser/dist/src/startup.d.ts +55 -0
  83. package/skills/dev-browser/dist/src/startup.d.ts.map +1 -0
  84. package/skills/dev-browser/dist/src/startup.js +81 -0
  85. package/skills/dev-browser/dist/src/startup.js.map +1 -0
  86. package/skills/dev-browser/dist/src/target-registry.d.ts +29 -0
  87. package/skills/dev-browser/dist/src/target-registry.d.ts.map +1 -0
  88. package/skills/dev-browser/dist/src/target-registry.js +135 -0
  89. package/skills/dev-browser/dist/src/target-registry.js.map +1 -0
  90. package/skills/dev-browser/dist/src/types.d.ts +27 -0
  91. package/skills/dev-browser/dist/src/types.d.ts.map +1 -0
  92. package/skills/dev-browser/dist/src/types.js +2 -0
  93. package/skills/dev-browser/dist/src/types.js.map +1 -0
  94. package/skills/dev-browser/package-lock.json +67 -1510
  95. package/skills/dev-browser/package.json +18 -14
  96. package/skills/dev-browser/references/scraping.md +94 -105
  97. package/skills/grok-search/defaults.json +3 -0
  98. package/skills/grok-search/gitignore.template +4 -0
  99. package/skills/grok-search/scripts/_dotenv.py +5 -1
  100. package/skills/grok-search/scripts/groksearch_cli.py +15 -33
  101. package/skills/prompt-enhancer/TEMPLATE.md +0 -4
  102. package/skills/prompt-enhancer/requirements.txt +0 -1
  103. package/skills/prompt-enhancer/scripts/_dotenv.py +5 -1
  104. package/skills/prompt-enhancer/scripts/enhance.py +4 -51
  105. package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +1 -16
  106. package/.skill-lock.json +0 -29
  107. package/lib/cli/logic.mjs +0 -213
  108. package/lib/cli.mjs +0 -3016
  109. package/skills/confidence-check/SKILL.md +0 -34
  110. package/skills/confidence-check/confidence.ts +0 -276
  111. package/skills/dev-browser/bun.lock +0 -460
  112. package/skills/dev-browser/scripts/start.ts +0 -280
  113. package/skills/dev-browser/src/client.ts +0 -474
  114. package/skills/dev-browser/src/entrypoint.ts +0 -157
  115. package/skills/dev-browser/src/index.ts +0 -289
  116. package/skills/dev-browser/src/relay.ts +0 -731
  117. package/skills/dev-browser/src/runtime.test.ts +0 -60
  118. package/skills/dev-browser/src/runtime.ts +0 -171
  119. package/skills/dev-browser/src/startup.test.ts +0 -95
  120. package/skills/dev-browser/src/startup.ts +0 -153
  121. package/skills/dev-browser/src/types.ts +0 -35
  122. package/skills/dev-browser/tsconfig.json +0 -36
  123. package/skills/dev-browser/vitest.config.ts +0 -12
  124. package/skills/sequential-think/SKILL.md +0 -198
  125. package/skills/sequential-think/scripts/.env.example +0 -5
  126. package/skills/sequential-think/scripts/sequential_think_cli.py +0 -253
@@ -2,30 +2,34 @@
2
2
  "name": "dev-browser",
3
3
  "version": "0.0.1",
4
4
  "type": "module",
5
- "imports": {
6
- "@/*": "./src/*"
5
+ "engines": {
6
+ "node": ">=22"
7
+ },
8
+ "packageManager": "npm@10.9.2",
9
+ "exports": {
10
+ "./client": {
11
+ "types": "./dist/src/client.d.ts",
12
+ "import": "./dist/src/client.js"
13
+ }
7
14
  },
8
15
  "scripts": {
9
- "start-server": "npx tsx scripts/start.ts standalone",
10
- "start-extension": "npx tsx scripts/start.ts extension",
11
- "dev": "npx tsx --watch src/index.ts",
16
+ "build": "tsc -p tsconfig.json",
17
+ "typecheck": "tsc -p tsconfig.json --noEmit",
18
+ "start": "node dist/scripts/start.js standalone",
19
+ "start-server": "node dist/scripts/start.js standalone",
20
+ "start-extension": "node dist/scripts/start.js extension",
12
21
  "test": "vitest run",
13
22
  "test:watch": "vitest"
14
23
  },
15
24
  "dependencies": {
16
- "@hono/node-server": "^1.19.7",
17
- "@hono/node-ws": "^1.2.0",
18
- "express": "^4.21.0",
19
- "hono": "^4.11.1",
25
+ "@hono/node-server": "^1.19.13",
26
+ "@hono/node-ws": "^1.3.1",
27
+ "hono": "^4.12.30",
20
28
  "playwright": "^1.49.0"
21
29
  },
22
30
  "devDependencies": {
23
- "@types/express": "^5.0.0",
24
- "tsx": "^4.21.0",
31
+ "@types/node": "^22.0.0",
25
32
  "typescript": "^5.0.0",
26
33
  "vitest": "^2.1.0"
27
- },
28
- "optionalDependencies": {
29
- "@rollup/rollup-linux-x64-gnu": "^4.0.0"
30
34
  }
31
35
  }
@@ -17,57 +17,106 @@ Scrolling is slow, unreliable, and wastes time. APIs return structured data with
17
17
 
18
18
  This prevents wasting time debugging a complex script when the issue is a simple path like `data.user.timeline` vs `data.user.result.timeline`.
19
19
 
20
- ## Step-by-Step Workflow
21
-
22
- ### 1. Capture Request Details
23
-
24
- First, intercept a request to understand URL structure and required headers:
25
-
26
- ```typescript
27
- import { connect, waitForPageLoad } from "@/client.js";
28
- import * as fs from "node:fs";
29
-
30
- const client = await connect();
31
- const page = await client.page("site");
32
-
33
- let capturedRequest = null;
34
- page.on("request", (request) => {
35
- const url = request.url();
36
- // Look for API endpoints (adjust pattern for your target site)
37
- if (url.includes("/api/") || url.includes("/graphql/")) {
38
- capturedRequest = {
39
- url: url,
40
- headers: request.headers(),
41
- method: request.method(),
42
- };
43
- fs.writeFileSync("tmp/request-details.json", JSON.stringify(capturedRequest, null, 2));
44
- console.log("Captured request:", url.substring(0, 80) + "...");
20
+ ## External Script Setup
21
+
22
+ Keep the installed skill directory read-only. Put scripts and temporary artifacts in the OS temporary directory or the task workspace, and remove them when the task finishes. External scripts receive the installed skill directory as their first argument and dynamically import the compiled client by file URL.
23
+
24
+ Never persist complete authentication headers. Keep captured headers in memory, and write only redacted metadata or final non-sensitive results.
25
+
26
+ ```javascript
27
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
28
+ import { tmpdir } from "node:os";
29
+ import { join } from "node:path";
30
+ import { pathToFileURL } from "node:url";
31
+
32
+ const skillDir = process.argv[2];
33
+ if (!skillDir) throw new Error("Pass the installed dev-browser skill directory");
34
+ const { connect, waitForPageLoad } = await import(
35
+ pathToFileURL(join(skillDir, "dist", "src", "client.js")).href
36
+ );
37
+
38
+ const workDir = await mkdtemp(join(tmpdir(), "dev-browser-scrape-"));
39
+ let client;
40
+ try {
41
+ client = await connect();
42
+ const page = await client.page("site");
43
+ let capturedRequest;
44
+
45
+ page.on("request", (request) => {
46
+ const url = request.url();
47
+ if (url.includes("/api/") || url.includes("/graphql/")) {
48
+ capturedRequest = {
49
+ url,
50
+ method: request.method(),
51
+ headers: request.headers(),
52
+ };
53
+ }
54
+ });
55
+
56
+ await page.goto("https://example.com/profile");
57
+ await waitForPageLoad(page);
58
+ await page.waitForTimeout(3000);
59
+
60
+ if (!capturedRequest) throw new Error("No matching API request captured");
61
+ const capturedUrl = new URL(capturedRequest.url);
62
+ console.log({
63
+ url: `${capturedUrl.origin}${capturedUrl.pathname}`,
64
+ method: capturedRequest.method,
65
+ headerNames: Object.keys(capturedRequest.headers),
66
+ });
67
+
68
+ const results = new Map();
69
+ let cursor;
70
+ do {
71
+ const params = { count: 20, ...(cursor ? { cursor } : {}) };
72
+ const url = new URL(capturedRequest.url);
73
+ url.searchParams.set("params", JSON.stringify(params));
74
+ const response = await page.evaluate(
75
+ async ({ url, headers }) => {
76
+ const result = await fetch(url, { headers });
77
+ return result.json();
78
+ },
79
+ { url: url.href, headers: capturedRequest.headers }
80
+ );
81
+
82
+ const entries = response?.data?.entries ?? [];
83
+ cursor = undefined;
84
+ for (const entry of entries) {
85
+ if (entry.type === "cursor-bottom") cursor = entry.value;
86
+ else if (entry.id) results.set(entry.id, entry);
87
+ }
88
+ await new Promise((resolve) => setTimeout(resolve, 500));
89
+ } while (cursor);
90
+
91
+ const resultPath = join(workDir, "results.json");
92
+ await writeFile(resultPath, JSON.stringify([...results.values()], null, 2));
93
+ console.log({ count: results.size, resultPath });
94
+ } finally {
95
+ try {
96
+ await client?.disconnect();
97
+ } finally {
98
+ await rm(workDir, { recursive: true, force: true });
45
99
  }
46
- });
100
+ }
101
+ ```
47
102
 
48
- await page.goto("https://example.com/profile");
49
- await waitForPageLoad(page);
50
- await page.waitForTimeout(3000);
103
+ Run it with quoted paths on Linux, PowerShell, or Command Prompt:
51
104
 
52
- await client.disconnect();
105
+ ```text
106
+ node "PATH_TO_SCRIPT.mjs" "PATH_TO_INSTALLED_DEV_BROWSER"
53
107
  ```
54
108
 
55
- ### 2. Capture Response to Understand Schema
109
+ If a result must survive the script, write the sanitized deliverable to the task workspace instead of `workDir`, then delete it at task completion unless the user asks to retain it.
56
110
 
57
- Save a raw response to inspect the data structure:
111
+ ## Step-by-Step Workflow
58
112
 
59
- ```typescript
60
- page.on("response", async (response) => {
61
- const url = response.url();
62
- if (url.includes("UserTweets") || url.includes("/api/data")) {
63
- const json = await response.json();
64
- fs.writeFileSync("tmp/api-response.json", JSON.stringify(json, null, 2));
65
- console.log("Captured response");
66
- }
67
- });
68
- ```
113
+ ### 1. Capture One Request
114
+
115
+ Match a narrow API or GraphQL URL pattern. Keep the request URL and headers in memory. Log only the URL origin/path, method, and header names while confirming the endpoint; query parameters can contain credentials.
69
116
 
70
- Then analyze the structure to find:
117
+ ### 2. Inspect One Response
118
+
119
+ Inspect the response in memory before building pagination. Find:
71
120
 
72
121
  - Where the data array lives (e.g., `data.user.result.timeline.instructions[].entries`)
73
122
  - Where pagination cursors are (e.g., `cursor-bottom` entries)
@@ -75,67 +124,7 @@ Then analyze the structure to find:
75
124
 
76
125
  ### 3. Replay API with Pagination
77
126
 
78
- Once you understand the schema, replay requests directly:
79
-
80
- ```typescript
81
- import { connect } from "@/client.js";
82
- import * as fs from "node:fs";
83
-
84
- const client = await connect();
85
- const page = await client.page("site");
86
-
87
- const results = new Map(); // Use Map for deduplication
88
- const headers = JSON.parse(fs.readFileSync("tmp/request-details.json", "utf8")).headers;
89
- const baseUrl = "https://example.com/api/data";
90
-
91
- let cursor = null;
92
- let hasMore = true;
93
-
94
- while (hasMore) {
95
- // Build URL with pagination cursor
96
- const params = { count: 20 };
97
- if (cursor) params.cursor = cursor;
98
- const url = `${baseUrl}?params=${encodeURIComponent(JSON.stringify(params))}`;
99
-
100
- // Execute fetch in browser context (has auth cookies/headers)
101
- const response = await page.evaluate(
102
- async ({ url, headers }) => {
103
- const res = await fetch(url, { headers });
104
- return res.json();
105
- },
106
- { url, headers }
107
- );
108
-
109
- // Extract data and cursor (adjust paths for your API)
110
- const entries = response?.data?.entries || [];
111
- for (const entry of entries) {
112
- if (entry.type === "cursor-bottom") {
113
- cursor = entry.value;
114
- } else if (entry.id && !results.has(entry.id)) {
115
- results.set(entry.id, {
116
- id: entry.id,
117
- text: entry.content,
118
- timestamp: entry.created_at,
119
- });
120
- }
121
- }
122
-
123
- console.log(`Fetched page, total: ${results.size}`);
124
-
125
- // Check stop conditions
126
- if (!cursor || entries.length === 0) hasMore = false;
127
-
128
- // Rate limiting - be respectful
129
- await new Promise((r) => setTimeout(r, 500));
130
- }
131
-
132
- // Export results
133
- const data = Array.from(results.values());
134
- fs.writeFileSync("tmp/results.json", JSON.stringify(data, null, 2));
135
- console.log(`Saved ${data.length} items`);
136
-
137
- await client.disconnect();
138
- ```
127
+ Replay through `page.evaluate(fetch)` so the browser supplies its session cookies. Pass only the in-memory headers required by the endpoint, deduplicate with a `Map`, stop when the cursor or results end, and rate-limit requests.
139
128
 
140
129
  ## Key Patterns
141
130
 
@@ -149,7 +138,7 @@ await client.disconnect();
149
138
 
150
139
  ## Tips
151
140
 
152
- - **Extension mode**: `page.context().cookies()` doesn't work - capture auth headers from intercepted requests instead
141
+ - **Extension mode**: `page.context().cookies()` doesn't work - keep intercepted auth headers in memory instead
153
142
  - **Rate limiting**: Add 500ms+ delays between requests to avoid blocks
154
143
  - **Stop conditions**: Check for empty results, missing cursor, or reaching a date/ID threshold
155
144
  - **GraphQL APIs**: URL params often include `variables` and `features` JSON objects - capture and reuse them
@@ -0,0 +1,3 @@
1
+ {
2
+ "model": "grok-4.20-auto"
3
+ }
@@ -0,0 +1,4 @@
1
+ GrokSearch_new/
2
+ .env
3
+ .venv/
4
+ *__pycache__*
@@ -9,6 +9,7 @@ def load_dotenv() -> bool:
9
9
  if not env_path.exists():
10
10
  return False
11
11
  try:
12
+ seen_keys = set()
12
13
  with open(env_path, "r", encoding="utf-8") as f:
13
14
  for line in f:
14
15
  line = line.strip()
@@ -21,7 +22,10 @@ def load_dotenv() -> bool:
21
22
  value.startswith("'") and value.endswith("'")
22
23
  ):
23
24
  value = value[1:-1]
24
- if key and not os.environ.get(key):
25
+ if not key or key in seen_keys:
26
+ continue
27
+ seen_keys.add(key)
28
+ if not os.environ.get(key):
25
29
  os.environ[key] = value
26
30
  return True
27
31
  except IOError:
@@ -15,6 +15,8 @@ from pathlib import Path
15
15
  from typing import Optional
16
16
  from urllib.parse import urlsplit, urlunsplit
17
17
 
18
+ from _dotenv import load_dotenv
19
+
18
20
  try:
19
21
  import httpx
20
22
  from tenacity import AsyncRetrying, retry_if_exception, stop_after_attempt, wait_random_exponential
@@ -24,38 +26,6 @@ except ImportError:
24
26
  sys.exit(1)
25
27
 
26
28
 
27
- # ============================================================================
28
- # .env 文件支持
29
- # ============================================================================
30
-
31
- def load_dotenv() -> bool:
32
- """从项目根目录的 .env 文件加载环境变量。"""
33
- env_path = Path(__file__).parent.parent / ".env"
34
- if env_path.exists():
35
- try:
36
- with open(env_path, 'r', encoding='utf-8') as f:
37
- for line in f:
38
- line = line.strip()
39
- if not line or line.startswith('#'):
40
- continue
41
- if '=' in line:
42
- key, _, value = line.partition('=')
43
- key = key.strip()
44
- value = value.strip()
45
- # 如果存在引号则移除
46
- if (value.startswith('"') and value.endswith('"')) or \
47
- (value.startswith("'") and value.endswith("'")):
48
- value = value[1:-1]
49
- # 允许 .env 覆盖空字符串环境变量
50
- if key and not os.environ.get(key):
51
- os.environ[key] = value
52
- return True
53
- except IOError:
54
- pass
55
- return False
56
-
57
-
58
- # 模块导入时加载 .env
59
29
  load_dotenv()
60
30
 
61
31
 
@@ -63,9 +33,21 @@ load_dotenv()
63
33
  # 配置
64
34
  # ============================================================================
65
35
 
36
+ def load_default_model() -> str:
37
+ defaults_path = Path(__file__).resolve().parent.parent / "defaults.json"
38
+ try:
39
+ defaults = json.loads(defaults_path.read_text(encoding="utf-8"))
40
+ model = defaults["model"]
41
+ except (OSError, json.JSONDecodeError, KeyError, TypeError) as error:
42
+ raise RuntimeError(f"无法读取 Grok 默认配置 {defaults_path}: {error}") from error
43
+ if not isinstance(model, str) or not model.strip():
44
+ raise RuntimeError(f"Grok 默认配置缺少有效 model: {defaults_path}")
45
+ return model
46
+
47
+
66
48
  class Config:
67
49
  _instance = None
68
- _DEFAULT_MODEL = "grok-4.20-auto"
50
+ _DEFAULT_MODEL = load_default_model()
69
51
 
70
52
  def __new__(cls):
71
53
  if cls._instance is None:
@@ -84,8 +84,4 @@ Identify and fix the sorting bug in the codebase.
84
84
  5. Add or update tests to cover the bug scenario
85
85
 
86
86
  # Constraints
87
- - Maintain backward compatibility
88
- - Do not change the function signature unless necessary
89
- - Add inline comments explaining the fix
90
87
  ```
91
-
@@ -1,2 +1 @@
1
- anthropic>=0.34,<1
2
1
  openai>=1.30,<2
@@ -9,6 +9,7 @@ def load_dotenv() -> bool:
9
9
  if not env_path.exists():
10
10
  return False
11
11
  try:
12
+ seen_keys = set()
12
13
  with open(env_path, "r", encoding="utf-8") as f:
13
14
  for line in f:
14
15
  line = line.strip()
@@ -21,7 +22,10 @@ def load_dotenv() -> bool:
21
22
  value.startswith("'") and value.endswith("'")
22
23
  ):
23
24
  value = value[1:-1]
24
- if key and not os.environ.get(key):
25
+ if not key or key in seen_keys:
26
+ continue
27
+ seen_keys.add(key)
28
+ if not os.environ.get(key):
25
29
  os.environ[key] = value
26
30
  return True
27
31
  except IOError:
@@ -72,19 +72,7 @@ def resolve_prompt(args: argparse.Namespace) -> str:
72
72
  raise ValueError("Missing prompt. Pass --prompt \"...\" or a positional prompt.")
73
73
 
74
74
 
75
- def first_non_empty(*values: str) -> str:
76
- for value in values:
77
- stripped = value.strip()
78
- if stripped:
79
- return stripped
80
- return ""
81
-
82
-
83
- def env_value(*names: str) -> str:
84
- return first_non_empty(*(os.environ.get(name, "") for name in names))
85
-
86
-
87
- def resolve_config(args: argparse.Namespace) -> Tuple[str, str, str, str]:
75
+ def resolve_config(args: argparse.Namespace) -> Tuple[str, str, str]:
88
76
  api_url = (args.api_url or os.environ.get("PE_API_URL", "")).strip()
89
77
  api_key = (args.api_key or os.environ.get("PE_API_KEY", "")).strip()
90
78
  model = (args.model or os.environ.get("PE_MODEL", "")).strip()
@@ -97,44 +85,12 @@ def resolve_config(args: argparse.Namespace) -> Tuple[str, str, str, str]:
97
85
  if not model:
98
86
  missing.append("model")
99
87
  if missing:
100
- has_openai_compatible_transport = bool(api_url or api_key)
101
- if has_openai_compatible_transport:
102
- raise ValueError(
103
- "Missing third-party OpenAI-compatible config: "
104
- + ", ".join(missing)
105
- + ". If these fields are unavailable, use the current agent directly."
106
- )
107
-
108
- legacy_anthropic_key = env_value("ANTHROPIC_API_KEY")
109
- if legacy_anthropic_key:
110
- return "anthropic", "", legacy_anthropic_key, first_non_empty(model, "claude-sonnet-4-20250514")
111
-
112
- legacy_openai_key = env_value("OPENAI_API_KEY")
113
- if legacy_openai_key:
114
- return "openai", "", legacy_openai_key, first_non_empty(model, "gpt-4o")
115
-
116
88
  raise ValueError(
117
89
  "Missing third-party OpenAI-compatible config: "
118
90
  + ", ".join(missing)
119
91
  + ". If these fields are unavailable, use the current agent directly."
120
92
  )
121
- return "openai-compatible", api_url, api_key, model
122
-
123
-
124
- def enhance_with_anthropic(prompt: str, api_key: str, model: str) -> str:
125
- try:
126
- import anthropic
127
- except ImportError:
128
- raise RuntimeError("Missing dependency: anthropic. Install dependencies for the configured provider.") from None
129
-
130
- client = anthropic.Anthropic(api_key=api_key)
131
- message = client.messages.create(
132
- model=model,
133
- max_tokens=2048,
134
- system=SYSTEM_PROMPT,
135
- messages=[{"role": "user", "content": prompt}],
136
- )
137
- return message.content[0].text
93
+ return api_url, api_key, model
138
94
 
139
95
 
140
96
  def _extract_response_text(response) -> str:
@@ -170,11 +126,8 @@ def main() -> None:
170
126
  try:
171
127
  args = parse_args()
172
128
  prompt = resolve_prompt(args)
173
- provider, api_url, api_key, model = resolve_config(args)
174
- if provider == "anthropic":
175
- print(enhance_with_anthropic(prompt, api_key, model))
176
- else:
177
- print(enhance_with_openai(prompt, api_key, model, api_url))
129
+ api_url, api_key, model = resolve_config(args)
130
+ print(enhance_with_openai(prompt, api_key, model, api_url))
178
131
  except Exception as exc:
179
132
  if debug_enabled():
180
133
  print(f"Error: {exc}", file=sys.stderr)
@@ -99,10 +99,6 @@ def first_non_empty(*values: str) -> str:
99
99
  return ""
100
100
 
101
101
 
102
- def env_value(*names: str) -> str:
103
- return first_non_empty(*(os.environ.get(name, "") for name in names))
104
-
105
-
106
102
  def argv_value(flag: str, argv: Optional[List[str]] = None) -> str:
107
103
  argv = sys.argv[1:] if argv is None else argv
108
104
  for index, token in enumerate(argv):
@@ -123,22 +119,11 @@ def resolved_provider(argv: Optional[List[str]] = None) -> Optional[str]:
123
119
 
124
120
  if api_url and api_key and model:
125
121
  return "openai"
126
- if api_url or api_key:
127
- return None
128
- if env_value("ANTHROPIC_API_KEY"):
129
- return "anthropic"
130
- if env_value("OPENAI_API_KEY"):
131
- return "openai"
132
122
  return None
133
123
 
134
124
 
135
125
  def required_modules() -> List[str]:
136
- provider = resolved_provider()
137
- if provider == "anthropic":
138
- return ["anthropic"]
139
- if provider == "openai":
140
- return ["openai"]
141
- return []
126
+ return ["openai"] if resolved_provider() == "openai" else []
142
127
 
143
128
 
144
129
  def install_targets(modules: List[str]) -> List[str]:
package/.skill-lock.json DELETED
@@ -1,29 +0,0 @@
1
- {
2
- "version": 3,
3
- "skills": {
4
- "git-commit": {
5
- "source": "github/awesome-copilot",
6
- "sourceType": "github",
7
- "sourceUrl": "https://github.com/github/awesome-copilot.git",
8
- "skillPath": "skills/git-commit/SKILL.md",
9
- "skillFolderHash": "883a6a7466f55a9cd9f22cf1cce2d9333fc9b998",
10
- "installedAt": "2026-03-12T08:25:08.313Z",
11
- "updatedAt": "2026-03-12T08:25:08.313Z"
12
- }
13
- },
14
- "dismissed": {
15
- "findSkillsPrompt": true
16
- },
17
- "lastSelectedAgents": [
18
- "amp",
19
- "cline",
20
- "codex",
21
- "cursor",
22
- "gemini-cli",
23
- "github-copilot",
24
- "kimi-cli",
25
- "opencode",
26
- "claude-code",
27
- "crush"
28
- ]
29
- }