skilld 1.2.3 → 1.4.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/README.md +21 -20
- package/dist/_chunks/agent.mjs +471 -17
- package/dist/_chunks/agent.mjs.map +1 -1
- package/dist/_chunks/assemble.mjs +2 -2
- package/dist/_chunks/assemble.mjs.map +1 -1
- package/dist/_chunks/cache.mjs +8 -2
- package/dist/_chunks/cache.mjs.map +1 -1
- package/dist/_chunks/cache2.mjs +2 -2
- package/dist/_chunks/cache2.mjs.map +1 -1
- package/dist/_chunks/cli-helpers.mjs +421 -0
- package/dist/_chunks/cli-helpers.mjs.map +1 -0
- package/dist/_chunks/detect.mjs +51 -22
- package/dist/_chunks/detect.mjs.map +1 -1
- package/dist/_chunks/detect2.mjs +2 -0
- package/dist/_chunks/embedding-cache.mjs +13 -4
- package/dist/_chunks/embedding-cache.mjs.map +1 -1
- package/dist/_chunks/formatting.mjs +1 -286
- package/dist/_chunks/formatting.mjs.map +1 -1
- package/dist/_chunks/index.d.mts.map +1 -1
- package/dist/_chunks/install.mjs +4 -3
- package/dist/_chunks/install.mjs.map +1 -1
- package/dist/_chunks/list.mjs +44 -5
- package/dist/_chunks/list.mjs.map +1 -1
- package/dist/_chunks/pool.mjs +3 -2
- package/dist/_chunks/pool.mjs.map +1 -1
- package/dist/_chunks/prompts.mjs +38 -4
- package/dist/_chunks/prompts.mjs.map +1 -1
- package/dist/_chunks/search-interactive.mjs +3 -2
- package/dist/_chunks/search-interactive.mjs.map +1 -1
- package/dist/_chunks/search.mjs +4 -3
- package/dist/_chunks/search.mjs.map +1 -1
- package/dist/_chunks/setup.mjs +27 -0
- package/dist/_chunks/setup.mjs.map +1 -0
- package/dist/_chunks/shared.mjs +6 -2
- package/dist/_chunks/shared.mjs.map +1 -1
- package/dist/_chunks/skills.mjs +1 -1
- package/dist/_chunks/sources.mjs +1 -1
- package/dist/_chunks/sync.mjs +389 -108
- package/dist/_chunks/sync.mjs.map +1 -1
- package/dist/_chunks/uninstall.mjs +16 -2
- package/dist/_chunks/uninstall.mjs.map +1 -1
- package/dist/agent/index.d.mts +22 -4
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +3 -3
- package/dist/cli.mjs +619 -328
- package/dist/cli.mjs.map +1 -1
- package/dist/retriv/index.d.mts +18 -3
- package/dist/retriv/index.d.mts.map +1 -1
- package/dist/retriv/index.mjs +30 -1
- package/dist/retriv/index.mjs.map +1 -1
- package/dist/retriv/worker.d.mts +2 -0
- package/dist/retriv/worker.d.mts.map +1 -1
- package/dist/retriv/worker.mjs +1 -0
- package/dist/retriv/worker.mjs.map +1 -1
- package/dist/sources/index.mjs +1 -1
- package/package.json +6 -5
- package/dist/_chunks/chunk.mjs +0 -15
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Skilld generates [agent skills](https://agentskills.io/home) from the references
|
|
|
21
21
|
<table>
|
|
22
22
|
<tbody>
|
|
23
23
|
<td align="center">
|
|
24
|
-
<sub>Made possible by my <a href="https://github.com/sponsors/harlan-zw">Sponsor Program 💖</a><br> Follow me <a href="https://twitter.com/harlan_zw">@harlan_zw</a> 🐦
|
|
24
|
+
<sub>Made possible by my <a href="https://github.com/sponsors/harlan-zw">Sponsor Program 💖</a><br> Follow me <a href="https://twitter.com/harlan_zw">@harlan_zw</a> 🐦 - Join <a href="https://discord.gg/275MBUBvgP">Discord</a> for help</sub><br>
|
|
25
25
|
</td>
|
|
26
26
|
</tbody>
|
|
27
27
|
</table>
|
|
@@ -32,11 +32,11 @@ Skilld generates [agent skills](https://agentskills.io/home) from the references
|
|
|
32
32
|
- 🌍 **Any Source: Opt-in** - Any NPM dependency or GitHub source, docs auto-resolved
|
|
33
33
|
- 📦 **Bleeding Edge Context** - Latest issues, discussions, and releases. Always use the latest best practices and avoid deprecated patterns.
|
|
34
34
|
- 📚 **Opt-in LLM Sections** - Enhance skills with LLM-generated `Best Practices`, `API Changes`, or your own custom prompts
|
|
35
|
-
- 🧩 **
|
|
35
|
+
- 🧩 **Use Any Agent** - Choose your agent: CLI , [pi](https://github.com/badlogic/pi-mono/tree/main/packages/ai) agents or no agent at all.
|
|
36
36
|
- 🔍 **Semantic Search** - Query indexed docs across all skills via [retriv](https://github.com/harlan-zw/retriv) embeddings
|
|
37
|
-
- 🧠 **Context-Aware** - Follows [Claude Code skill best practices](https://code.claude.com/docs/en/skills#add-supporting-files): SKILL.md stays under 500 lines, references are separate files the agent discovers on-demand
|
|
37
|
+
- 🧠 **Context-Aware** - Follows [Claude Code skill best practices](https://code.claude.com/docs/en/skills#add-supporting-files): SKILL.md stays under 500 lines, references are separate files the agent discovers on-demand - not inlined into context
|
|
38
38
|
- 🎯 **Safe & Versioned** - Prompt injection sanitization, version-aware caching, auto-updates on new releases
|
|
39
|
-
- 🤝 **Ecosystem** - Compatible with [`npx skills`](https://skills.sh/) and [skills-npm](https://github.com/antfu/skills-npm)
|
|
39
|
+
- 🤝 **Ecosystem** - Compatible with [`npx skills`](https://skills.sh/) and [skills-npm](https://github.com/antfu/skills-npm). Skilld auto-detects and uses skills-npm packages when available.
|
|
40
40
|
|
|
41
41
|
## Quick Start
|
|
42
42
|
|
|
@@ -56,11 +56,11 @@ npx -y skilld add vue
|
|
|
56
56
|
|
|
57
57
|
If you need to re-configure skilld, just run `npx -y skilld config` to update your agent, model, or preferences.
|
|
58
58
|
|
|
59
|
-
**No agent CLI?** No problem
|
|
59
|
+
**No agent CLI?** No problem - choose "No agent" when prompted. You get a base skill immediately, plus portable prompts you can run in any LLM:
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
62
|
npx -y skilld add vue
|
|
63
|
-
# Choose "No agent"
|
|
63
|
+
# Choose "No agent" -> base skill + prompts exported
|
|
64
64
|
# Paste prompts into ChatGPT/Claude web, save outputs, then:
|
|
65
65
|
npx -y skilld assemble
|
|
66
66
|
```
|
|
@@ -195,12 +195,12 @@ No Claude, Gemini, or Codex CLI? Choose "No agent" when prompted. You get a base
|
|
|
195
195
|
|
|
196
196
|
```bash
|
|
197
197
|
skilld add vue
|
|
198
|
-
# Choose "No agent"
|
|
198
|
+
# Choose "No agent" -> installs to .claude/skills/vue-skilld/
|
|
199
199
|
|
|
200
200
|
# What you get:
|
|
201
|
-
# SKILL.md
|
|
202
|
-
# PROMPT_*.md
|
|
203
|
-
# references/
|
|
201
|
+
# SKILL.md <- base skill (works immediately)
|
|
202
|
+
# PROMPT_*.md <- prompts to enhance it with any LLM
|
|
203
|
+
# references/ <- docs, issues, releases as real files
|
|
204
204
|
|
|
205
205
|
# Run each PROMPT_*.md in ChatGPT/Claude web/any LLM
|
|
206
206
|
# Save outputs as _BEST_PRACTICES.md, _API_CHANGES.md, then:
|
|
@@ -220,7 +220,7 @@ skilld eject vue --out ./skills/ # Custom path
|
|
|
220
220
|
skilld eject vue --from 2025-07-01 # Only recent releases/issues
|
|
221
221
|
```
|
|
222
222
|
|
|
223
|
-
Share via `skilld add owner/repo`
|
|
223
|
+
Share via `skilld add owner/repo` - consumers get fully functional skills with no LLM cost.
|
|
224
224
|
|
|
225
225
|
### CLI Options
|
|
226
226
|
|
|
@@ -242,20 +242,20 @@ Several approaches exist for steering agent knowledge. Each fills a different ni
|
|
|
242
242
|
|
|
243
243
|
| Approach | Versioned | Curated | No Opt-in | Local | Any LLM |
|
|
244
244
|
|:---------|:---------:|:-------:|:---------:|:-----:|:-------:|
|
|
245
|
-
| **Manual rules** |
|
|
246
|
-
| **llms.txt** | ~ |
|
|
247
|
-
| **MCP servers** |
|
|
248
|
-
| **skills.sh** |
|
|
249
|
-
| **skills-npm** |
|
|
250
|
-
| **skilld** |
|
|
245
|
+
| **Manual rules** | - | yes | yes | yes | yes |
|
|
246
|
+
| **llms.txt** | ~ | - | - | - | yes |
|
|
247
|
+
| **MCP servers** | yes | - | - | - | - |
|
|
248
|
+
| **skills.sh** | - | ~ | yes | - | - |
|
|
249
|
+
| **skills-npm** | yes | yes | - | yes | - |
|
|
250
|
+
| **skilld** | yes | yes | yes | yes | yes |
|
|
251
251
|
|
|
252
|
-
> **Versioned**
|
|
252
|
+
> **Versioned** - tied to your installed package version. **Curated** - distilled best practices, not raw docs. **No Opt-in** - works without the package author doing anything. **Local** - runs on your machine, no external service dependency. **Any LLM** - works with any LLM, not just agent CLIs.
|
|
253
253
|
|
|
254
254
|
- **Manual rules** (CLAUDE.md, .cursorrules): full control, but you need to already know the best practices and maintain them across every dep.
|
|
255
255
|
- **[llms.txt](https://llmstxt.org/)**: standard convention for exposing docs to LLMs, but it's full docs not curated guidance and requires author adoption.
|
|
256
|
-
- **MCP servers
|
|
256
|
+
- **MCP servers** (Context7, etc.): live, version-aware responses, but adds per-request latency and the maintainer has to build and maintain a server.
|
|
257
257
|
- **[skills.sh](https://skills.sh/)**: easy skill sharing with a growing ecosystem, but community-sourced without version-awareness or author oversight.
|
|
258
|
-
- **[skills-npm](https://github.com/antfu/skills-npm)**: the ideal end-state: zero-token skills shipped by the package author, but requires every maintainer to opt in.
|
|
258
|
+
- **[skills-npm](https://github.com/antfu/skills-npm)**: the ideal end-state: zero-token skills shipped by the package author, but requires every maintainer to opt in. Skilld auto-detects and uses skills-npm packages when available.
|
|
259
259
|
- **skilld**: generates version-aware skills from existing docs, changelogs, issues, and discussions. Works for any package without author opt-in.
|
|
260
260
|
|
|
261
261
|
## Telemetry
|
|
@@ -274,6 +274,7 @@ DO_NOT_TRACK=1
|
|
|
274
274
|
## Related
|
|
275
275
|
|
|
276
276
|
- [skills-npm](https://github.com/antfu/skills-npm) - Convention for shipping agent skills in npm packages
|
|
277
|
+
- [agentskills.io](https://agentskills.io) - Agent skills specification
|
|
277
278
|
- [mdream](https://github.com/harlan-zw/mdream) - HTML to Markdown converter
|
|
278
279
|
- [retriv](https://github.com/harlan-zw/retriv) - Vector search with sqlite-vec
|
|
279
280
|
|
package/dist/_chunks/agent.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { t as __exportAll } from "./chunk.mjs";
|
|
2
1
|
import { n as sanitizeMarkdown } from "./sanitize.mjs";
|
|
3
2
|
import { g as readCachedSection, v as writeSections } from "./cache.mjs";
|
|
4
3
|
import { i as resolveSkilldCommand } from "./shared.mjs";
|
|
5
4
|
import { a as targets, t as detectInstalledAgents } from "./detect.mjs";
|
|
6
|
-
import { c as SECTION_OUTPUT_FILES, f as getSectionValidator, l as buildAllSectionPrompts, m as wrapSection, s as SECTION_MERGE_ORDER } from "./prompts.mjs";
|
|
5
|
+
import { c as SECTION_OUTPUT_FILES, f as getSectionValidator, l as buildAllSectionPrompts, m as wrapSection, p as portabilizePrompt, s as SECTION_MERGE_ORDER } from "./prompts.mjs";
|
|
7
6
|
import { homedir } from "node:os";
|
|
8
7
|
import { dirname, join } from "pathe";
|
|
9
8
|
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -12,10 +11,24 @@ import { isWindows } from "std-env";
|
|
|
12
11
|
import { glob } from "tinyglobby";
|
|
13
12
|
import { findDynamicImports, findStaticImports } from "mlly";
|
|
14
13
|
import { createHash } from "node:crypto";
|
|
15
|
-
import { setTimeout } from "node:timers/promises";
|
|
14
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
16
15
|
import { promisify } from "node:util";
|
|
16
|
+
import { getEnvApiKey, getModel, getModels, getProviders, streamSimple } from "@mariozechner/pi-ai";
|
|
17
|
+
import { getOAuthApiKey, getOAuthProvider, getOAuthProviders } from "@mariozechner/pi-ai/oauth";
|
|
17
18
|
import { readFile } from "node:fs/promises";
|
|
18
19
|
import { parseSync } from "oxc-parser";
|
|
20
|
+
//#region \0rolldown/runtime.js
|
|
21
|
+
var __defProp = Object.defineProperty;
|
|
22
|
+
var __exportAll = (all, no_symbols) => {
|
|
23
|
+
let target = {};
|
|
24
|
+
for (var name in all) __defProp(target, name, {
|
|
25
|
+
get: all[name],
|
|
26
|
+
enumerable: true
|
|
27
|
+
});
|
|
28
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
29
|
+
return target;
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
19
32
|
//#region src/agent/clis/claude.ts
|
|
20
33
|
var claude_exports = /* @__PURE__ */ __exportAll({
|
|
21
34
|
agentId: () => agentId$2,
|
|
@@ -268,6 +281,324 @@ function parseLine(line) {
|
|
|
268
281
|
return {};
|
|
269
282
|
}
|
|
270
283
|
//#endregion
|
|
284
|
+
//#region src/agent/clis/pi-ai.ts
|
|
285
|
+
function isPiAiModel(model) {
|
|
286
|
+
return model.startsWith("pi:");
|
|
287
|
+
}
|
|
288
|
+
/** Parse a pi:provider/model-id string → { provider, modelId } */
|
|
289
|
+
function parsePiAiModelId(model) {
|
|
290
|
+
if (!model.startsWith("pi:")) return null;
|
|
291
|
+
const rest = model.slice(3);
|
|
292
|
+
const slashIdx = rest.indexOf("/");
|
|
293
|
+
if (slashIdx === -1) return null;
|
|
294
|
+
return {
|
|
295
|
+
provider: rest.slice(0, slashIdx),
|
|
296
|
+
modelId: rest.slice(slashIdx + 1)
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/** pi coding agent stores auth here; env var can override */
|
|
300
|
+
const PI_AGENT_AUTH_PATH = join(process.env.PI_CODING_AGENT_DIR || join(homedir(), ".pi", "agent"), "auth.json");
|
|
301
|
+
/** skilld's own auth file — used when user logs in via skilld */
|
|
302
|
+
const SKILLD_AUTH_PATH = join(homedir(), ".skilld", "pi-ai-auth.json");
|
|
303
|
+
function readAuthFile(path) {
|
|
304
|
+
if (!existsSync(path)) return {};
|
|
305
|
+
try {
|
|
306
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
307
|
+
} catch {
|
|
308
|
+
return {};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/** Load auth from pi coding agent first (~/.pi/agent/auth.json), then skilld's own */
|
|
312
|
+
function loadAuth() {
|
|
313
|
+
const piAuth = readAuthFile(PI_AGENT_AUTH_PATH);
|
|
314
|
+
return {
|
|
315
|
+
...readAuthFile(SKILLD_AUTH_PATH),
|
|
316
|
+
...piAuth
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/** Save auth to skilld's own file — never writes to pi agent's auth */
|
|
320
|
+
function saveAuth(auth) {
|
|
321
|
+
mkdirSync(join(homedir(), ".skilld"), {
|
|
322
|
+
recursive: true,
|
|
323
|
+
mode: 448
|
|
324
|
+
});
|
|
325
|
+
writeFileSync(SKILLD_AUTH_PATH, JSON.stringify(auth, null, 2), { mode: 384 });
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Overrides for model-provider → OAuth-provider mapping.
|
|
329
|
+
* Most providers share the same ID in both systems (auto-matched).
|
|
330
|
+
* Only list exceptions where the IDs diverge.
|
|
331
|
+
*/
|
|
332
|
+
const OAUTH_PROVIDER_OVERRIDES = {
|
|
333
|
+
google: "google-gemini-cli",
|
|
334
|
+
openai: "openai-codex"
|
|
335
|
+
};
|
|
336
|
+
/** Resolve model provider ID → OAuth provider ID */
|
|
337
|
+
function resolveOAuthProviderId(modelProvider) {
|
|
338
|
+
if (OAUTH_PROVIDER_OVERRIDES[modelProvider]) return OAUTH_PROVIDER_OVERRIDES[modelProvider];
|
|
339
|
+
if (new Set(getOAuthProviders().map((p) => p.id)).has(modelProvider)) return modelProvider;
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
/** Resolve API key for a provider — checks env vars first, then OAuth credentials */
|
|
343
|
+
async function resolveApiKey(provider) {
|
|
344
|
+
const envKey = getEnvApiKey(provider);
|
|
345
|
+
if (envKey) return envKey;
|
|
346
|
+
const oauthProviderId = resolveOAuthProviderId(provider);
|
|
347
|
+
if (!oauthProviderId) return null;
|
|
348
|
+
const auth = loadAuth();
|
|
349
|
+
if (!auth[oauthProviderId]) return null;
|
|
350
|
+
const result = await getOAuthApiKey(oauthProviderId, auth);
|
|
351
|
+
if (!result) return null;
|
|
352
|
+
const skilldAuth = readAuthFile(SKILLD_AUTH_PATH);
|
|
353
|
+
skilldAuth[oauthProviderId] = {
|
|
354
|
+
type: "oauth",
|
|
355
|
+
...result.newCredentials
|
|
356
|
+
};
|
|
357
|
+
saveAuth(skilldAuth);
|
|
358
|
+
return result.apiKey;
|
|
359
|
+
}
|
|
360
|
+
/** Get available OAuth providers for login */
|
|
361
|
+
function getOAuthProviderList() {
|
|
362
|
+
const auth = loadAuth();
|
|
363
|
+
return getOAuthProviders().map((p) => ({
|
|
364
|
+
id: p.id,
|
|
365
|
+
name: p.name ?? p.id,
|
|
366
|
+
loggedIn: !!auth[p.id]
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
/** Run OAuth login for a provider, saving credentials to ~/.skilld/ */
|
|
370
|
+
async function loginOAuthProvider(providerId, callbacks) {
|
|
371
|
+
const provider = getOAuthProvider(providerId);
|
|
372
|
+
if (!provider) return false;
|
|
373
|
+
const credentials = await provider.login({
|
|
374
|
+
onAuth: (info) => callbacks.onAuth(info.url, info.instructions),
|
|
375
|
+
onPrompt: async (prompt) => callbacks.onPrompt(prompt.message, prompt.placeholder),
|
|
376
|
+
onProgress: (msg) => callbacks.onProgress?.(msg)
|
|
377
|
+
});
|
|
378
|
+
const auth = loadAuth();
|
|
379
|
+
auth[providerId] = {
|
|
380
|
+
type: "oauth",
|
|
381
|
+
...credentials
|
|
382
|
+
};
|
|
383
|
+
saveAuth(auth);
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
/** Remove OAuth credentials for a provider */
|
|
387
|
+
function logoutOAuthProvider(providerId) {
|
|
388
|
+
const auth = loadAuth();
|
|
389
|
+
delete auth[providerId];
|
|
390
|
+
saveAuth(auth);
|
|
391
|
+
}
|
|
392
|
+
const MIN_CONTEXT_WINDOW = 32e3;
|
|
393
|
+
/** Legacy model patterns — old generations that clutter the model list */
|
|
394
|
+
const LEGACY_MODEL_PATTERNS = [
|
|
395
|
+
/^claude-3-/,
|
|
396
|
+
/^claude-3\.5-/,
|
|
397
|
+
/^claude-3\.7-/,
|
|
398
|
+
/^gpt-4(?!\.\d)/,
|
|
399
|
+
/^o1/,
|
|
400
|
+
/^o3-mini/,
|
|
401
|
+
/^gemini-1\./,
|
|
402
|
+
/^gemini-2\.0/,
|
|
403
|
+
/^gemini-live-/,
|
|
404
|
+
/-preview-\d{2}-\d{2,4}$/,
|
|
405
|
+
/-\d{8}$/
|
|
406
|
+
];
|
|
407
|
+
function isLegacyModel(modelId) {
|
|
408
|
+
return LEGACY_MODEL_PATTERNS.some((p) => p.test(modelId));
|
|
409
|
+
}
|
|
410
|
+
/** Preferred model per provider for auto-selection (cheapest reliable option) */
|
|
411
|
+
const RECOMMENDED_MODELS = {
|
|
412
|
+
anthropic: /haiku/,
|
|
413
|
+
google: /flash/,
|
|
414
|
+
openai: /gpt-4\.1-mini/
|
|
415
|
+
};
|
|
416
|
+
/** Get all pi-ai models for providers with auth configured */
|
|
417
|
+
function getAvailablePiAiModels() {
|
|
418
|
+
const providers = getProviders();
|
|
419
|
+
const auth = loadAuth();
|
|
420
|
+
const available = [];
|
|
421
|
+
const recommendedPicked = /* @__PURE__ */ new Set();
|
|
422
|
+
for (const provider of providers) {
|
|
423
|
+
let authSource = "none";
|
|
424
|
+
if (getEnvApiKey(provider)) authSource = "env";
|
|
425
|
+
else {
|
|
426
|
+
const oauthId = resolveOAuthProviderId(provider);
|
|
427
|
+
if (oauthId && auth[oauthId]) authSource = "oauth";
|
|
428
|
+
}
|
|
429
|
+
if (authSource === "none") continue;
|
|
430
|
+
const models = getModels(provider);
|
|
431
|
+
const recPattern = RECOMMENDED_MODELS[provider];
|
|
432
|
+
let recModelId = null;
|
|
433
|
+
if (recPattern) {
|
|
434
|
+
for (const model of models) if (!isLegacyModel(model.id) && recPattern.test(model.id)) {
|
|
435
|
+
recModelId = model.id;
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
for (const model of models) {
|
|
440
|
+
if (model.contextWindow && model.contextWindow < MIN_CONTEXT_WINDOW) continue;
|
|
441
|
+
if (isLegacyModel(model.id)) continue;
|
|
442
|
+
const id = `pi:${provider}/${model.id}`;
|
|
443
|
+
const ctx = model.contextWindow ? ` · ${Math.round(model.contextWindow / 1e3)}k ctx` : "";
|
|
444
|
+
const cost = model.cost?.input ? ` · $${model.cost.input}/Mtok` : "";
|
|
445
|
+
const isRecommended = model.id === recModelId && !recommendedPicked.has(provider);
|
|
446
|
+
if (isRecommended) recommendedPicked.add(provider);
|
|
447
|
+
available.push({
|
|
448
|
+
id,
|
|
449
|
+
name: model.name || model.id,
|
|
450
|
+
hint: `${authSource === "oauth" ? "OAuth" : "API key"}${ctx}${cost}`,
|
|
451
|
+
authSource,
|
|
452
|
+
recommended: isRecommended
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return available;
|
|
457
|
+
}
|
|
458
|
+
const REFERENCE_SUBDIRS = [
|
|
459
|
+
"docs",
|
|
460
|
+
"issues",
|
|
461
|
+
"discussions",
|
|
462
|
+
"releases"
|
|
463
|
+
];
|
|
464
|
+
const MAX_REFERENCE_CHARS = 15e4;
|
|
465
|
+
/** Read reference files from .skilld/ and format as inline context */
|
|
466
|
+
function collectReferenceContent(skillDir) {
|
|
467
|
+
const skilldDir = join(skillDir, ".skilld");
|
|
468
|
+
if (!existsSync(skilldDir)) return "";
|
|
469
|
+
const files = [];
|
|
470
|
+
let totalChars = 0;
|
|
471
|
+
for (const subdir of REFERENCE_SUBDIRS) {
|
|
472
|
+
const dirPath = join(skilldDir, subdir);
|
|
473
|
+
if (!existsSync(dirPath)) continue;
|
|
474
|
+
const indexPath = join(dirPath, "_INDEX.md");
|
|
475
|
+
if (existsSync(indexPath) && totalChars < MAX_REFERENCE_CHARS) {
|
|
476
|
+
const content = sanitizeMarkdown(readFileSync(indexPath, "utf-8"));
|
|
477
|
+
if (totalChars + content.length <= MAX_REFERENCE_CHARS) {
|
|
478
|
+
files.push({
|
|
479
|
+
path: `references/${subdir}/_INDEX.md`,
|
|
480
|
+
content
|
|
481
|
+
});
|
|
482
|
+
totalChars += content.length;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const entries = readdirSync(dirPath, { recursive: true });
|
|
486
|
+
for (const entry of entries) {
|
|
487
|
+
if (totalChars >= MAX_REFERENCE_CHARS) break;
|
|
488
|
+
const entryStr = String(entry);
|
|
489
|
+
if (!entryStr.endsWith(".md") || entryStr === "_INDEX.md") continue;
|
|
490
|
+
const fullPath = join(dirPath, entryStr);
|
|
491
|
+
if (!existsSync(fullPath)) continue;
|
|
492
|
+
try {
|
|
493
|
+
const content = sanitizeMarkdown(readFileSync(fullPath, "utf-8"));
|
|
494
|
+
if (totalChars + content.length > MAX_REFERENCE_CHARS) continue;
|
|
495
|
+
files.push({
|
|
496
|
+
path: `references/${subdir}/${entryStr}`,
|
|
497
|
+
content
|
|
498
|
+
});
|
|
499
|
+
totalChars += content.length;
|
|
500
|
+
} catch {}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const readmePath = join(skilldDir, "pkg", "README.md");
|
|
504
|
+
if (existsSync(readmePath) && totalChars < MAX_REFERENCE_CHARS) {
|
|
505
|
+
const content = sanitizeMarkdown(readFileSync(readmePath, "utf-8"));
|
|
506
|
+
if (totalChars + content.length <= MAX_REFERENCE_CHARS) {
|
|
507
|
+
files.push({
|
|
508
|
+
path: "references/pkg/README.md",
|
|
509
|
+
content
|
|
510
|
+
});
|
|
511
|
+
totalChars += content.length;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
const pkgDir = join(skilldDir, "pkg");
|
|
515
|
+
if (existsSync(pkgDir) && totalChars < MAX_REFERENCE_CHARS) try {
|
|
516
|
+
const pkgEntries = readdirSync(pkgDir, { recursive: true });
|
|
517
|
+
for (const entry of pkgEntries) {
|
|
518
|
+
const entryStr = String(entry);
|
|
519
|
+
if (!entryStr.endsWith(".d.ts")) continue;
|
|
520
|
+
const fullPath = join(pkgDir, entryStr);
|
|
521
|
+
if (!existsSync(fullPath)) continue;
|
|
522
|
+
const content = sanitizeMarkdown(readFileSync(fullPath, "utf-8"));
|
|
523
|
+
if (totalChars + content.length <= MAX_REFERENCE_CHARS) {
|
|
524
|
+
files.push({
|
|
525
|
+
path: `references/pkg/${entryStr}`,
|
|
526
|
+
content
|
|
527
|
+
});
|
|
528
|
+
totalChars += content.length;
|
|
529
|
+
}
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
} catch {}
|
|
533
|
+
if (files.length === 0) return "";
|
|
534
|
+
return `<reference-files>\n${files.map((f) => `<file path="${f.path}">\n${f.content.replaceAll("</file>", "</file>").replaceAll("</reference-files>", "</reference-files>")}\n</file>`).join("\n")}\n</reference-files>`;
|
|
535
|
+
}
|
|
536
|
+
/** Optimize a single section using pi-ai direct API */
|
|
537
|
+
async function optimizeSectionPiAi(opts) {
|
|
538
|
+
const parsed = parsePiAiModelId(opts.model);
|
|
539
|
+
if (!parsed) throw new Error(`Invalid pi-ai model ID: ${opts.model}. Expected format: pi:provider/model-id`);
|
|
540
|
+
const model = getModel(parsed.provider, parsed.modelId);
|
|
541
|
+
const apiKey = await resolveApiKey(parsed.provider);
|
|
542
|
+
const portablePrompt = portabilizePrompt(opts.prompt, opts.section);
|
|
543
|
+
const references = collectReferenceContent(opts.skillDir);
|
|
544
|
+
const fullPrompt = references ? `${portablePrompt}\n\n## Reference Content\n\nThe following files are provided inline for your reference:\n\n${references}` : portablePrompt;
|
|
545
|
+
opts.onProgress?.({
|
|
546
|
+
chunk: "[starting...]",
|
|
547
|
+
type: "reasoning",
|
|
548
|
+
text: "",
|
|
549
|
+
reasoning: "",
|
|
550
|
+
section: opts.section
|
|
551
|
+
});
|
|
552
|
+
const stream = streamSimple(model, {
|
|
553
|
+
systemPrompt: "You are a technical documentation expert generating SKILL.md sections for AI agent skills. Output clean, structured markdown following the format instructions exactly.",
|
|
554
|
+
messages: [{
|
|
555
|
+
role: "user",
|
|
556
|
+
content: [{
|
|
557
|
+
type: "text",
|
|
558
|
+
text: fullPrompt
|
|
559
|
+
}],
|
|
560
|
+
timestamp: Date.now()
|
|
561
|
+
}]
|
|
562
|
+
}, {
|
|
563
|
+
reasoning: "medium",
|
|
564
|
+
maxTokens: 16384,
|
|
565
|
+
...apiKey ? { apiKey } : {}
|
|
566
|
+
});
|
|
567
|
+
let text = "";
|
|
568
|
+
let usage;
|
|
569
|
+
let cost;
|
|
570
|
+
for await (const event of stream) {
|
|
571
|
+
if (opts.signal?.aborted) throw new Error("pi-ai request timed out");
|
|
572
|
+
switch (event.type) {
|
|
573
|
+
case "text_delta":
|
|
574
|
+
text += event.delta;
|
|
575
|
+
opts.onProgress?.({
|
|
576
|
+
chunk: event.delta,
|
|
577
|
+
type: "text",
|
|
578
|
+
text,
|
|
579
|
+
reasoning: "",
|
|
580
|
+
section: opts.section
|
|
581
|
+
});
|
|
582
|
+
break;
|
|
583
|
+
case "done":
|
|
584
|
+
if (event.message?.usage) {
|
|
585
|
+
usage = {
|
|
586
|
+
input: event.message.usage.input,
|
|
587
|
+
output: event.message.usage.output
|
|
588
|
+
};
|
|
589
|
+
cost = event.message.usage.cost?.total;
|
|
590
|
+
}
|
|
591
|
+
break;
|
|
592
|
+
case "error": throw new Error(event.error?.errorMessage ?? "pi-ai stream error");
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return {
|
|
596
|
+
text,
|
|
597
|
+
usage,
|
|
598
|
+
cost
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
//#endregion
|
|
271
602
|
//#region src/agent/clis/index.ts
|
|
272
603
|
const TOOL_VERBS = {
|
|
273
604
|
Read: "Reading",
|
|
@@ -286,6 +617,9 @@ const TOOL_VERBS = {
|
|
|
286
617
|
function createToolProgress(log) {
|
|
287
618
|
let lastMsg = "";
|
|
288
619
|
let repeatCount = 0;
|
|
620
|
+
/** Per-section timestamp of last "Writing..." emission — throttles text_delta spam */
|
|
621
|
+
const lastTextEmit = /* @__PURE__ */ new Map();
|
|
622
|
+
const TEXT_THROTTLE_MS = 2e3;
|
|
289
623
|
function emit(msg) {
|
|
290
624
|
if (msg === lastMsg) {
|
|
291
625
|
repeatCount++;
|
|
@@ -298,6 +632,10 @@ function createToolProgress(log) {
|
|
|
298
632
|
}
|
|
299
633
|
return ({ type, chunk, section }) => {
|
|
300
634
|
if (type === "text") {
|
|
635
|
+
const key = section ?? "";
|
|
636
|
+
const now = Date.now();
|
|
637
|
+
if (now - (lastTextEmit.get(key) ?? 0) < TEXT_THROTTLE_MS) return;
|
|
638
|
+
lastTextEmit.set(key, now);
|
|
301
639
|
emit(`${section ? `\x1B[90m[${section}]\x1B[0m ` : ""}Writing...`);
|
|
302
640
|
return;
|
|
303
641
|
}
|
|
@@ -338,18 +676,42 @@ const CLI_PARSE_LINE = {
|
|
|
338
676
|
gemini: parseLine,
|
|
339
677
|
codex: parseLine$1
|
|
340
678
|
};
|
|
679
|
+
/** Map CLI agent IDs to their LLM provider name (not the agent/tool name) */
|
|
680
|
+
const CLI_PROVIDER_NAMES = {
|
|
681
|
+
"claude-code": "Anthropic",
|
|
682
|
+
"gemini-cli": "Google",
|
|
683
|
+
"codex": "OpenAI"
|
|
684
|
+
};
|
|
685
|
+
const PI_PROVIDER_NAMES = {
|
|
686
|
+
"anthropic": "Anthropic",
|
|
687
|
+
"google": "Google",
|
|
688
|
+
"google-antigravity": "Antigravity",
|
|
689
|
+
"google-gemini-cli": "Google Gemini",
|
|
690
|
+
"google-vertex": "Google Vertex",
|
|
691
|
+
"openai": "OpenAI",
|
|
692
|
+
"openai-codex": "OpenAI Codex",
|
|
693
|
+
"github-copilot": "GitHub Copilot",
|
|
694
|
+
"groq": "Groq",
|
|
695
|
+
"mistral": "Mistral",
|
|
696
|
+
"xai": "xAI"
|
|
697
|
+
};
|
|
341
698
|
const CLI_MODELS = Object.fromEntries(CLI_DEFS.flatMap((def) => Object.entries(def.models).map(([id, entry]) => [id, {
|
|
342
699
|
...entry,
|
|
343
700
|
cli: def.cli,
|
|
344
701
|
agentId: def.agentId
|
|
345
702
|
}])));
|
|
346
703
|
function getModelName(id) {
|
|
704
|
+
if (isPiAiModel(id)) return parsePiAiModelId(id)?.modelId ?? id;
|
|
347
705
|
return CLI_MODELS[id]?.name ?? id;
|
|
348
706
|
}
|
|
349
707
|
function getModelLabel(id) {
|
|
708
|
+
if (isPiAiModel(id)) {
|
|
709
|
+
const parsed = parsePiAiModelId(id);
|
|
710
|
+
return parsed ? `${PI_PROVIDER_NAMES[parsed.provider] ?? parsed.provider} · ${parsed.modelId}` : id;
|
|
711
|
+
}
|
|
350
712
|
const config = CLI_MODELS[id];
|
|
351
713
|
if (!config) return id;
|
|
352
|
-
return `${
|
|
714
|
+
return `${CLI_PROVIDER_NAMES[config.agentId] ?? config.cli} · ${config.name}`;
|
|
353
715
|
}
|
|
354
716
|
async function getAvailableModels() {
|
|
355
717
|
const execAsync = promisify(exec);
|
|
@@ -365,14 +727,37 @@ async function getAvailableModels() {
|
|
|
365
727
|
}
|
|
366
728
|
}));
|
|
367
729
|
const availableAgentIds = new Set(cliChecks.filter((id) => id != null));
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
730
|
+
const cliModels = Object.entries(CLI_MODELS).filter(([_, config]) => availableAgentIds.has(config.agentId)).map(([id, config]) => {
|
|
731
|
+
const providerName = CLI_PROVIDER_NAMES[config.agentId] ?? targets[config.agentId]?.displayName ?? config.agentId;
|
|
732
|
+
return {
|
|
733
|
+
id,
|
|
734
|
+
name: config.name,
|
|
735
|
+
hint: config.hint,
|
|
736
|
+
recommended: config.recommended,
|
|
737
|
+
agentId: config.agentId,
|
|
738
|
+
agentName: providerName,
|
|
739
|
+
provider: config.agentId,
|
|
740
|
+
providerName: `${providerName} (via ${config.cli} CLI)`,
|
|
741
|
+
vendorGroup: providerName
|
|
742
|
+
};
|
|
743
|
+
});
|
|
744
|
+
const piAiEntries = getAvailablePiAiModels().map((m) => {
|
|
745
|
+
const piProvider = parsePiAiModelId(m.id)?.provider ?? "pi-ai";
|
|
746
|
+
const displayName = PI_PROVIDER_NAMES[piProvider] ?? piProvider;
|
|
747
|
+
const authLabel = m.authSource === "env" ? "API" : "OAuth";
|
|
748
|
+
return {
|
|
749
|
+
id: m.id,
|
|
750
|
+
name: m.name,
|
|
751
|
+
hint: m.hint,
|
|
752
|
+
recommended: m.recommended,
|
|
753
|
+
agentId: "pi-ai",
|
|
754
|
+
agentName: `pi-ai (${m.authSource})`,
|
|
755
|
+
provider: `pi:${piProvider}:${m.authSource}`,
|
|
756
|
+
providerName: `${displayName} (${authLabel})`,
|
|
757
|
+
vendorGroup: displayName
|
|
758
|
+
};
|
|
759
|
+
});
|
|
760
|
+
return [...cliModels, ...piAiEntries];
|
|
376
761
|
}
|
|
377
762
|
/** Resolve symlinks in .skilld/ to get real paths for --add-dir */
|
|
378
763
|
function resolveReferenceDirs(skillDir) {
|
|
@@ -416,9 +801,72 @@ function setCache(prompt, model, section, text) {
|
|
|
416
801
|
timestamp: Date.now()
|
|
417
802
|
}), { mode: 384 });
|
|
418
803
|
}
|
|
419
|
-
|
|
804
|
+
async function optimizeSectionViaPiAi(opts) {
|
|
805
|
+
const { section, prompt, outputFile, skillDir, model, onProgress, timeout, debug } = opts;
|
|
806
|
+
const skilldDir = join(skillDir, ".skilld");
|
|
807
|
+
const outputPath = join(skilldDir, outputFile);
|
|
808
|
+
writeFileSync(join(skilldDir, `PROMPT_${section}.md`), prompt);
|
|
809
|
+
try {
|
|
810
|
+
const ac = new AbortController();
|
|
811
|
+
const timer = setTimeout(() => ac.abort(), timeout);
|
|
812
|
+
const result = await optimizeSectionPiAi({
|
|
813
|
+
section,
|
|
814
|
+
prompt,
|
|
815
|
+
skillDir,
|
|
816
|
+
model,
|
|
817
|
+
onProgress,
|
|
818
|
+
signal: ac.signal
|
|
819
|
+
}).finally(() => clearTimeout(timer));
|
|
820
|
+
const raw = result.text.trim();
|
|
821
|
+
if (debug) {
|
|
822
|
+
const logsDir = join(skilldDir, "logs");
|
|
823
|
+
const logName = section.toUpperCase().replace(/-/g, "_");
|
|
824
|
+
mkdirSync(logsDir, { recursive: true });
|
|
825
|
+
if (raw) writeFileSync(join(logsDir, `${logName}.md`), raw);
|
|
826
|
+
}
|
|
827
|
+
if (!raw) return {
|
|
828
|
+
section,
|
|
829
|
+
content: "",
|
|
830
|
+
wasOptimized: false,
|
|
831
|
+
error: "pi-ai returned empty response"
|
|
832
|
+
};
|
|
833
|
+
const content = cleanSectionOutput(raw);
|
|
834
|
+
if (content) writeFileSync(outputPath, content);
|
|
835
|
+
const validator = getSectionValidator(section);
|
|
836
|
+
const warnings = (content && validator ? validator(content) : []).map((w) => ({
|
|
837
|
+
section,
|
|
838
|
+
warning: w.warning
|
|
839
|
+
}));
|
|
840
|
+
return {
|
|
841
|
+
section,
|
|
842
|
+
content,
|
|
843
|
+
wasOptimized: !!content,
|
|
844
|
+
warnings: warnings?.length ? warnings : void 0,
|
|
845
|
+
usage: result.usage,
|
|
846
|
+
cost: result.cost
|
|
847
|
+
};
|
|
848
|
+
} catch (err) {
|
|
849
|
+
return {
|
|
850
|
+
section,
|
|
851
|
+
content: "",
|
|
852
|
+
wasOptimized: false,
|
|
853
|
+
error: err.message
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
/** Spawn a single CLI process for one section, or call pi-ai directly */
|
|
420
858
|
function optimizeSection(opts) {
|
|
421
859
|
const { section, prompt, outputFile, skillDir, model, onProgress, timeout, debug, preExistingFiles } = opts;
|
|
860
|
+
if (isPiAiModel(model)) return optimizeSectionViaPiAi({
|
|
861
|
+
section,
|
|
862
|
+
prompt,
|
|
863
|
+
outputFile,
|
|
864
|
+
skillDir,
|
|
865
|
+
model,
|
|
866
|
+
onProgress,
|
|
867
|
+
timeout,
|
|
868
|
+
debug
|
|
869
|
+
});
|
|
422
870
|
const cliConfig = CLI_MODELS[model];
|
|
423
871
|
if (!cliConfig) return Promise.resolve({
|
|
424
872
|
section,
|
|
@@ -579,7 +1027,13 @@ async function optimizeDocs(opts) {
|
|
|
579
1027
|
wasOptimized: false,
|
|
580
1028
|
error: "No valid sections to generate"
|
|
581
1029
|
};
|
|
582
|
-
if (
|
|
1030
|
+
if (isPiAiModel(model)) {
|
|
1031
|
+
if (!new Set(getAvailablePiAiModels().map((m) => m.id)).has(model)) return {
|
|
1032
|
+
optimized: "",
|
|
1033
|
+
wasOptimized: false,
|
|
1034
|
+
error: `Pi model unavailable or not authenticated: ${model}`
|
|
1035
|
+
};
|
|
1036
|
+
} else if (!CLI_MODELS[model]) return {
|
|
583
1037
|
optimized: "",
|
|
584
1038
|
wasOptimized: false,
|
|
585
1039
|
error: `No CLI mapping for model: ${model}`
|
|
@@ -659,7 +1113,7 @@ async function optimizeDocs(opts) {
|
|
|
659
1113
|
preExistingFiles
|
|
660
1114
|
});
|
|
661
1115
|
if (i === 0) return run();
|
|
662
|
-
return setTimeout(i * STAGGER_MS).then(run);
|
|
1116
|
+
return setTimeout$1(i * STAGGER_MS).then(run);
|
|
663
1117
|
})) : [];
|
|
664
1118
|
const allResults = [...cachedResults];
|
|
665
1119
|
let totalUsage;
|
|
@@ -694,7 +1148,7 @@ async function optimizeDocs(opts) {
|
|
|
694
1148
|
reasoning: "",
|
|
695
1149
|
section
|
|
696
1150
|
});
|
|
697
|
-
await setTimeout(STAGGER_MS);
|
|
1151
|
+
await setTimeout$1(STAGGER_MS);
|
|
698
1152
|
const result = await optimizeSection({
|
|
699
1153
|
section,
|
|
700
1154
|
prompt,
|
|
@@ -990,6 +1444,6 @@ function isNodeBuiltin(pkg) {
|
|
|
990
1444
|
return NODE_BUILTINS.has(base.split("/")[0]);
|
|
991
1445
|
}
|
|
992
1446
|
//#endregion
|
|
993
|
-
export { getModelLabel as a, getAvailableModels as i, cleanSectionOutput as n, getModelName as o, createToolProgress as r, optimizeDocs as s, detectImportedPackages as t };
|
|
1447
|
+
export { getModelLabel as a, getOAuthProviderList as c, __exportAll as d, getAvailableModels as i, loginOAuthProvider as l, cleanSectionOutput as n, getModelName as o, createToolProgress as r, optimizeDocs as s, detectImportedPackages as t, logoutOAuthProvider as u };
|
|
994
1448
|
|
|
995
1449
|
//# sourceMappingURL=agent.mjs.map
|