@cremini/skillpack 1.1.8 → 1.2.0

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 (26) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +342 -91
  3. package/dist/runtime/registry.js +244 -0
  4. package/package.json +2 -2
  5. package/templates/builtin-skills/skill-creator/LICENSE.txt +202 -0
  6. package/templates/builtin-skills/skill-creator/SKILL.md +171 -0
  7. package/templates/builtin-skills/skill-creator/agents/analyzer.md +274 -0
  8. package/templates/builtin-skills/skill-creator/agents/comparator.md +202 -0
  9. package/templates/builtin-skills/skill-creator/agents/grader.md +223 -0
  10. package/templates/builtin-skills/skill-creator/assets/eval_review.html +146 -0
  11. package/templates/builtin-skills/skill-creator/eval-viewer/generate_review.py +471 -0
  12. package/templates/builtin-skills/skill-creator/eval-viewer/viewer.html +1325 -0
  13. package/templates/builtin-skills/skill-creator/references/schemas.md +430 -0
  14. package/templates/builtin-skills/skill-creator/scripts/__init__.py +0 -0
  15. package/templates/builtin-skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  16. package/templates/builtin-skills/skill-creator/scripts/generate_report.py +326 -0
  17. package/templates/builtin-skills/skill-creator/scripts/improve_description.py +247 -0
  18. package/templates/builtin-skills/skill-creator/scripts/package_skill.py +136 -0
  19. package/templates/builtin-skills/skill-creator/scripts/quick_validate.py +103 -0
  20. package/templates/builtin-skills/skill-creator/scripts/run_eval.py +310 -0
  21. package/templates/builtin-skills/skill-creator/scripts/run_loop.py +328 -0
  22. package/templates/builtin-skills/skill-creator/scripts/utils.py +47 -0
  23. package/web/js/api-key-dialog.js +3 -5
  24. package/web/js/chat-apps-dialog.js +4 -10
  25. package/web/js/chat.js +8 -8
  26. package/web/js/settings.js +3 -8
package/dist/cli.js CHANGED
@@ -114,7 +114,7 @@ var init_config = __esm({
114
114
  console.warn(" Warning: Failed to parse data/config.json:", err);
115
115
  }
116
116
  }
