@cremini/skillpack 1.2.5 → 1.2.6

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
@@ -126,6 +126,10 @@ The main use case is to **run local agents on your computer and integrate them w
126
126
 
127
127
  Download [Company Deep Research](https://github.com/FinpeakInc/downloads/releases/download/v.0.0.1/Company-Deep-Research.zip) and try it! More examples can be found at [skillpack.sh](https://skillpack.sh)
128
128
 
129
+ ## Questions?
130
+
131
+ Join our Discord at https://discord.gg/nj8Br4ePJc
132
+
129
133
  ## License
130
134
 
131
135
  MIT
package/dist/cli.js CHANGED
@@ -329,6 +329,20 @@ var init_commands = __esm({
329
329
  }
330
330
  });
331
331
 
332
+ // src/runtime/adapters/types.ts
333
+ var types_exports = {};
334
+ __export(types_exports, {
335
+ isMessageSender: () => isMessageSender
336
+ });
337
+ function isMessageSender(adapter) {
338
+ return typeof adapter.sendMessage === "function";
339
+ }
340
+ var init_types = __esm({
341
+ "src/runtime/adapters/types.ts"() {
342
+ "use strict";
343
+ }
344
+ });
345
+
332
346
  // src/runtime/adapters/markdown.ts
333
347
  function unwrapMarkdownSourceBlocks(text) {
334
348
  return text.replace(
@@ -449,7 +463,7 @@ var telegram_exports = {};
449
463
  __export(telegram_exports, {
450
464
  TelegramAdapter: () => TelegramAdapter
451
465
  });
452
- import fs12 from "fs";
466
+ import fs13 from "fs";
453
467
  import TelegramBot from "node-telegram-bot-api";
454
468
  var MAX_MESSAGE_LENGTH, ACK_REACTION, TelegramAdapter;
455
469
  var init_telegram = __esm({
@@ -469,12 +483,14 @@ var init_telegram = __esm({
469
483
  agent = null;
470
484
  options;
471
485
  rootDir = "";
486
+ ipcBroadcaster = null;
472
487
  constructor(options) {
473
488
  this.options = options;
474
489
  }
475
490
  async start(ctx) {
476
491
  this.agent = ctx.agent;
477
492
  this.rootDir = ctx.rootDir;
493
+ this.ipcBroadcaster = ctx.ipcBroadcaster ?? null;
478
494
  this.bot = new TelegramBot(this.options.token, { polling: true });
479
495
  this.bot.on("message", (msg) => {
480
496
  this.handleTelegramMessage(msg).catch((err) => {
@@ -516,6 +532,15 @@ var init_telegram = __esm({
516
532
  const messageId = msg.message_id;
517
533
  const text = (msg.text || msg.caption || "").trim();
518
534
  const channelId = `telegram-${chatId}`;
535
+ this.ipcBroadcaster?.broadcastInbound(
536
+ channelId,
537
+ "telegram",
538
+ {
539
+ id: String(msg.from?.id || ""),
540
+ username: msg.from?.username || msg.from?.first_name || ""
541
+ },
542
+ text
543
+ );
519
544
  const attachments = await this.extractAttachments(msg, channelId);
520
545
  if (!text && attachments.length === 0) return;
521
546
  await this.tryAckReaction(chatId, messageId);
@@ -545,6 +570,7 @@ var init_telegram = __esm({
545
570
  });
546
571
  break;
547
572
  }
573
+ this.ipcBroadcaster?.broadcastAgentEvent(channelId, event);
548
574
  };
549
575
  try {
550
576
  const userText = text || "(User sent an attachment)";
@@ -755,7 +781,7 @@ var init_telegram = __esm({
755
781
  async sendFileSafe(chatId, filePath, caption) {
756
782
  if (!this.bot) return;
757
783
  try {
758
- if (!fs12.existsSync(filePath)) {
784
+ if (!fs13.existsSync(filePath)) {
759
785
  console.error(`[Telegram] File not found for sending: ${filePath}`);
760
786
  return;
761
787
  }
@@ -775,8 +801,8 @@ var slack_exports = {};
775
801
  __export(slack_exports, {
776
802
  SlackAdapter: () => SlackAdapter
777
803
  });
778
- import fs13 from "fs";
779
- import path12 from "path";
804
+ import fs14 from "fs";
805
+ import path13 from "path";
780
806
  import { App, LogLevel } from "@slack/bolt";
781
807
  var INLINE_COMMANDS, SLASH_COMMANDS, MAX_MESSAGE_LENGTH2, ACK_REACTION2, PROCESSING_MESSAGE, SlackAdapter;
782
808
  var init_slack = __esm({
@@ -807,12 +833,14 @@ var init_slack = __esm({
807
833
  botUserId = null;
808
834
  lastThreadByChannel = /* @__PURE__ */ new Map();
809
835
  rootDir = "";
836
+ ipcBroadcaster = null;
810
837
  constructor(options) {
811
838
  this.options = options;
812
839
  }
813
840
  async start(ctx) {
814
841
  this.agent = ctx.agent;
815
842
  this.rootDir = ctx.rootDir;
843
+ this.ipcBroadcaster = ctx.ipcBroadcaster ?? null;
816
844
  this.app = new App({
817
845
  token: this.options.botToken,
818
846
  appToken: this.options.appToken,
@@ -898,6 +926,15 @@ var init_slack = __esm({
898
926
  const teamId = this.getTeamId(body, context);
899
927
  const channelId = `slack-dm-${teamId}-${event.channel}`;
900
928
  const route = { channel: event.channel };
929
+ this.ipcBroadcaster?.broadcastInbound(
930
+ channelId,
931
+ "slack",
932
+ {
933
+ id: String(event.user || ""),
934
+ username: String(event.user || "")
935
+ },
936
+ text
937
+ );
901
938
  const attachments = await this.extractSlackFiles(event, channelId, client);
902
939
  if (!text && attachments.length === 0) return;
903
940
  await this.tryAckReaction(client, event);
@@ -928,6 +965,15 @@ var init_slack = __esm({
928
965
  threadTs
929
966
  );
930
967
  const text = this.stripBotMention(event.text || "").trim();
968
+ this.ipcBroadcaster?.broadcastInbound(
969
+ channelId,
970
+ "slack",
971
+ {
972
+ id: String(event.user || ""),
973
+ username: String(event.user || "")
974
+ },
975
+ text
976
+ );
931
977
  const attachments = await this.extractSlackFiles(event, channelId, client);
932
978
  if (!text && attachments.length === 0) {
933
979
  await this.sendSafe(
@@ -987,6 +1033,7 @@ var init_slack = __esm({
987
1033
  caption: event.caption
988
1034
  });
989
1035
  }
1036
+ this.ipcBroadcaster?.broadcastAgentEvent(channelId, event);
990
1037
  };
991
1038
  try {
992
1039
  const result = await this.agent.handleMessage(
@@ -1390,12 +1437,12 @@ var init_slack = __esm({
1390
1437
  */
1391
1438
  async sendFileSafe(client, route, filePath, caption) {
1392
1439
  try {
1393
- if (!fs13.existsSync(filePath)) {
1440
+ if (!fs14.existsSync(filePath)) {
1394
1441
  console.error(`[Slack] File not found for sending: ${filePath}`);
1395
1442
  return;
1396
1443
  }
1397
- const filename = path12.basename(filePath);
1398
- const fileContent = fs13.readFileSync(filePath);
1444
+ const filename = path13.basename(filePath);
1445
+ const fileContent = fs14.readFileSync(filePath);
1399
1446
  await client.files.uploadV2({
1400
1447
  channel_id: route.channel,
1401
1448
  thread_ts: route.threadTs,
@@ -1412,20 +1459,6 @@ var init_slack = __esm({
1412
1459
  }
1413
1460
  });
1414
1461
 
1415
- // src/runtime/adapters/types.ts
1416
- var types_exports = {};
1417
- __export(types_exports, {
1418
- isMessageSender: () => isMessageSender
1419
- });
1420
- function isMessageSender(adapter) {
1421
- return typeof adapter.sendMessage === "function";
1422
- }
1423
- var init_types = __esm({
1424
- "src/runtime/adapters/types.ts"() {
1425
- "use strict";
1426
- }
1427
- });
1428
-
1429
1462
  // src/runtime/adapters/scheduler.ts
1430
1463
  var scheduler_exports = {};
1431
1464
  __export(scheduler_exports, {
@@ -2436,15 +2469,15 @@ async function interactiveCreate(workDir) {
2436
2469
  }
2437
2470
 
2438
2471
  // src/commands/run.ts
2439
- import path14 from "path";
2440
- import fs15 from "fs";
2472
+ import path15 from "path";
2473
+ import fs16 from "fs";
2441
2474
  import inquirer2 from "inquirer";
2442
2475
  import chalk4 from "chalk";
2443
2476
 
2444
2477
  // src/runtime/server.ts
2445
2478
  import express from "express";
2446
- import path13 from "path";
2447
- import fs14 from "fs";
2479
+ import path14 from "path";
2480
+ import fs15 from "fs";
2448
2481
  import { fileURLToPath as fileURLToPath2 } from "url";
2449
2482
  import { createServer } from "http";
2450
2483
  import { exec } from "child_process";
@@ -5861,6 +5894,9 @@ ${text}`;
5861
5894
  /** Reserved: restore a historical session */
5862
5895
  async restoreSession(_sessionId) {
5863
5896
  }
5897
+ getActiveChannelIds() {
5898
+ return Array.from(this.channels.keys());
5899
+ }
5864
5900
  };
5865
5901
 
5866
5902
  // src/runtime/adapters/web.ts
@@ -5896,9 +5932,11 @@ var WebAdapter = class {
5896
5932
  name = "web";
5897
5933
  wss = null;
5898
5934
  agent = null;
5935
+ ipcBroadcaster = null;
5899
5936
  async start(ctx) {
5900
5937
  const { agent, server, app, rootDir, lifecycle } = ctx;
5901
5938
  this.agent = agent;
5939
+ this.ipcBroadcaster = ctx.ipcBroadcaster ?? null;
5902
5940
  const currentConf = configManager.getConfig();
5903
5941
  let apiKey = currentConf.apiKey || "";
5904
5942
  let currentProvider = currentConf.provider || "openai";
@@ -6174,6 +6212,7 @@ var WebAdapter = class {
6174
6212
  }
6175
6213
  const onEvent = (event) => {
6176
6214
  sendWsEvent(ws, event);
6215
+ this.ipcBroadcaster?.broadcastAgentEvent(channelId, event);
6177
6216
  };
6178
6217
  const result = await agent.handleMessage("web", channelId, text, onEvent);
6179
6218
  if (result.errorMessage) {
@@ -6191,6 +6230,378 @@ var WebAdapter = class {
6191
6230
  }
6192
6231
  };
6193
6232
 
6233
+ // src/runtime/adapters/ipc.ts
6234
+ init_config();
6235
+
6236
+ // src/runtime/services/conversation.ts
6237
+ import fs11 from "fs";
6238
+ import path11 from "path";
6239
+ import {
6240
+ parseSessionEntries
6241
+ } from "@mariozechner/pi-coding-agent";
6242
+ var ConversationService = class {
6243
+ constructor(rootDir) {
6244
+ this.rootDir = rootDir;
6245
+ }
6246
+ /**
6247
+ * Scan data/sessions and return conversation summaries sorted by recency.
6248
+ */
6249
+ listConversations(activeChannels) {
6250
+ const sessionsDir = path11.resolve(this.rootDir, "data", "sessions");
6251
+ const channelIds = new Set(activeChannels);
6252
+ if (fs11.existsSync(sessionsDir)) {
6253
+ for (const entry of fs11.readdirSync(sessionsDir)) {
6254
+ const channelDir = path11.join(sessionsDir, entry);
6255
+ try {
6256
+ if (fs11.statSync(channelDir).isDirectory()) {
6257
+ channelIds.add(entry);
6258
+ }
6259
+ } catch {
6260
+ }
6261
+ }
6262
+ }
6263
+ const results = [];
6264
+ for (const channelId of channelIds) {
6265
+ const channelDir = path11.join(sessionsDir, channelId);
6266
+ const sessionFile = this.findLatestSessionFile(channelDir);
6267
+ let messageCount = 0;
6268
+ let lastMessageAt = "";
6269
+ let lastMessagePreview = "";
6270
+ if (sessionFile) {
6271
+ const entries = this.loadEntries(sessionFile);
6272
+ const messages = entries.filter(
6273
+ (entry) => entry.type === "message"
6274
+ );
6275
+ messageCount = messages.length;
6276
+ const lastMessage = messages[messages.length - 1];
6277
+ if (lastMessage) {
6278
+ lastMessageAt = lastMessage.timestamp;
6279
+ lastMessagePreview = this.extractTextPreview(lastMessage, 100);
6280
+ }
6281
+ }
6282
+ results.push({
6283
+ channelId,
6284
+ platform: this.detectPlatform(channelId),
6285
+ sessionFile,
6286
+ messageCount,
6287
+ lastMessageAt,
6288
+ lastMessagePreview
6289
+ });
6290
+ }
6291
+ return results.sort((a, b) => {
6292
+ const recency = (b.lastMessageAt || "").localeCompare(a.lastMessageAt || "");
6293
+ if (recency !== 0) return recency;
6294
+ return a.channelId.localeCompare(b.channelId);
6295
+ });
6296
+ }
6297
+ /**
6298
+ * Load latest messages for a channel in a simplified format.
6299
+ */
6300
+ getMessages(channelId, limit = 100) {
6301
+ const channelDir = path11.resolve(
6302
+ this.rootDir,
6303
+ "data",
6304
+ "sessions",
6305
+ channelId
6306
+ );
6307
+ const sessionFile = this.findLatestSessionFile(channelDir);
6308
+ if (!sessionFile) return [];
6309
+ const safeLimit = Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 100;
6310
+ if (safeLimit === 0) return [];
6311
+ const entries = this.loadEntries(sessionFile);
6312
+ const messages = [];
6313
+ for (const entry of entries) {
6314
+ if (entry.type !== "message") continue;
6315
+ const role = entry.message?.role;
6316
+ if (role !== "user" && role !== "assistant") continue;
6317
+ const text = this.extractText(entry.message);
6318
+ if (!text) continue;
6319
+ const toolCalls = role === "assistant" ? this.extractToolCallSummaries(entry.message) : void 0;
6320
+ messages.push({
6321
+ id: entry.id,
6322
+ role,
6323
+ text,
6324
+ timestamp: entry.timestamp,
6325
+ toolCalls
6326
+ });
6327
+ }
6328
+ return messages.slice(-safeLimit);
6329
+ }
6330
+ findLatestSessionFile(channelDir) {
6331
+ if (!fs11.existsSync(channelDir)) return null;
6332
+ let stats;
6333
+ try {
6334
+ stats = fs11.statSync(channelDir);
6335
+ } catch {
6336
+ return null;
6337
+ }
6338
+ if (!stats.isDirectory()) return null;
6339
+ const files = fs11.readdirSync(channelDir).filter((file) => file.endsWith(".jsonl")).sort((a, b) => b.localeCompare(a));
6340
+ return files[0] ? path11.join(channelDir, files[0]) : null;
6341
+ }
6342
+ loadEntries(filePath) {
6343
+ try {
6344
+ const content = fs11.readFileSync(filePath, "utf-8");
6345
+ const fileEntries = parseSessionEntries(content);
6346
+ return fileEntries.filter((entry) => entry.type !== "session");
6347
+ } catch (err) {
6348
+ console.warn(`[ConversationService] Failed to load ${filePath}:`, err);
6349
+ return [];
6350
+ }
6351
+ }
6352
+ extractText(message) {
6353
+ if (!message?.content) return "";
6354
+ if (typeof message.content === "string") return message.content.trim();
6355
+ if (!Array.isArray(message.content)) return "";
6356
+ return message.content.filter((item) => item?.type === "text").map((item) => typeof item?.text === "string" ? item.text : "").join("").trim();
6357
+ }
6358
+ extractTextPreview(entry, maxLen) {
6359
+ const text = this.extractText(entry.message);
6360
+ return text.length > maxLen ? `${text.slice(0, maxLen)}\u2026` : text;
6361
+ }
6362
+ extractToolCallSummaries(message) {
6363
+ if (!Array.isArray(message?.content)) return void 0;
6364
+ const toolCalls = message.content.filter((item) => item?.type === "toolCall").map((item) => ({
6365
+ name: typeof item?.name === "string" && item.name ? item.name : "unknown",
6366
+ isError: false
6367
+ }));
6368
+ return toolCalls.length > 0 ? toolCalls : void 0;
6369
+ }
6370
+ detectPlatform(channelId) {
6371
+ if (channelId.startsWith("telegram-")) return "telegram";
6372
+ if (channelId.startsWith("slack-")) return "slack";
6373
+ if (channelId.startsWith("scheduler-")) return "scheduler";
6374
+ return "web";
6375
+ }
6376
+ };
6377
+
6378
+ // src/runtime/adapters/ipc.ts
6379
+ init_types();
6380
+ var IpcAdapter = class {
6381
+ name = "ipc";
6382
+ agent = null;
6383
+ rootDir = "";
6384
+ adapterMap = null;
6385
+ conversationService = null;
6386
+ createdChannels = /* @__PURE__ */ new Set();
6387
+ messageListener;
6388
+ started = false;
6389
+ async start(ctx) {
6390
+ if (typeof process.send !== "function") {
6391
+ return;
6392
+ }
6393
+ this.agent = ctx.agent;
6394
+ this.rootDir = ctx.rootDir;
6395
+ this.adapterMap = ctx.adapterMap ?? null;
6396
+ this.conversationService = new ConversationService(ctx.rootDir);
6397
+ this.messageListener = (message) => {
6398
+ if (!this.isIpcRequest(message)) return;
6399
+ void this.handleRequest(message);
6400
+ };
6401
+ process.on("message", this.messageListener);
6402
+ this.started = true;
6403
+ console.log("[IpcAdapter] Started");
6404
+ }
6405
+ async stop() {
6406
+ if (this.messageListener) {
6407
+ process.off("message", this.messageListener);
6408
+ this.messageListener = void 0;
6409
+ }
6410
+ if (this.started) {
6411
+ console.log("[IpcAdapter] Stopped");
6412
+ }
6413
+ this.started = false;
6414
+ }
6415
+ notifyReady(port) {
6416
+ this.sendIpc({
6417
+ type: "ready",
6418
+ port
6419
+ });
6420
+ }
6421
+ broadcastInbound(channelId, platform, sender, text) {
6422
+ this.sendIpc({
6423
+ type: "inbound_message",
6424
+ channelId,
6425
+ platform,
6426
+ sender,
6427
+ text,
6428
+ timestamp: Date.now()
6429
+ });
6430
+ }
6431
+ broadcastAgentEvent(channelId, event) {
6432
+ this.sendIpc({
6433
+ type: "agent_event",
6434
+ channelId,
6435
+ event
6436
+ });
6437
+ }
6438
+ isIpcRequest(message) {
6439
+ if (!message || typeof message !== "object") return false;
6440
+ const maybe = message;
6441
+ return typeof maybe.id === "string" && typeof maybe.type === "string";
6442
+ }
6443
+ async handleRequest(request) {
6444
+ if (!this.agent || !this.conversationService) {
6445
+ this.replyError(request.id, "IPC adapter is not ready yet");
6446
+ return;
6447
+ }
6448
+ try {
6449
+ switch (request.type) {
6450
+ case "get_conversations": {
6451
+ const activeChannels = new Set(this.agent.getActiveChannelIds());
6452
+ for (const channelId of this.createdChannels) {
6453
+ activeChannels.add(channelId);
6454
+ }
6455
+ const conversations = this.conversationService.listConversations(activeChannels);
6456
+ this.reply(request.id, conversations);
6457
+ return;
6458
+ }
6459
+ case "create_conversation": {
6460
+ const channelId = `web-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
6461
+ this.createdChannels.add(channelId);
6462
+ this.reply(request.id, { channelId });
6463
+ return;
6464
+ }
6465
+ case "get_messages": {
6466
+ if (!request.channelId || typeof request.channelId !== "string") {
6467
+ this.replyError(request.id, "channelId is required");
6468
+ return;
6469
+ }
6470
+ const messages = this.conversationService.getMessages(
6471
+ request.channelId,
6472
+ request.limit ?? 100
6473
+ );
6474
+ this.reply(request.id, messages);
6475
+ return;
6476
+ }
6477
+ case "send_message": {
6478
+ if (!request.channelId || typeof request.channelId !== "string") {
6479
+ this.replyError(request.id, "channelId is required");
6480
+ return;
6481
+ }
6482
+ if (typeof request.text !== "string") {
6483
+ this.replyError(request.id, "text is required");
6484
+ return;
6485
+ }
6486
+ const platform = this.detectPlatform(request.channelId);
6487
+ this.createdChannels.add(request.channelId);
6488
+ let fullText = "";
6489
+ const result = await this.agent.handleMessage(
6490
+ platform,
6491
+ request.channelId,
6492
+ request.text,
6493
+ (event) => {
6494
+ if (event.type === "text_delta") {
6495
+ fullText += event.delta;
6496
+ }
6497
+ this.broadcastAgentEvent(request.channelId, event);
6498
+ }
6499
+ );
6500
+ if (fullText.trim() && platform !== "web" && platform !== "scheduler") {
6501
+ const adapter = this.adapterMap?.get(platform);
6502
+ if (adapter && isMessageSender(adapter)) {
6503
+ await adapter.sendMessage(request.channelId, fullText);
6504
+ }
6505
+ }
6506
+ this.reply(request.id, {
6507
+ ...result,
6508
+ text: fullText
6509
+ });
6510
+ return;
6511
+ }
6512
+ case "command": {
6513
+ if (!request.channelId || typeof request.channelId !== "string") {
6514
+ this.replyError(request.id, "channelId is required");
6515
+ return;
6516
+ }
6517
+ const result = await this.agent.handleCommand(request.command, request.channelId);
6518
+ this.reply(request.id, result);
6519
+ return;
6520
+ }
6521
+ case "get_config": {
6522
+ this.reply(request.id, configManager.getConfig());
6523
+ return;
6524
+ }
6525
+ case "update_config": {
6526
+ configManager.save(this.rootDir, request.updates || {});
6527
+ const updated = configManager.getConfig();
6528
+ const provider = updated.provider || "openai";
6529
+ this.agent.updateAuth(provider, updated.apiKey);
6530
+ this.reply(request.id, updated);
6531
+ return;
6532
+ }
6533
+ case "get_status": {
6534
+ this.reply(request.id, {
6535
+ status: "running",
6536
+ pid: process.pid
6537
+ });
6538
+ return;
6539
+ }
6540
+ case "get_scheduled_jobs": {
6541
+ const scheduler = this.getSchedulerAdapter();
6542
+ this.reply(request.id, scheduler ? scheduler.listJobs() : []);
6543
+ return;
6544
+ }
6545
+ case "add_scheduled_job": {
6546
+ const scheduler = this.getSchedulerAdapter();
6547
+ if (!scheduler) {
6548
+ this.replyError(request.id, "Scheduler adapter is not available");
6549
+ return;
6550
+ }
6551
+ const result = scheduler.addJob(request.job);
6552
+ if (!result.success) {
6553
+ this.replyError(request.id, result.message);
6554
+ return;
6555
+ }
6556
+ this.reply(request.id, result);
6557
+ return;
6558
+ }
6559
+ case "remove_scheduled_job": {
6560
+ const scheduler = this.getSchedulerAdapter();
6561
+ if (!scheduler) {
6562
+ this.replyError(request.id, "Scheduler adapter is not available");
6563
+ return;
6564
+ }
6565
+ const result = scheduler.removeJob(request.name);
6566
+ if (!result.success) {
6567
+ this.replyError(request.id, result.message);
6568
+ return;
6569
+ }
6570
+ this.reply(request.id, result);
6571
+ return;
6572
+ }
6573
+ }
6574
+ } catch (err) {
6575
+ this.replyError(
6576
+ request.id,
6577
+ err instanceof Error ? err.message : String(err)
6578
+ );
6579
+ }
6580
+ }
6581
+ getSchedulerAdapter() {
6582
+ const adapter = this.adapterMap?.get("scheduler");
6583
+ if (!adapter) return null;
6584
+ return adapter;
6585
+ }
6586
+ detectPlatform(channelId) {
6587
+ if (channelId.startsWith("telegram-")) return "telegram";
6588
+ if (channelId.startsWith("slack-")) return "slack";
6589
+ if (channelId.startsWith("scheduler-")) return "scheduler";
6590
+ return "web";
6591
+ }
6592
+ sendIpc(payload) {
6593
+ if (typeof process.send === "function") {
6594
+ process.send(payload);
6595
+ }
6596
+ }
6597
+ reply(id, data) {
6598
+ this.sendIpc({ id, type: "result", data });
6599
+ }
6600
+ replyError(id, message) {
6601
+ this.sendIpc({ id, type: "error", message });
6602
+ }
6603
+ };
6604
+
6194
6605
  // src/runtime/server.ts
6195
6606
  init_config();
6196
6607
 
@@ -6267,28 +6678,28 @@ var Lifecycle = class {
6267
6678
 
6268
6679
  // src/runtime/registry.ts
6269
6680
  import crypto from "crypto";
6270
- import fs11 from "fs";
6681
+ import fs12 from "fs";
6271
6682
  import os from "os";
6272
- import path11 from "path";
6273
- var SKILLPACK_HOME = path11.join(os.homedir(), ".skillpack");
6274
- var LEGACY_REGISTRY_FILE = path11.join(SKILLPACK_HOME, "registry.json");
6275
- var REGISTRY_DIR = path11.join(SKILLPACK_HOME, "registry.d");
6683
+ import path12 from "path";
6684
+ var SKILLPACK_HOME = path12.join(os.homedir(), ".skillpack");
6685
+ var LEGACY_REGISTRY_FILE = path12.join(SKILLPACK_HOME, "registry.json");
6686
+ var REGISTRY_DIR = path12.join(SKILLPACK_HOME, "registry.d");
6276
6687
  var migrationChecked = false;
6277
6688
  function ensureHomeDir() {
6278
- if (!fs11.existsSync(SKILLPACK_HOME)) {
6279
- fs11.mkdirSync(SKILLPACK_HOME, { recursive: true });
6689
+ if (!fs12.existsSync(SKILLPACK_HOME)) {
6690
+ fs12.mkdirSync(SKILLPACK_HOME, { recursive: true });
6280
6691
  }
6281
6692
  }
6282
6693
  function ensureRegistryDir() {
6283
6694
  ensureHomeDir();
6284
- if (!fs11.existsSync(REGISTRY_DIR)) {
6285
- fs11.mkdirSync(REGISTRY_DIR, { recursive: true });
6695
+ if (!fs12.existsSync(REGISTRY_DIR)) {
6696
+ fs12.mkdirSync(REGISTRY_DIR, { recursive: true });
6286
6697
  }
6287
6698
  }
6288
6699
  function canonicalizeDir(dir) {
6289
- const resolved = path11.resolve(dir);
6700
+ const resolved = path12.resolve(dir);
6290
6701
  try {
6291
- return fs11.realpathSync(resolved);
6702
+ return fs12.realpathSync(resolved);
6292
6703
  } catch {
6293
6704
  return resolved;
6294
6705
  }
@@ -6297,7 +6708,7 @@ function hashDir(dir) {
6297
6708
  return crypto.createHash("md5").update(canonicalizeDir(dir)).digest("hex");
6298
6709
  }
6299
6710
  function getEntryPathForCanonicalDir(dir) {
6300
- return path11.join(REGISTRY_DIR, `${hashDir(dir)}.json`);
6711
+ return path12.join(REGISTRY_DIR, `${hashDir(dir)}.json`);
6301
6712
  }
6302
6713
  function getEntryPath(dir) {
6303
6714
  ensureRegistryReady();
@@ -6305,11 +6716,11 @@ function getEntryPath(dir) {
6305
6716
  }
6306
6717
  function listEntryFiles() {
6307
6718
  ensureRegistryReady();
6308
- return fs11.readdirSync(REGISTRY_DIR).filter((file) => file.endsWith(".json")).sort().map((file) => path11.join(REGISTRY_DIR, file));
6719
+ return fs12.readdirSync(REGISTRY_DIR).filter((file) => file.endsWith(".json")).sort().map((file) => path12.join(REGISTRY_DIR, file));
6309
6720
  }
6310
6721
  function readEntryFile(filePath) {
6311
6722
  try {
6312
- const raw = fs11.readFileSync(filePath, "utf-8");
6723
+ const raw = fs12.readFileSync(filePath, "utf-8");
6313
6724
  const data = JSON.parse(raw);
6314
6725
  if (typeof data?.dir !== "string" || typeof data?.name !== "string" || typeof data?.version !== "string" || typeof data?.port !== "number" || typeof data?.pid !== "number" && data?.pid !== null || data?.status !== "running" && data?.status !== "stopped") {
6315
6726
  return null;
@@ -6342,8 +6753,8 @@ function writeEntryFile(entry) {
6342
6753
  };
6343
6754
  const entryPath = getEntryPathForCanonicalDir(normalized.dir);
6344
6755
  const tmpPath = createTmpPath(entryPath);
6345
- fs11.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), "utf-8");
6346
- fs11.renameSync(tmpPath, entryPath);
6756
+ fs12.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), "utf-8");
6757
+ fs12.renameSync(tmpPath, entryPath);
6347
6758
  }
6348
6759
  function migrateLegacyRegistryIfNeeded() {
6349
6760
  if (migrationChecked) {
@@ -6351,14 +6762,14 @@ function migrateLegacyRegistryIfNeeded() {
6351
6762
  }
6352
6763
  migrationChecked = true;
6353
6764
  ensureRegistryDir();
6354
- if (!fs11.existsSync(LEGACY_REGISTRY_FILE)) {
6765
+ if (!fs12.existsSync(LEGACY_REGISTRY_FILE)) {
6355
6766
  return;
6356
6767
  }
6357
6768
  if (listEntryFiles().length > 0) {
6358
6769
  return;
6359
6770
  }
6360
6771
  try {
6361
- const raw = fs11.readFileSync(LEGACY_REGISTRY_FILE, "utf-8");
6772
+ const raw = fs12.readFileSync(LEGACY_REGISTRY_FILE, "utf-8");
6362
6773
  const data = JSON.parse(raw);
6363
6774
  const packs = Array.isArray(data?.packs) ? data.packs : [];
6364
6775
  for (const pack of packs) {
@@ -6371,7 +6782,7 @@ function migrateLegacyRegistryIfNeeded() {
6371
6782
  } catch {
6372
6783
  }
6373
6784
  }
6374
- fs11.renameSync(LEGACY_REGISTRY_FILE, `${LEGACY_REGISTRY_FILE}.legacy`);
6785
+ fs12.renameSync(LEGACY_REGISTRY_FILE, `${LEGACY_REGISTRY_FILE}.legacy`);
6375
6786
  } catch (err) {
6376
6787
  console.warn(" [Registry] Failed to migrate legacy registry.json:", err);
6377
6788
  }
@@ -6424,7 +6835,7 @@ function deregister(dir, pid) {
6424
6835
  }
6425
6836
 
6426
6837
  // src/runtime/server.ts
6427
- var __dirname = path13.dirname(fileURLToPath2(import.meta.url));
6838
+ var __dirname = path14.dirname(fileURLToPath2(import.meta.url));
6428
6839
  async function startServer(options) {
6429
6840
  const {
6430
6841
  rootDir,
@@ -6440,8 +6851,8 @@ async function startServer(options) {
6440
6851
  const baseUrl = dataConfig.baseUrl?.trim() || void 0;
6441
6852
  const modelId = dataConfig.modelId?.trim() || (SUPPORTED_PROVIDERS[provider]?.defaultModelId ?? SUPPORTED_PROVIDERS.openai.defaultModelId);
6442
6853
  const apiProtocol = dataConfig.apiProtocol;
6443
- const packageRoot = path13.resolve(__dirname, "..");
6444
- const webDir = fs14.existsSync(path13.join(rootDir, "web")) ? path13.join(rootDir, "web") : path13.join(packageRoot, "web");
6854
+ const packageRoot = path14.resolve(__dirname, "..");
6855
+ const webDir = fs15.existsSync(path14.join(rootDir, "web")) ? path14.join(rootDir, "web") : path14.join(packageRoot, "web");
6445
6856
  const app = express();
6446
6857
  app.use(express.json());
6447
6858
  app.use(express.static(webDir));
@@ -6470,8 +6881,31 @@ async function startServer(options) {
6470
6881
  });
6471
6882
  const adapters = [];
6472
6883
  const adapterMap = /* @__PURE__ */ new Map();
6884
+ const hasIpcChannel = typeof process.send === "function";
6885
+ const ipcAdapter = new IpcAdapter();
6886
+ if (hasIpcChannel) {
6887
+ await ipcAdapter.start({
6888
+ agent,
6889
+ server,
6890
+ app,
6891
+ rootDir,
6892
+ lifecycle,
6893
+ adapterMap
6894
+ });
6895
+ adapters.push(ipcAdapter);
6896
+ adapterMap.set(ipcAdapter.name, ipcAdapter);
6897
+ }
6898
+ const ipcBroadcaster = hasIpcChannel ? ipcAdapter : void 0;
6473
6899
  const webAdapter = new WebAdapter();
6474
- await webAdapter.start({ agent, server, app, rootDir, lifecycle, adapterMap });
6900
+ await webAdapter.start({
6901
+ agent,
6902
+ server,
6903
+ app,
6904
+ rootDir,
6905
+ lifecycle,
6906
+ adapterMap,
6907
+ ipcBroadcaster
6908
+ });
6475
6909
  adapters.push(webAdapter);
6476
6910
  adapterMap.set(webAdapter.name, webAdapter);
6477
6911
  if (dataConfig.adapters?.telegram?.token) {
@@ -6480,7 +6914,15 @@ async function startServer(options) {
6480
6914
  const telegramAdapter = new TelegramAdapter2({
6481
6915
  token: dataConfig.adapters.telegram.token
6482
6916
  });
6483
- await telegramAdapter.start({ agent, server, app, rootDir, lifecycle });
6917
+ await telegramAdapter.start({
6918
+ agent,
6919
+ server,
6920
+ app,
6921
+ rootDir,
6922
+ lifecycle,
6923
+ adapterMap,
6924
+ ipcBroadcaster
6925
+ });
6484
6926
  adapters.push(telegramAdapter);
6485
6927
  adapterMap.set(telegramAdapter.name, telegramAdapter);
6486
6928
  } catch (err) {
@@ -6500,7 +6942,15 @@ async function startServer(options) {
6500
6942
  botToken: slackConfig.botToken,
6501
6943
  appToken: slackConfig.appToken
6502
6944
  });
6503
- await slackAdapter.start({ agent, server, app, rootDir, lifecycle });
6945
+ await slackAdapter.start({
6946
+ agent,
6947
+ server,
6948
+ app,
6949
+ rootDir,
6950
+ lifecycle,
6951
+ adapterMap,
6952
+ ipcBroadcaster
6953
+ });
6504
6954
  adapters.push(slackAdapter);
6505
6955
  adapterMap.set(slackAdapter.name, slackAdapter);
6506
6956
  } catch (err) {
@@ -6563,6 +7013,9 @@ async function startServer(options) {
6563
7013
  } catch (err) {
6564
7014
  console.warn(" [Registry] Could not register pack:", err);
6565
7015
  }
7016
+ if (hasIpcChannel) {
7017
+ ipcAdapter.notifyReady(typeof actualPort === "number" ? actualPort : port);
7018
+ }
6566
7019
  if (!daemonRun) {
6567
7020
  const cmd = process.platform === "darwin" ? `open ${url}` : process.platform === "win32" ? `start ${url}` : `xdg-open ${url}`;
6568
7021
  exec(cmd, (err) => {
@@ -6610,23 +7063,23 @@ function findMissingSkills(workDir, config) {
6610
7063
  });
6611
7064
  }
6612
7065
  function copyStartTemplates2(workDir) {
6613
- const templateDir = path14.resolve(
7066
+ const templateDir = path15.resolve(
6614
7067
  new URL("../templates", import.meta.url).pathname
6615
7068
  );
6616
7069
  for (const file of ["start.sh", "start.bat"]) {
6617
- const src = path14.join(templateDir, file);
6618
- const dest = path14.join(workDir, file);
6619
- if (fs15.existsSync(src)) {
6620
- fs15.copyFileSync(src, dest);
7070
+ const src = path15.join(templateDir, file);
7071
+ const dest = path15.join(workDir, file);
7072
+ if (fs16.existsSync(src)) {
7073
+ fs16.copyFileSync(src, dest);
6621
7074
  if (file === "start.sh") {
6622
- fs15.chmodSync(dest, 493);
7075
+ fs16.chmodSync(dest, 493);
6623
7076
  }
6624
7077
  }
6625
7078
  }
6626
7079
  }
6627
7080
  async function runCommand(directory) {
6628
- const workDir = directory ? path14.resolve(directory) : process.cwd();
6629
- fs15.mkdirSync(workDir, { recursive: true });
7081
+ const workDir = directory ? path15.resolve(directory) : process.cwd();
7082
+ fs16.mkdirSync(workDir, { recursive: true });
6630
7083
  if (!configExists(workDir)) {
6631
7084
  console.log(chalk4.blue("\n No skillpack.json found. Let's set one up.\n"));
6632
7085
  const { name, description } = await inquirer2.prompt([
@@ -6671,11 +7124,14 @@ async function runCommand(directory) {
6671
7124
  }
6672
7125
 
6673
7126
  // src/cli.ts
6674
- import fs16 from "fs";
7127
+ import fs17 from "fs";
7128
+ import path16 from "path";
7129
+ import { fileURLToPath as fileURLToPath3 } from "url";
6675
7130
  var packageJson = JSON.parse(
6676
- fs16.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
7131
+ fs17.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
6677
7132
  );
6678
7133
  var program = new Command();
7134
+ var cliFilePath = path16.resolve(fileURLToPath3(import.meta.url));
6679
7135
  program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);
6680
7136
  program.command("create [directory]").description("Create a skills pack interactively").option("--config <path-or-url>", "Initialize from a local or remote skillpack.json").action(async (directory, options) => {
6681
7137
  await createCommand(directory, options);
@@ -6693,4 +7149,12 @@ program.command("zip").description("Package the current pack as a zip file (skil
6693
7149
  process.exit(1);
6694
7150
  }
6695
7151
  });
6696
- program.parse();
7152
+ function normalizeUserArgs(argv) {
7153
+ if (argv.length === 0) return argv;
7154
+ const firstArg = argv[0];
7155
+ if (firstArg && path16.resolve(firstArg) === cliFilePath) {
7156
+ return argv.slice(1);
7157
+ }
7158
+ return argv;
7159
+ }
7160
+ program.parse(normalizeUserArgs(process.argv.slice(2)), { from: "user" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cremini/skillpack",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "Pack AI Skills into Local Agents",
5
5
  "type": "module",
6
6
  "repository": {
@@ -29,6 +29,7 @@
29
29
  "dev": "tsup --watch",
30
30
  "check": "tsc --noEmit",
31
31
  "format": "prettier --write .",
32
+ "prepare": "npm run build",
32
33
  "create": "tsup && node dist/cli.js create",
33
34
  "run": "tsup && node dist/cli.js run",
34
35
  "zip": "tsup && node dist/cli.js zip"
@@ -65,4 +66,4 @@
65
66
  "tsup": "^8.5.1",
66
67
  "typescript": "^5.9.3"
67
68
  }
68
- }
69
+ }
@@ -5,106 +5,55 @@ description: Create new skills, modify and improve existing skills, and measure
5
5
 
6
6
  # Skill Creator
7
7
 
8
- A skill for creating new skills and iteratively improving them inside this SkillPack.
9
-
10
- At a high level, the process of creating a skill goes like this:
11
-
12
- - Decide what the skill should do and when it should trigger.
13
- - Write a draft of the skill.
14
- - Create a few realistic test prompts.
15
- - Run the tests, review the results with the user, and improve the skill.
16
- - Repeat until the skill is good enough for the user's needs.
17
-
18
- Your job when using this skill is to figure out where the user is in this process and help them move forward without overcomplicating things.
19
-
20
- ## Communicating with the user
21
-
22
- Adjust your language to the user's level of familiarity. Avoid unnecessary jargon. Briefly explain terms like "frontmatter", "assertion", or "benchmark" if the user does not appear comfortable with them.
23
-
24
- If the user clearly wants a lightweight collaboration rather than a full evaluation loop, keep things simple and iterate directly with them.
8
+ Create new skills and iteratively improve them inside this SkillPack. Determine where the user is in the create → draft → test → improve cycle and help them move forward.
25
9
 
26
10
  ## Pack-specific rules
27
11
 
28
- This SkillPack uses a fixed project-level skills directory and config file:
29
-
30
- - Skills directory: `{{SKILLS_PATH}}`
31
- - SkillPack config: `{{PACK_CONFIG_PATH}}`
32
-
33
- These paths override any generic advice you may know from other environments.
34
-
35
- When creating or updating skills in this SkillPack:
36
-
37
- - Always place the skill under `{{SKILLS_PATH}}/<skill-name>/`.
38
- - Always write the main skill file to `{{SKILLS_PATH}}/<skill-name>/SKILL.md`.
39
- - Treat `skill-name` as the canonical directory name unless the user explicitly asks to preserve an existing directory layout.
40
- - Never create new skills inside the current workspace directory just because the active cwd is elsewhere.
12
+ All skills live under `{{SKILLS_PATH}}/<skill-name>/SKILL.md` and config is at `{{PACK_CONFIG_PATH}}`. These paths override any generic advice. Never create skills in the current workspace directory always use `{{SKILLS_PATH}}`.
41
13
 
42
14
  ## Creating a skill
43
15
 
44
16
  ### Capture intent
45
17
 
46
- Start by understanding the user's intent. The current conversation may already contain the workflow the user wants to capture. Extract answers from the conversation first, then fill the gaps with targeted questions.
47
-
48
- Confirm these points before writing the first draft:
18
+ Extract answers from the current conversation first, then fill gaps with targeted questions. Confirm before writing the first draft:
49
19
 
50
20
  1. What should this skill enable the model to do?
51
21
  2. When should this skill trigger?
52
22
  3. What output should it produce?
53
23
  4. Does the user want a lightweight draft, or a tested and iterated skill?
54
24
 
55
- ### Interview and research
56
-
57
- Ask about:
58
-
59
- - edge cases
60
- - input/output formats
61
- - example prompts or files
62
- - success criteria
63
- - dependencies or required tools
64
-
65
- Wait to write test prompts until these basics are clear enough.
25
+ Clarify edge cases, input/output formats, success criteria, and dependencies as needed before writing test prompts.
66
26
 
67
27
  ### Write the skill
68
28
 
69
- Create the skill directory at `{{SKILLS_PATH}}/<skill-name>/`.
70
-
71
- Create `SKILL.md` with YAML frontmatter. The frontmatter must include:
72
-
73
- - `name`
74
- - `description`
29
+ Create the skill at `{{SKILLS_PATH}}/<skill-name>/SKILL.md`. Example template:
75
30
 
76
- The `description` is the primary triggering mechanism. Make it concrete and slightly "pushy": include both what the skill does and the situations where it should be used.
31
+ ```yaml
32
+ ---
33
+ name: example-skill
34
+ description: "Analyze competitor pricing pages and generate a comparison matrix. Use when the user wants to benchmark pricing tiers, feature gaps, or positioning against specific competitors."
35
+ ---
77
36
 
78
- Keep the skill practical:
37
+ # Example Skill
79
38
 
80
- - Put "when to use" information in the `description`, not buried in the body.
81
- - Keep the body focused on the workflow, decisions, and output expectations.
82
- - If the skill needs deterministic helpers, place them under `scripts/`.
83
- - If the skill needs long reference material, place it under `references/` and tell the model when to read it.
39
+ ## Workflow
84
40
 
85
- ### Required save location
41
+ 1. Collect the target URLs from the user.
42
+ 2. Extract pricing tiers, features, and limits from each page.
43
+ 3. Generate a comparison matrix as a markdown table.
86
44
 
87
- For a newly created skill named `example-skill`, the target layout must be:
45
+ ## Output
88
46
 
89
- ```text
90
- {{SKILLS_PATH}}/example-skill/
91
- {{SKILLS_PATH}}/example-skill/SKILL.md
47
+ Return a markdown table with one column per competitor and one row per feature.
92
48
  ```
93
49
 
94
- If the user is improving an existing skill, preserve the existing skill name unless they explicitly request a rename.
95
-
96
- ### Update skillpack.json
50
+ The `description` is the primary triggering mechanism make it concrete with both what the skill does and when to use it. Keep the body focused on workflow, decisions, and output expectations. Place deterministic helpers under `scripts/` and long reference material under `references/`.
97
51
 
98
- After you create or update a skill, you must sync `{{PACK_CONFIG_PATH}}`.
52
+ Preserve the existing skill name when improving unless the user explicitly requests a rename.
99
53
 
100
- Do not guess the metadata from memory. Instead:
54
+ ### Sync skillpack.json
101
55
 
102
- 1. Read the final `SKILL.md`.
103
- 2. Parse the YAML frontmatter.
104
- 3. Extract:
105
- - `name`
106
- - `description`
107
- 4. Upsert an entry into the `skills` array in `{{PACK_CONFIG_PATH}}`:
56
+ After creating or updating a skill, sync `{{PACK_CONFIG_PATH}}`. Read the final `SKILL.md`, parse the YAML frontmatter, and upsert into the `skills` array:
108
57
 
109
58
  ```json
110
59
  {
@@ -114,40 +63,17 @@ Do not guess the metadata from memory. Instead:
114
63
  }
115
64
  ```
116
65
 
117
- Rules for this update:
118
-
119
- - `name` must come from `frontmatter.name`.
120
- - `description` must come from `frontmatter.description`.
121
- - `source` must be `./skills/<frontmatter.name>`.
122
- - If an entry for the same skill already exists, update it instead of creating a duplicate.
66
+ All three fields must come from frontmatter — do not guess from memory. Update existing entries instead of creating duplicates.
123
67
 
124
68
  ### Writing guide
125
69
 
126
- Prefer imperative, clear instructions. Explain why important constraints exist. Avoid overly rigid language unless strict behavior is actually required.
127
-
128
- Useful structure:
129
-
130
- - purpose
131
- - trigger guidance
132
- - required inputs
133
- - step-by-step workflow
134
- - output format
135
- - edge cases
136
-
137
- If the skill supports multiple domains or frameworks, organize the references by variant and tell the model how to choose the right one.
70
+ Prefer imperative instructions. Structure skills as: purpose, trigger guidance, required inputs, step-by-step workflow, output format, edge cases. For multi-domain skills, organize references by variant and tell the model how to choose.
138
71
 
139
72
  ## Test and iterate
140
73
 
141
- After drafting the skill, propose 2-3 realistic test prompts. The prompts should sound like something a real user would actually say.
142
-
143
- If the user wants evaluation:
144
-
145
- - run the test prompts with the skill
146
- - compare the outputs against the user's expectations
147
- - note what worked and what failed
148
- - revise the skill
74
+ After drafting the skill, propose 23 realistic test prompts phrased as a real user would.
149
75
 
150
- If the user does not want a heavy evaluation loop, do at least a lightweight sanity check before calling the skill complete.
76
+ If the user wants evaluation, run the prompts, compare outputs against expectations, note failures, and revise. Otherwise, do at least a lightweight sanity check before calling the skill complete.
151
77
 
152
78
  ## Improving an existing skill
153
79