@cremini/skillpack 1.1.9 → 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.
package/dist/cli.js CHANGED
@@ -293,7 +293,7 @@ var telegram_exports = {};
293
293
  __export(telegram_exports, {
294
294
  TelegramAdapter: () => TelegramAdapter
295
295
  });
296
- import fs10 from "fs";
296
+ import fs11 from "fs";
297
297
  import TelegramBot from "node-telegram-bot-api";
298
298
  var COMMANDS2, MAX_MESSAGE_LENGTH, ACK_REACTION, TelegramAdapter;
299
299
  var init_telegram = __esm({
@@ -604,7 +604,7 @@ var init_telegram = __esm({
604
604
  async sendFileSafe(chatId, filePath, caption) {
605
605
  if (!this.bot) return;
606
606
  try {
607
- if (!fs10.existsSync(filePath)) {
607
+ if (!fs11.existsSync(filePath)) {
608
608
  console.error(`[Telegram] File not found for sending: ${filePath}`);
609
609
  return;
610
610
  }
@@ -624,8 +624,8 @@ var slack_exports = {};
624
624
  __export(slack_exports, {
625
625
  SlackAdapter: () => SlackAdapter
626
626
  });
627
- import fs11 from "fs";
628
- import path10 from "path";
627
+ import fs12 from "fs";
628
+ import path11 from "path";
629
629
  import { App, LogLevel } from "@slack/bolt";
630
630
  var INLINE_COMMANDS, SLASH_COMMANDS, MAX_MESSAGE_LENGTH2, ACK_REACTION2, SlackAdapter;
631
631
  var init_slack = __esm({
@@ -1103,12 +1103,12 @@ var init_slack = __esm({
1103
1103
  */
1104
1104
  async sendFileSafe(client, route, filePath, caption) {
1105
1105
  try {
1106
- if (!fs11.existsSync(filePath)) {
1106
+ if (!fs12.existsSync(filePath)) {
1107
1107
  console.error(`[Slack] File not found for sending: ${filePath}`);
1108
1108
  return;
1109
1109
  }
1110
- const filename = path10.basename(filePath);
1111
- const fileContent = fs11.readFileSync(filePath);
1110
+ const filename = path11.basename(filePath);
1111
+ const fileContent = fs12.readFileSync(filePath);
1112
1112
  await client.files.uploadV2({
1113
1113
  channel_id: route.channel,
1114
1114
  thread_ts: route.threadTs,
@@ -2043,15 +2043,15 @@ async function interactiveCreate(workDir) {
2043
2043
  }
2044
2044
 
2045
2045
  // src/commands/run.ts
2046
- import path12 from "path";
2047
- import fs13 from "fs";
2046
+ import path13 from "path";
2047
+ import fs14 from "fs";
2048
2048
  import inquirer2 from "inquirer";
2049
2049
  import chalk4 from "chalk";
2050
2050
 
2051
2051
  // src/runtime/server.ts
2052
2052
  import express from "express";
2053
- import path11 from "path";
2054
- import fs12 from "fs";
2053
+ import path12 from "path";
2054
+ import fs13 from "fs";
2055
2055
  import { fileURLToPath as fileURLToPath2 } from "url";
2056
2056
  import { createServer } from "http";
2057
2057
  import { exec } from "child_process";
@@ -5349,8 +5349,7 @@ var WebAdapter = class {
5349
5349
  apiKey: conf.apiKey || "",
5350
5350
  provider: conf.provider || "openai",
5351
5351
  baseUrl: conf.baseUrl || "",
5352
- adapters: conf.adapters || {},
5353
- runtimeControl: lifecycle.getRuntimeControl()
5352
+ adapters: conf.adapters || {}
5354
5353
  });
5355
5354
  });
5356
5355
  app.get("/api/skills", (_req, res) => {
@@ -5383,22 +5382,12 @@ var WebAdapter = class {
5383
5382
  provider: newConf.provider,
5384
5383
  baseUrl: newConf.baseUrl || "",
5385
5384
  adapters: newConf.adapters,
5386
- requiresRestart,
5387
- runtimeControl: lifecycle.getRuntimeControl()
5385
+ requiresRestart
5388
5386
  });
5389
5387
  });
5390
5388
  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
- }
5400
5389
  const result = await lifecycle.requestRestart("web");
5401
- res.status(202).json({ ...result, runtimeControl });
5390
+ res.status(202).json(result);
5402
5391
  });
5403
5392
  app.delete("/api/chat", (_req, res) => {
5404
5393
  res.json({ success: true });
@@ -5590,29 +5579,18 @@ init_config();
5590
5579
  var SHUTDOWN_EXIT_CODE = 64;
5591
5580
  var RESTART_EXIT_CODE = 75;
5592
5581
  var STOP_TIMEOUT_MS = 3e3;
5593
- function detectProcessManager() {
5594
- return process.env.PACK_ROOT ? "wrapper" : "none";
5595
- }
5596
5582
  var Lifecycle = class {
5597
5583
  server;
5598
5584
  exitFn;
5599
- processManager;
5600
5585
  adapters = [];
5601
5586
  stopReason = null;
5602
5587
  constructor(server, exitFn = (code) => process.exit(code)) {
5603
5588
  this.server = server;
5604
5589
  this.exitFn = exitFn;
5605
- this.processManager = detectProcessManager();
5606
5590
  }
5607
5591
  registerAdapters(adapters) {
5608
5592
  this.adapters = adapters;
5609
5593
  }
5610
- getRuntimeControl() {
5611
- return {
5612
- canManagedRestart: this.processManager === "wrapper",
5613
- processManager: this.processManager
5614
- };
5615
- }
5616
5594
  async requestRestart(trigger) {
5617
5595
  return this.requestStop("restart", trigger);
5618
5596
  }
@@ -5668,26 +5646,198 @@ var Lifecycle = class {
5668
5646
  }
5669
5647
  };
5670
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
+
5671
5807
  // src/runtime/server.ts
5672
- var __dirname = path11.dirname(fileURLToPath2(import.meta.url));
5808
+ var __dirname = path12.dirname(fileURLToPath2(import.meta.url));
5673
5809
  async function startServer(options) {
5674
5810
  const {
5675
5811
  rootDir,
5676
5812
  host = process.env.HOST || "127.0.0.1",
5677
5813
  port = Number(process.env.PORT) || 26313,
5678
- firstRun = true
5814
+ daemonRun = false
5679
5815
  } = options;
5680
5816
  const dataConfig = configManager.load(rootDir);
5681
5817
  const apiKey = dataConfig.apiKey || "";
5682
5818
  const provider = dataConfig.provider || "openai";
5819
+ const canonicalRootDir = canonicalizeDir(rootDir);
5820
+ const packConfig = loadConfig(canonicalRootDir);
5683
5821
  const baseUrl = dataConfig.baseUrl?.trim() || void 0;
5684
5822
  const modelId = provider === "anthropic" ? "claude-opus-4-6" : "gpt-5.4";
5685
- const packageRoot = path11.resolve(__dirname, "..");
5686
- 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");
5687
5825
  const app = express();
5688
5826
  app.use(express.json());
5689
5827
  app.use(express.static(webDir));
5690
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
+ });
5691
5841
  const lifecycle = new Lifecycle(server);
5692
5842
  const agent = new PackAgent({
5693
5843
  apiKey,
@@ -5782,7 +5932,17 @@ async function startServer(options) {
5782
5932
  Skills Pack Server`);
5783
5933
  console.log(` Running at ${url}
5784
5934
  `);
5785
- 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) {
5786
5946
  const cmd = process.platform === "darwin" ? `open ${url}` : process.platform === "win32" ? `start ${url}` : `xdg-open ${url}`;
5787
5947
  exec(cmd, (err) => {
5788
5948
  if (err) console.warn(` Could not open browser: ${err.message}`);
@@ -5790,9 +5950,11 @@ async function startServer(options) {
5790
5950
  }
5791
5951
  });
5792
5952
  process.on("SIGINT", () => {
5953
+ deregister(canonicalRootDir, process.pid);
5793
5954
  void lifecycle.requestShutdown("signal");
5794
5955
  });
5795
5956
  process.on("SIGTERM", () => {
5957
+ deregister(canonicalRootDir, process.pid);
5796
5958
  void lifecycle.requestShutdown("signal");
5797
5959
  });
5798
5960
  await new Promise((resolve, reject) => {
@@ -5827,23 +5989,23 @@ function findMissingSkills(workDir, config) {
5827
5989
  });
5828
5990
  }
5829
5991
  function copyStartTemplates2(workDir) {
5830
- const templateDir = path12.resolve(
5992
+ const templateDir = path13.resolve(
5831
5993
  new URL("../templates", import.meta.url).pathname
5832
5994
  );
5833
5995
  for (const file of ["start.sh", "start.bat"]) {
5834
- const src = path12.join(templateDir, file);
5835
- const dest = path12.join(workDir, file);
5836
- if (fs13.existsSync(src)) {
5837
- 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);
5838
6000
  if (file === "start.sh") {
5839
- fs13.chmodSync(dest, 493);
6001
+ fs14.chmodSync(dest, 493);
5840
6002
  }
5841
6003
  }
5842
6004
  }
5843
6005
  }
5844
6006
  async function runCommand(directory) {
5845
- const workDir = directory ? path12.resolve(directory) : process.cwd();
5846
- fs13.mkdirSync(workDir, { recursive: true });
6007
+ const workDir = directory ? path13.resolve(directory) : process.cwd();
6008
+ fs14.mkdirSync(workDir, { recursive: true });
5847
6009
  if (!configExists(workDir)) {
5848
6010
  console.log(chalk4.blue("\n No skillpack.json found. Let's set one up.\n"));
5849
6011
  const { name, description } = await inquirer2.prompt([
@@ -5879,13 +6041,16 @@ async function runCommand(directory) {
5879
6041
  console.warn(chalk4.yellow(` Warning: Some skills could not be installed: ${err}`));
5880
6042
  }
5881
6043
  }
5882
- await startServer({ rootDir: workDir, firstRun: true });
6044
+ await startServer({
6045
+ rootDir: workDir,
6046
+ daemonRun: process.env.DAEMON_RUN === "1"
6047
+ });
5883
6048
  }
5884
6049
 
5885
6050
  // src/cli.ts
5886
- import fs14 from "fs";
6051
+ import fs15 from "fs";
5887
6052
  var packageJson = JSON.parse(
5888
- fs14.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
6053
+ fs15.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
5889
6054
  );
5890
6055
  var program = new Command();
5891
6056
  program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/runtime/registry.ts
4
+ import crypto from "crypto";
5
+ import fs from "fs";
6
+ import os from "os";
7
+ import path from "path";
8
+ var SKILLPACK_HOME = path.join(os.homedir(), ".skillpack");
9
+ var LEGACY_REGISTRY_FILE = path.join(SKILLPACK_HOME, "registry.json");
10
+ var REGISTRY_DIR = path.join(SKILLPACK_HOME, "registry.d");
11
+ var migrationChecked = false;
12
+ function getRegistryPath() {
13
+ ensureRegistryReady();
14
+ return REGISTRY_DIR;
15
+ }
16
+ function ensureHomeDir() {
17
+ if (!fs.existsSync(SKILLPACK_HOME)) {
18
+ fs.mkdirSync(SKILLPACK_HOME, { recursive: true });
19
+ }
20
+ }
21
+ function ensureRegistryDir() {
22
+ ensureHomeDir();
23
+ if (!fs.existsSync(REGISTRY_DIR)) {
24
+ fs.mkdirSync(REGISTRY_DIR, { recursive: true });
25
+ }
26
+ }
27
+ function canonicalizeDir(dir) {
28
+ const resolved = path.resolve(dir);
29
+ try {
30
+ return fs.realpathSync(resolved);
31
+ } catch {
32
+ return resolved;
33
+ }
34
+ }
35
+ function hashDir(dir) {
36
+ return crypto.createHash("md5").update(canonicalizeDir(dir)).digest("hex");
37
+ }
38
+ function getEntryPathForCanonicalDir(dir) {
39
+ return path.join(REGISTRY_DIR, `${hashDir(dir)}.json`);
40
+ }
41
+ function getEntryPath(dir) {
42
+ ensureRegistryReady();
43
+ return getEntryPathForCanonicalDir(canonicalizeDir(dir));
44
+ }
45
+ function listEntryFiles() {
46
+ ensureRegistryReady();
47
+ return fs.readdirSync(REGISTRY_DIR).filter((file) => file.endsWith(".json")).sort().map((file) => path.join(REGISTRY_DIR, file));
48
+ }
49
+ function readEntryFile(filePath) {
50
+ try {
51
+ const raw = fs.readFileSync(filePath, "utf-8");
52
+ const data = JSON.parse(raw);
53
+ 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") {
54
+ return null;
55
+ }
56
+ return {
57
+ dir: canonicalizeDir(data.dir),
58
+ name: data.name,
59
+ version: data.version,
60
+ port: data.port,
61
+ pid: data.pid,
62
+ status: data.status,
63
+ startedAt: data.startedAt,
64
+ stoppedAt: data.stoppedAt,
65
+ updatedAt: data.updatedAt
66
+ };
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+ function createTmpPath(entryPath) {
72
+ const suffix = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
73
+ return `${entryPath}.tmp.${suffix}`;
74
+ }
75
+ function writeEntryFile(entry) {
76
+ ensureRegistryReady();
77
+ const normalized = {
78
+ ...entry,
79
+ dir: canonicalizeDir(entry.dir),
80
+ updatedAt: entry.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
81
+ };
82
+ const entryPath = getEntryPathForCanonicalDir(normalized.dir);
83
+ const tmpPath = createTmpPath(entryPath);
84
+ fs.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), "utf-8");
85
+ fs.renameSync(tmpPath, entryPath);
86
+ }
87
+ function migrateLegacyRegistryIfNeeded() {
88
+ if (migrationChecked) {
89
+ return;
90
+ }
91
+ migrationChecked = true;
92
+ ensureRegistryDir();
93
+ if (!fs.existsSync(LEGACY_REGISTRY_FILE)) {
94
+ return;
95
+ }
96
+ if (listEntryFiles().length > 0) {
97
+ return;
98
+ }
99
+ try {
100
+ const raw = fs.readFileSync(LEGACY_REGISTRY_FILE, "utf-8");
101
+ const data = JSON.parse(raw);
102
+ const packs = Array.isArray(data?.packs) ? data.packs : [];
103
+ for (const pack of packs) {
104
+ try {
105
+ writeEntryFile({
106
+ ...pack,
107
+ dir: canonicalizeDir(pack.dir),
108
+ updatedAt: pack.updatedAt ?? pack.stoppedAt ?? pack.startedAt ?? (/* @__PURE__ */ new Date()).toISOString()
109
+ });
110
+ } catch {
111
+ }
112
+ }
113
+ fs.renameSync(LEGACY_REGISTRY_FILE, `${LEGACY_REGISTRY_FILE}.legacy`);
114
+ } catch (err) {
115
+ console.warn(" [Registry] Failed to migrate legacy registry.json:", err);
116
+ }
117
+ }
118
+ function ensureRegistryReady() {
119
+ ensureRegistryDir();
120
+ migrateLegacyRegistryIfNeeded();
121
+ }
122
+ function entriesEqual(a, b) {
123
+ return a.dir === b.dir && a.name === b.name && a.version === b.version && a.port === b.port && a.pid === b.pid && a.status === b.status && a.startedAt === b.startedAt && a.stoppedAt === b.stoppedAt;
124
+ }
125
+ function readEntry(dir) {
126
+ ensureRegistryReady();
127
+ return readEntryFile(getEntryPath(dir));
128
+ }
129
+ function writeEntry(entry) {
130
+ writeEntryFile(entry);
131
+ }
132
+ function deleteEntry(dir) {
133
+ ensureRegistryReady();
134
+ const entryPath = getEntryPath(dir);
135
+ if (fs.existsSync(entryPath)) {
136
+ fs.unlinkSync(entryPath);
137
+ }
138
+ }
139
+ function readRegistry() {
140
+ return { packs: readAll() };
141
+ }
142
+ function writeRegistry(data) {
143
+ ensureRegistryReady();
144
+ const nextPaths = /* @__PURE__ */ new Set();
145
+ for (const pack of data.packs) {
146
+ const normalized = {
147
+ ...pack,
148
+ dir: canonicalizeDir(pack.dir),
149
+ updatedAt: pack.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
150
+ };
151
+ const entryPath = getEntryPathForCanonicalDir(normalized.dir);
152
+ nextPaths.add(entryPath);
153
+ writeEntryFile(normalized);
154
+ }
155
+ for (const existingPath of listEntryFiles()) {
156
+ if (!nextPaths.has(existingPath)) {
157
+ fs.unlinkSync(existingPath);
158
+ }
159
+ }
160
+ }
161
+ function register(opts) {
162
+ try {
163
+ const now = (/* @__PURE__ */ new Date()).toISOString();
164
+ const entry = {
165
+ dir: canonicalizeDir(opts.dir),
166
+ name: opts.name,
167
+ version: opts.version,
168
+ port: opts.port,
169
+ pid: process.pid,
170
+ status: "running",
171
+ startedAt: now,
172
+ updatedAt: now
173
+ };
174
+ writeEntryFile(entry);
175
+ console.log(` [Registry] Registered "${opts.name}" (pid ${process.pid})`);
176
+ } catch (err) {
177
+ console.warn(" [Registry] Failed to register:", err);
178
+ }
179
+ }
180
+ function deregister(dir, pid) {
181
+ try {
182
+ const entry = readEntry(dir);
183
+ if (!entry || entry.pid !== pid) {
184
+ return;
185
+ }
186
+ const now = (/* @__PURE__ */ new Date()).toISOString();
187
+ writeEntryFile({
188
+ ...entry,
189
+ pid: null,
190
+ status: "stopped",
191
+ stoppedAt: now,
192
+ updatedAt: now
193
+ });
194
+ console.log(` [Registry] Deregistered "${entry.name}"`);
195
+ } catch (err) {
196
+ console.warn(" [Registry] Failed to deregister:", err);
197
+ }
198
+ }
199
+ function readAll() {
200
+ return listEntryFiles().map((entryPath) => readEntryFile(entryPath)).filter((entry) => entry !== null);
201
+ }
202
+ function isPidAlive(pid) {
203
+ try {
204
+ process.kill(pid, 0);
205
+ return true;
206
+ } catch {
207
+ return false;
208
+ }
209
+ }
210
+ function validateEntries() {
211
+ const entries = readAll();
212
+ const now = (/* @__PURE__ */ new Date()).toISOString();
213
+ for (const entry of entries) {
214
+ if (entry.status === "running" && entry.pid !== null && !isPidAlive(entry.pid)) {
215
+ writeEntryFile({
216
+ ...entry,
217
+ pid: null,
218
+ status: "stopped",
219
+ stoppedAt: now,
220
+ updatedAt: now
221
+ });
222
+ }
223
+ }
224
+ const nextEntries = readAll();
225
+ if (entries.length === nextEntries.length && entries.every((entry, index) => entriesEqual(entry, nextEntries[index]))) {
226
+ return entries;
227
+ }
228
+ return nextEntries;
229
+ }
230
+ export {
231
+ canonicalizeDir,
232
+ deleteEntry,
233
+ deregister,
234
+ getEntryPath,
235
+ getRegistryPath,
236
+ isPidAlive,
237
+ readAll,
238
+ readEntry,
239
+ readRegistry,
240
+ register,
241
+ validateEntries,
242
+ writeEntry,
243
+ writeRegistry
244
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cremini/skillpack",
3
- "version": "1.1.9",
3
+ "version": "1.2.0",
4
4
  "description": "Pack AI Skills into Local Agents",
5
5
  "type": "module",
6
6
  "repository": {
@@ -65,4 +65,4 @@
65
65
  "tsup": "^8.5.1",
66
66
  "typescript": "^5.9.3"
67
67
  }
68
- }
68
+ }
@@ -136,19 +136,17 @@ async function handleSave() {
136
136
  apiKeyInput.value = "";
137
137
  }
138
138
 
139
- state.config.runtimeControl = res.runtimeControl;
139
+
140
140
  state.restartRequired = !!res.requiresRestart;
141
141
 
142
142
  updateApiKeyButton();
143
143
 
144
144
  if (res.requiresRestart) {
145
145
  setStatus(
146
- res.runtimeControl?.canManagedRestart
147
- ? "API key saved. Restart service to apply changes."
148
- : "API key saved. Restart the service manually to apply changes.",
146
+ "API key saved. Restart service to apply changes.",
149
147
  "warning",
150
148
  );
151
- updateRestartButton(!!res.runtimeControl?.canManagedRestart);
149
+ updateRestartButton(true);
152
150
  } else {
153
151
  setStatus("API key saved successfully", "success");
154
152
  // 延迟关闭让用户看到成功消息
@@ -101,14 +101,11 @@ function populateForm() {
101
101
 
102
102
  // Restart required status
103
103
  if (state.restartRequired) {
104
- const canRestart = config.runtimeControl?.canManagedRestart;
105
104
  setStatus(
106
- canRestart
107
- ? "Settings changed. Restart service to apply."
108
- : "Settings changed. Restart the service manually to apply.",
105
+ "Settings changed. Restart service to apply.",
109
106
  "warning",
110
107
  );
111
- updateRestartButton(canRestart);
108
+ updateRestartButton(true);
112
109
  } else {
113
110
  setStatus("", "");
114
111
  updateRestartButton(false);
@@ -139,17 +136,14 @@ async function handleSave() {
139
136
  const res = await saveConfigData(updates);
140
137
 
141
138
  state.config.adapters = res.adapters;
142
- state.config.runtimeControl = res.runtimeControl;
143
139
  state.restartRequired = !!res.requiresRestart;
144
140
 
145
141
  if (res.requiresRestart) {
146
142
  setStatus(
147
- res.runtimeControl?.canManagedRestart
148
- ? "Settings saved. Restart service to apply changes."
149
- : "Settings saved. Restart the service manually to apply changes.",
143
+ "Settings saved. Restart service to apply changes.",
150
144
  "warning",
151
145
  );
152
- updateRestartButton(!!res.runtimeControl?.canManagedRestart);
146
+ updateRestartButton(true);
153
147
  } else {
154
148
  close();
155
149
  }
@@ -72,9 +72,7 @@ function populateForm() {
72
72
 
73
73
  if (state.restartRequired) {
74
74
  setStatus(
75
- config.runtimeControl?.canManagedRestart
76
- ? "Settings saved. Restart service to apply changes."
77
- : "Settings saved. Restart the service manually to apply changes.",
75
+ "Settings saved. Restart service to apply changes.",
78
76
  "warning",
79
77
  );
80
78
  updateRestartButton(true);
@@ -139,7 +137,6 @@ async function handleSave() {
139
137
  // Update local config
140
138
  state.config.provider = res.provider;
141
139
  state.config.adapters = res.adapters;
142
- state.config.runtimeControl = res.runtimeControl;
143
140
  if (updates.key) {
144
141
  state.config.hasApiKey = true;
145
142
  state.config.apiKey = updates.key;
@@ -156,12 +153,10 @@ async function handleSave() {
156
153
 
157
154
  if (res.requiresRestart) {
158
155
  setStatus(
159
- res.runtimeControl.canManagedRestart
160
- ? "Settings saved. Restart service to apply changes."
161
- : "Settings saved. Restart the service manually to apply changes.",
156
+ "Settings saved. Restart service to apply changes.",
162
157
  "warning",
163
158
  );
164
- updateRestartButton(res.runtimeControl.canManagedRestart);
159
+ updateRestartButton(true);
165
160
  return;
166
161
  }
167
162