@cydm/pie 1.0.6 → 1.0.8

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 (59) hide show
  1. package/README.md +156 -9
  2. package/dist/builtin/extensions/ask-user/index.js +2 -3
  3. package/dist/builtin/extensions/init/index.js +70 -68
  4. package/dist/builtin/extensions/kimi-attachments/index.js +3 -3
  5. package/dist/builtin/extensions/plan-mode/index.js +85 -87
  6. package/dist/builtin/extensions/subagent/index.js +17 -7
  7. package/dist/builtin/extensions/todo/index.js +51 -22
  8. package/dist/builtin/skills/browser-tools/CHANGELOG.md +2 -44
  9. package/dist/builtin/skills/browser-tools/README.md +10 -99
  10. package/dist/builtin/skills/browser-tools/SKILL.md +21 -174
  11. package/dist/builtin/skills/browser-tools/package.json +6 -13
  12. package/dist/builtin/skills/browser-tools/playwright-cli.js +24 -0
  13. package/dist/builtin/skills/skill-creator/SKILL.md +17 -17
  14. package/dist/builtin/skills/skill-creator/eval-viewer/generate_review.mjs +285 -0
  15. package/dist/builtin/skills/skill-creator/eval-viewer/viewer.html +1 -1
  16. package/dist/builtin/skills/skill-creator/scripts/aggregate_benchmark.mjs +271 -0
  17. package/dist/builtin/skills/skill-creator/scripts/claude_cli.mjs +115 -0
  18. package/dist/builtin/skills/skill-creator/scripts/generate_report.mjs +224 -0
  19. package/dist/builtin/skills/skill-creator/scripts/improve_description.mjs +198 -0
  20. package/dist/builtin/skills/skill-creator/scripts/package_skill.mjs +132 -0
  21. package/dist/builtin/skills/skill-creator/scripts/pie_runner.mjs +115 -0
  22. package/dist/builtin/skills/skill-creator/scripts/quick_validate.mjs +44 -0
  23. package/dist/builtin/skills/skill-creator/scripts/run_eval.mjs +169 -0
  24. package/dist/builtin/skills/skill-creator/scripts/run_loop.mjs +297 -0
  25. package/dist/builtin/skills/skill-creator/scripts/skill_metadata.mjs +134 -0
  26. package/dist/chunks/chunk-6WD2NFIC.js +8383 -0
  27. package/dist/chunks/{chunk-MWFBYJOI.js → chunk-A5JSJAPK.js} +3973 -1313
  28. package/dist/chunks/chunk-NTYHFBUA.js +36 -0
  29. package/dist/chunks/chunk-ZRONUKTW.js +989 -0
  30. package/dist/chunks/{src-EGWRDMLB.js → src-3X3HBT2G.js} +1 -2
  31. package/dist/chunks/typescript-GSKWJIO4.js +210747 -0
  32. package/dist/cli.js +15261 -12502
  33. package/models.schema.json +238 -0
  34. package/package.json +34 -8
  35. package/dist/builtin/skills/browser-tools/browser-content.js +0 -103
  36. package/dist/builtin/skills/browser-tools/browser-cookies.js +0 -35
  37. package/dist/builtin/skills/browser-tools/browser-eval.js +0 -49
  38. package/dist/builtin/skills/browser-tools/browser-hn-scraper.js +0 -108
  39. package/dist/builtin/skills/browser-tools/browser-nav.js +0 -44
  40. package/dist/builtin/skills/browser-tools/browser-pick.js +0 -162
  41. package/dist/builtin/skills/browser-tools/browser-screenshot.js +0 -34
  42. package/dist/builtin/skills/browser-tools/browser-start.js +0 -86
  43. package/dist/builtin/skills/skill-creator/eval-viewer/generate_review.py +0 -471
  44. package/dist/builtin/skills/skill-creator/scripts/__init__.py +0 -0
  45. package/dist/builtin/skills/skill-creator/scripts/aggregate_benchmark.py +0 -401
  46. package/dist/builtin/skills/skill-creator/scripts/generate_report.py +0 -326
  47. package/dist/builtin/skills/skill-creator/scripts/improve_description.py +0 -247
  48. package/dist/builtin/skills/skill-creator/scripts/package_skill.py +0 -136
  49. package/dist/builtin/skills/skill-creator/scripts/quick_validate.py +0 -103
  50. package/dist/builtin/skills/skill-creator/scripts/run_eval.py +0 -310
  51. package/dist/builtin/skills/skill-creator/scripts/run_loop.py +0 -328
  52. package/dist/builtin/skills/skill-creator/scripts/utils.py +0 -47
  53. package/dist/chunks/capabilities-FENCOHVA.js +0 -9
  54. package/dist/chunks/chunk-JYBXCEJJ.js +0 -315
  55. package/dist/chunks/chunk-RID3574D.js +0 -2718
  56. package/dist/chunks/chunk-TBJ25UWB.js +0 -3657
  57. package/dist/chunks/chunk-XZXLO7YB.js +0 -322
  58. package/dist/chunks/file-logger-AL5VVZHH.js +0 -22
  59. package/dist/chunks/src-WRUACRN2.js +0 -132
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "node:path";
4
+ import { readFile, writeFile } from "node:fs/promises";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ function escapeHtml(value) {
8
+ return String(value ?? "")
9
+ .replaceAll("&", "&")
10
+ .replaceAll("<", "&lt;")
11
+ .replaceAll(">", "&gt;")
12
+ .replaceAll('"', "&quot;");
13
+ }
14
+
15
+ export function generateHtml(data, { autoRefresh = false, skillName = "" } = {}) {
16
+ const history = Array.isArray(data?.history) ? data.history : [];
17
+ const titlePrefix = skillName ? `${escapeHtml(skillName)} — ` : "";
18
+ const refreshTag = autoRefresh ? ' <meta http-equiv="refresh" content="5">\n' : "";
19
+
20
+ const trainQueries = [];
21
+ const testQueries = [];
22
+ if (history.length > 0) {
23
+ for (const result of history[0].train_results ?? history[0].results ?? []) {
24
+ trainQueries.push({ query: result.query, should_trigger: result.should_trigger ?? true });
25
+ }
26
+ for (const result of history[0].test_results ?? []) {
27
+ testQueries.push({ query: result.query, should_trigger: result.should_trigger ?? true });
28
+ }
29
+ }
30
+
31
+ let html = `<!DOCTYPE html>
32
+ <html>
33
+ <head>
34
+ <meta charset="utf-8">
35
+ ${refreshTag} <title>${titlePrefix}Skill Description Optimization</title>
36
+ <link rel="preconnect" href="https://fonts.googleapis.com">
37
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
38
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600&family=Lora:wght@400;500&display=swap" rel="stylesheet">
39
+ <style>
40
+ body { font-family: 'Lora', Georgia, serif; max-width: 100%; margin: 0 auto; padding: 20px; background: #faf9f5; color: #141413; }
41
+ h1 { font-family: 'Poppins', sans-serif; color: #141413; }
42
+ .explainer, .summary { background: white; padding: 15px; border-radius: 6px; margin-bottom: 20px; border: 1px solid #e8e6dc; }
43
+ .explainer { color: #b0aea5; font-size: 0.875rem; line-height: 1.6; }
44
+ .summary p { margin: 5px 0; }
45
+ .best { color: #788c5d; font-weight: bold; }
46
+ .table-container { overflow-x: auto; width: 100%; }
47
+ table { border-collapse: collapse; background: white; border: 1px solid #e8e6dc; border-radius: 6px; font-size: 12px; min-width: 100%; }
48
+ th, td { padding: 8px; text-align: left; border: 1px solid #e8e6dc; white-space: normal; word-wrap: break-word; }
49
+ th { font-family: 'Poppins', sans-serif; background: #141413; color: #faf9f5; font-weight: 500; }
50
+ th.test-col { background: #6a9bcc; }
51
+ th.query-col { min-width: 200px; }
52
+ td.description { font-family: monospace; font-size: 11px; word-wrap: break-word; max-width: 400px; }
53
+ td.result { text-align: center; font-size: 16px; min-width: 40px; }
54
+ td.test-result { background: #f0f6fc; }
55
+ .pass { color: #788c5d; }
56
+ .fail { color: #c44; }
57
+ .rate { font-size: 9px; color: #b0aea5; display: block; }
58
+ tr:hover { background: #faf9f5; }
59
+ .score { display: inline-block; padding: 2px 6px; border-radius: 4px; font-weight: bold; font-size: 11px; }
60
+ .score-good { background: #eef2e8; color: #788c5d; }
61
+ .score-ok { background: #fef3c7; color: #d97706; }
62
+ .score-bad { background: #fceaea; color: #c44; }
63
+ .best-row { background: #f5f8f2; }
64
+ th.positive-col { border-bottom: 3px solid #788c5d; }
65
+ th.negative-col { border-bottom: 3px solid #c44; }
66
+ .legend { font-family: 'Poppins', sans-serif; display: flex; gap: 20px; margin-bottom: 10px; font-size: 13px; align-items: center; }
67
+ .legend-item { display: flex; align-items: center; gap: 6px; }
68
+ .legend-swatch { width: 16px; height: 16px; border-radius: 3px; display: inline-block; }
69
+ .swatch-positive { background: #141413; border-bottom: 3px solid #788c5d; }
70
+ .swatch-negative { background: #141413; border-bottom: 3px solid #c44; }
71
+ .swatch-test { background: #6a9bcc; }
72
+ .swatch-train { background: #141413; }
73
+ </style>
74
+ </head>
75
+ <body>
76
+ <h1>${titlePrefix}Skill Description Optimization</h1>
77
+ <div class="explainer">
78
+ <strong>Optimizing your skill's description.</strong> This page updates automatically as Claude tests different versions of your skill's description. Each row is an iteration — a new description attempt. The columns show test queries: green checkmarks mean the skill triggered correctly (or correctly didn't trigger), red crosses mean it got it wrong.
79
+ </div>
80
+ <div class="summary">
81
+ <p><strong>Original:</strong> ${escapeHtml(data?.original_description ?? "N/A")}</p>
82
+ <p class="best"><strong>Best:</strong> ${escapeHtml(data?.best_description ?? "N/A")}</p>
83
+ <p><strong>Best Score:</strong> ${escapeHtml(data?.best_score ?? "N/A")}</p>
84
+ <p><strong>Iterations:</strong> ${escapeHtml(data?.iterations_run ?? 0)} | <strong>Train:</strong> ${escapeHtml(data?.train_size ?? "?")} | <strong>Test:</strong> ${escapeHtml(data?.test_size ?? "?")}</p>
85
+ </div>
86
+ <div class="legend">
87
+ <span style="font-weight:600">Query columns:</span>
88
+ <span class="legend-item"><span class="legend-swatch swatch-positive"></span> Should trigger</span>
89
+ <span class="legend-item"><span class="legend-swatch swatch-negative"></span> Should NOT trigger</span>
90
+ <span class="legend-item"><span class="legend-swatch swatch-train"></span> Train</span>
91
+ <span class="legend-item"><span class="legend-swatch swatch-test"></span> Test</span>
92
+ </div>
93
+ <div class="table-container">
94
+ <table>
95
+ <thead>
96
+ <tr>
97
+ <th>Iter</th>
98
+ <th>Train</th>
99
+ <th>Test</th>
100
+ <th class="query-col">Description</th>
101
+ `;
102
+
103
+ for (const info of trainQueries) {
104
+ html += ` <th class="${info.should_trigger ? "positive-col" : "negative-col"}">${escapeHtml(info.query)}</th>\n`;
105
+ }
106
+ for (const info of testQueries) {
107
+ html += ` <th class="test-col ${info.should_trigger ? "positive-col" : "negative-col"}">${escapeHtml(info.query)}</th>\n`;
108
+ }
109
+ html += ` </tr>
110
+ </thead>
111
+ <tbody>
112
+ `;
113
+
114
+ const bestIteration = history.length === 0
115
+ ? null
116
+ : testQueries.length > 0
117
+ ? history.reduce((best, item) => (item.test_passed ?? -1) > (best?.test_passed ?? -1) ? item : best, null)?.iteration
118
+ : history.reduce((best, item) => (item.train_passed ?? item.passed ?? -1) > (best?.train_passed ?? best?.passed ?? -1) ? item : best, null)?.iteration;
119
+
120
+ for (const item of history) {
121
+ const trainResults = item.train_results ?? item.results ?? [];
122
+ const testResults = item.test_results ?? [];
123
+ const trainByQuery = new Map(trainResults.map((entry) => [entry.query, entry]));
124
+ const testByQuery = new Map(testResults.map((entry) => [entry.query, entry]));
125
+
126
+ const aggregateRuns = (results) => {
127
+ let correct = 0;
128
+ let total = 0;
129
+ for (const result of results) {
130
+ const runs = result.runs ?? 0;
131
+ const triggers = result.triggers ?? 0;
132
+ total += runs;
133
+ correct += result.should_trigger ? triggers : runs - triggers;
134
+ }
135
+ return { correct, total };
136
+ };
137
+
138
+ const trainAgg = aggregateRuns(trainResults);
139
+ const testAgg = aggregateRuns(testResults);
140
+ const scoreClass = ({ correct, total }) => {
141
+ const ratio = total > 0 ? correct / total : 0;
142
+ if (ratio >= 0.8) return "score-good";
143
+ if (ratio >= 0.5) return "score-ok";
144
+ return "score-bad";
145
+ };
146
+
147
+ html += ` <tr class="${item.iteration === bestIteration ? "best-row" : ""}">
148
+ <td>${escapeHtml(item.iteration)}</td>
149
+ <td><span class="score ${scoreClass(trainAgg)}">${trainAgg.correct}/${trainAgg.total}</span></td>
150
+ <td><span class="score ${scoreClass(testAgg)}">${testAgg.correct}/${testAgg.total}</span></td>
151
+ <td class="description">${escapeHtml(item.description ?? "")}</td>
152
+ `;
153
+ for (const info of trainQueries) {
154
+ const result = trainByQuery.get(info.query) ?? {};
155
+ html += ` <td class="result ${result.pass ? "pass" : "fail"}">${result.pass ? "✓" : "✗"}<span class="rate">${result.triggers ?? 0}/${result.runs ?? 0}</span></td>\n`;
156
+ }
157
+ for (const info of testQueries) {
158
+ const result = testByQuery.get(info.query) ?? {};
159
+ html += ` <td class="result test-result ${result.pass ? "pass" : "fail"}">${result.pass ? "✓" : "✗"}<span class="rate">${result.triggers ?? 0}/${result.runs ?? 0}</span></td>\n`;
160
+ }
161
+ html += " </tr>\n";
162
+ }
163
+
164
+ html += ` </tbody>
165
+ </table>
166
+ </div>
167
+ </body>
168
+ </html>
169
+ `;
170
+ return html;
171
+ }
172
+
173
+ async function main(argv = process.argv) {
174
+ const args = parseArgs(argv.slice(2));
175
+ const inputPath = args._[0];
176
+ if (!inputPath) {
177
+ throw new Error("Usage: node generate_report.mjs <input.json> [-o output.html] [--skill-name name]");
178
+ }
179
+ const raw = inputPath === "-" ? await readStdin() : await readFile(inputPath, "utf8");
180
+ const data = JSON.parse(raw);
181
+ const output = generateHtml(data, { skillName: args["skill-name"] ?? "", autoRefresh: false });
182
+ if (args.output || args.o) {
183
+ await writeFile(args.output || args.o, output);
184
+ process.stderr.write(`Report written to ${args.output || args.o}\n`);
185
+ return;
186
+ }
187
+ process.stdout.write(output);
188
+ }
189
+
190
+ function parseArgs(args) {
191
+ const out = { _: [] };
192
+ for (let i = 0; i < args.length; i += 1) {
193
+ const item = args[i];
194
+ if (!item.startsWith("-")) {
195
+ out._.push(item);
196
+ continue;
197
+ }
198
+ const key = item.startsWith("--") ? item.slice(2) : item.slice(1);
199
+ const next = args[i + 1];
200
+ if (!next || next.startsWith("-")) {
201
+ out[key] = true;
202
+ continue;
203
+ }
204
+ out[key] = next;
205
+ i += 1;
206
+ }
207
+ return out;
208
+ }
209
+
210
+ async function readStdin() {
211
+ let data = "";
212
+ for await (const chunk of process.stdin) {
213
+ data += String(chunk);
214
+ }
215
+ return data;
216
+ }
217
+
218
+ const entryPath = fileURLToPath(import.meta.url);
219
+ if (process.argv[1] && path.resolve(process.argv[1]) === entryPath) {
220
+ main().catch((error) => {
221
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
222
+ process.exitCode = 1;
223
+ });
224
+ }
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "node:path";
4
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
5
+ import { fileURLToPath } from "node:url";
6
+ import { callPieText } from "./pie_runner.mjs";
7
+ import { parseSkillFile } from "./skill_metadata.mjs";
8
+
9
+ export async function improveDescription({
10
+ skillName,
11
+ skillContent,
12
+ currentDescription,
13
+ evalResults,
14
+ history,
15
+ model,
16
+ testResults = null,
17
+ logDir = null,
18
+ iteration = null,
19
+ }) {
20
+ const failedTriggers = evalResults.results.filter((r) => r.should_trigger && !r.pass);
21
+ const falseTriggers = evalResults.results.filter((r) => !r.should_trigger && !r.pass);
22
+
23
+ const trainScore = `${evalResults.summary.passed}/${evalResults.summary.total}`;
24
+ const scoresSummary = testResults
25
+ ? `Train: ${trainScore}, Test: ${testResults.summary.passed}/${testResults.summary.total}`
26
+ : `Train: ${trainScore}`;
27
+
28
+ let prompt = `You are optimizing a skill description for a Claude Code skill called "${skillName}". A "skill" is sort of like a prompt, but with progressive disclosure -- there's a title and description that Claude sees when deciding whether to use the skill, and then if it does use the skill, it reads the .md file which has lots more details and potentially links to other resources in the skill folder like helper files and scripts and additional documentation or examples.
29
+
30
+ The description appears in Claude's "available_skills" list. When a user sends a query, Claude decides whether to invoke the skill based solely on the title and on this description. Your goal is to write a description that triggers for relevant queries, and doesn't trigger for irrelevant ones.
31
+
32
+ Here's the current description:
33
+ <current_description>
34
+ "${currentDescription}"
35
+ </current_description>
36
+
37
+ Current scores (${scoresSummary}):
38
+ <scores_summary>
39
+ `;
40
+
41
+ if (failedTriggers.length > 0) {
42
+ prompt += "FAILED TO TRIGGER (should have triggered but didn't):\n";
43
+ for (const result of failedTriggers) {
44
+ prompt += ` - "${result.query}" (triggered ${result.triggers}/${result.runs} times)\n`;
45
+ }
46
+ prompt += "\n";
47
+ }
48
+
49
+ if (falseTriggers.length > 0) {
50
+ prompt += "FALSE TRIGGERS (triggered but shouldn't have):\n";
51
+ for (const result of falseTriggers) {
52
+ prompt += ` - "${result.query}" (triggered ${result.triggers}/${result.runs} times)\n`;
53
+ }
54
+ prompt += "\n";
55
+ }
56
+
57
+ if (history.length > 0) {
58
+ prompt += "PREVIOUS ATTEMPTS (do NOT repeat these — try something structurally different):\n\n";
59
+ for (const item of history) {
60
+ const trainScoreText = `${item.train_passed ?? item.passed ?? 0}/${item.train_total ?? item.total ?? 0}`;
61
+ const testScoreText = item.test_passed !== undefined && item.test_passed !== null
62
+ ? `${item.test_passed}/${item.test_total}`
63
+ : null;
64
+ prompt += `<attempt train=${trainScoreText}${testScoreText ? `, test=${testScoreText}` : ""}>\n`;
65
+ prompt += `Description: "${item.description}"\n`;
66
+ if (Array.isArray(item.results)) {
67
+ prompt += "Train results:\n";
68
+ for (const result of item.results) {
69
+ prompt += ` [${result.pass ? "PASS" : "FAIL"}] "${String(result.query).slice(0, 80)}" (triggered ${result.triggers}/${result.runs})\n`;
70
+ }
71
+ }
72
+ if (item.note) {
73
+ prompt += `Note: ${item.note}\n`;
74
+ }
75
+ prompt += "</attempt>\n\n";
76
+ }
77
+ }
78
+
79
+ prompt += `</scores_summary>
80
+
81
+ Skill content (for context on what the skill does):
82
+ <skill_content>
83
+ ${skillContent}
84
+ </skill_content>
85
+
86
+ Based on the failures, write a new and improved description that is more likely to trigger correctly. When I say "based on the failures", it's a bit of a tricky line to walk because we don't want to overfit to the specific cases you're seeing. So what I DON'T want you to do is produce an ever-expanding list of specific queries that this skill should or shouldn't trigger for. Instead, try to generalize from the failures to broader categories of user intent and situations where this skill would be useful or not useful. The reason for this is twofold:
87
+
88
+ 1. Avoid overfitting
89
+ 2. The list might get loooong and it's injected into ALL queries and there might be a lot of skills, so we don't want to blow too much space on any given description.
90
+
91
+ Concretely, your description should not be more than about 100-200 words, even if that comes at the cost of accuracy. There is a hard limit of 1024 characters — descriptions over that will be truncated, so stay comfortably under it.
92
+
93
+ Here are some tips that we've found to work well in writing these descriptions:
94
+ - The skill should be phrased in the imperative -- "Use this skill for" rather than "this skill does"
95
+ - The skill description should focus on the user's intent, what they are trying to achieve, vs. the implementation details of how the skill works.
96
+ - The description competes with other skills for Claude's attention — make it distinctive and immediately recognizable.
97
+ - If you're getting lots of failures after repeated attempts, change things up. Try different sentence structures or wordings.
98
+
99
+ I'd encourage you to be creative and mix up the style in different iterations since you'll have multiple opportunities to try different approaches and we'll just grab the highest-scoring one at the end.
100
+
101
+ Please respond with only the new description text in <new_description> tags, nothing else.`;
102
+
103
+ const transcript = { iteration, prompt };
104
+ const text = await callPieText(prompt);
105
+ transcript.response = text;
106
+
107
+ let description = parseDescription(text);
108
+ transcript.parsed_description = description;
109
+ transcript.char_count = description.length;
110
+ transcript.over_limit = description.length > 1024;
111
+
112
+ if (description.length > 1024) {
113
+ const shortenPrompt = `${prompt}
114
+
115
+ ---
116
+
117
+ A previous attempt produced this description, which at ${description.length} characters is over the 1024-character hard limit:
118
+
119
+ "${description}"
120
+
121
+ Rewrite it to be under 1024 characters while keeping the most important trigger words and intent coverage. Respond with only the new description in <new_description> tags.`;
122
+ const shortenText = await callPieText(shortenPrompt);
123
+ const shortened = parseDescription(shortenText);
124
+ transcript.rewrite_prompt = shortenPrompt;
125
+ transcript.rewrite_response = shortenText;
126
+ transcript.rewrite_description = shortened;
127
+ transcript.rewrite_char_count = shortened.length;
128
+ description = shortened;
129
+ }
130
+
131
+ transcript.final_description = description;
132
+ if (logDir) {
133
+ await mkdir(logDir, { recursive: true });
134
+ await writeFile(path.join(logDir, `improve_iter_${iteration ?? "unknown"}.json`), JSON.stringify(transcript, null, 2));
135
+ }
136
+
137
+ return description;
138
+ }
139
+
140
+ function parseDescription(text) {
141
+ const match = text.match(/<new_description>([\s\S]*?)<\/new_description>/);
142
+ return (match ? match[1] : text).trim().replace(/^"(.*)"$/s, "$1");
143
+ }
144
+
145
+ async function main(argv = process.argv) {
146
+ const args = parseArgs(argv.slice(2));
147
+ const skillPath = path.resolve(args["skill-path"]);
148
+ const evalResults = JSON.parse(await readFile(args["eval-results"], "utf8"));
149
+ const history = args.history ? JSON.parse(await readFile(args.history, "utf8")) : [];
150
+ const { name, content } = await parseSkillFile(skillPath);
151
+ const currentDescription = evalResults.description;
152
+
153
+ const description = await improveDescription({
154
+ skillName: name,
155
+ skillContent: content,
156
+ currentDescription,
157
+ evalResults,
158
+ history,
159
+ model: args.model,
160
+ });
161
+
162
+ const output = {
163
+ description,
164
+ history: history.concat([{
165
+ description: currentDescription,
166
+ passed: evalResults.summary.passed,
167
+ failed: evalResults.summary.failed,
168
+ total: evalResults.summary.total,
169
+ results: evalResults.results,
170
+ }]),
171
+ };
172
+ process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
173
+ }
174
+
175
+ function parseArgs(args) {
176
+ const flags = {};
177
+ for (let i = 0; i < args.length; i += 1) {
178
+ const value = args[i];
179
+ if (!value.startsWith("--")) continue;
180
+ const key = value.slice(2);
181
+ const next = args[i + 1];
182
+ if (!next || next.startsWith("--")) {
183
+ flags[key] = true;
184
+ continue;
185
+ }
186
+ flags[key] = next;
187
+ i += 1;
188
+ }
189
+ return flags;
190
+ }
191
+
192
+ const entryPath = fileURLToPath(import.meta.url);
193
+ if (process.argv[1] && path.resolve(process.argv[1]) === entryPath) {
194
+ main().catch((error) => {
195
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
196
+ process.exitCode = 1;
197
+ });
198
+ }
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "node:path";
4
+ import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
5
+ import { fileURLToPath } from "node:url";
6
+ import JSZip from "jszip";
7
+ import { validateSkill } from "./quick_validate.mjs";
8
+
9
+ const EXCLUDE_DIRS = new Set(["__pycache__", "node_modules"]);
10
+ const EXCLUDE_GLOBS = [/\.pyc$/i];
11
+ const EXCLUDE_FILES = new Set([".DS_Store"]);
12
+ const ROOT_EXCLUDE_DIRS = new Set(["evals"]);
13
+
14
+ function shouldExclude(relPath) {
15
+ const parts = relPath.split("/").filter(Boolean);
16
+ if (parts.some((part) => EXCLUDE_DIRS.has(part))) {
17
+ return true;
18
+ }
19
+ if (parts.length > 1 && ROOT_EXCLUDE_DIRS.has(parts[1])) {
20
+ return true;
21
+ }
22
+ const name = parts[parts.length - 1] || "";
23
+ if (EXCLUDE_FILES.has(name)) {
24
+ return true;
25
+ }
26
+ return EXCLUDE_GLOBS.some((pattern) => pattern.test(name));
27
+ }
28
+
29
+ async function collectFiles(dir, baseDir, acc = []) {
30
+ const entries = await readdir(dir, { withFileTypes: true });
31
+ for (const entry of entries) {
32
+ const fullPath = path.join(dir, entry.name);
33
+ if (entry.isDirectory()) {
34
+ await collectFiles(fullPath, baseDir, acc);
35
+ continue;
36
+ }
37
+ if (!entry.isFile()) continue;
38
+ acc.push({
39
+ fullPath,
40
+ arcname: path.relative(baseDir, fullPath).replace(/\\/g, "/"),
41
+ });
42
+ }
43
+ return acc;
44
+ }
45
+
46
+ export async function packageSkill(skillPath, outputDir) {
47
+ const resolvedSkillPath = path.resolve(skillPath);
48
+ const skillStats = await stat(resolvedSkillPath).catch(() => null);
49
+ if (!skillStats) {
50
+ process.stdout.write(`❌ Error: Skill folder not found: ${resolvedSkillPath}\n`);
51
+ return null;
52
+ }
53
+ if (!skillStats.isDirectory()) {
54
+ process.stdout.write(`❌ Error: Path is not a directory: ${resolvedSkillPath}\n`);
55
+ return null;
56
+ }
57
+
58
+ const skillMdPath = path.join(resolvedSkillPath, "SKILL.md");
59
+ const skillMdStats = await stat(skillMdPath).catch(() => null);
60
+ if (!skillMdStats || !skillMdStats.isFile()) {
61
+ process.stdout.write(`❌ Error: SKILL.md not found in ${resolvedSkillPath}\n`);
62
+ return null;
63
+ }
64
+
65
+ process.stdout.write("🔍 Validating skill...\n");
66
+ const validation = await validateSkill(resolvedSkillPath);
67
+ if (!validation.valid) {
68
+ process.stdout.write(`❌ Validation failed: ${validation.message}\n`);
69
+ process.stdout.write(" Please fix the validation errors before packaging.\n");
70
+ return null;
71
+ }
72
+ process.stdout.write(`✅ ${validation.message}\n\n`);
73
+
74
+ const outputPath = outputDir ? path.resolve(outputDir) : process.cwd();
75
+ await mkdir(outputPath, { recursive: true });
76
+
77
+ const skillName = path.basename(resolvedSkillPath);
78
+ const archivePath = path.join(outputPath, `${skillName}.skill`);
79
+ const zip = new JSZip();
80
+ const baseDir = path.dirname(resolvedSkillPath);
81
+ const files = await collectFiles(resolvedSkillPath, baseDir);
82
+
83
+ for (const file of files) {
84
+ if (shouldExclude(file.arcname)) {
85
+ process.stdout.write(` Skipped: ${file.arcname}\n`);
86
+ continue;
87
+ }
88
+ zip.file(file.arcname, await readFile(file.fullPath));
89
+ process.stdout.write(` Added: ${file.arcname}\n`);
90
+ }
91
+
92
+ const buffer = await zip.generateAsync({
93
+ type: "nodebuffer",
94
+ compression: "DEFLATE",
95
+ compressionOptions: { level: 9 },
96
+ });
97
+ await writeFile(archivePath, buffer);
98
+
99
+ process.stdout.write(`\n✅ Successfully packaged skill to: ${archivePath}\n`);
100
+ return archivePath;
101
+ }
102
+
103
+ async function main(argv = process.argv) {
104
+ if (argv.length < 3) {
105
+ process.stdout.write("Usage: node package_skill.mjs <path/to/skill-folder> [output-directory]\n");
106
+ process.stdout.write("\nExample:\n");
107
+ process.stdout.write(" node package_skill.mjs skills/public/my-skill\n");
108
+ process.stdout.write(" node package_skill.mjs skills/public/my-skill ./dist\n");
109
+ process.exitCode = 1;
110
+ return;
111
+ }
112
+
113
+ const skillPath = argv[2];
114
+ const outputDir = argv[3];
115
+
116
+ process.stdout.write(`📦 Packaging skill: ${skillPath}\n`);
117
+ if (outputDir) {
118
+ process.stdout.write(` Output directory: ${outputDir}\n`);
119
+ }
120
+ process.stdout.write("\n");
121
+
122
+ const result = await packageSkill(skillPath, outputDir);
123
+ process.exitCode = result ? 0 : 1;
124
+ }
125
+
126
+ const entryPath = fileURLToPath(import.meta.url);
127
+ if (process.argv[1] && path.resolve(process.argv[1]) === entryPath) {
128
+ main().catch((error) => {
129
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
130
+ process.exitCode = 1;
131
+ });
132
+ }
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from "node:crypto";
4
+ import { spawn } from "node:child_process";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
9
+ const REPO_ROOT = path.resolve(SCRIPT_DIR, "../../../../../../");
10
+
11
+ export function findProjectRoot(start = process.cwd()) {
12
+ return path.resolve(start);
13
+ }
14
+
15
+ export function uuidFragment() {
16
+ return crypto.randomBytes(4).toString("hex");
17
+ }
18
+
19
+ export function openInBrowser(target) {
20
+ const platform = process.platform;
21
+ if (platform === "darwin") {
22
+ spawn("open", [target], { detached: true, stdio: "ignore" }).unref();
23
+ return;
24
+ }
25
+ if (platform === "win32") {
26
+ spawn("cmd", ["/c", "start", "", target], { detached: true, stdio: "ignore" }).unref();
27
+ return;
28
+ }
29
+ spawn("xdg-open", [target], { detached: true, stdio: "ignore" }).unref();
30
+ }
31
+
32
+ function getPieCliPath(cwd = process.cwd()) {
33
+ return path.join(REPO_ROOT, "products/cli/dist/cli.js");
34
+ }
35
+
36
+ export async function runPieJsonPrompt(prompt, { cwd = process.cwd(), timeout = 300_000, sessionId = null } = {}) {
37
+ return new Promise((resolve, reject) => {
38
+ const args = [getPieCliPath(cwd), prompt, "--json-output"];
39
+ if (sessionId) {
40
+ args.push("--session-id", sessionId);
41
+ }
42
+
43
+ const child = spawn("node", args, {
44
+ cwd,
45
+ env: { ...process.env },
46
+ stdio: ["ignore", "pipe", "pipe"],
47
+ });
48
+
49
+ let stdout = "";
50
+ let stderr = "";
51
+ let settled = false;
52
+
53
+ const cleanup = () => {
54
+ clearTimeout(timer);
55
+ child.stdout.removeAllListeners();
56
+ child.stderr.removeAllListeners();
57
+ child.removeAllListeners();
58
+ };
59
+
60
+ const finish = (cb) => {
61
+ if (settled) return;
62
+ settled = true;
63
+ cleanup();
64
+ try {
65
+ child.kill("SIGKILL");
66
+ } catch {}
67
+ cb();
68
+ };
69
+
70
+ const tryResolveFromStdout = () => {
71
+ const lines = stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
72
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
73
+ const line = lines[i];
74
+ if (!line.startsWith("{")) continue;
75
+ try {
76
+ const parsed = JSON.parse(line);
77
+ if (Object.prototype.hasOwnProperty.call(parsed, "ok")) {
78
+ finish(() => resolve(parsed));
79
+ return true;
80
+ }
81
+ } catch {}
82
+ }
83
+ return false;
84
+ };
85
+
86
+ const timer = setTimeout(() => {
87
+ finish(() => reject(new Error(`pie prompt timed out after ${Math.round(timeout / 1000)}s`)));
88
+ }, timeout);
89
+
90
+ child.stdout.on("data", (chunk) => {
91
+ stdout += String(chunk);
92
+ tryResolveFromStdout();
93
+ });
94
+ child.stderr.on("data", (chunk) => {
95
+ stderr += String(chunk);
96
+ });
97
+ child.on("error", (error) => {
98
+ finish(() => reject(error));
99
+ });
100
+ child.on("close", (code) => {
101
+ if (tryResolveFromStdout()) {
102
+ return;
103
+ }
104
+ finish(() => reject(new Error(`pie prompt exited ${code}\nstdout: ${stdout}\nstderr: ${stderr}`)));
105
+ });
106
+ });
107
+ }
108
+
109
+ export async function callPieText(prompt, { cwd = process.cwd(), timeout = 300_000, sessionId = null } = {}) {
110
+ const response = await runPieJsonPrompt(prompt, { cwd, timeout, sessionId });
111
+ if (!response?.ok) {
112
+ throw new Error(response?.error || "Pie prompt failed");
113
+ }
114
+ return String(response.text || "");
115
+ }