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.
- package/README.md +55 -182
- package/bin/abelworkflow.mjs +5 -3
- package/lib/cli/args.mjs +76 -0
- package/lib/cli/main.mjs +387 -0
- package/lib/cli/prompts.mjs +119 -0
- package/lib/config/dotenv.mjs +102 -0
- package/lib/config/jsonc.mjs +80 -0
- package/lib/config/store.mjs +310 -0
- package/lib/config/toml.mjs +638 -0
- package/lib/installer/assets.mjs +326 -0
- package/lib/installer/install.mjs +167 -0
- package/lib/installer/links.mjs +427 -0
- package/lib/installer/render.mjs +46 -0
- package/lib/installer/state.mjs +182 -0
- package/lib/paths.mjs +99 -0
- package/lib/providers/claude.mjs +388 -0
- package/lib/providers/codex.mjs +433 -0
- package/lib/providers/pi.mjs +362 -0
- package/lib/providers/skills.mjs +164 -0
- package/lib/templates/codex/agents/default.toml +7 -0
- package/lib/templates/codex/agents/explorer.toml +7 -0
- package/lib/templates/codex/agents/planner.toml +7 -0
- package/lib/templates/codex/agents/reviewer.toml +7 -0
- package/lib/templates/codex/agents/worker.toml +7 -0
- package/lib/templates/codex/config-base.toml +11 -3
- package/{AGENTS.md → lib/templates/workflow/AGENTS.md} +8 -3
- package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
- package/{commands → lib/templates/workflow/commands}/abel-implement.md +3 -8
- package/{commands → lib/templates/workflow/commands}/abel-init.md +1 -1
- package/{commands → lib/templates/workflow/commands}/abel-plan.md +1 -7
- package/{commands → lib/templates/workflow/commands}/abel-research.md +1 -1
- package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
- package/lib/tools/cli-installer.mjs +277 -0
- package/package.json +43 -10
- package/skills/context7-auto-research/SKILL.md +5 -5
- package/skills/context7-auto-research/{context7-api.js → context7-api.cjs} +5 -8
- package/skills/dev-browser/SKILL.md +53 -33
- package/skills/dev-browser/dist/scripts/start.d.ts +2 -0
- package/skills/dev-browser/dist/scripts/start.d.ts.map +1 -0
- package/skills/dev-browser/dist/scripts/start.js +118 -0
- package/skills/dev-browser/dist/scripts/start.js.map +1 -0
- package/skills/dev-browser/dist/src/client.d.ts +93 -0
- package/skills/dev-browser/dist/src/client.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/client.js +310 -0
- package/skills/dev-browser/dist/src/client.js.map +1 -0
- package/skills/dev-browser/dist/src/entrypoint.d.ts +29 -0
- package/skills/dev-browser/dist/src/entrypoint.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/entrypoint.js +106 -0
- package/skills/dev-browser/dist/src/entrypoint.js.map +1 -0
- package/skills/dev-browser/dist/src/index.d.ts +4 -0
- package/skills/dev-browser/dist/src/index.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/index.js +2 -0
- package/skills/dev-browser/dist/src/index.js.map +1 -0
- package/skills/dev-browser/dist/src/page-api.d.ts +25 -0
- package/skills/dev-browser/dist/src/page-api.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/page-api.js +104 -0
- package/skills/dev-browser/dist/src/page-api.js.map +1 -0
- package/skills/dev-browser/dist/src/relay.d.ts +27 -0
- package/skills/dev-browser/dist/src/relay.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/relay.js +521 -0
- package/skills/dev-browser/dist/src/relay.js.map +1 -0
- package/skills/dev-browser/dist/src/runtime.d.ts +45 -0
- package/skills/dev-browser/dist/src/runtime.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/runtime.js +54 -0
- package/skills/dev-browser/dist/src/runtime.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +23 -0
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/browser-script.ts → dist/src/snapshot/browser-script.js} +21 -30
- package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/index.d.ts +14 -0
- package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/index.ts → dist/src/snapshot/index.js} +2 -2
- package/skills/dev-browser/dist/src/snapshot/index.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts +13 -0
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/inject.ts → dist/src/snapshot/inject.js} +2 -2
- package/skills/dev-browser/dist/src/snapshot/inject.js.map +1 -0
- package/skills/dev-browser/dist/src/standalone.d.ts +32 -0
- package/skills/dev-browser/dist/src/standalone.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/standalone.js +174 -0
- package/skills/dev-browser/dist/src/standalone.js.map +1 -0
- package/skills/dev-browser/dist/src/startup.d.ts +55 -0
- package/skills/dev-browser/dist/src/startup.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/startup.js +81 -0
- package/skills/dev-browser/dist/src/startup.js.map +1 -0
- package/skills/dev-browser/dist/src/target-registry.d.ts +29 -0
- package/skills/dev-browser/dist/src/target-registry.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/target-registry.js +135 -0
- package/skills/dev-browser/dist/src/target-registry.js.map +1 -0
- package/skills/dev-browser/dist/src/types.d.ts +27 -0
- package/skills/dev-browser/dist/src/types.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/types.js +2 -0
- package/skills/dev-browser/dist/src/types.js.map +1 -0
- package/skills/dev-browser/package-lock.json +67 -1510
- package/skills/dev-browser/package.json +18 -14
- package/skills/dev-browser/references/scraping.md +94 -105
- package/skills/grok-search/defaults.json +3 -0
- package/skills/grok-search/gitignore.template +4 -0
- package/skills/grok-search/scripts/_dotenv.py +5 -1
- package/skills/grok-search/scripts/groksearch_cli.py +15 -33
- package/skills/prompt-enhancer/TEMPLATE.md +0 -4
- package/skills/prompt-enhancer/requirements.txt +0 -1
- package/skills/prompt-enhancer/scripts/_dotenv.py +5 -1
- package/skills/prompt-enhancer/scripts/enhance.py +4 -51
- package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +1 -16
- package/.skill-lock.json +0 -29
- package/lib/cli/logic.mjs +0 -213
- package/lib/cli.mjs +0 -3016
- package/skills/confidence-check/SKILL.md +0 -34
- package/skills/confidence-check/confidence.ts +0 -276
- package/skills/dev-browser/bun.lock +0 -460
- package/skills/dev-browser/scripts/start.ts +0 -280
- package/skills/dev-browser/src/client.ts +0 -474
- package/skills/dev-browser/src/entrypoint.ts +0 -157
- package/skills/dev-browser/src/index.ts +0 -289
- package/skills/dev-browser/src/relay.ts +0 -731
- package/skills/dev-browser/src/runtime.test.ts +0 -60
- package/skills/dev-browser/src/runtime.ts +0 -171
- package/skills/dev-browser/src/startup.test.ts +0 -95
- package/skills/dev-browser/src/startup.ts +0 -153
- package/skills/dev-browser/src/types.ts +0 -35
- package/skills/dev-browser/tsconfig.json +0 -36
- package/skills/dev-browser/vitest.config.ts +0 -12
- package/skills/sequential-think/SKILL.md +0 -198
- package/skills/sequential-think/scripts/.env.example +0 -5
- 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
|
-
"
|
|
6
|
-
"
|
|
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
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
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.
|
|
17
|
-
"@hono/node-ws": "^1.
|
|
18
|
-
"
|
|
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/
|
|
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
|
-
##
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
import {
|
|
28
|
-
import
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
49
|
-
await waitForPageLoad(page);
|
|
50
|
-
await page.waitForTimeout(3000);
|
|
103
|
+
Run it with quoted paths on Linux, PowerShell, or Command Prompt:
|
|
51
104
|
|
|
52
|
-
|
|
105
|
+
```text
|
|
106
|
+
node "PATH_TO_SCRIPT.mjs" "PATH_TO_INSTALLED_DEV_BROWSER"
|
|
53
107
|
```
|
|
54
108
|
|
|
55
|
-
|
|
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
|
-
|
|
111
|
+
## Step-by-Step Workflow
|
|
58
112
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 -
|
|
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
|
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
-
}
|