@cremini/skillpack 1.1.8-beta.1 → 1.1.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.
Files changed (26) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +177 -256
  3. package/package.json +2 -2
  4. package/templates/builtin-skills/skill-creator/LICENSE.txt +202 -0
  5. package/templates/builtin-skills/skill-creator/SKILL.md +171 -0
  6. package/templates/builtin-skills/skill-creator/agents/analyzer.md +274 -0
  7. package/templates/builtin-skills/skill-creator/agents/comparator.md +202 -0
  8. package/templates/builtin-skills/skill-creator/agents/grader.md +223 -0
  9. package/templates/builtin-skills/skill-creator/assets/eval_review.html +146 -0
  10. package/templates/builtin-skills/skill-creator/eval-viewer/generate_review.py +471 -0
  11. package/templates/builtin-skills/skill-creator/eval-viewer/viewer.html +1325 -0
  12. package/templates/builtin-skills/skill-creator/references/schemas.md +430 -0
  13. package/templates/builtin-skills/skill-creator/scripts/__init__.py +0 -0
  14. package/templates/builtin-skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  15. package/templates/builtin-skills/skill-creator/scripts/generate_report.py +326 -0
  16. package/templates/builtin-skills/skill-creator/scripts/improve_description.py +247 -0
  17. package/templates/builtin-skills/skill-creator/scripts/package_skill.py +136 -0
  18. package/templates/builtin-skills/skill-creator/scripts/quick_validate.py +103 -0
  19. package/templates/builtin-skills/skill-creator/scripts/run_eval.py +310 -0
  20. package/templates/builtin-skills/skill-creator/scripts/run_loop.py +328 -0
  21. package/templates/builtin-skills/skill-creator/scripts/utils.py +47 -0
  22. package/web/js/api-key-dialog.js +5 -3
  23. package/web/js/chat-apps-dialog.js +10 -4
  24. package/web/js/chat.js +8 -8
  25. package/web/js/settings.js +8 -3
  26. package/dist/runtime/registry.js +0 -244
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 fs11 from "fs";
296
+ import fs10 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 (!fs11.existsSync(filePath)) {
607
+ if (!fs10.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 fs12 from "fs";
623
- import path11 from "path";
627
+ import fs11 from "fs";
628
+ import path10 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 (!fs12.existsSync(filePath)) {
1106
+ if (!fs11.existsSync(filePath)) {
1101
1107
  console.error(`[Slack] File not found for sending: ${filePath}`);
1102
1108
  return;
1103
1109
  }
1104
- const filename = path11.basename(filePath);
1105
- const fileContent = fs12.readFileSync(filePath);
1110
+ const filename = path10.basename(filePath);
1111
+ const fileContent = fs11.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 path13 from "path";
2040
- import fs14 from "fs";
2046
+ import path12 from "path";
2047
+ import fs13 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 path12 from "path";
2047
- import fs13 from "fs";
2048
- import { fileURLToPath } from "url";
2053
+ import path11 from "path";
2054
+ import fs12 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,7 +5348,9 @@ var WebAdapter = class {
5269
5348
  hasApiKey: !!conf.apiKey,
5270
5349
  apiKey: conf.apiKey || "",
5271
5350
  provider: conf.provider || "openai",
5272
- adapters: conf.adapters || {}
5351
+ baseUrl: conf.baseUrl || "",
5352
+ adapters: conf.adapters || {},
5353
+ runtimeControl: lifecycle.getRuntimeControl()
5273
5354
  });
5274
5355
  });
