skilld 1.5.1 → 1.5.3
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/dist/THIRD-PARTY-LICENSES.md +38 -0
- package/dist/_chunks/agent.mjs +281 -147
- package/dist/_chunks/agent.mjs.map +1 -1
- package/dist/_chunks/assemble.mjs +3 -1
- package/dist/_chunks/assemble.mjs.map +1 -1
- package/dist/_chunks/author.mjs +21 -12
- package/dist/_chunks/author.mjs.map +1 -1
- package/dist/_chunks/cache.mjs +4 -3
- package/dist/_chunks/cache.mjs.map +1 -1
- package/dist/_chunks/cache2.mjs +1 -0
- package/dist/_chunks/cache2.mjs.map +1 -1
- package/dist/_chunks/cli-helpers.mjs +48 -80
- package/dist/_chunks/cli-helpers.mjs.map +1 -1
- package/dist/_chunks/cli-helpers2.mjs +4 -2
- package/dist/_chunks/embedding-cache.mjs +1 -0
- package/dist/_chunks/index.d.mts.map +1 -1
- package/dist/_chunks/index3.d.mts.map +1 -1
- package/dist/_chunks/install.mjs +13 -8
- package/dist/_chunks/install.mjs.map +1 -1
- package/dist/_chunks/libs/@sinclair/typebox.mjs +2748 -0
- package/dist/_chunks/libs/@sinclair/typebox.mjs.map +1 -0
- package/dist/_chunks/list.mjs +4 -2
- package/dist/_chunks/list.mjs.map +1 -1
- package/dist/_chunks/lockfile.mjs +14 -1
- package/dist/_chunks/lockfile.mjs.map +1 -1
- package/dist/_chunks/package-json.mjs +107 -0
- package/dist/_chunks/package-json.mjs.map +1 -0
- package/dist/_chunks/prepare2.mjs +4 -2
- package/dist/_chunks/prepare2.mjs.map +1 -1
- package/dist/_chunks/prompts.mjs +10 -15
- package/dist/_chunks/prompts.mjs.map +1 -1
- package/dist/_chunks/rolldown-runtime.mjs +13 -0
- package/dist/_chunks/sanitize.mjs +3 -0
- package/dist/_chunks/sanitize.mjs.map +1 -1
- package/dist/_chunks/search-interactive.mjs +3 -1
- package/dist/_chunks/search-interactive.mjs.map +1 -1
- package/dist/_chunks/search.mjs +3 -1
- package/dist/_chunks/search2.mjs +1 -1
- package/dist/_chunks/setup.mjs +4 -2
- package/dist/_chunks/setup.mjs.map +1 -1
- package/dist/_chunks/sources.mjs +19 -21
- package/dist/_chunks/sources.mjs.map +1 -1
- package/dist/_chunks/sync-shared.mjs +3 -1
- package/dist/_chunks/sync-shared2.mjs +13 -8
- package/dist/_chunks/sync-shared2.mjs.map +1 -1
- package/dist/_chunks/sync.mjs +2 -2
- package/dist/_chunks/sync2.mjs +3 -1
- package/dist/_chunks/uninstall.mjs +4 -2
- package/dist/_chunks/uninstall.mjs.map +1 -1
- package/dist/_chunks/wizard.mjs +2 -2
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +3 -1
- package/dist/cache/index.mjs +1 -0
- package/dist/cli.mjs +18 -6
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/sources/index.mjs +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Licenses of Bundled Dependencies
|
|
2
|
+
|
|
3
|
+
The published artifact additionally contains code with the following licenses:
|
|
4
|
+
MIT
|
|
5
|
+
|
|
6
|
+
# Bundled Dependencies
|
|
7
|
+
|
|
8
|
+
## @sinclair/typebox
|
|
9
|
+
|
|
10
|
+
License: MIT
|
|
11
|
+
By: sinclairzx81
|
|
12
|
+
Repository: https://github.com/sinclairzx81/typebox-legacy
|
|
13
|
+
|
|
14
|
+
> TypeBox
|
|
15
|
+
>
|
|
16
|
+
> Json Schema Type Builder with Static Type Resolution for TypeScript
|
|
17
|
+
>
|
|
18
|
+
> The MIT License (MIT)
|
|
19
|
+
>
|
|
20
|
+
> Copyright (c) 2017-2026 Haydn Paterson
|
|
21
|
+
>
|
|
22
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
23
|
+
> of this software and associated documentation files (the "Software"), to deal
|
|
24
|
+
> in the Software without restriction, including without limitation the rights
|
|
25
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
26
|
+
> copies of the Software, and to permit persons to whom the Software is
|
|
27
|
+
> furnished to do so, subject to the following conditions:
|
|
28
|
+
>
|
|
29
|
+
> The above copyright notice and this permission notice shall be included in
|
|
30
|
+
> all copies or substantial portions of the Software.
|
|
31
|
+
>
|
|
32
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
33
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
34
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
35
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
36
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
37
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
38
|
+
> THE SOFTWARE.
|
package/dist/_chunks/agent.mjs
CHANGED
|
@@ -1,34 +1,25 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime.mjs";
|
|
1
2
|
import { n as sanitizeMarkdown } from "./sanitize.mjs";
|
|
2
3
|
import { h as writeSections, m as readCachedSection } from "./cache.mjs";
|
|
3
4
|
import { i as resolveSkilldCommand } from "./shared.mjs";
|
|
4
5
|
import { a as targets, t as detectInstalledAgents } from "./detect.mjs";
|
|
5
|
-
import { c as SECTION_OUTPUT_FILES, f as getSectionValidator, l as buildAllSectionPrompts, m as wrapSection,
|
|
6
|
+
import { c as SECTION_OUTPUT_FILES, f as getSectionValidator, l as buildAllSectionPrompts, m as wrapSection, s as SECTION_MERGE_ORDER } from "./prompts.mjs";
|
|
7
|
+
import { t as Type } from "./libs/@sinclair/typebox.mjs";
|
|
6
8
|
import { homedir } from "node:os";
|
|
7
9
|
import { dirname, join } from "pathe";
|
|
8
10
|
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, unlinkSync, writeFileSync } from "node:fs";
|
|
9
|
-
import { exec, spawn } from "node:child_process";
|
|
11
|
+
import { exec, execFileSync, spawn } from "node:child_process";
|
|
10
12
|
import { isWindows } from "std-env";
|
|
11
13
|
import { glob } from "tinyglobby";
|
|
12
14
|
import { findDynamicImports, findStaticImports } from "mlly";
|
|
13
15
|
import { createHash } from "node:crypto";
|
|
14
16
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
15
17
|
import { promisify } from "node:util";
|
|
18
|
+
import { resolve as resolve$1 } from "node:path";
|
|
16
19
|
import { getEnvApiKey, getModel, getModels, getProviders, streamSimple } from "@mariozechner/pi-ai";
|
|
17
20
|
import { getOAuthApiKey, getOAuthProvider, getOAuthProviders } from "@mariozechner/pi-ai/oauth";
|
|
18
21
|
import { readFile } from "node:fs/promises";
|
|
19
22
|
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
|
|
32
23
|
//#region src/agent/clis/claude.ts
|
|
33
24
|
var claude_exports = /* @__PURE__ */ __exportAll({
|
|
34
25
|
agentId: () => agentId$2,
|
|
@@ -455,93 +446,147 @@ function getAvailablePiAiModels() {
|
|
|
455
446
|
}
|
|
456
447
|
return available;
|
|
457
448
|
}
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
449
|
+
const TOOLS = [
|
|
450
|
+
{
|
|
451
|
+
name: "Read",
|
|
452
|
+
description: "Read a file. Path is relative to the working directory (e.g. \"./.skilld/docs/api.md\").",
|
|
453
|
+
parameters: Type.Object({ path: Type.String({ description: "File path to read" }) })
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
name: "Glob",
|
|
457
|
+
description: "List files matching a glob pattern (e.g. \"./.skilld/docs/*.md\"). Returns newline-separated paths.",
|
|
458
|
+
parameters: Type.Object({
|
|
459
|
+
pattern: Type.String({ description: "Glob pattern" }),
|
|
460
|
+
no_ignore: Type.Optional(Type.Boolean({ description: "Include gitignored files" }))
|
|
461
|
+
})
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: "Write",
|
|
465
|
+
description: "Write content to a file.",
|
|
466
|
+
parameters: Type.Object({
|
|
467
|
+
path: Type.String({ description: "File path to write" }),
|
|
468
|
+
content: Type.String({ description: "File content" })
|
|
469
|
+
})
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: "Bash",
|
|
473
|
+
description: "Run a shell command. Use for `skilld search`, `skilld validate`, etc.",
|
|
474
|
+
parameters: Type.Object({ command: Type.String({ description: "Shell command to run" }) })
|
|
475
|
+
}
|
|
463
476
|
];
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (
|
|
488
|
-
const
|
|
489
|
-
if (
|
|
490
|
-
|
|
491
|
-
|
|
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 {}
|
|
477
|
+
const MAX_TOOL_TURNS = 30;
|
|
478
|
+
const SAFE_COMMANDS = new Set([
|
|
479
|
+
"skilld",
|
|
480
|
+
"ls",
|
|
481
|
+
"cat",
|
|
482
|
+
"find"
|
|
483
|
+
]);
|
|
484
|
+
const SHELL_META_RE = /[;&|`$()<>]/;
|
|
485
|
+
/** Resolve a path safely within skilldDir, blocking traversal */
|
|
486
|
+
function resolveSandboxedPath(p, skilldDir) {
|
|
487
|
+
const resolved = resolve$1(skilldDir, String(p).replace(/^\.\/\.skilld\//, "./").replace(/^\.skilld\//, "./").replace(/^\.\//, ""));
|
|
488
|
+
if (!resolved.startsWith(`${skilldDir}/`) && resolved !== skilldDir) throw new Error(`Path traversal blocked: ${p}`);
|
|
489
|
+
return resolved;
|
|
490
|
+
}
|
|
491
|
+
/** Match a file path against a glob pattern using simple segment matching (no regex from user input) */
|
|
492
|
+
function globMatch(filePath, pattern) {
|
|
493
|
+
const segments = pattern.split("**");
|
|
494
|
+
if (segments.length === 1) {
|
|
495
|
+
const parts = pattern.split("*");
|
|
496
|
+
if (parts.length === 1) return filePath === pattern;
|
|
497
|
+
let pos = 0;
|
|
498
|
+
for (let i = 0; i < parts.length; i++) {
|
|
499
|
+
const part = parts[i];
|
|
500
|
+
if (!part) continue;
|
|
501
|
+
const idx = filePath.indexOf(part, pos);
|
|
502
|
+
if (idx === -1) return false;
|
|
503
|
+
if (i === 0 && idx !== 0) return false;
|
|
504
|
+
pos = idx + part.length;
|
|
501
505
|
}
|
|
506
|
+
if (parts.at(-1) !== "") return pos === filePath.length;
|
|
507
|
+
return true;
|
|
502
508
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
if (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
509
|
+
let remaining = filePath;
|
|
510
|
+
for (let i = 0; i < segments.length; i++) {
|
|
511
|
+
const seg = segments[i];
|
|
512
|
+
if (!seg) continue;
|
|
513
|
+
const segParts = seg.split("*");
|
|
514
|
+
let pos = 0;
|
|
515
|
+
let matched = false;
|
|
516
|
+
for (let attempt = remaining.indexOf(segParts[0], 0); attempt !== -1; attempt = remaining.indexOf(segParts[0], attempt + 1)) {
|
|
517
|
+
pos = attempt;
|
|
518
|
+
matched = true;
|
|
519
|
+
for (const sp of segParts) {
|
|
520
|
+
if (!sp) continue;
|
|
521
|
+
const idx = remaining.indexOf(sp, pos);
|
|
522
|
+
if (idx === -1) {
|
|
523
|
+
matched = false;
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
pos = idx + sp.length;
|
|
527
|
+
}
|
|
528
|
+
if (matched) break;
|
|
512
529
|
}
|
|
530
|
+
if (!matched) return false;
|
|
531
|
+
remaining = remaining.slice(pos);
|
|
513
532
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
/** Execute a tool call against the .skilld/ directory */
|
|
536
|
+
function executeTool(toolCall, skilldDir) {
|
|
537
|
+
const args = toolCall.arguments;
|
|
538
|
+
switch (toolCall.name) {
|
|
539
|
+
case "Read": {
|
|
540
|
+
const filePath = resolveSandboxedPath(args.path, skilldDir);
|
|
541
|
+
if (!existsSync(filePath)) return `Error: file not found: ${args.path}`;
|
|
542
|
+
return sanitizeMarkdown(readFileSync(filePath, "utf-8"));
|
|
543
|
+
}
|
|
544
|
+
case "Glob": {
|
|
545
|
+
const pattern = String(args.pattern).replace(/^\.\/\.skilld\//, "./").replace(/^\.skilld\//, "./").replace(/^\.\//, "");
|
|
546
|
+
const results = [];
|
|
547
|
+
const walkDir = (dir, prefix) => {
|
|
548
|
+
if (!existsSync(dir)) return;
|
|
549
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
550
|
+
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
551
|
+
if (entry.isDirectory()) walkDir(join(dir, entry.name), relPath);
|
|
552
|
+
else results.push(`./.skilld/${relPath}`);
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
const baseDir = pattern.split("*")[0]?.replace(/\/$/, "") ?? "";
|
|
556
|
+
walkDir(join(skilldDir, baseDir), baseDir);
|
|
557
|
+
const matched = results.filter((r) => globMatch(r.replace(/^\.\/\.skilld\//, ""), pattern));
|
|
558
|
+
return matched.length > 0 ? matched.join("\n") : `No files matching: ${args.pattern}`;
|
|
559
|
+
}
|
|
560
|
+
case "Write":
|
|
561
|
+
writeFileSync(resolveSandboxedPath(args.path, skilldDir), sanitizeMarkdown(String(args.content)));
|
|
562
|
+
return "File written successfully.";
|
|
563
|
+
case "Bash": {
|
|
564
|
+
const cmd = String(args.command).trim();
|
|
565
|
+
const parts = cmd.split(/\s+/);
|
|
566
|
+
const bin = parts[0] ?? "";
|
|
567
|
+
if (!SAFE_COMMANDS.has(bin) || SHELL_META_RE.test(cmd)) return `Error: command not allowed. Only skilld, ls, cat, find commands are permitted.`;
|
|
568
|
+
try {
|
|
569
|
+
return execFileSync(bin, parts.slice(1), {
|
|
570
|
+
cwd: skilldDir,
|
|
571
|
+
timeout: 15e3,
|
|
572
|
+
encoding: "utf-8",
|
|
573
|
+
maxBuffer: 512 * 1024
|
|
574
|
+
}).trim();
|
|
575
|
+
} catch (err) {
|
|
576
|
+
return `Error: ${err.message}`;
|
|
529
577
|
}
|
|
530
|
-
break;
|
|
531
578
|
}
|
|
532
|
-
|
|
533
|
-
|
|
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>`;
|
|
579
|
+
default: return `Unknown tool: ${toolCall.name}`;
|
|
580
|
+
}
|
|
535
581
|
}
|
|
536
|
-
/** Optimize a single section using pi-ai
|
|
582
|
+
/** Optimize a single section using pi-ai agentic API with tool use */
|
|
537
583
|
async function optimizeSectionPiAi(opts) {
|
|
538
584
|
const parsed = parsePiAiModelId(opts.model);
|
|
539
585
|
if (!parsed) throw new Error(`Invalid pi-ai model ID: ${opts.model}. Expected format: pi:provider/model-id`);
|
|
540
586
|
const model = getModel(parsed.provider, parsed.modelId);
|
|
541
587
|
const apiKey = await resolveApiKey(parsed.provider);
|
|
542
|
-
const
|
|
543
|
-
const
|
|
544
|
-
const fullPrompt = references ? `${portablePrompt}\n\n## Reference Content\n\nThe following files are provided inline for your reference:\n\n${references}` : portablePrompt;
|
|
588
|
+
const skilldDir = join(opts.skillDir, ".skilld");
|
|
589
|
+
const fullPrompt = opts.prompt;
|
|
545
590
|
opts.onProgress?.({
|
|
546
591
|
chunk: "[starting...]",
|
|
547
592
|
type: "reasoning",
|
|
@@ -549,53 +594,103 @@ async function optimizeSectionPiAi(opts) {
|
|
|
549
594
|
reasoning: "",
|
|
550
595
|
section: opts.section
|
|
551
596
|
});
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
timestamp: Date.now()
|
|
561
|
-
}]
|
|
562
|
-
}, {
|
|
563
|
-
reasoning: "medium",
|
|
564
|
-
maxTokens: 16384,
|
|
565
|
-
...apiKey ? { apiKey } : {}
|
|
566
|
-
});
|
|
597
|
+
const messages = [{
|
|
598
|
+
role: "user",
|
|
599
|
+
content: [{
|
|
600
|
+
type: "text",
|
|
601
|
+
text: fullPrompt
|
|
602
|
+
}],
|
|
603
|
+
timestamp: Date.now()
|
|
604
|
+
}];
|
|
567
605
|
let text = "";
|
|
568
|
-
let
|
|
569
|
-
let
|
|
570
|
-
|
|
606
|
+
let completed = false;
|
|
607
|
+
let totalUsage;
|
|
608
|
+
let totalCost;
|
|
609
|
+
let lastWriteContent = "";
|
|
610
|
+
for (let turn = 0; turn < MAX_TOOL_TURNS; turn++) {
|
|
571
611
|
if (opts.signal?.aborted) throw new Error("pi-ai request timed out");
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
612
|
+
const eventStream = streamSimple(model, {
|
|
613
|
+
systemPrompt: "You are a technical documentation expert generating SKILL.md sections for AI agent skills. Follow the format instructions exactly. Use the provided tools to explore reference files in ./.skilld/ before writing your output.",
|
|
614
|
+
messages,
|
|
615
|
+
tools: TOOLS
|
|
616
|
+
}, {
|
|
617
|
+
reasoning: turn === 0 ? "medium" : void 0,
|
|
618
|
+
maxTokens: 16384,
|
|
619
|
+
...apiKey ? { apiKey } : {}
|
|
620
|
+
});
|
|
621
|
+
let assistantMessage;
|
|
622
|
+
let turnText = "";
|
|
623
|
+
for await (const event of eventStream) {
|
|
624
|
+
if (opts.signal?.aborted) throw new Error("pi-ai request timed out");
|
|
625
|
+
switch (event.type) {
|
|
626
|
+
case "text_delta":
|
|
627
|
+
turnText += event.delta;
|
|
628
|
+
opts.onProgress?.({
|
|
629
|
+
chunk: event.delta,
|
|
630
|
+
type: "text",
|
|
631
|
+
text: turnText,
|
|
632
|
+
reasoning: "",
|
|
633
|
+
section: opts.section
|
|
634
|
+
});
|
|
635
|
+
break;
|
|
636
|
+
case "toolcall_end": {
|
|
637
|
+
const tc = event.toolCall;
|
|
638
|
+
const hint = tc.name === "Read" || tc.name === "Write" ? `[${tc.name}: ${tc.arguments.path}]` : tc.name === "Bash" ? `[${tc.name}: ${tc.arguments.command}]` : `[${tc.name}: ${tc.arguments.pattern}]`;
|
|
639
|
+
opts.onProgress?.({
|
|
640
|
+
chunk: hint,
|
|
641
|
+
type: "reasoning",
|
|
642
|
+
text: "",
|
|
643
|
+
reasoning: hint,
|
|
644
|
+
section: opts.section
|
|
645
|
+
});
|
|
646
|
+
break;
|
|
590
647
|
}
|
|
591
|
-
|
|
592
|
-
|
|
648
|
+
case "done":
|
|
649
|
+
assistantMessage = event.message;
|
|
650
|
+
break;
|
|
651
|
+
case "error": throw new Error(event.error?.errorMessage ?? "pi-ai stream error");
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (!assistantMessage) throw new Error("pi-ai stream ended without a message");
|
|
655
|
+
if (assistantMessage.usage) {
|
|
656
|
+
if (totalUsage) {
|
|
657
|
+
totalUsage.input += assistantMessage.usage.input;
|
|
658
|
+
totalUsage.output += assistantMessage.usage.output;
|
|
659
|
+
} else totalUsage = {
|
|
660
|
+
input: assistantMessage.usage.input,
|
|
661
|
+
output: assistantMessage.usage.output
|
|
662
|
+
};
|
|
663
|
+
totalCost = (totalCost ?? 0) + (assistantMessage.usage.cost?.total ?? 0);
|
|
664
|
+
}
|
|
665
|
+
messages.push(assistantMessage);
|
|
666
|
+
const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
|
|
667
|
+
if (toolCalls.length === 0) {
|
|
668
|
+
text = turnText;
|
|
669
|
+
completed = true;
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
for (const tc of toolCalls) {
|
|
673
|
+
const result = executeTool(tc, skilldDir);
|
|
674
|
+
if (tc.name === "Write") lastWriteContent = String(tc.arguments.content);
|
|
675
|
+
messages.push({
|
|
676
|
+
role: "toolResult",
|
|
677
|
+
toolCallId: tc.id,
|
|
678
|
+
toolName: tc.name,
|
|
679
|
+
content: [{
|
|
680
|
+
type: "text",
|
|
681
|
+
text: result
|
|
682
|
+
}],
|
|
683
|
+
isError: result.startsWith("Error:"),
|
|
684
|
+
timestamp: Date.now()
|
|
685
|
+
});
|
|
593
686
|
}
|
|
594
687
|
}
|
|
688
|
+
if (!completed) throw new Error(`pi-ai exceeded ${MAX_TOOL_TURNS} tool turns without completing`);
|
|
595
689
|
return {
|
|
596
|
-
text,
|
|
597
|
-
|
|
598
|
-
|
|
690
|
+
text: text || lastWriteContent,
|
|
691
|
+
fullPrompt,
|
|
692
|
+
usage: totalUsage,
|
|
693
|
+
cost: totalCost
|
|
599
694
|
};
|
|
600
695
|
}
|
|
601
696
|
//#endregion
|
|
@@ -630,16 +725,22 @@ function createToolProgress(log) {
|
|
|
630
725
|
log.message(msg);
|
|
631
726
|
}
|
|
632
727
|
}
|
|
633
|
-
return ({ type, chunk, section }) => {
|
|
728
|
+
return ({ type, chunk, text, section }) => {
|
|
634
729
|
if (type === "text") {
|
|
635
730
|
const key = section ?? "";
|
|
636
731
|
const now = Date.now();
|
|
637
732
|
if (now - (lastTextEmit.get(key) ?? 0) < TEXT_THROTTLE_MS) return;
|
|
638
733
|
lastTextEmit.set(key, now);
|
|
639
|
-
|
|
734
|
+
const prefix = section ? `\x1B[90m[${section}]\x1B[0m ` : "";
|
|
735
|
+
const items = text ? text.match(/^- (?:BREAKING|DEPRECATED|NEW|CHANGED|REMOVED|Use |Do |Set |Add |Avoid |Always |Never |Prefer |Check |Ensure )/gm)?.length ?? 0 : 0;
|
|
736
|
+
emit(items > 0 ? `${prefix}Writing... \x1B[90m(${items} items)\x1B[0m` : `${prefix}Writing...`);
|
|
640
737
|
return;
|
|
641
738
|
}
|
|
642
739
|
if (type !== "reasoning" || !chunk.startsWith("[")) return;
|
|
740
|
+
if (/^\[(?:starting|retrying|cached)/.test(chunk)) {
|
|
741
|
+
emit(`${section ? `\x1B[90m[${section}]\x1B[0m ` : ""}${chunk.slice(1, -1)}`);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
643
744
|
const match = chunk.match(/^\[([^:[\]]+)(?::\s(.+))?\]$/);
|
|
644
745
|
if (!match) return;
|
|
645
746
|
const names = match[1].split(",").map((n) => n.trim());
|
|
@@ -805,6 +906,9 @@ async function optimizeSectionViaPiAi(opts) {
|
|
|
805
906
|
const { section, prompt, outputFile, skillDir, model, onProgress, timeout, debug } = opts;
|
|
806
907
|
const skilldDir = join(skillDir, ".skilld");
|
|
807
908
|
const outputPath = join(skilldDir, outputFile);
|
|
909
|
+
const logsDir = join(skilldDir, "logs");
|
|
910
|
+
const logName = section.toUpperCase().replace(/-/g, "_");
|
|
911
|
+
if (existsSync(outputPath)) unlinkSync(outputPath);
|
|
808
912
|
writeFileSync(join(skilldDir, `PROMPT_${section}.md`), prompt);
|
|
809
913
|
try {
|
|
810
914
|
const ac = new AbortController();
|
|
@@ -819,9 +923,8 @@ async function optimizeSectionViaPiAi(opts) {
|
|
|
819
923
|
}).finally(() => clearTimeout(timer));
|
|
820
924
|
const raw = result.text.trim();
|
|
821
925
|
if (debug) {
|
|
822
|
-
const logsDir = join(skilldDir, "logs");
|
|
823
|
-
const logName = section.toUpperCase().replace(/-/g, "_");
|
|
824
926
|
mkdirSync(logsDir, { recursive: true });
|
|
927
|
+
writeFileSync(join(skilldDir, `PROMPT_${section}.md`), result.fullPrompt);
|
|
825
928
|
if (raw) writeFileSync(join(logsDir, `${logName}.md`), raw);
|
|
826
929
|
}
|
|
827
930
|
if (!raw) return {
|
|
@@ -846,11 +949,16 @@ async function optimizeSectionViaPiAi(opts) {
|
|
|
846
949
|
cost: result.cost
|
|
847
950
|
};
|
|
848
951
|
} catch (err) {
|
|
952
|
+
const errMsg = err.message;
|
|
953
|
+
if (debug || errMsg) {
|
|
954
|
+
mkdirSync(logsDir, { recursive: true });
|
|
955
|
+
writeFileSync(join(logsDir, `${logName}.stderr.log`), errMsg);
|
|
956
|
+
}
|
|
849
957
|
return {
|
|
850
958
|
section,
|
|
851
959
|
content: "",
|
|
852
960
|
wasOptimized: false,
|
|
853
|
-
error:
|
|
961
|
+
error: errMsg
|
|
854
962
|
};
|
|
855
963
|
}
|
|
856
964
|
}
|
|
@@ -1141,15 +1249,28 @@ async function optimizeDocs(opts) {
|
|
|
1141
1249
|
prompt
|
|
1142
1250
|
});
|
|
1143
1251
|
}
|
|
1144
|
-
for (const { section, prompt } of retryQueue) {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1252
|
+
for (const { index, section, prompt } of retryQueue) {
|
|
1253
|
+
const rateLimitDelay = parseRateLimitDelay(getRetryError(spawnResults[index]));
|
|
1254
|
+
if (rateLimitDelay != null) {
|
|
1255
|
+
const waitSec = Math.max(rateLimitDelay, 5);
|
|
1256
|
+
onProgress?.({
|
|
1257
|
+
chunk: `[${section}] Rate limited, waiting ${waitSec}s...`,
|
|
1258
|
+
type: "reasoning",
|
|
1259
|
+
text: "",
|
|
1260
|
+
reasoning: "",
|
|
1261
|
+
section
|
|
1262
|
+
});
|
|
1263
|
+
await setTimeout$1(waitSec * 1e3);
|
|
1264
|
+
} else {
|
|
1265
|
+
onProgress?.({
|
|
1266
|
+
chunk: `[${section}: retrying...]`,
|
|
1267
|
+
type: "reasoning",
|
|
1268
|
+
text: "",
|
|
1269
|
+
reasoning: "",
|
|
1270
|
+
section
|
|
1271
|
+
});
|
|
1272
|
+
await setTimeout$1(STAGGER_MS);
|
|
1273
|
+
}
|
|
1153
1274
|
const result = await optimizeSection({
|
|
1154
1275
|
section,
|
|
1155
1276
|
prompt,
|
|
@@ -1212,6 +1333,22 @@ async function optimizeDocs(opts) {
|
|
|
1212
1333
|
debugLogsDir
|
|
1213
1334
|
};
|
|
1214
1335
|
}
|
|
1336
|
+
/** Check if an error string indicates a rate limit (429) */
|
|
1337
|
+
function isRateLimitError(error) {
|
|
1338
|
+
if (!error) return false;
|
|
1339
|
+
return /\b429\b/.test(error) || /rate.?limit/i.test(error) || /exhausted.*capacity/i.test(error) || /quota.*reset/i.test(error);
|
|
1340
|
+
}
|
|
1341
|
+
/** Parse delay hint from rate limit error (e.g. "reset after 5s" → 5). Returns undefined if not a rate limit. */
|
|
1342
|
+
function parseRateLimitDelay(error) {
|
|
1343
|
+
if (!error || !isRateLimitError(error)) return void 0;
|
|
1344
|
+
const match = error.match(/reset\s+after\s+(\d+)s/i);
|
|
1345
|
+
return match ? Number(match[1]) : 10;
|
|
1346
|
+
}
|
|
1347
|
+
/** Extract error string from a PromiseSettledResult */
|
|
1348
|
+
function getRetryError(result) {
|
|
1349
|
+
if (result.status === "rejected") return String(result.reason);
|
|
1350
|
+
return result.value.error;
|
|
1351
|
+
}
|
|
1215
1352
|
/** Shorten absolute paths for display: /home/user/project/.claude/skills/vue/SKILL.md → .claude/.../SKILL.md */
|
|
1216
1353
|
function shortenPath(p) {
|
|
1217
1354
|
const refIdx = p.indexOf(".skilld/");
|
|
@@ -1243,10 +1380,7 @@ function cleanSectionOutput(content) {
|
|
|
1243
1380
|
else cleaned = cleaned.slice(afterOpen).trim();
|
|
1244
1381
|
}
|
|
1245
1382
|
const firstMarker = cleaned.match(/^(##\s|- (?:BREAKING|DEPRECATED|NEW): )/m);
|
|
1246
|
-
if (firstMarker?.index && firstMarker.index > 0)
|
|
1247
|
-
const preamble = cleaned.slice(0, firstMarker.index);
|
|
1248
|
-
if (/\b(?:function|const |let |var |export |return |import |async |class )\b/.test(preamble)) cleaned = cleaned.slice(firstMarker.index).trim();
|
|
1249
|
-
}
|
|
1383
|
+
if (firstMarker?.index && firstMarker.index > 0) cleaned = cleaned.slice(firstMarker.index).trim();
|
|
1250
1384
|
const headingMatch = cleaned.match(/^(## .+)\n/);
|
|
1251
1385
|
if (headingMatch) {
|
|
1252
1386
|
const heading = headingMatch[1];
|