@cremini/skillpack 1.0.8 → 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 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
package/dist/cli.js CHANGED
@@ -450,6 +450,25 @@ async function bundle(workDir) {
450
450
  function parseSkillNames(value) {
451
451
  return value.split(",").map((name) => name.trim()).filter(Boolean);
452
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
+ }
453
472
  async function createCommand(directory) {
454
473
  const workDir = directory ? path5.resolve(directory) : process.cwd();
455
474
  if (directory) {
@@ -493,7 +512,10 @@ async function createCommand(directory) {
493
512
  chalk3.dim(" Supported formats: owner/repo, GitHub URL, or local path")
494
513
  );
495
514
  console.log(chalk3.dim(" Example source: vercel-labs/agent-skills"));
496
- console.log(chalk3.dim(" Example skill names: frontend-design, skill-creator\n"));
515
+ console.log(
516
+ chalk3.dim(" Example inline skill: vercel-labs/agent-skills --skill find-skills")
517
+ );
518
+ console.log();
497
519
  while (true) {
498
520
  const { source } = await inquirer.prompt([
499
521
  {
@@ -505,16 +527,24 @@ async function createCommand(directory) {
505
527
  if (!source.trim()) {
506
528
  break;
507
529
  }
508
- const { skillNames } = await inquirer.prompt([
509
- {
510
- type: "input",
511
- name: "skillNames",
512
- message: "Skill names (comma-separated):",
513
- validate: (value) => parseSkillNames(value).length > 0 ? true : "Enter at least one skill name"
514
- }
515
- ]);
516
- const nextSkills = parseSkillNames(skillNames).map((skillName) => ({
517
- source: source.trim(),
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,
518
548
  name: skillName,
519
549
  description: ""
520
550
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cremini/skillpack",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Turn Skills into a Standalone App with UI",
5
5
  "type": "module",
6
6
  "repository": {
@@ -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) {
@@ -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 = marked.parse("```json\n" + safeInput + "\n```");
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
  }