@cremini/skillpack 1.0.7 → 1.0.9
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 +4 -2
- package/dist/cli.js +60 -12
- package/package.json +1 -1
- package/runtime/server/chat-proxy.js +68 -0
- package/runtime/start.bat +0 -0
- package/runtime/start.sh +0 -0
- package/runtime/web/app.js +17 -3
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# SkillPack.sh - Pack AI Skills into Standalone Apps
|
|
2
2
|
|
|
3
|
+
Skillpack by Cremini is built on the idea of distributed intelligence, much like cremini mushrooms that grow from a vast, interconnected mycelial network.
|
|
4
|
+
|
|
3
5
|
Go to [skillpack.sh](https://skillpack.sh) to pack skills and try existing skill packs.
|
|
4
6
|
|
|
5
|
-
One command to orchestrate [Skills](https://skills.sh), tools, mcps into a standalone app users can download and use on their own computer!
|
|
7
|
+
One command to orchestrate [Skills](https://skills.sh), tools, mcps into a standalone app users can download and use on their own computer to address a well-defined problem or complete specific tasks!
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
npx @cremini/skillpack create
|
|
@@ -84,7 +86,7 @@ skillpack/
|
|
|
84
86
|
|
|
85
87
|
```bash
|
|
86
88
|
# macOS / Linux
|
|
87
|
-
|
|
89
|
+
./start.sh
|
|
88
90
|
|
|
89
91
|
# Windows
|
|
90
92
|
start.bat
|
package/dist/cli.js
CHANGED
|
@@ -317,6 +317,13 @@ import fs3 from "fs";
|
|
|
317
317
|
import path3 from "path";
|
|
318
318
|
import { fileURLToPath } from "url";
|
|
319
319
|
var __dirname = path3.dirname(fileURLToPath(import.meta.url));
|
|
320
|
+
var EXECUTABLE_RUNTIME_FILES = /* @__PURE__ */ new Set(["start.sh", "start.bat"]);
|
|
321
|
+
function isExecutableRuntimeFile(relativePath) {
|
|
322
|
+
return EXECUTABLE_RUNTIME_FILES.has(relativePath);
|
|
323
|
+
}
|
|
324
|
+
function withExecuteBits(mode) {
|
|
325
|
+
return mode | 73;
|
|
326
|
+
}
|
|
320
327
|
function getRuntimeDir() {
|
|
321
328
|
const projectRoot = path3.resolve(__dirname, "..");
|
|
322
329
|
return path3.join(projectRoot, "runtime");
|
|
@@ -374,6 +381,16 @@ function copyRuntimeTemplate(runtimeDir, workDir) {
|
|
|
374
381
|
fs3.chmodSync(destinationPath, entry.stats.mode);
|
|
375
382
|
}
|
|
376
383
|
}
|
|
384
|
+
function ensureRuntimeLaunchersExecutable(workDir) {
|
|
385
|
+
for (const relativePath of EXECUTABLE_RUNTIME_FILES) {
|
|
386
|
+
const filePath = path3.join(workDir, relativePath);
|
|
387
|
+
if (!fs3.existsSync(filePath)) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const currentMode = fs3.statSync(filePath).mode;
|
|
391
|
+
fs3.chmodSync(filePath, withExecuteBits(currentMode));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
377
394
|
function addRuntimeFiles(archive, runtimeDir, prefix) {
|
|
378
395
|
const entries = collectRuntimeTemplateEntries(runtimeDir);
|
|
379
396
|
for (const entry of entries) {
|
|
@@ -387,7 +404,7 @@ function addRuntimeFiles(archive, runtimeDir, prefix) {
|
|
|
387
404
|
}
|
|
388
405
|
archive.file(entry.absolutePath, {
|
|
389
406
|
name: archivePath,
|
|
390
|
-
mode: entry.stats.mode
|
|
407
|
+
mode: isExecutableRuntimeFile(entry.relativePath) ? withExecuteBits(entry.stats.mode) : entry.stats.mode
|
|
391
408
|
});
|
|
392
409
|
}
|
|
393
410
|
}
|
|
@@ -433,6 +450,25 @@ async function bundle(workDir) {
|
|
|
433
450
|
function parseSkillNames(value) {
|
|
434
451
|
return value.split(",").map((name) => name.trim()).filter(Boolean);
|
|
435
452
|
}
|
|
453
|
+
function normalizeSourceInput(value) {
|
|
454
|
+
return value.trim().replace(/^npx\s+skills\s+add\s+/u, "");
|
|
455
|
+
}
|
|
456
|
+
function parseSourceInput(value) {
|
|
457
|
+
const trimmedValue = normalizeSourceInput(value);
|
|
458
|
+
const skillFlagIndex = trimmedValue.indexOf(" --skill ");
|
|
459
|
+
if (skillFlagIndex === -1) {
|
|
460
|
+
return {
|
|
461
|
+
source: trimmedValue,
|
|
462
|
+
inlineSkillNames: []
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const source = trimmedValue.slice(0, skillFlagIndex).trim();
|
|
466
|
+
const inlineSkillValue = trimmedValue.slice(skillFlagIndex + " --skill ".length).trim();
|
|
467
|
+
return {
|
|
468
|
+
source,
|
|
469
|
+
inlineSkillNames: inlineSkillValue.split(/[,\s]+/).map((name) => name.trim()).filter(Boolean)
|
|
470
|
+
};
|
|
471
|
+
}
|
|
436
472
|
async function createCommand(directory) {
|
|
437
473
|
const workDir = directory ? path5.resolve(directory) : process.cwd();
|
|
438
474
|
if (directory) {
|
|
@@ -476,7 +512,10 @@ async function createCommand(directory) {
|
|
|
476
512
|
chalk3.dim(" Supported formats: owner/repo, GitHub URL, or local path")
|
|
477
513
|
);
|
|
478
514
|
console.log(chalk3.dim(" Example source: vercel-labs/agent-skills"));
|
|
479
|
-
console.log(
|
|
515
|
+
console.log(
|
|
516
|
+
chalk3.dim(" Example inline skill: vercel-labs/agent-skills --skill find-skills")
|
|
517
|
+
);
|
|
518
|
+
console.log();
|
|
480
519
|
while (true) {
|
|
481
520
|
const { source } = await inquirer.prompt([
|
|
482
521
|
{
|
|
@@ -488,16 +527,24 @@ async function createCommand(directory) {
|
|
|
488
527
|
if (!source.trim()) {
|
|
489
528
|
break;
|
|
490
529
|
}
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
530
|
+
const parsedSource = parseSourceInput(source);
|
|
531
|
+
let skillNames = parsedSource.inlineSkillNames;
|
|
532
|
+
if (skillNames.length === 0) {
|
|
533
|
+
console.log(
|
|
534
|
+
chalk3.dim(" Example skill names: frontend-design, skill-creator")
|
|
535
|
+
);
|
|
536
|
+
const promptResult = await inquirer.prompt([
|
|
537
|
+
{
|
|
538
|
+
type: "input",
|
|
539
|
+
name: "skillNames",
|
|
540
|
+
message: "Skill names (comma-separated):",
|
|
541
|
+
validate: (value) => parseSkillNames(value).length > 0 ? true : "Enter at least one skill name"
|
|
542
|
+
}
|
|
543
|
+
]);
|
|
544
|
+
skillNames = parseSkillNames(promptResult.skillNames);
|
|
545
|
+
}
|
|
546
|
+
const nextSkills = skillNames.map((skillName) => ({
|
|
547
|
+
source: parsedSource.source,
|
|
501
548
|
name: skillName,
|
|
502
549
|
description: ""
|
|
503
550
|
}));
|
|
@@ -610,6 +657,7 @@ async function initCommand(directory, options) {
|
|
|
610
657
|
installConfiguredSkills(workDir, config);
|
|
611
658
|
refreshDescriptionsAndSave(workDir, config);
|
|
612
659
|
copyRuntimeTemplate(getRuntimeDir(), workDir);
|
|
660
|
+
ensureRuntimeLaunchersExecutable(workDir);
|
|
613
661
|
if (options.bundle) {
|
|
614
662
|
await bundle(workDir);
|
|
615
663
|
}
|
package/package.json
CHANGED
|
@@ -12,6 +12,34 @@ const DEBUG = true;
|
|
|
12
12
|
const log = (...args) => DEBUG && console.log(...args);
|
|
13
13
|
const write = (data) => DEBUG && process.stdout.write(data);
|
|
14
14
|
|
|
15
|
+
function getAssistantDiagnostics(message) {
|
|
16
|
+
if (!message || message.role !== "assistant") {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const stopReason = message.stopReason;
|
|
21
|
+
const errorMessage =
|
|
22
|
+
message.errorMessage ||
|
|
23
|
+
(stopReason === "error" || stopReason === "aborted"
|
|
24
|
+
? `Request ${stopReason}`
|
|
25
|
+
: "");
|
|
26
|
+
|
|
27
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
28
|
+
const text = content
|
|
29
|
+
.filter((item) => item?.type === "text")
|
|
30
|
+
.map((item) => item.text || "")
|
|
31
|
+
.join("")
|
|
32
|
+
.trim();
|
|
33
|
+
const toolCalls = content.filter((item) => item?.type === "toolCall").length;
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
stopReason,
|
|
37
|
+
errorMessage,
|
|
38
|
+
hasText: text.length > 0,
|
|
39
|
+
toolCalls,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
15
43
|
/**
|
|
16
44
|
* Handle incoming WebSocket connection using pi-coding-agent
|
|
17
45
|
* @param {import("ws").WebSocket} ws
|
|
@@ -24,6 +52,8 @@ export async function handleWsConnection(
|
|
|
24
52
|
{ apiKey, rootDir, provider = "openai", modelId = "gpt-5.4" },
|
|
25
53
|
) {
|
|
26
54
|
try {
|
|
55
|
+
let turnHadVisibleOutput = false;
|
|
56
|
+
|
|
27
57
|
// Create an in-memory auth storage to avoid touching disk
|
|
28
58
|
const authStorage = AuthStorage.inMemory({
|
|
29
59
|
[provider]: { type: "api_key", key: apiKey },
|
|
@@ -78,6 +108,7 @@ export async function handleWsConnection(
|
|
|
78
108
|
|
|
79
109
|
case "message_update":
|
|
80
110
|
if (event.assistantMessageEvent?.type === "text_delta") {
|
|
111
|
+
turnHadVisibleOutput = true;
|
|
81
112
|
write(event.assistantMessageEvent.delta);
|
|
82
113
|
ws.send(
|
|
83
114
|
JSON.stringify({
|
|
@@ -86,6 +117,7 @@ export async function handleWsConnection(
|
|
|
86
117
|
}),
|
|
87
118
|
);
|
|
88
119
|
} else if (event.assistantMessageEvent?.type === "thinking_delta") {
|
|
120
|
+
turnHadVisibleOutput = true;
|
|
89
121
|
ws.send(
|
|
90
122
|
JSON.stringify({
|
|
91
123
|
type: "thinking_delta",
|
|
@@ -97,6 +129,17 @@ export async function handleWsConnection(
|
|
|
97
129
|
|
|
98
130
|
case "message_end":
|
|
99
131
|
log(`\n--- [Message End: ${event.message?.role}] ---`);
|
|
132
|
+
if (event.message?.role === "assistant") {
|
|
133
|
+
const diagnostics = getAssistantDiagnostics(event.message);
|
|
134
|
+
if (diagnostics) {
|
|
135
|
+
log(
|
|
136
|
+
`[Assistant Diagnostics] stopReason=${diagnostics.stopReason || "unknown"} text=${diagnostics.hasText ? "yes" : "no"} toolCalls=${diagnostics.toolCalls}`,
|
|
137
|
+
);
|
|
138
|
+
if (diagnostics.errorMessage) {
|
|
139
|
+
log(`[Assistant Error] ${diagnostics.errorMessage}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
100
143
|
ws.send(
|
|
101
144
|
JSON.stringify({
|
|
102
145
|
type: "message_end",
|
|
@@ -106,6 +149,7 @@ export async function handleWsConnection(
|
|
|
106
149
|
break;
|
|
107
150
|
|
|
108
151
|
case "tool_execution_start":
|
|
152
|
+
turnHadVisibleOutput = true;
|
|
109
153
|
log(`\n>>> [Tool Execution Start: ${event.toolName}] >>>`);
|
|
110
154
|
log("Args:", JSON.stringify(event.args, null, 2));
|
|
111
155
|
ws.send(
|
|
@@ -118,6 +162,7 @@ export async function handleWsConnection(
|
|
|
118
162
|
break;
|
|
119
163
|
|
|
120
164
|
case "tool_execution_end":
|
|
165
|
+
turnHadVisibleOutput = true;
|
|
121
166
|
log(`<<< [Tool Execution End: ${event.toolName}] <<<`);
|
|
122
167
|
log(`Error: ${event.isError ? "Yes" : "No"}`);
|
|
123
168
|
ws.send(
|
|
@@ -142,8 +187,31 @@ export async function handleWsConnection(
|
|
|
142
187
|
try {
|
|
143
188
|
const payload = JSON.parse(data.toString());
|
|
144
189
|
if (payload.text) {
|
|
190
|
+
turnHadVisibleOutput = false;
|
|
191
|
+
|
|
145
192
|
// Send prompt to the agent, the session will handle message history natively
|
|
146
193
|
await session.prompt(payload.text);
|
|
194
|
+
|
|
195
|
+
const lastMessage = session.state.messages.at(-1);
|
|
196
|
+
const diagnostics = getAssistantDiagnostics(lastMessage);
|
|
197
|
+
if (diagnostics?.errorMessage) {
|
|
198
|
+
ws.send(JSON.stringify({ error: diagnostics.errorMessage }));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (
|
|
203
|
+
diagnostics &&
|
|
204
|
+
!diagnostics.hasText &&
|
|
205
|
+
diagnostics.toolCalls === 0 &&
|
|
206
|
+
!turnHadVisibleOutput
|
|
207
|
+
) {
|
|
208
|
+
const emptyResponseError =
|
|
209
|
+
"Assistant returned no visible output. Check the server logs for stopReason/provider details.";
|
|
210
|
+
log(`[Assistant Warning] ${emptyResponseError}`);
|
|
211
|
+
ws.send(JSON.stringify({ error: emptyResponseError }));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
147
215
|
ws.send(JSON.stringify({ done: true }));
|
|
148
216
|
}
|
|
149
217
|
} catch (err) {
|
package/runtime/start.bat
CHANGED
|
File without changes
|
package/runtime/start.sh
CHANGED
|
File without changes
|
package/runtime/web/app.js
CHANGED
|
@@ -181,7 +181,7 @@ function renderMarkdown(mdText, { renderEmbeddedMarkdown = true } = {}) {
|
|
|
181
181
|
return escapeHtml(mdText);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
const html = marked.parse(mdText);
|
|
184
|
+
const html = ensureLinksOpenInNewTab(marked.parse(mdText));
|
|
185
185
|
if (!renderEmbeddedMarkdown) {
|
|
186
186
|
return html;
|
|
187
187
|
}
|
|
@@ -189,6 +189,18 @@ function renderMarkdown(mdText, { renderEmbeddedMarkdown = true } = {}) {
|
|
|
189
189
|
return renderEmbeddedMarkdownBlocks(html);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
function ensureLinksOpenInNewTab(html) {
|
|
193
|
+
const template = document.createElement("template");
|
|
194
|
+
template.innerHTML = html;
|
|
195
|
+
|
|
196
|
+
template.content.querySelectorAll("a[href]").forEach((linkEl) => {
|
|
197
|
+
linkEl.setAttribute("target", "_blank");
|
|
198
|
+
linkEl.setAttribute("rel", "noopener noreferrer");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return template.innerHTML;
|
|
202
|
+
}
|
|
203
|
+
|
|
192
204
|
function renderEmbeddedMarkdownBlocks(html) {
|
|
193
205
|
const template = document.createElement("template");
|
|
194
206
|
template.innerHTML = html;
|
|
@@ -365,7 +377,9 @@ function handleAgentEvent(event) {
|
|
|
365
377
|
|
|
366
378
|
let inputHtml = "";
|
|
367
379
|
if (typeof marked !== "undefined") {
|
|
368
|
-
inputHtml =
|
|
380
|
+
inputHtml = ensureLinksOpenInNewTab(
|
|
381
|
+
marked.parse("```json\n" + safeInput + "\n```"),
|
|
382
|
+
);
|
|
369
383
|
} else {
|
|
370
384
|
inputHtml = escapeHtml(safeInput);
|
|
371
385
|
}
|
|
@@ -438,7 +452,7 @@ function handleAgentEvent(event) {
|
|
|
438
452
|
: "```json\n" + safeResult + "\n```";
|
|
439
453
|
|
|
440
454
|
if (typeof marked !== "undefined") {
|
|
441
|
-
resultEl.innerHTML = marked.parse(mdText);
|
|
455
|
+
resultEl.innerHTML = ensureLinksOpenInNewTab(marked.parse(mdText));
|
|
442
456
|
} else {
|
|
443
457
|
resultEl.textContent = safeResult;
|
|
444
458
|
}
|