5275
5356
  app.get("/api/skills", (_req, res) => {
@@ -5277,7 +5358,7 @@ var WebAdapter = class {
5277
5358
  res.json(config.skills || []);
5278
5359
  });
5279
5360
  app.post("/api/config/update", (req, res) => {
5280
- const { key, provider, adapters } = req.body;
5361
+ const { key, provider, baseUrl, adapters } = req.body;
5281
5362
  const updates = {};
5282
5363
  const beforeConfig = JSON.parse(JSON.stringify(configManager.getConfig()));
5283
5364
  if (key !== void 0) {
@@ -5288,6 +5369,9 @@ var WebAdapter = class {
5288
5369
  updates.provider = provider;
5289
5370
  currentProvider = provider;
5290
5371
  }
5372
+ if (baseUrl !== void 0) {
5373
+ updates.baseUrl = baseUrl;
5374
+ }
5291
5375
  if (adapters !== void 0) {
5292
5376
  updates.adapters = adapters;
5293
5377
  }
@@ -5297,13 +5381,24 @@ var WebAdapter = class {
5297
5381
  res.json({
5298
5382
  success: true,
5299
5383
  provider: newConf.provider,
5384
+ baseUrl: newConf.baseUrl || "",
5300
5385
  adapters: newConf.adapters,
5301
- requiresRestart
5386
+ requiresRestart,
5387
+ runtimeControl: lifecycle.getRuntimeControl()
5302
5388
  });
5303
5389
  });
5304
5390
  app.post("/api/runtime/restart", async (_req, res) => {
5391
+ const runtimeControl = lifecycle.getRuntimeControl();
5392
+ if (!runtimeControl.canManagedRestart) {
5393
+ res.status(409).json({
5394
+ success: false,
5395
+ message: "Managed restart is unavailable for this process.",
5396
+ runtimeControl
5397
+ });
5398
+ return;
5399
+ }
5305
5400
  const result = await lifecycle.requestRestart("web");
5306
- res.status(202).json(result);
5401
+ res.status(202).json({ ...result, runtimeControl });
5307
5402
  });
5308
5403
  app.delete("/api/chat", (_req, res) => {
5309
5404
  res.json({ success: true });
@@ -5472,7 +5567,7 @@ var WebAdapter = class {
5472
5567
  if (ws.readyState !== ws.OPEN) return;
5473
5568
  ws.send(JSON.stringify(event));
5474
5569
  };
5475
- const result = await agent.handleMessage(channelId, text, onEvent);
5570
+ const result = await agent.handleMessage("web", channelId, text, onEvent);
5476
5571
  if (result.errorMessage) {
5477
5572
  ws.send(JSON.stringify({ error: result.errorMessage }));
5478
5573
  return;
@@ -5495,18 +5590,29 @@ init_config();
5495
5590
  var SHUTDOWN_EXIT_CODE = 64;
5496
5591
  var RESTART_EXIT_CODE = 75;
5497
5592
  var STOP_TIMEOUT_MS = 3e3;
5593
+ function detectProcessManager() {
5594
+ return process.env.PACK_ROOT ? "wrapper" : "none";
5595
+ }
5498
5596
  var Lifecycle = class {
5499
5597
  server;
5500
5598
  exitFn;
5599
+ processManager;
5501
5600
  adapters = [];
5502
5601
  stopReason = null;
5503
5602
  constructor(server, exitFn = (code) => process.exit(code)) {
5504
5603
  this.server = server;
5505
5604
  this.exitFn = exitFn;
5605
+ this.processManager = detectProcessManager();
5506
5606
  }
5507
5607
  registerAdapters(adapters) {
5508
5608
  this.adapters = adapters;
5509
5609
  }
5610
+ getRuntimeControl() {
5611
+ return {
5612
+ canManagedRestart: this.processManager === "wrapper",
5613
+ processManager: this.processManager
5614
+ };
5615
+ }
5510
5616
  async requestRestart(trigger) {
5511
5617
  return this.requestStop("restart", trigger);
5512
5618
  }
@@ -5562,203 +5668,33 @@ var Lifecycle = class {
5562
5668
  }
5563
5669
  };
5564
5670
 
5565
- // src/runtime/registry.ts
5566
- import crypto from "crypto";
5567
- import fs10 from "fs";
5568
- import os from "os";
5569
- import path10 from "path";
5570
- var SKILLPACK_HOME = path10.join(os.homedir(), ".skillpack");
5571
- var LEGACY_REGISTRY_FILE = path10.join(SKILLPACK_HOME, "registry.json");
5572
- var REGISTRY_DIR = path10.join(SKILLPACK_HOME, "registry.d");
5573
- var migrationChecked = false;
5574
- function ensureHomeDir() {
5575
- if (!fs10.existsSync(SKILLPACK_HOME)) {
5576
- fs10.mkdirSync(SKILLPACK_HOME, { recursive: true });
5577
- }
5578
- }
5579
- function ensureRegistryDir() {
5580
- ensureHomeDir();
5581
- if (!fs10.existsSync(REGISTRY_DIR)) {
5582
- fs10.mkdirSync(REGISTRY_DIR, { recursive: true });
5583
- }
5584
- }
5585
- function canonicalizeDir(dir) {
5586
- const resolved = path10.resolve(dir);
5587
- try {
5588
- return fs10.realpathSync(resolved);
5589
- } catch {
5590
- return resolved;
5591
- }
5592
- }
5593
- function hashDir(dir) {
5594
- return crypto.createHash("md5").update(canonicalizeDir(dir)).digest("hex");
5595
- }
5596
- function getEntryPathForCanonicalDir(dir) {
5597
- return path10.join(REGISTRY_DIR, `${hashDir(dir)}.json`);
5598
- }
5599
- function getEntryPath(dir) {
5600
- ensureRegistryReady();
5601
- return getEntryPathForCanonicalDir(canonicalizeDir(dir));
5602
- }
5603
- function listEntryFiles() {
5604
- ensureRegistryReady();
5605
- return fs10.readdirSync(REGISTRY_DIR).filter((file) => file.endsWith(".json")).sort().map((file) => path10.join(REGISTRY_DIR, file));
5606
- }
5607
- function readEntryFile(filePath) {
5608
- try {
5609
- const raw = fs10.readFileSync(filePath, "utf-8");
5610
- const data = JSON.parse(raw);
5611
- 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") {
5612
- return null;
5613
- }
5614
- return {
5615
- dir: canonicalizeDir(data.dir),
5616
- name: data.name,
5617
- version: data.version,
5618
- port: data.port,
5619
- pid: data.pid,
5620
- status: data.status,
5621
- startedAt: data.startedAt,
5622
- stoppedAt: data.stoppedAt,
5623
- updatedAt: data.updatedAt
5624
- };
5625
- } catch {
5626
- return null;
5627
- }
5628
- }
5629
- function createTmpPath(entryPath) {
5630
- const suffix = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
5631
- return `${entryPath}.tmp.${suffix}`;
5632
- }
5633
- function writeEntryFile(entry) {
5634
- ensureRegistryReady();
5635
- const normalized = {
5636
- ...entry,
5637
- dir: canonicalizeDir(entry.dir),
5638
- updatedAt: entry.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
5639
- };
5640
- const entryPath = getEntryPathForCanonicalDir(normalized.dir);
5641
- const tmpPath = createTmpPath(entryPath);
5642
- fs10.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), "utf-8");
5643
- fs10.renameSync(tmpPath, entryPath);
5644
- }
5645
- function migrateLegacyRegistryIfNeeded() {
5646
- if (migrationChecked) {
5647
- return;
5648
- }
5649
- migrationChecked = true;
5650
- ensureRegistryDir();
5651
- if (!fs10.existsSync(LEGACY_REGISTRY_FILE)) {
5652
- return;
5653
- }
5654
- if (listEntryFiles().length > 0) {
5655
- return;
5656
- }
5657
- try {
5658
- const raw = fs10.readFileSync(LEGACY_REGISTRY_FILE, "utf-8");
5659
- const data = JSON.parse(raw);
5660
- const packs = Array.isArray(data?.packs) ? data.packs : [];
5661
- for (const pack of packs) {
5662
- try {
5663
- writeEntryFile({
5664
- ...pack,
5665
- dir: canonicalizeDir(pack.dir),
5666
- updatedAt: pack.updatedAt ?? pack.stoppedAt ?? pack.startedAt ?? (/* @__PURE__ */ new Date()).toISOString()
5667
- });
5668
- } catch {
5669
- }
5670
- }
5671
- fs10.renameSync(LEGACY_REGISTRY_FILE, `${LEGACY_REGISTRY_FILE}.legacy`);
5672
- } catch (err) {
5673
- console.warn(" [Registry] Failed to migrate legacy registry.json:", err);
5674
- }
5675
- }
5676
- function ensureRegistryReady() {
5677
- ensureRegistryDir();
5678
- migrateLegacyRegistryIfNeeded();
5679
- }
5680
- function readEntry(dir) {
5681
- ensureRegistryReady();
5682
- return readEntryFile(getEntryPath(dir));
5683
- }
5684
- function register(opts) {
5685
- try {
5686
- const now = (/* @__PURE__ */ new Date()).toISOString();
5687
- const entry = {
5688
- dir: canonicalizeDir(opts.dir),
5689
- name: opts.name,
5690
- version: opts.version,
5691
- port: opts.port,
5692
- pid: process.pid,
5693
- status: "running",
5694
- startedAt: now,
5695
- updatedAt: now
5696
- };
5697
- writeEntryFile(entry);
5698
- console.log(` [Registry] Registered "${opts.name}" (pid ${process.pid})`);
5699
- } catch (err) {
5700
- console.warn(" [Registry] Failed to register:", err);
5701
- }
5702
- }
5703
- function deregister(dir, pid) {
5704
- try {
5705
- const entry = readEntry(dir);
5706
- if (!entry || entry.pid !== pid) {
5707
- return;
5708
- }
5709
- const now = (/* @__PURE__ */ new Date()).toISOString();
5710
- writeEntryFile({
5711
- ...entry,
5712
- pid: null,
5713
- status: "stopped",
5714
- stoppedAt: now,
5715
- updatedAt: now
5716
- });
5717
- console.log(` [Registry] Deregistered "${entry.name}"`);
5718
- } catch (err) {
5719
- console.warn(" [Registry] Failed to deregister:", err);
5720
- }
5721
- }
5722
-
5723
5671
  // src/runtime/server.ts
5724
- var __dirname = path12.dirname(fileURLToPath(import.meta.url));
5672
+ var __dirname = path11.dirname(fileURLToPath2(import.meta.url));
5725
5673
  async function startServer(options) {
5726
5674
  const {
5727
5675
  rootDir,
5728
5676
  host = process.env.HOST || "127.0.0.1",
5729
5677
  port = Number(process.env.PORT) || 26313,
5730
- daemonRun = false
5678
+ firstRun = true
5731
5679
  } = options;
5732
5680
  const dataConfig = configManager.load(rootDir);
5733
5681
  const apiKey = dataConfig.apiKey || "";
5734
5682
  const provider = dataConfig.provider || "openai";
5735
- const canonicalRootDir = canonicalizeDir(rootDir);
5736
- const packConfig = loadConfig(canonicalRootDir);
5683
+ const baseUrl = dataConfig.baseUrl?.trim() || void 0;
5737
5684
  const modelId = provider === "anthropic" ? "claude-opus-4-6" : "gpt-5.4";
5738
- const packageRoot = path12.resolve(__dirname, "..");
5739
- const webDir = fs13.existsSync(path12.join(rootDir, "web")) ? path12.join(rootDir, "web") : path12.join(packageRoot, "web");
5685
+ const packageRoot = path11.resolve(__dirname, "..");
5686
+ const webDir = fs12.existsSync(path11.join(rootDir, "web")) ? path11.join(rootDir, "web") : path11.join(packageRoot, "web");
5740
5687
  const app = express();
5741
5688
  app.use(express.json());
5742
5689
  app.use(express.static(webDir));
5743
5690
  const server = createServer(app);
5744
- app.get("/api/health", (_req, res) => {
5745
- const address = server.address();
5746
- const actualPort = typeof address === "string" ? port : address?.port ?? port;
5747
- res.json({
5748
- status: "ok",
5749
- dir: canonicalRootDir,
5750
- name: packConfig.name,
5751
- version: packConfig.version,
5752
- port: actualPort,
5753
- pid: process.pid
5754
- });
5755
- });
5756
5691
  const lifecycle = new Lifecycle(server);
5757
5692
  const agent = new PackAgent({
5758
5693
  apiKey,
5759
5694
  rootDir,
5760
5695
  provider,
5761
5696
  modelId,
5697
+ baseUrl,
5762
5698
  lifecycleHandler: lifecycle
5763
5699
  });
5764
5700
  const adapters = [];
@@ -5846,17 +5782,7 @@ async function startServer(options) {
5846
5782
  Skills Pack Server`);
5847
5783
  console.log(` Running at ${url}
5848
5784
  `);
5849
- try {
5850
- register({
5851
- dir: canonicalRootDir,
5852
- name: packConfig.name,
5853
- version: packConfig.version,
5854
- port: typeof actualPort === "number" ? actualPort : port
5855
- });
5856
- } catch (err) {
5857
- console.warn(" [Registry] Could not register pack:", err);
5858
- }
5859
- if (!daemonRun) {
5785
+ if (firstRun) {
5860
5786
  const cmd = process.platform === "darwin" ? `open ${url}` : process.platform === "win32" ? `start ${url}` : `xdg-open ${url}`;
5861
5787
  exec(cmd, (err) => {
5862
5788
  if (err) console.warn(` Could not open browser: ${err.message}`);
@@ -5864,11 +5790,9 @@ async function startServer(options) {
5864
5790
  }
5865
5791
  });
5866
5792
  process.on("SIGINT", () => {
5867
- deregister(canonicalRootDir, process.pid);
5868
5793
  void lifecycle.requestShutdown("signal");
5869
5794
  });
5870
5795
  process.on("SIGTERM", () => {
5871
- deregister(canonicalRootDir, process.pid);
5872
5796
  void lifecycle.requestShutdown("signal");
5873
5797
  });
5874
5798
  await new Promise((resolve, reject) => {
@@ -5903,23 +5827,23 @@ function findMissingSkills(workDir, config) {
5903
5827
  });
5904
5828
  }
5905
5829
  function copyStartTemplates2(workDir) {
5906
- const templateDir = path13.resolve(
5830
+ const templateDir = path12.resolve(
5907
5831
  new URL("../templates", import.meta.url).pathname
5908
5832
  );
5909
5833
  for (const file of ["start.sh", "start.bat"]) {
5910
- const src = path13.join(templateDir, file);
5911
- const dest = path13.join(workDir, file);
5912
- if (fs14.existsSync(src)) {
5913
- fs14.copyFileSync(src, dest);
5834
+ const src = path12.join(templateDir, file);
5835
+ const dest = path12.join(workDir, file);
5836
+ if (fs13.existsSync(src)) {
5837
+ fs13.copyFileSync(src, dest);
5914
5838
  if (file === "start.sh") {
5915
- fs14.chmodSync(dest, 493);
5839
+ fs13.chmodSync(dest, 493);
5916
5840
  }
5917
5841
  }
5918
5842
  }
5919
5843
  }
5920
5844
  async function runCommand(directory) {
5921
- const workDir = directory ? path13.resolve(directory) : process.cwd();
5922
- fs14.mkdirSync(workDir, { recursive: true });
5845
+ const workDir = directory ? path12.resolve(directory) : process.cwd();
5846
+ fs13.mkdirSync(workDir, { recursive: true });
5923
5847
  if (!configExists(workDir)) {
5924
5848
  console.log(chalk4.blue("\n No skillpack.json found. Let's set one up.\n"));
5925
5849
  const { name, description } = await inquirer2.prompt([
@@ -5955,16 +5879,13 @@ async function runCommand(directory) {
5955
5879
  console.warn(chalk4.yellow(` Warning: Some skills could not be installed: ${err}`));
5956
5880
  }
5957
5881
  }
5958
- await startServer({
5959
- rootDir: workDir,
5960
- daemonRun: process.env.DAEMON_RUN === "1"
5961
- });
5882
+ await startServer({ rootDir: workDir, firstRun: true });
5962
5883
  }
5963
5884
 
5964
5885
  // src/cli.ts
5965
- import fs15 from "fs";
5886
+ import fs14 from "fs";
5966
5887
  var packageJson = JSON.parse(
5967
- fs15.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
5888
+ fs14.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
5968
5889
  );
5969
5890
  var program = new Command();
5970
5891
  program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);