117
- let { apiKey = "", provider = "openai" } = this.configData;
117
+ let { apiKey = "", provider = "openai", baseUrl = "" } = this.configData;
118
118
  if (!apiKey) {
119
119
  if (process.env.OPENAI_API_KEY) {
120
120
  apiKey = process.env.OPENAI_API_KEY;
@@ -126,6 +126,7 @@ var init_config = __esm({
126
126
  }
127
127
  this.configData.apiKey = apiKey;
128
128
  this.configData.provider = provider;
129
+ this.configData.baseUrl = baseUrl?.trim() || void 0;
129
130
  return this.configData;
130
131
  }
131
132
  getConfig() {
@@ -141,6 +142,9 @@ var init_config = __esm({
141
142
  }
142
143
  if (updates.apiKey !== void 0) this.configData.apiKey = updates.apiKey;
143
144
  if (updates.provider !== void 0) this.configData.provider = updates.provider;
145
+ if (updates.baseUrl !== void 0) {
146
+ this.configData.baseUrl = updates.baseUrl?.trim() || void 0;
147
+ }
144
148
  if (updates.adapters !== void 0) {
145
149
  const merged = { ...this.configData.adapters || {} };
146
150
  for (const [adapterKey, adapterVal] of Object.entries(updates.adapters)) {
@@ -289,7 +293,7 @@ var telegram_exports = {};
289
293
  __export(telegram_exports, {
290
294
  TelegramAdapter: () => TelegramAdapter
291
295
  });
292
- import fs10 from "fs";
296
+ import fs11 from "fs";
293
297
  import TelegramBot from "node-telegram-bot-api";
294
298
  var COMMANDS2, MAX_MESSAGE_LENGTH, ACK_REACTION, TelegramAdapter;
295
299
  var init_telegram = __esm({
@@ -397,6 +401,7 @@ var init_telegram = __esm({
397
401
  try {
398
402
  const userText = text || "(User sent an attachment)";
399
403
  const result = await this.agent.handleMessage(
404
+ "telegram",
400
405
  channelId,
401
406
  userText,
402
407
  onEvent,
@@ -599,7 +604,7 @@ var init_telegram = __esm({
599
604
  async sendFileSafe(chatId, filePath, caption) {
600
605
  if (!this.bot) return;
601
606
  try {
602
- if (!fs10.existsSync(filePath)) {
607
+ if (!fs11.existsSync(filePath)) {
603
608
  console.error(`[Telegram] File not found for sending: ${filePath}`);
604
609
  return;
605
610
  }
@@ -619,8 +624,8 @@ var slack_exports = {};
619
624
  __export(slack_exports, {
620
625
  SlackAdapter: () => SlackAdapter
621
626
  });
622
- import fs11 from "fs";
623
- import path10 from "path";
627
+ import fs12 from "fs";
628
+ import path11 from "path";
624
629
  import { App, LogLevel } from "@slack/bolt";
625
630
  var INLINE_COMMANDS, SLASH_COMMANDS, MAX_MESSAGE_LENGTH2, ACK_REACTION2, SlackAdapter;
626
631
  var init_slack = __esm({
@@ -830,6 +835,7 @@ var init_slack = __esm({
830
835
  };
831
836
  try {
832
837
  const result = await this.agent.handleMessage(
838
+ "slack",
833
839
  channelId,
834
840
  text,
835
841
  onEvent,
@@ -1097,12 +1103,12 @@ var init_slack = __esm({
1097
1103
  */
1098
1104
  async sendFileSafe(client, route, filePath, caption) {
1099
1105
  try {
1100
- if (!fs11.existsSync(filePath)) {
1106
+ if (!fs12.existsSync(filePath)) {
1101
1107
  console.error(`[Slack] File not found for sending: ${filePath}`);
1102
1108
  return;
1103
1109
  }
1104
- const filename = path10.basename(filePath);
1105
- const fileContent = fs11.readFileSync(filePath);
1110
+ const filename = path11.basename(filePath);
1111
+ const fileContent = fs12.readFileSync(filePath);
1106
1112
  await client.files.uploadV2({
1107
1113
  channel_id: route.channel,
1108
1114
  thread_ts: route.threadTs,
@@ -1274,6 +1280,7 @@ var init_scheduler = __esm({
1274
1280
  };
1275
1281
  try {
1276
1282
  const result = await this.agent.handleMessage(
1283
+ "scheduler",
1277
1284
  channelId,
1278
1285
  jobConfig.prompt,
1279
1286
  onEvent
@@ -2036,16 +2043,16 @@ async function interactiveCreate(workDir) {
2036
2043
  }
2037
2044
 
2038
2045
  // src/commands/run.ts
2039
- import path12 from "path";
2040
- import fs13 from "fs";
2046
+ import path13 from "path";
2047
+ import fs14 from "fs";
2041
2048
  import inquirer2 from "inquirer";
2042
2049
  import chalk4 from "chalk";
2043
2050
 
2044
2051
  // src/runtime/server.ts
2045
2052
  import express from "express";
2046
- import path11 from "path";
2047
- import fs12 from "fs";
2048
- import { fileURLToPath } from "url";
2053
+ import path12 from "path";
2054
+ import fs13 from "fs";
2055
+ import { fileURLToPath as fileURLToPath2 } from "url";
2049
2056
  import { createServer } from "http";
2050
2057
  import { exec } from "child_process";
2051
2058
 
@@ -2053,6 +2060,7 @@ import { exec } from "child_process";
2053
2060
  init_attachment_utils();
2054
2061
  import path7 from "path";
2055
2062
  import fs7 from "fs";
2063
+ import { fileURLToPath } from "url";
2056
2064
  import {
2057
2065
  AuthStorage,
2058
2066
  createAgentSession,
@@ -4785,16 +4793,6 @@ var ManageScheduleParams = Type.Object({
4785
4793
  description: "The work prompt to execute when the task triggers. Required for add. Describe only what to do each run; do not repeat timing, cron, or 'every N minutes' instructions here."
4786
4794
  })
4787
4795
  ),
4788
- notifyAdapter: Type.Optional(
4789
- Type.String({
4790
- description: "Target adapter name for result notification: 'telegram' or 'slack'. Required for add."
4791
- })
4792
- ),
4793
- notifyChannelId: Type.Optional(
4794
- Type.String({
4795
- description: "Target channelId for result notification (e.g. 'telegram-123456'). Required for add."
4796
- })
4797
- ),
4798
4796
  timezone: Type.Optional(
4799
4797
  Type.String({
4800
4798
  description: "Optional timezone for the cron schedule, e.g. 'Asia/Shanghai', 'America/New_York'."
@@ -4804,7 +4802,7 @@ var ManageScheduleParams = Type.Object({
4804
4802
  function textResult(text) {
4805
4803
  return { content: [{ type: "text", text }], details: void 0 };
4806
4804
  }
4807
- function createManageScheduleTool(schedulerRef, _rootDirRef) {
4805
+ function createManageScheduleTool(schedulerRef, adapter, channelId) {
4808
4806
  return {
4809
4807
  name: "manage_scheduled_task",
4810
4808
  label: "Manage Scheduled Task",
@@ -4812,7 +4810,7 @@ function createManageScheduleTool(schedulerRef, _rootDirRef) {
4812
4810
  "Manage scheduled tasks (cron jobs) that automatically execute prompts and push results to IM channels.",
4813
4811
  "",
4814
4812
  "Actions:",
4815
- "- add: Create a new scheduled task. Requires: name, cron, prompt, notifyAdapter, notifyChannelId. The prompt must describe only the work for each run, not the schedule itself.",
4813
+ "- add: Create a new scheduled task. Requires: name, cron, prompt. The notification target always uses the current Telegram or Slack chat. The prompt must describe only the work for each run, not the schedule itself.",
4816
4814
  "- list: List all scheduled tasks with their status.",
4817
4815
  "- remove: Remove a scheduled task by name.",
4818
4816
  "- trigger: Manually trigger a scheduled task by name (runs immediately).",
@@ -4823,10 +4821,7 @@ function createManageScheduleTool(schedulerRef, _rootDirRef) {
4823
4821
  "Examples:",
4824
4822
  " '0 9 * * 1-5' = every weekday at 9:00 AM",
4825
4823
  " '0 18 * * 5' = every Friday at 6:00 PM",
4826
- " '*/30 * * * *' = every 30 minutes",
4827
- "",
4828
- "notifyAdapter: 'telegram' or 'slack'",
4829
- "notifyChannelId: the channel ID where result will be sent (e.g. 'telegram-123456')"
4824
+ " '*/30 * * * *' = every 30 minutes"
4830
4825
  ].join("\n"),
4831
4826
  parameters: ManageScheduleParams,
4832
4827
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
@@ -4856,9 +4851,9 @@ ${lines.join("\n")}`
4856
4851
  "Error: 'name', 'cron', and 'prompt' are required for adding a task."
4857
4852
  );
4858
4853
  }
4859
- if (!params.notifyAdapter || !params.notifyChannelId) {
4854
+ if (adapter !== "telegram" && adapter !== "slack") {
4860
4855
  return textResult(
4861
- "Error: 'notifyAdapter' and 'notifyChannelId' are required for adding a task."
4856
+ "Error: Scheduled tasks can only be created from a Telegram or Slack."
4862
4857
  );
4863
4858
  }
4864
4859
  const jobConfig = {
@@ -4866,8 +4861,8 @@ ${lines.join("\n")}`
4866
4861
  cron: params.cron,
4867
4862
  prompt: params.prompt,
4868
4863
  notify: {
4869
- adapter: params.notifyAdapter,
4870
- channelId: params.notifyChannelId
4864
+ adapter,
4865
+ channelId
4871
4866
  },
4872
4867
  enabled: true,
4873
4868
  timezone: params.timezone
@@ -4924,6 +4919,75 @@ ${lines.join("\n")}`
4924
4919
  var DEBUG = true;
4925
4920
  var log = (...args) => DEBUG && console.log(...args);
4926
4921
  var write = (data) => DEBUG && process.stdout.write(data);
4922
+ var BUILTIN_SKILL_CREATOR_NAME = "skill-creator";
4923
+ var BUILTIN_SKILL_CREATOR_DESCRIPTION = "Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.";
4924
+ var BUILTIN_SKILL_CREATOR_TEMPLATE_DIR = fileURLToPath(
4925
+ new URL("../templates/builtin-skills/skill-creator", import.meta.url)
4926
+ );
4927
+ function materializeBuiltinSkillCreator(rootDir, skillsPath) {
4928
+ if (!fs7.existsSync(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR)) {
4929
+ log(
4930
+ `[PackAgent] Built-in skill-creator template missing: ${BUILTIN_SKILL_CREATOR_TEMPLATE_DIR}`
4931
+ );
4932
+ return null;
4933
+ }
4934
+ const packConfigPath = path7.resolve(rootDir, "skillpack.json");
4935
+ const skillDir = path7.resolve(skillsPath, BUILTIN_SKILL_CREATOR_NAME);
4936
+ const skillPath = path7.join(skillDir, "SKILL.md");
4937
+ const renderTemplate = (content) => content.replaceAll("{{SKILLS_PATH}}", skillsPath).replaceAll("{{PACK_CONFIG_PATH}}", packConfigPath);
4938
+ const copyDir = (srcDir, destDir) => {
4939
+ fs7.mkdirSync(destDir, { recursive: true });
4940
+ for (const entry of fs7.readdirSync(srcDir, { withFileTypes: true })) {
4941
+ if (entry.name === ".DS_Store") {
4942
+ continue;
4943
+ }
4944
+ const srcPath = path7.join(srcDir, entry.name);
4945
+ const destPath = path7.join(destDir, entry.name);
4946
+ if (entry.isDirectory()) {
4947
+ copyDir(srcPath, destPath);
4948
+ continue;
4949
+ }
4950
+ if (!entry.isFile()) {
4951
+ continue;
4952
+ }
4953
+ if (entry.name.endsWith(".md") || entry.name.endsWith(".py")) {
4954
+ const content = fs7.readFileSync(srcPath, "utf-8");
4955
+ fs7.writeFileSync(destPath, renderTemplate(content), "utf-8");
4956
+ continue;
4957
+ }
4958
+ fs7.copyFileSync(srcPath, destPath);
4959
+ }
4960
+ };
4961
+ if (!fs7.existsSync(skillDir)) {
4962
+ copyDir(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR, skillDir);
4963
+ }
4964
+ if (!fs7.existsSync(skillPath)) {
4965
+ log(
4966
+ `[PackAgent] Materialized built-in skill-creator but SKILL.md is missing: ${skillPath}`
4967
+ );
4968
+ return null;
4969
+ }
4970
+ return {
4971
+ name: BUILTIN_SKILL_CREATOR_NAME,
4972
+ description: BUILTIN_SKILL_CREATOR_DESCRIPTION,
4973
+ filePath: skillPath,
4974
+ baseDir: skillDir,
4975
+ source: "path",
4976
+ disableModelInvocation: false
4977
+ };
4978
+ }
4979
+ function overrideBuiltinSkillCreator(base, materializedSkill) {
4980
+ if (!materializedSkill) {
4981
+ return base;
4982
+ }
4983
+ const filtered = base.skills.filter(
4984
+ (skill) => skill.name !== BUILTIN_SKILL_CREATOR_NAME
4985
+ );
4986
+ return {
4987
+ skills: [materializedSkill, ...filtered],
4988
+ diagnostics: base.diagnostics
4989
+ };
4990
+ }
4927
4991
  function getAssistantDiagnostics(message) {
4928
4992
  if (!message || message.role !== "assistant") {
4929
4993
  return null;
@@ -4949,17 +5013,9 @@ var PackAgent = class {
4949
5013
  fileOutputCallbackRef = {
4950
5014
  current: null
4951
5015
  };
4952
- sendFileToolDef = createSendFileTool(this.fileOutputCallbackRef);
4953
5016
  schedulerRef = { current: null };
4954
- rootDirRef;
4955
- scheduleToolDef;
4956
5017
  constructor(options) {
4957
5018
  this.options = options;
4958
- this.rootDirRef = { current: options.rootDir };
4959
- this.scheduleToolDef = createManageScheduleTool(
4960
- this.schedulerRef,
4961
- this.rootDirRef
4962
- );
4963
5019
  }
4964
5020
  /**
4965
5021
  * Inject scheduler reference (called by server.ts after adapter init).
@@ -4967,22 +5023,33 @@ var PackAgent = class {
4967
5023
  setScheduler(scheduler) {
4968
5024
  this.schedulerRef.current = scheduler;
4969
5025
  }
5026
+ createCustomTools(adapter, channelId) {
5027
+ const tools = [createSendFileTool(this.fileOutputCallbackRef)];
5028
+ if (adapter === "telegram" || adapter === "slack") {
5029
+ tools.push(createManageScheduleTool(this.schedulerRef, adapter, channelId));
5030
+ }
5031
+ return tools;
5032
+ }
4970
5033
  /**
4971
5034
  * Lazily create (or return existing) session for a channel.
4972
5035
  */
4973
- async getOrCreateSession(channelId) {
5036
+ async getOrCreateSession(adapter, channelId) {
4974
5037
  const existing = this.channels.get(channelId);
4975
5038
  if (existing) return existing;
4976
5039
  const pendingCreation = this.pendingSessionCreations.get(channelId);
4977
5040
  if (pendingCreation) return pendingCreation;
4978
5041
  const createSessionPromise = (async () => {
4979
- const { apiKey, rootDir, provider, modelId } = this.options;
5042
+ const { apiKey, rootDir, provider, modelId, baseUrl } = this.options;
4980
5043
  const authStorage = AuthStorage.inMemory({
4981
5044
  [provider]: { type: "api_key", key: apiKey }
4982
5045
  });
4983
5046
  authStorage.setRuntimeApiKey(provider, apiKey);
4984
5047
  const modelRegistry = new ModelRegistry(authStorage);
4985
- const model = modelRegistry.find(provider, modelId);
5048
+ const resolvedModel = modelRegistry.find(provider, modelId);
5049
+ const model = resolvedModel && baseUrl ? { ...resolvedModel, baseUrl } : resolvedModel;
5050
+ if (resolvedModel && baseUrl) {
5051
+ log(`[PackAgent] Overriding ${provider}/${modelId} baseUrl -> ${baseUrl}`);
5052
+ }
4986
5053
  const sessionDir = path7.resolve(
4987
5054
  rootDir,
4988
5055
  "data",
@@ -5002,12 +5069,23 @@ var PackAgent = class {
5002
5069
  log(`[PackAgent] Workspace dir: ${workspaceDir}`);
5003
5070
  const skillsPath = path7.resolve(rootDir, "skills");
5004
5071
  log(`[PackAgent] Loading skills from: ${skillsPath}`);
5072
+ const materializedSkillCreator = materializeBuiltinSkillCreator(
5073
+ rootDir,
5074
+ skillsPath
5075
+ );
5076
+ if (materializedSkillCreator) {
5077
+ log(
5078
+ `[PackAgent] Materialized built-in skill-creator to: ${materializedSkillCreator.filePath}`
5079
+ );
5080
+ }
5005
5081
  const resourceLoader = new DefaultResourceLoader({
5006
5082
  cwd: rootDir,
5007
- additionalSkillPaths: [skillsPath]
5083
+ additionalSkillPaths: [skillsPath],
5084
+ skillsOverride: (base) => overrideBuiltinSkillCreator(base, materializedSkillCreator)
5008
5085
  });
5009
5086
  await resourceLoader.reload();
5010
5087
  const tools = createCodingTools(workspaceDir);
5088
+ const customTools = this.createCustomTools(adapter, channelId);
5011
5089
  const { session } = await createAgentSession({
5012
5090
  cwd: workspaceDir,
5013
5091
  authStorage,
@@ -5016,7 +5094,7 @@ var PackAgent = class {
5016
5094
  resourceLoader,
5017
5095
  model,
5018
5096
  tools,
5019
- customTools: [this.sendFileToolDef, this.scheduleToolDef]
5097
+ customTools
5020
5098
  });
5021
5099
  const channelSession = {
5022
5100
  session,
@@ -5033,8 +5111,8 @@ var PackAgent = class {
5033
5111
  this.pendingSessionCreations.delete(channelId);
5034
5112
  }
5035
5113
  }
5036
- async handleMessage(channelId, text, onEvent, attachments) {
5037
- const cs = await this.getOrCreateSession(channelId);
5114
+ async handleMessage(adapter, channelId, text, onEvent, attachments) {
5115
+ const cs = await this.getOrCreateSession(adapter, channelId);
5038
5116
  const run = async () => {
5039
5117
  cs.running = true;
5040
5118
  let turnHadVisibleOutput = false;
@@ -5243,6 +5321,7 @@ function getRuntimeConfigSignature(config) {
5243
5321
  return JSON.stringify({
5244
5322
  apiKey: config.apiKey || "",
5245
5323
  provider: config.provider || "openai",
5324
+ baseUrl: config.baseUrl || "",
5246
5325
  telegramToken: config.adapters?.telegram?.token || "",
5247
5326
  slackBotToken: config.adapters?.slack?.botToken || "",
5248
5327
  slackAppToken: config.adapters?.slack?.appToken || ""
@@ -5269,8 +5348,8 @@ var WebAdapter = class {
5269
5348
  hasApiKey: !!conf.apiKey,
5270
5349
  apiKey: conf.apiKey || "",
5271
5350
  provider: conf.provider || "openai",
5272
- adapters: conf.adapters || {},
5273
- runtimeControl: lifecycle.getRuntimeControl()
5351
+ baseUrl: conf.baseUrl || "",
5352
+ adapters: conf.adapters || {}
5274
5353
  });
5275
5354
  });
5276
5355
  app.get("/api/skills", (_req, res) => {
@@ -5278,7 +5357,7 @@ var WebAdapter = class {
5278
5357
  res.json(config.skills || []);
5279
5358
  });
5280
5359
  app.post("/api/config/update", (req, res) => {
5281
- const { key, provider, adapters } = req.body;
5360
+ const { key, provider, baseUrl, adapters } = req.body;
5282
5361
  const updates = {};
5283
5362
  const beforeConfig = JSON.parse(JSON.stringify(configManager.getConfig()));
5284
5363
  if (key !== void 0) {
@@ -5289,6 +5368,9 @@ var WebAdapter = class {
5289
5368
  updates.provider = provider;
5290
5369
  currentProvider = provider;
5291
5370
  }
5371
+ if (baseUrl !== void 0) {
5372
+ updates.baseUrl = baseUrl;
5373
+ }
5292
5374
  if (adapters !== void 0) {
5293
5375
  updates.adapters = adapters;
5294
5376
  }
@@ -5298,23 +5380,14 @@ var WebAdapter = class {
5298
5380
  res.json({
5299
5381
  success: true,
5300
5382
  provider: newConf.provider,
5383
+ baseUrl: newConf.baseUrl || "",
5301
5384
  adapters: newConf.adapters,
5302
- requiresRestart,
5303
- runtimeControl: lifecycle.getRuntimeControl()
5385
+ requiresRestart
5304
5386
  });
5305
5387
  });
5306
5388
  app.post("/api/runtime/restart", async (_req, res) => {
5307
- const runtimeControl = lifecycle.getRuntimeControl();
5308
- if (!runtimeControl.canManagedRestart) {
5309
- res.status(409).json({
5310
- success: false,
5311
- message: "Managed restart is unavailable for this process.",
5312
- runtimeControl
5313
- });
5314
- return;
5315
- }
5316
5389
  const result = await lifecycle.requestRestart("web");
5317
- res.status(202).json({ ...result, runtimeControl });
5390
+ res.status(202).json(result);
5318
5391
  });
5319
5392
  app.delete("/api/chat", (_req, res) => {
5320
5393
  res.json({ success: true });
@@ -5483,7 +5556,7 @@ var WebAdapter = class {
5483
5556
  if (ws.readyState !== ws.OPEN) return;
5484
5557
  ws.send(JSON.stringify(event));
5485
5558
  };
5486
- const result = await agent.handleMessage(channelId, text, onEvent);
5559
+ const result = await agent.handleMessage("web", channelId, text, onEvent);
5487
5560
  if (result.errorMessage) {
5488
5561
  ws.send(JSON.stringify({ error: result.errorMessage }));
5489
5562
  return;
@@ -5506,29 +5579,18 @@ init_config();
5506
5579
  var SHUTDOWN_EXIT_CODE = 64;
5507
5580
  var RESTART_EXIT_CODE = 75;
5508
5581
  var STOP_TIMEOUT_MS = 3e3;
5509
- function detectProcessManager() {
5510
- return process.env.PACK_ROOT ? "wrapper" : "none";
5511
- }
5512
5582
  var Lifecycle = class {
5513
5583
  server;
5514
5584
  exitFn;
5515
- processManager;
5516
5585
  adapters = [];
5517
5586
  stopReason = null;
5518
5587
  constructor(server, exitFn = (code) => process.exit(code)) {
5519
5588
  this.server = server;
5520
5589
  this.exitFn = exitFn;
5521
- this.processManager = detectProcessManager();
5522
5590
  }
5523
5591
  registerAdapters(adapters) {
5524
5592
  this.adapters = adapters;
5525
5593
  }
5526
- getRuntimeControl() {
5527
- return {
5528
- canManagedRestart: this.processManager === "wrapper",
5529
- processManager: this.processManager
5530
- };
5531
- }
5532
5594
  async requestRestart(trigger) {
5533
5595
  return this.requestStop("restart", trigger);
5534
5596
  }
@@ -5584,31 +5646,205 @@ var Lifecycle = class {
5584
5646
  }
5585
5647
  };
5586
5648
 
5649
+ // src/runtime/registry.ts
5650
+ import crypto from "crypto";
5651
+ import fs10 from "fs";
5652
+ import os from "os";
5653
+ import path10 from "path";
5654
+ var SKILLPACK_HOME = path10.join(os.homedir(), ".skillpack");
5655
+ var LEGACY_REGISTRY_FILE = path10.join(SKILLPACK_HOME, "registry.json");
5656
+ var REGISTRY_DIR = path10.join(SKILLPACK_HOME, "registry.d");
5657
+ var migrationChecked = false;
5658
+ function ensureHomeDir() {
5659
+ if (!fs10.existsSync(SKILLPACK_HOME)) {
5660
+ fs10.mkdirSync(SKILLPACK_HOME, { recursive: true });
5661
+ }
5662
+ }
5663
+ function ensureRegistryDir() {
5664
+ ensureHomeDir();
5665
+ if (!fs10.existsSync(REGISTRY_DIR)) {
5666
+ fs10.mkdirSync(REGISTRY_DIR, { recursive: true });
5667
+ }
5668
+ }
5669
+ function canonicalizeDir(dir) {
5670
+ const resolved = path10.resolve(dir);
5671
+ try {
5672
+ return fs10.realpathSync(resolved);
5673
+ } catch {
5674
+ return resolved;
5675
+ }
5676
+ }
5677
+ function hashDir(dir) {
5678
+ return crypto.createHash("md5").update(canonicalizeDir(dir)).digest("hex");
5679
+ }
5680
+ function getEntryPathForCanonicalDir(dir) {
5681
+ return path10.join(REGISTRY_DIR, `${hashDir(dir)}.json`);
5682
+ }
5683
+ function getEntryPath(dir) {
5684
+ ensureRegistryReady();
5685
+ return getEntryPathForCanonicalDir(canonicalizeDir(dir));
5686
+ }
5687
+ function listEntryFiles() {
5688
+ ensureRegistryReady();
5689
+ return fs10.readdirSync(REGISTRY_DIR).filter((file) => file.endsWith(".json")).sort().map((file) => path10.join(REGISTRY_DIR, file));
5690
+ }
5691
+ function readEntryFile(filePath) {
5692
+ try {
5693
+ const raw = fs10.readFileSync(filePath, "utf-8");
5694
+ const data = JSON.parse(raw);
5695
+ 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") {
5696
+ return null;
5697
+ }
5698
+ return {
5699
+ dir: canonicalizeDir(data.dir),
5700
+ name: data.name,
5701
+ version: data.version,
5702
+ port: data.port,
5703
+ pid: data.pid,
5704
+ status: data.status,
5705
+ startedAt: data.startedAt,
5706
+ stoppedAt: data.stoppedAt,
5707
+ updatedAt: data.updatedAt
5708
+ };
5709
+ } catch {
5710
+ return null;
5711
+ }
5712
+ }
5713
+ function createTmpPath(entryPath) {
5714
+ const suffix = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
5715
+ return `${entryPath}.tmp.${suffix}`;
5716
+ }
5717
+ function writeEntryFile(entry) {
5718
+ ensureRegistryReady();
5719
+ const normalized = {
5720
+ ...entry,
5721
+ dir: canonicalizeDir(entry.dir),
5722
+ updatedAt: entry.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
5723
+ };
5724
+ const entryPath = getEntryPathForCanonicalDir(normalized.dir);
5725
+ const tmpPath = createTmpPath(entryPath);
5726
+ fs10.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), "utf-8");
5727
+ fs10.renameSync(tmpPath, entryPath);
5728
+ }
5729
+ function migrateLegacyRegistryIfNeeded() {
5730
+ if (migrationChecked) {
5731
+ return;
5732
+ }
5733
+ migrationChecked = true;
5734
+ ensureRegistryDir();
5735
+ if (!fs10.existsSync(LEGACY_REGISTRY_FILE)) {
5736
+ return;
5737
+ }
5738
+ if (listEntryFiles().length > 0) {
5739
+ return;
5740
+ }
5741
+ try {
5742
+ const raw = fs10.readFileSync(LEGACY_REGISTRY_FILE, "utf-8");
5743
+ const data = JSON.parse(raw);
5744
+ const packs = Array.isArray(data?.packs) ? data.packs : [];
5745
+ for (const pack of packs) {
5746
+ try {
5747
+ writeEntryFile({
5748
+ ...pack,
5749
+ dir: canonicalizeDir(pack.dir),
5750
+ updatedAt: pack.updatedAt ?? pack.stoppedAt ?? pack.startedAt ?? (/* @__PURE__ */ new Date()).toISOString()
5751
+ });
5752
+ } catch {
5753
+ }
5754
+ }
5755
+ fs10.renameSync(LEGACY_REGISTRY_FILE, `${LEGACY_REGISTRY_FILE}.legacy`);
5756
+ } catch (err) {
5757
+ console.warn(" [Registry] Failed to migrate legacy registry.json:", err);
5758
+ }
5759
+ }
5760
+ function ensureRegistryReady() {
5761
+ ensureRegistryDir();
5762
+ migrateLegacyRegistryIfNeeded();
5763
+ }
5764
+ function readEntry(dir) {
5765
+ ensureRegistryReady();
5766
+ return readEntryFile(getEntryPath(dir));
5767
+ }
5768
+ function register(opts) {
5769
+ try {
5770
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5771
+ const entry = {
5772
+ dir: canonicalizeDir(opts.dir),
5773
+ name: opts.name,
5774
+ version: opts.version,
5775
+ port: opts.port,
5776
+ pid: process.pid,
5777
+ status: "running",
5778
+ startedAt: now,
5779
+ updatedAt: now
5780
+ };
5781
+ writeEntryFile(entry);
5782
+ console.log(` [Registry] Registered "${opts.name}" (pid ${process.pid})`);
5783
+ } catch (err) {
5784
+ console.warn(" [Registry] Failed to register:", err);
5785
+ }
5786
+ }
5787
+ function deregister(dir, pid) {
5788
+ try {
5789
+ const entry = readEntry(dir);
5790
+ if (!entry || entry.pid !== pid) {
5791
+ return;
5792
+ }
5793
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5794
+ writeEntryFile({
5795
+ ...entry,
5796
+ pid: null,
5797
+ status: "stopped",
5798
+ stoppedAt: now,
5799
+ updatedAt: now
5800
+ });
5801
+ console.log(` [Registry] Deregistered "${entry.name}"`);
5802
+ } catch (err) {
5803
+ console.warn(" [Registry] Failed to deregister:", err);
5804
+ }
5805
+ }
5806
+
5587
5807
  // src/runtime/server.ts
5588
- var __dirname = path11.dirname(fileURLToPath(import.meta.url));
5808
+ var __dirname = path12.dirname(fileURLToPath2(import.meta.url));
5589
5809
  async function startServer(options) {
5590
5810
  const {
5591
5811
  rootDir,
5592
5812
  host = process.env.HOST || "127.0.0.1",
5593
5813
  port = Number(process.env.PORT) || 26313,
5594
- firstRun = true
5814
+ daemonRun = false
5595
5815
  } = options;
5596
5816
  const dataConfig = configManager.load(rootDir);
5597
5817
  const apiKey = dataConfig.apiKey || "";
5598
5818
  const provider = dataConfig.provider || "openai";
5819
+ const canonicalRootDir = canonicalizeDir(rootDir);
5820
+ const packConfig = loadConfig(canonicalRootDir);
5821
+ const baseUrl = dataConfig.baseUrl?.trim() || void 0;
5599
5822
  const modelId = provider === "anthropic" ? "claude-opus-4-6" : "gpt-5.4";
5600
- const packageRoot = path11.resolve(__dirname, "..");
5601
- const webDir = fs12.existsSync(path11.join(rootDir, "web")) ? path11.join(rootDir, "web") : path11.join(packageRoot, "web");
5823
+ const packageRoot = path12.resolve(__dirname, "..");
5824
+ const webDir = fs13.existsSync(path12.join(rootDir, "web")) ? path12.join(rootDir, "web") : path12.join(packageRoot, "web");
5602
5825
  const app = express();
5603
5826
  app.use(express.json());
5604
5827
  app.use(express.static(webDir));
5605
5828
  const server = createServer(app);
5829
+ app.get("/api/health", (_req, res) => {
5830
+ const address = server.address();
5831
+ const actualPort = typeof address === "string" ? port : address?.port ?? port;
5832
+ res.json({
5833
+ status: "ok",
5834
+ dir: canonicalRootDir,
5835
+ name: packConfig.name,
5836
+ version: packConfig.version,
5837
+ port: actualPort,
5838
+ pid: process.pid
5839
+ });
5840
+ });
5606
5841
  const lifecycle = new Lifecycle(server);
5607
5842
  const agent = new PackAgent({
5608
5843
  apiKey,
5609
5844
  rootDir,
5610
5845
  provider,
5611
5846
  modelId,
5847
+ baseUrl,
5612
5848
  lifecycleHandler: lifecycle
5613
5849
  });
5614
5850
  const adapters = [];
@@ -5696,7 +5932,17 @@ async function startServer(options) {
5696
5932
  Skills Pack Server`);
5697
5933
  console.log(` Running at ${url}
5698
5934
  `);
5699
- if (firstRun) {
5935
+ try {
5936
+ register({
5937
+ dir: canonicalRootDir,
5938
+ name: packConfig.name,
5939
+ version: packConfig.version,
5940
+ port: typeof actualPort === "number" ? actualPort : port
5941
+ });
5942
+ } catch (err) {
5943
+ console.warn(" [Registry] Could not register pack:", err);
5944
+ }
5945
+ if (!daemonRun) {
5700
5946
  const cmd = process.platform === "darwin" ? `open ${url}` : process.platform === "win32" ? `start ${url}` : `xdg-open ${url}`;
5701
5947
  exec(cmd, (err) => {
5702
5948
  if (err) console.warn(` Could not open browser: ${err.message}`);
@@ -5704,9 +5950,11 @@ async function startServer(options) {
5704
5950
  }
5705
5951
  });
5706
5952
  process.on("SIGINT", () => {
5953
+ deregister(canonicalRootDir, process.pid);
5707
5954
  void lifecycle.requestShutdown("signal");
5708
5955
  });
5709
5956
  process.on("SIGTERM", () => {
5957
+ deregister(canonicalRootDir, process.pid);
5710
5958
  void lifecycle.requestShutdown("signal");
5711
5959
  });
5712
5960
  await new Promise((resolve, reject) => {
@@ -5741,23 +5989,23 @@ function findMissingSkills(workDir, config) {
5741
5989
  });
5742
5990
  }
5743
5991
  function copyStartTemplates2(workDir) {
5744
- const templateDir = path12.resolve(
5992
+ const templateDir = path13.resolve(
5745
5993
  new URL("../templates", import.meta.url).pathname
5746
5994
  );
5747
5995
  for (const file of ["start.sh", "start.bat"]) {
5748
- const src = path12.join(templateDir, file);
5749
- const dest = path12.join(workDir, file);
5750
- if (fs13.existsSync(src)) {
5751
- fs13.copyFileSync(src, dest);
5996
+ const src = path13.join(templateDir, file);
5997
+ const dest = path13.join(workDir, file);
5998
+ if (fs14.existsSync(src)) {
5999
+ fs14.copyFileSync(src, dest);
5752
6000
  if (file === "start.sh") {
5753
- fs13.chmodSync(dest, 493);
6001
+ fs14.chmodSync(dest, 493);
5754
6002
  }
5755
6003
  }
5756
6004
  }
5757
6005
  }
5758
6006
  async function runCommand(directory) {
5759
- const workDir = directory ? path12.resolve(directory) : process.cwd();
5760
- fs13.mkdirSync(workDir, { recursive: true });
6007
+ const workDir = directory ? path13.resolve(directory) : process.cwd();
6008
+ fs14.mkdirSync(workDir, { recursive: true });
5761
6009
  if (!configExists(workDir)) {
5762
6010
  console.log(chalk4.blue("\n No skillpack.json found. Let's set one up.\n"));
5763
6011
  const { name, description } = await inquirer2.prompt([
@@ -5793,13 +6041,16 @@ async function runCommand(directory) {
5793
6041
  console.warn(chalk4.yellow(` Warning: Some skills could not be installed: ${err}`));
5794
6042
  }
5795
6043
  }
5796
- await startServer({ rootDir: workDir, firstRun: true });
6044
+ await startServer({
6045
+ rootDir: workDir,
6046
+ daemonRun: process.env.DAEMON_RUN === "1"
6047
+ });
5797
6048
  }
5798
6049
 
5799
6050
  // src/cli.ts
5800
- import fs14 from "fs";
6051
+ import fs15 from "fs";
5801
6052
  var packageJson = JSON.parse(
5802
- fs14.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
6053
+ fs15.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
5803
6054
  );
5804
6055
  var program = new Command();
5805
6056
  program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);