@cremini/skillpack 1.1.9 → 1.2.1

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
@@ -9,89 +9,46 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // src/runtime/adapters/attachment-utils.ts
12
+ // src/runtime/config.ts
13
13
  import fs5 from "fs";
14
14
  import path5 from "path";
15
- import { pipeline } from "stream/promises";
16
- import { Readable } from "stream";
17
- function getAttachmentDir(rootDir, channelId) {
18
- return path5.resolve(rootDir, "data", "sessions", channelId, ATTACHMENTS_DIR);
19
- }
20
- function sanitizeFilename(name) {
21
- return name.replace(/[/\\:*?"<>|]/g, "_").replace(/\s+/g, "_");
22
- }
23
- async function downloadAndSaveAttachment(rootDir, channelId, url, filename, mimeType, headers) {
24
- const dir = getAttachmentDir(rootDir, channelId);
25
- fs5.mkdirSync(dir, { recursive: true });
26
- const ts = Date.now();
27
- const safeName = sanitizeFilename(filename);
28
- const storedName = `${ts}-${safeName}`;
29
- const fullPath = path5.join(dir, storedName);
30
- const response = await fetch(url, { headers });
31
- if (!response.ok) {
32
- throw new Error(
33
- `Failed to download attachment from ${url}: ${response.status} ${response.statusText}`
34
- );
35
- }
36
- const body = response.body;
37
- if (!body) {
38
- throw new Error(`Empty response body when downloading ${url}`);
39
- }
40
- const nodeStream = Readable.fromWeb(body);
41
- const writeStream = fs5.createWriteStream(fullPath);
42
- await pipeline(nodeStream, writeStream);
43
- const stats = fs5.statSync(fullPath);
44
- const detectedMime = mimeType || response.headers.get("content-type")?.split(";")[0] || void 0;
45
- return {
46
- filename,
47
- localPath: fullPath,
48
- mimeType: detectedMime,
49
- size: stats.size
50
- };
51
- }
52
- function isImageMime(mimeType) {
53
- return !!mimeType && mimeType.startsWith("image/");
54
- }
55
- function formatSize(bytes) {
56
- if (bytes === void 0 || bytes === null) return "";
57
- if (bytes < 1024) return `${bytes}B`;
58
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
59
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
60
- }
61
- function formatAttachmentsPrompt(attachments) {
62
- if (attachments.length === 0) return "";
63
- const lines = attachments.map((a) => {
64
- const meta = [a.mimeType, formatSize(a.size)].filter(Boolean).join(", ");
65
- return `- ${a.filename} (${meta}) \u2192 ${a.localPath}`;
66
- });
67
- return `[Attachments]
68
- ${lines.join("\n")}`;
69
- }
70
- function attachmentsToImageContent(attachments) {
71
- return attachments.filter((a) => isImageMime(a.mimeType)).map((a) => {
72
- const buffer = fs5.readFileSync(a.localPath);
73
- return {
74
- type: "image",
75
- data: buffer.toString("base64"),
76
- mimeType: a.mimeType || "image/png"
77
- };
78
- });
79
- }
80
- var ATTACHMENTS_DIR;
81
- var init_attachment_utils = __esm({
82
- "src/runtime/adapters/attachment-utils.ts"() {
83
- "use strict";
84
- ATTACHMENTS_DIR = "attachments";
85
- }
86
- });
87
-
88
- // src/runtime/config.ts
89
- import fs8 from "fs";
90
- import path8 from "path";
91
- var ConfigManager, configManager;
15
+ var SUPPORTED_PROVIDERS, ConfigManager, configManager, ConfigFileAuthBackend;
92
16
  var init_config = __esm({
93
17
  "src/runtime/config.ts"() {
94
18
  "use strict";
19
+ SUPPORTED_PROVIDERS = {
20
+ openai: {
21
+ label: "OpenAI",
22
+ defaultModelId: "gpt-5.4",
23
+ authType: "api_key",
24
+ envKey: "OPENAI_API_KEY",
25
+ placeholder: "sk-proj-...",
26
+ supportsBaseUrl: true
27
+ },
28
+ anthropic: {
29
+ label: "Anthropic",
30
+ defaultModelId: "claude-opus-4-6",
31
+ authType: "api_key",
32
+ envKey: "ANTHROPIC_API_KEY",
33
+ placeholder: "sk-ant-api03-...",
34
+ supportsBaseUrl: true
35
+ },
36
+ google: {
37
+ label: "Google (Gemini)",
38
+ defaultModelId: "gemini-2.5-pro",
39
+ authType: "api_key",
40
+ envKey: "GOOGLE_API_KEY",
41
+ placeholder: "AIza...",
42
+ supportsBaseUrl: false
43
+ },
44
+ "openai-codex": {
45
+ label: "OpenAI Codex",
46
+ defaultModelId: "gpt-5.4",
47
+ authType: "oauth",
48
+ oauthProviderId: "openai-codex",
49
+ supportsBaseUrl: false
50
+ }
51
+ };
95
52
  ConfigManager = class _ConfigManager {
96
53
  static instance;
97
54
  configData = {};
@@ -105,10 +62,10 @@ var init_config = __esm({
105
62
  return _ConfigManager.instance;
106
63
  }
107
64
  load(rootDir) {
108
- this.configPath = path8.join(rootDir, "data", "config.json");
109
- if (fs8.existsSync(this.configPath)) {
65
+ this.configPath = path5.join(rootDir, "data", "config.json");
66
+ if (fs5.existsSync(this.configPath)) {
110
67
  try {
111
- this.configData = JSON.parse(fs8.readFileSync(this.configPath, "utf-8"));
68
+ this.configData = JSON.parse(fs5.readFileSync(this.configPath, "utf-8"));
112
69
  console.log(" Loaded config from data/config.json");
113
70
  } catch (err) {
114
71
  console.warn(" Warning: Failed to parse data/config.json:", err);
@@ -122,6 +79,9 @@ var init_config = __esm({
122
79
  } else if (process.env.ANTHROPIC_API_KEY) {
123
80
  apiKey = process.env.ANTHROPIC_API_KEY;
124
81
  provider = "anthropic";
82
+ } else if (process.env.GOOGLE_API_KEY) {
83
+ apiKey = process.env.GOOGLE_API_KEY;
84
+ provider = "google";
125
85
  }
126
86
  }
127
87
  this.configData.apiKey = apiKey;
@@ -133,12 +93,12 @@ var init_config = __esm({
133
93
  return this.configData;
134
94
  }
135
95
  save(rootDir, updates) {
136
- const configDir = path8.join(rootDir, "data");
96
+ const configDir = path5.join(rootDir, "data");
137
97
  if (!this.configPath) {
138
- this.configPath = path8.join(rootDir, "data", "config.json");
98
+ this.configPath = path5.join(rootDir, "data", "config.json");
139
99
  }
140
- if (!fs8.existsSync(configDir)) {
141
- fs8.mkdirSync(configDir, { recursive: true });
100
+ if (!fs5.existsSync(configDir)) {
101
+ fs5.mkdirSync(configDir, { recursive: true });
142
102
  }
143
103
  if (updates.apiKey !== void 0) this.configData.apiKey = updates.apiKey;
144
104
  if (updates.provider !== void 0) this.configData.provider = updates.provider;
@@ -160,7 +120,7 @@ var init_config = __esm({
160
120
  this.configData.scheduledJobs = updates.scheduledJobs;
161
121
  }
162
122
  try {
163
- fs8.writeFileSync(
123
+ fs5.writeFileSync(
164
124
  this.configPath,
165
125
  JSON.stringify(this.configData, null, 2),
166
126
  "utf-8"
@@ -171,6 +131,137 @@ var init_config = __esm({
171
131
  }
172
132
  };
173
133
  configManager = ConfigManager.getInstance();
134
+ ConfigFileAuthBackend = class {
135
+ constructor(configPath) {
136
+ this.configPath = configPath;
137
+ }
138
+ ensureFile() {
139
+ const dir = path5.dirname(this.configPath);
140
+ if (!fs5.existsSync(dir)) {
141
+ fs5.mkdirSync(dir, { recursive: true });
142
+ }
143
+ if (!fs5.existsSync(this.configPath)) {
144
+ fs5.writeFileSync(this.configPath, "{}", "utf-8");
145
+ }
146
+ }
147
+ readAuthJson() {
148
+ this.ensureFile();
149
+ try {
150
+ const raw = fs5.readFileSync(this.configPath, "utf-8");
151
+ const config = JSON.parse(raw);
152
+ if (config._auth && typeof config._auth === "object") {
153
+ return JSON.stringify(config._auth);
154
+ }
155
+ return void 0;
156
+ } catch {
157
+ return void 0;
158
+ }
159
+ }
160
+ writeAuthJson(authJson) {
161
+ this.ensureFile();
162
+ try {
163
+ const raw = fs5.readFileSync(this.configPath, "utf-8");
164
+ const config = JSON.parse(raw);
165
+ config._auth = JSON.parse(authJson);
166
+ fs5.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
167
+ } catch {
168
+ const config = { _auth: JSON.parse(authJson) };
169
+ fs5.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
170
+ }
171
+ }
172
+ withLock(fn) {
173
+ const current = this.readAuthJson();
174
+ const { result, next } = fn(current);
175
+ if (next !== void 0) {
176
+ this.writeAuthJson(next);
177
+ }
178
+ return result;
179
+ }
180
+ async withLockAsync(fn) {
181
+ const current = this.readAuthJson();
182
+ const { result, next } = await fn(current);
183
+ if (next !== void 0) {
184
+ this.writeAuthJson(next);
185
+ }
186
+ return result;
187
+ }
188
+ };
189
+ }
190
+ });
191
+
192
+ // src/runtime/adapters/attachment-utils.ts
193
+ import fs6 from "fs";
194
+ import path6 from "path";
195
+ import { pipeline } from "stream/promises";
196
+ import { Readable } from "stream";
197
+ function getAttachmentDir(rootDir, channelId) {
198
+ return path6.resolve(rootDir, "data", "sessions", channelId, ATTACHMENTS_DIR);
199
+ }
200
+ function sanitizeFilename(name) {
201
+ return name.replace(/[/\\:*?"<>|]/g, "_").replace(/\s+/g, "_");
202
+ }
203
+ async function downloadAndSaveAttachment(rootDir, channelId, url, filename, mimeType, headers) {
204
+ const dir = getAttachmentDir(rootDir, channelId);
205
+ fs6.mkdirSync(dir, { recursive: true });
206
+ const ts = Date.now();
207
+ const safeName = sanitizeFilename(filename);
208
+ const storedName = `${ts}-${safeName}`;
209
+ const fullPath = path6.join(dir, storedName);
210
+ const response = await fetch(url, { headers });
211
+ if (!response.ok) {
212
+ throw new Error(
213
+ `Failed to download attachment from ${url}: ${response.status} ${response.statusText}`
214
+ );
215
+ }
216
+ const body = response.body;
217
+ if (!body) {
218
+ throw new Error(`Empty response body when downloading ${url}`);
219
+ }
220
+ const nodeStream = Readable.fromWeb(body);
221
+ const writeStream = fs6.createWriteStream(fullPath);
222
+ await pipeline(nodeStream, writeStream);
223
+ const stats = fs6.statSync(fullPath);
224
+ const detectedMime = mimeType || response.headers.get("content-type")?.split(";")[0] || void 0;
225
+ return {
226
+ filename,
227
+ localPath: fullPath,
228
+ mimeType: detectedMime,
229
+ size: stats.size
230
+ };
231
+ }
232
+ function isImageMime(mimeType) {
233
+ return !!mimeType && mimeType.startsWith("image/");
234
+ }
235
+ function formatSize(bytes) {
236
+ if (bytes === void 0 || bytes === null) return "";
237
+ if (bytes < 1024) return `${bytes}B`;
238
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
239
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
240
+ }
241
+ function formatAttachmentsPrompt(attachments) {
242
+ if (attachments.length === 0) return "";
243
+ const lines = attachments.map((a) => {
244
+ const meta = [a.mimeType, formatSize(a.size)].filter(Boolean).join(", ");
245
+ return `- ${a.filename} (${meta}) \u2192 ${a.localPath}`;
246
+ });
247
+ return `[Attachments]
248
+ ${lines.join("\n")}`;
249
+ }
250
+ function attachmentsToImageContent(attachments) {
251
+ return attachments.filter((a) => isImageMime(a.mimeType)).map((a) => {
252
+ const buffer = fs6.readFileSync(a.localPath);
253
+ return {
254
+ type: "image",
255
+ data: buffer.toString("base64"),
256
+ mimeType: a.mimeType || "image/png"
257
+ };
258
+ });
259
+ }
260
+ var ATTACHMENTS_DIR;
261
+ var init_attachment_utils = __esm({
262
+ "src/runtime/adapters/attachment-utils.ts"() {
263
+ "use strict";
264
+ ATTACHMENTS_DIR = "attachments";
174
265
  }
175
266
  });
176
267
 
@@ -293,7 +384,7 @@ var telegram_exports = {};
293
384
  __export(telegram_exports, {
294
385
  TelegramAdapter: () => TelegramAdapter
295
386
  });
296
- import fs10 from "fs";
387
+ import fs11 from "fs";
297
388
  import TelegramBot from "node-telegram-bot-api";
298
389
  var COMMANDS2, MAX_MESSAGE_LENGTH, ACK_REACTION, TelegramAdapter;
299
390
  var init_telegram = __esm({
@@ -604,7 +695,7 @@ var init_telegram = __esm({
604
695
  async sendFileSafe(chatId, filePath, caption) {
605
696
  if (!this.bot) return;
606
697
  try {
607
- if (!fs10.existsSync(filePath)) {
698
+ if (!fs11.existsSync(filePath)) {
608
699
  console.error(`[Telegram] File not found for sending: ${filePath}`);
609
700
  return;
610
701
  }
@@ -624,8 +715,8 @@ var slack_exports = {};
624
715
  __export(slack_exports, {
625
716
  SlackAdapter: () => SlackAdapter
626
717
  });
627
- import fs11 from "fs";
628
- import path10 from "path";
718
+ import fs12 from "fs";
719
+ import path11 from "path";
629
720
  import { App, LogLevel } from "@slack/bolt";
630
721
  var INLINE_COMMANDS, SLASH_COMMANDS, MAX_MESSAGE_LENGTH2, ACK_REACTION2, SlackAdapter;
631
722
  var init_slack = __esm({
@@ -1103,12 +1194,12 @@ var init_slack = __esm({
1103
1194
  */
1104
1195
  async sendFileSafe(client, route, filePath, caption) {
1105
1196
  try {
1106
- if (!fs11.existsSync(filePath)) {
1197
+ if (!fs12.existsSync(filePath)) {
1107
1198
  console.error(`[Slack] File not found for sending: ${filePath}`);
1108
1199
  return;
1109
1200
  }
1110
- const filename = path10.basename(filePath);
1111
- const fileContent = fs11.readFileSync(filePath);
1201
+ const filename = path11.basename(filePath);
1202
+ const fileContent = fs12.readFileSync(filePath);
1112
1203
  await client.files.uploadV2({
1113
1204
  channel_id: route.channel,
1114
1205
  thread_ts: route.threadTs,
@@ -2043,23 +2134,24 @@ async function interactiveCreate(workDir) {
2043
2134
  }
2044
2135
 
2045
2136
  // src/commands/run.ts
2046
- import path12 from "path";
2047
- import fs13 from "fs";
2137
+ import path13 from "path";
2138
+ import fs14 from "fs";
2048
2139
  import inquirer2 from "inquirer";
2049
2140
  import chalk4 from "chalk";
2050
2141
 
2051
2142
  // src/runtime/server.ts
2052
2143
  import express from "express";
2053
- import path11 from "path";
2054
- import fs12 from "fs";
2144
+ import path12 from "path";
2145
+ import fs13 from "fs";
2055
2146
  import { fileURLToPath as fileURLToPath2 } from "url";
2056
2147
  import { createServer } from "http";
2057
2148
  import { exec } from "child_process";
2058
2149
 
2059
2150
  // src/runtime/agent.ts
2151
+ init_config();
2060
2152
  init_attachment_utils();
2061
- import path7 from "path";
2062
- import fs7 from "fs";
2153
+ import path8 from "path";
2154
+ import fs8 from "fs";
2063
2155
  import { fileURLToPath } from "url";
2064
2156
  import {
2065
2157
  AuthStorage,
@@ -2071,8 +2163,8 @@ import {
2071
2163
  } from "@mariozechner/pi-coding-agent";
2072
2164
 
2073
2165
  // src/runtime/tools/send-file-tool.ts
2074
- import fs6 from "fs";
2075
- import path6 from "path";
2166
+ import fs7 from "fs";
2167
+ import path7 from "path";
2076
2168
 
2077
2169
  // node_modules/@sinclair/typebox/build/esm/type/guard/value.mjs
2078
2170
  var value_exports = {};
@@ -4712,7 +4804,7 @@ var MIME_BY_EXT = {
4712
4804
  ".ogg": "audio/ogg"
4713
4805
  };
4714
4806
  function detectMimeType(filePath) {
4715
- const ext = path6.extname(filePath).toLowerCase();
4807
+ const ext = path7.extname(filePath).toLowerCase();
4716
4808
  return MIME_BY_EXT[ext];
4717
4809
  }
4718
4810
  function createSendFileTool(fileOutputCallbackRef) {
@@ -4724,13 +4816,13 @@ function createSendFileTool(fileOutputCallbackRef) {
4724
4816
  parameters: SendFileParams,
4725
4817
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
4726
4818
  const { filePath, caption } = params;
4727
- if (!fs6.existsSync(filePath)) {
4819
+ if (!fs7.existsSync(filePath)) {
4728
4820
  return {
4729
4821
  content: [{ type: "text", text: `Error: File not found: ${filePath}` }],
4730
4822
  details: void 0
4731
4823
  };
4732
4824
  }
4733
- const stats = fs6.statSync(filePath);
4825
+ const stats = fs7.statSync(filePath);
4734
4826
  if (!stats.isFile()) {
4735
4827
  return {
4736
4828
  content: [
@@ -4739,7 +4831,7 @@ function createSendFileTool(fileOutputCallbackRef) {
4739
4831
  details: void 0
4740
4832
  };
4741
4833
  }
4742
- const filename = path6.basename(filePath);
4834
+ const filename = path7.basename(filePath);
4743
4835
  const mimeType = detectMimeType(filePath);
4744
4836
  const callback = fileOutputCallbackRef.current;
4745
4837
  if (callback) {
@@ -4925,24 +5017,24 @@ var BUILTIN_SKILL_CREATOR_TEMPLATE_DIR = fileURLToPath(
4925
5017
  new URL("../templates/builtin-skills/skill-creator", import.meta.url)
4926
5018
  );
4927
5019
  function materializeBuiltinSkillCreator(rootDir, skillsPath) {
4928
- if (!fs7.existsSync(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR)) {
5020
+ if (!fs8.existsSync(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR)) {
4929
5021
  log(
4930
5022
  `[PackAgent] Built-in skill-creator template missing: ${BUILTIN_SKILL_CREATOR_TEMPLATE_DIR}`
4931
5023
  );
4932
5024
  return null;
4933
5025
  }
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");
5026
+ const packConfigPath = path8.resolve(rootDir, "skillpack.json");
5027
+ const skillDir = path8.resolve(skillsPath, BUILTIN_SKILL_CREATOR_NAME);
5028
+ const skillPath = path8.join(skillDir, "SKILL.md");
4937
5029
  const renderTemplate = (content) => content.replaceAll("{{SKILLS_PATH}}", skillsPath).replaceAll("{{PACK_CONFIG_PATH}}", packConfigPath);
4938
5030
  const copyDir = (srcDir, destDir) => {
4939
- fs7.mkdirSync(destDir, { recursive: true });
4940
- for (const entry of fs7.readdirSync(srcDir, { withFileTypes: true })) {
5031
+ fs8.mkdirSync(destDir, { recursive: true });
5032
+ for (const entry of fs8.readdirSync(srcDir, { withFileTypes: true })) {
4941
5033
  if (entry.name === ".DS_Store") {
4942
5034
  continue;
4943
5035
  }
4944
- const srcPath = path7.join(srcDir, entry.name);
4945
- const destPath = path7.join(destDir, entry.name);
5036
+ const srcPath = path8.join(srcDir, entry.name);
5037
+ const destPath = path8.join(destDir, entry.name);
4946
5038
  if (entry.isDirectory()) {
4947
5039
  copyDir(srcPath, destPath);
4948
5040
  continue;
@@ -4951,17 +5043,17 @@ function materializeBuiltinSkillCreator(rootDir, skillsPath) {
4951
5043
  continue;
4952
5044
  }
4953
5045
  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");
5046
+ const content = fs8.readFileSync(srcPath, "utf-8");
5047
+ fs8.writeFileSync(destPath, renderTemplate(content), "utf-8");
4956
5048
  continue;
4957
5049
  }
4958
- fs7.copyFileSync(srcPath, destPath);
5050
+ fs8.copyFileSync(srcPath, destPath);
4959
5051
  }
4960
5052
  };
4961
- if (!fs7.existsSync(skillDir)) {
5053
+ if (!fs8.existsSync(skillDir)) {
4962
5054
  copyDir(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR, skillDir);
4963
5055
  }
4964
- if (!fs7.existsSync(skillPath)) {
5056
+ if (!fs8.existsSync(skillPath)) {
4965
5057
  log(
4966
5058
  `[PackAgent] Materialized built-in skill-creator but SKILL.md is missing: ${skillPath}`
4967
5059
  );
@@ -5014,8 +5106,29 @@ var PackAgent = class {
5014
5106
  current: null
5015
5107
  };
5016
5108
  schedulerRef = { current: null };
5109
+ authStorage;
5017
5110
  constructor(options) {
5018
5111
  this.options = options;
5112
+ const configPath = path8.resolve(options.rootDir, "data", "config.json");
5113
+ const backend = new ConfigFileAuthBackend(configPath);
5114
+ this.authStorage = AuthStorage.fromStorage(backend);
5115
+ const providerMeta = SUPPORTED_PROVIDERS[options.provider];
5116
+ if (providerMeta?.authType === "api_key" && options.apiKey) {
5117
+ this.authStorage.setRuntimeApiKey(options.provider, options.apiKey);
5118
+ }
5119
+ }
5120
+ /** Get the shared AuthStorage instance (used by OAuth API endpoints) */
5121
+ getAuthStorage() {
5122
+ return this.authStorage;
5123
+ }
5124
+ /** Update runtime auth when provider/apiKey changes */
5125
+ updateAuth(provider, apiKey) {
5126
+ this.authStorage.removeRuntimeApiKey(this.options.provider);
5127
+ this.options.provider = provider;
5128
+ if (apiKey) {
5129
+ this.options.apiKey = apiKey;
5130
+ this.authStorage.setRuntimeApiKey(provider, apiKey);
5131
+ }
5019
5132
  }
5020
5133
  /**
5021
5134
  * Inject scheduler reference (called by server.ts after adapter init).
@@ -5039,35 +5152,32 @@ var PackAgent = class {
5039
5152
  const pendingCreation = this.pendingSessionCreations.get(channelId);
5040
5153
  if (pendingCreation) return pendingCreation;
5041
5154
  const createSessionPromise = (async () => {
5042
- const { apiKey, rootDir, provider, modelId, baseUrl } = this.options;
5043
- const authStorage = AuthStorage.inMemory({
5044
- [provider]: { type: "api_key", key: apiKey }
5045
- });
5046
- authStorage.setRuntimeApiKey(provider, apiKey);
5155
+ const { rootDir, provider, modelId, baseUrl } = this.options;
5156
+ const authStorage = this.authStorage;
5047
5157
  const modelRegistry = new ModelRegistry(authStorage);
5048
5158
  const resolvedModel = modelRegistry.find(provider, modelId);
5049
5159
  const model = resolvedModel && baseUrl ? { ...resolvedModel, baseUrl } : resolvedModel;
5050
5160
  if (resolvedModel && baseUrl) {
5051
5161
  log(`[PackAgent] Overriding ${provider}/${modelId} baseUrl -> ${baseUrl}`);
5052
5162
  }
5053
- const sessionDir = path7.resolve(
5163
+ const sessionDir = path8.resolve(
5054
5164
  rootDir,
5055
5165
  "data",
5056
5166
  "sessions",
5057
5167
  channelId
5058
5168
  );
5059
- fs7.mkdirSync(sessionDir, { recursive: true });
5169
+ fs8.mkdirSync(sessionDir, { recursive: true });
5060
5170
  const sessionManager = SessionManager.continueRecent(rootDir, sessionDir);
5061
5171
  log(`[PackAgent] Session dir: ${sessionDir}`);
5062
- const workspaceDir = path7.resolve(
5172
+ const workspaceDir = path8.resolve(
5063
5173
  rootDir,
5064
5174
  "data",
5065
5175
  "workspaces",
5066
5176
  channelId
5067
5177
  );
5068
- fs7.mkdirSync(workspaceDir, { recursive: true });
5178
+ fs8.mkdirSync(workspaceDir, { recursive: true });
5069
5179
  log(`[PackAgent] Workspace dir: ${workspaceDir}`);
5070
- const skillsPath = path7.resolve(rootDir, "skills");
5180
+ const skillsPath = path8.resolve(rootDir, "skills");
5071
5181
  log(`[PackAgent] Loading skills from: ${skillsPath}`);
5072
5182
  const materializedSkillCreator = materializeBuiltinSkillCreator(
5073
5183
  rootDir,
@@ -5249,9 +5359,9 @@ ${text}`;
5249
5359
  this.channels.delete(channelId);
5250
5360
  }
5251
5361
  const { rootDir } = this.options;
5252
- const sessionDir = path7.resolve(rootDir, "data", "sessions", channelId);
5253
- if (fs7.existsSync(sessionDir)) {
5254
- fs7.rmSync(sessionDir, { recursive: true, force: true });
5362
+ const sessionDir = path8.resolve(rootDir, "data", "sessions", channelId);
5363
+ if (fs8.existsSync(sessionDir)) {
5364
+ fs8.rmSync(sessionDir, { recursive: true, force: true });
5255
5365
  log(`[PackAgent] Cleared session dir: ${sessionDir}`);
5256
5366
  }
5257
5367
  return {
@@ -5340,6 +5450,9 @@ var WebAdapter = class {
5340
5450
  app.get("/api/config", (_req, res) => {
5341
5451
  const config = getPackConfig(rootDir);
5342
5452
  const conf = configManager.getConfig();
5453
+ const currentProvider2 = conf.provider || "openai";
5454
+ const providerMeta = SUPPORTED_PROVIDERS[currentProvider2];
5455
+ const oauthConnected = providerMeta?.authType === "oauth" ? agent.getAuthStorage().hasAuth(currentProvider2) : false;
5343
5456
  res.json({
5344
5457
  name: config.name,
5345
5458
  description: config.description,
@@ -5347,10 +5460,11 @@ var WebAdapter = class {
5347
5460
  skills: config.skills || [],
5348
5461
  hasApiKey: !!conf.apiKey,
5349
5462
  apiKey: conf.apiKey || "",
5350
- provider: conf.provider || "openai",
5463
+ provider: currentProvider2,
5351
5464
  baseUrl: conf.baseUrl || "",
5352
5465
  adapters: conf.adapters || {},
5353
- runtimeControl: lifecycle.getRuntimeControl()
5466
+ supportedProviders: SUPPORTED_PROVIDERS,
5467
+ oauthConnected
5354
5468
  });
5355
5469
  });
5356
5470
  app.get("/api/skills", (_req, res) => {
@@ -5376,29 +5490,65 @@ var WebAdapter = class {
5376
5490
  updates.adapters = adapters;
5377
5491
  }
5378
5492
  configManager.save(rootDir, updates);
5379
- const newConf = configManager.getConfig();
5380
- const requiresRestart = getRuntimeConfigSignature(beforeConfig) !== getRuntimeConfigSignature(newConf);
5493
+ agent.updateAuth(currentProvider, apiKey);
5494
+ const afterConfig = configManager.getConfig();
5495
+ const requiresRestart = getRuntimeConfigSignature(beforeConfig) !== getRuntimeConfigSignature(afterConfig);
5381
5496
  res.json({
5382
- success: true,
5383
- provider: newConf.provider,
5384
- baseUrl: newConf.baseUrl || "",
5385
- adapters: newConf.adapters,
5386
- requiresRestart,
5387
- runtimeControl: lifecycle.getRuntimeControl()
5497
+ ...afterConfig,
5498
+ requiresRestart
5388
5499
  });
5389
5500
  });
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
5501
+ app.post("/api/oauth/login", async (req, res) => {
5502
+ const { provider } = req.body;
5503
+ const meta = SUPPORTED_PROVIDERS[provider];
5504
+ if (!meta || meta.authType !== "oauth") {
5505
+ return res.status(400).json({ error: "Provider does not support OAuth" });
5506
+ }
5507
+ try {
5508
+ const authStorage = agent.getAuthStorage();
5509
+ let authUrl = "";
5510
+ const loginPromise = authStorage.login(provider, {
5511
+ onAuth: (info) => {
5512
+ authUrl = info.url;
5513
+ },
5514
+ onPrompt: async (prompt) => {
5515
+ return "";
5516
+ },
5517
+ onProgress: (msg) => {
5518
+ console.log(`[OAuth] ${provider} login progress: ${msg}`);
5519
+ }
5397
5520
  });
5398
- return;
5521
+ await new Promise((r) => setTimeout(r, 1500));
5522
+ if (authUrl) {
5523
+ res.json({ status: "pending", authUrl });
5524
+ } else {
5525
+ res.json({ status: "pending" });
5526
+ }
5527
+ loginPromise.catch((err) => {
5528
+ console.error(`[OAuth] ${provider} login error:`, err);
5529
+ });
5530
+ } catch (err) {
5531
+ res.status(500).json({ error: String(err) });
5532
+ }
5533
+ });
5534
+ app.get("/api/oauth/status", (_req, res) => {
5535
+ const conf = configManager.getConfig();
5536
+ const provider = conf.provider || "openai";
5537
+ const meta = SUPPORTED_PROVIDERS[provider];
5538
+ if (!meta || meta.authType !== "oauth") {
5539
+ return res.json({ connected: false });
5399
5540
  }
5541
+ const connected = agent.getAuthStorage().hasAuth(provider);
5542
+ res.json({ connected, provider });
5543
+ });
5544
+ app.post("/api/oauth/logout", (req, res) => {
5545
+ const { provider } = req.body;
5546
+ agent.getAuthStorage().logout(provider);
5547
+ res.json({ success: true });
5548
+ });
5549
+ app.post("/api/runtime/restart", async (_req, res) => {
5400
5550
  const result = await lifecycle.requestRestart("web");
5401
- res.status(202).json({ ...result, runtimeControl });
5551
+ res.status(202).json(result);
5402
5552
  });
5403
5553
  app.delete("/api/chat", (_req, res) => {
5404
5554
  res.json({ success: true });
@@ -5519,8 +5669,10 @@ var WebAdapter = class {
5519
5669
  `http://${request.headers.host || "127.0.0.1"}`
5520
5670
  );
5521
5671
  const _reqProvider = url.searchParams.get("provider") || currentProvider;
5522
- if (!apiKey) {
5523
- ws.send(JSON.stringify({ error: "Please set an API key first" }));
5672
+ const providerMeta = SUPPORTED_PROVIDERS[_reqProvider];
5673
+ const hasAuth = providerMeta?.authType === "oauth" ? agent.getAuthStorage().hasAuth(_reqProvider) : !!apiKey;
5674
+ if (!hasAuth) {
5675
+ ws.send(JSON.stringify({ error: "Please configure authentication first" }));
5524
5676
  ws.close();
5525
5677
  return;
5526
5678
  }
@@ -5590,29 +5742,18 @@ init_config();
5590
5742
  var SHUTDOWN_EXIT_CODE = 64;
5591
5743
  var RESTART_EXIT_CODE = 75;
5592
5744
  var STOP_TIMEOUT_MS = 3e3;
5593
- function detectProcessManager() {
5594
- return process.env.PACK_ROOT ? "wrapper" : "none";
5595
- }
5596
5745
  var Lifecycle = class {
5597
5746
  server;
5598
5747
  exitFn;
5599
- processManager;
5600
5748
  adapters = [];
5601
5749
  stopReason = null;
5602
5750
  constructor(server, exitFn = (code) => process.exit(code)) {
5603
5751
  this.server = server;
5604
5752
  this.exitFn = exitFn;
5605
- this.processManager = detectProcessManager();
5606
5753
  }
5607
5754
  registerAdapters(adapters) {
5608
5755
  this.adapters = adapters;
5609
5756
  }
5610
- getRuntimeControl() {
5611
- return {
5612
- canManagedRestart: this.processManager === "wrapper",
5613
- processManager: this.processManager
5614
- };
5615
- }
5616
5757
  async requestRestart(trigger) {
5617
5758
  return this.requestStop("restart", trigger);
5618
5759
  }
@@ -5668,26 +5809,198 @@ var Lifecycle = class {
5668
5809
  }
5669
5810
  };
5670
5811
 
5812
+ // src/runtime/registry.ts
5813
+ import crypto from "crypto";
5814
+ import fs10 from "fs";
5815
+ import os from "os";
5816
+ import path10 from "path";
5817
+ var SKILLPACK_HOME = path10.join(os.homedir(), ".skillpack");
5818
+ var LEGACY_REGISTRY_FILE = path10.join(SKILLPACK_HOME, "registry.json");
5819
+ var REGISTRY_DIR = path10.join(SKILLPACK_HOME, "registry.d");
5820
+ var migrationChecked = false;
5821
+ function ensureHomeDir() {
5822
+ if (!fs10.existsSync(SKILLPACK_HOME)) {
5823
+ fs10.mkdirSync(SKILLPACK_HOME, { recursive: true });
5824
+ }
5825
+ }
5826
+ function ensureRegistryDir() {
5827
+ ensureHomeDir();
5828
+ if (!fs10.existsSync(REGISTRY_DIR)) {
5829
+ fs10.mkdirSync(REGISTRY_DIR, { recursive: true });
5830
+ }
5831
+ }
5832
+ function canonicalizeDir(dir) {
5833
+ const resolved = path10.resolve(dir);
5834
+ try {
5835
+ return fs10.realpathSync(resolved);
5836
+ } catch {
5837
+ return resolved;
5838
+ }
5839
+ }
5840
+ function hashDir(dir) {
5841
+ return crypto.createHash("md5").update(canonicalizeDir(dir)).digest("hex");
5842
+ }
5843
+ function getEntryPathForCanonicalDir(dir) {
5844
+ return path10.join(REGISTRY_DIR, `${hashDir(dir)}.json`);
5845
+ }
5846
+ function getEntryPath(dir) {
5847
+ ensureRegistryReady();
5848
+ return getEntryPathForCanonicalDir(canonicalizeDir(dir));
5849
+ }
5850
+ function listEntryFiles() {
5851
+ ensureRegistryReady();
5852
+ return fs10.readdirSync(REGISTRY_DIR).filter((file) => file.endsWith(".json")).sort().map((file) => path10.join(REGISTRY_DIR, file));
5853
+ }
5854
+ function readEntryFile(filePath) {
5855
+ try {
5856
+ const raw = fs10.readFileSync(filePath, "utf-8");
5857
+ const data = JSON.parse(raw);
5858
+ 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") {
5859
+ return null;
5860
+ }
5861
+ return {
5862
+ dir: canonicalizeDir(data.dir),
5863
+ name: data.name,
5864
+ version: data.version,
5865
+ port: data.port,
5866
+ pid: data.pid,
5867
+ status: data.status,
5868
+ startedAt: data.startedAt,
5869
+ stoppedAt: data.stoppedAt,
5870
+ updatedAt: data.updatedAt
5871
+ };
5872
+ } catch {
5873
+ return null;
5874
+ }
5875
+ }
5876
+ function createTmpPath(entryPath) {
5877
+ const suffix = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
5878
+ return `${entryPath}.tmp.${suffix}`;
5879
+ }
5880
+ function writeEntryFile(entry) {
5881
+ ensureRegistryReady();
5882
+ const normalized = {
5883
+ ...entry,
5884
+ dir: canonicalizeDir(entry.dir),
5885
+ updatedAt: entry.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
5886
+ };
5887
+ const entryPath = getEntryPathForCanonicalDir(normalized.dir);
5888
+ const tmpPath = createTmpPath(entryPath);
5889
+ fs10.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), "utf-8");
5890
+ fs10.renameSync(tmpPath, entryPath);
5891
+ }
5892
+ function migrateLegacyRegistryIfNeeded() {
5893
+ if (migrationChecked) {
5894
+ return;
5895
+ }
5896
+ migrationChecked = true;
5897
+ ensureRegistryDir();
5898
+ if (!fs10.existsSync(LEGACY_REGISTRY_FILE)) {
5899
+ return;
5900
+ }
5901
+ if (listEntryFiles().length > 0) {
5902
+ return;
5903
+ }
5904
+ try {
5905
+ const raw = fs10.readFileSync(LEGACY_REGISTRY_FILE, "utf-8");
5906
+ const data = JSON.parse(raw);
5907
+ const packs = Array.isArray(data?.packs) ? data.packs : [];
5908
+ for (const pack of packs) {
5909
+ try {
5910
+ writeEntryFile({
5911
+ ...pack,
5912
+ dir: canonicalizeDir(pack.dir),
5913
+ updatedAt: pack.updatedAt ?? pack.stoppedAt ?? pack.startedAt ?? (/* @__PURE__ */ new Date()).toISOString()
5914
+ });
5915
+ } catch {
5916
+ }
5917
+ }
5918
+ fs10.renameSync(LEGACY_REGISTRY_FILE, `${LEGACY_REGISTRY_FILE}.legacy`);
5919
+ } catch (err) {
5920
+ console.warn(" [Registry] Failed to migrate legacy registry.json:", err);
5921
+ }
5922
+ }
5923
+ function ensureRegistryReady() {
5924
+ ensureRegistryDir();
5925
+ migrateLegacyRegistryIfNeeded();
5926
+ }
5927
+ function readEntry(dir) {
5928
+ ensureRegistryReady();
5929
+ return readEntryFile(getEntryPath(dir));
5930
+ }
5931
+ function register(opts) {
5932
+ try {
5933
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5934
+ const entry = {
5935
+ dir: canonicalizeDir(opts.dir),
5936
+ name: opts.name,
5937
+ version: opts.version,
5938
+ port: opts.port,
5939
+ pid: process.pid,
5940
+ status: "running",
5941
+ startedAt: now,
5942
+ updatedAt: now
5943
+ };
5944
+ writeEntryFile(entry);
5945
+ console.log(` [Registry] Registered "${opts.name}" (pid ${process.pid})`);
5946
+ } catch (err) {
5947
+ console.warn(" [Registry] Failed to register:", err);
5948
+ }
5949
+ }
5950
+ function deregister(dir, pid) {
5951
+ try {
5952
+ const entry = readEntry(dir);
5953
+ if (!entry || entry.pid !== pid) {
5954
+ return;
5955
+ }
5956
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5957
+ writeEntryFile({
5958
+ ...entry,
5959
+ pid: null,
5960
+ status: "stopped",
5961
+ stoppedAt: now,
5962
+ updatedAt: now
5963
+ });
5964
+ console.log(` [Registry] Deregistered "${entry.name}"`);
5965
+ } catch (err) {
5966
+ console.warn(" [Registry] Failed to deregister:", err);
5967
+ }
5968
+ }
5969
+
5671
5970
  // src/runtime/server.ts
5672
- var __dirname = path11.dirname(fileURLToPath2(import.meta.url));
5971
+ var __dirname = path12.dirname(fileURLToPath2(import.meta.url));
5673
5972
  async function startServer(options) {
5674
5973
  const {
5675
5974
  rootDir,
5676
5975
  host = process.env.HOST || "127.0.0.1",
5677
5976
  port = Number(process.env.PORT) || 26313,
5678
- firstRun = true
5977
+ daemonRun = false
5679
5978
  } = options;
5680
5979
  const dataConfig = configManager.load(rootDir);
5681
5980
  const apiKey = dataConfig.apiKey || "";
5682
5981
  const provider = dataConfig.provider || "openai";
5982
+ const canonicalRootDir = canonicalizeDir(rootDir);
5983
+ const packConfig = loadConfig(canonicalRootDir);
5683
5984
  const baseUrl = dataConfig.baseUrl?.trim() || void 0;
5684
- 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");
5985
+ const modelId = SUPPORTED_PROVIDERS[provider]?.defaultModelId ?? SUPPORTED_PROVIDERS.openai.defaultModelId;
5986
+ const packageRoot = path12.resolve(__dirname, "..");
5987
+ const webDir = fs13.existsSync(path12.join(rootDir, "web")) ? path12.join(rootDir, "web") : path12.join(packageRoot, "web");
5687
5988
  const app = express();
5688
5989
  app.use(express.json());
5689
5990
  app.use(express.static(webDir));
5690
5991
  const server = createServer(app);
5992
+ app.get("/api/health", (_req, res) => {
5993
+ const address = server.address();
5994
+ const actualPort = typeof address === "string" ? port : address?.port ?? port;
5995
+ res.json({
5996
+ status: "ok",
5997
+ dir: canonicalRootDir,
5998
+ name: packConfig.name,
5999
+ version: packConfig.version,
6000
+ port: actualPort,
6001
+ pid: process.pid
6002
+ });
6003
+ });
5691
6004
  const lifecycle = new Lifecycle(server);
5692
6005
  const agent = new PackAgent({
5693
6006
  apiKey,
@@ -5782,7 +6095,17 @@ async function startServer(options) {
5782
6095
  Skills Pack Server`);
5783
6096
  console.log(` Running at ${url}
5784
6097
  `);
5785
- if (firstRun) {
6098
+ try {
6099
+ register({
6100
+ dir: canonicalRootDir,
6101
+ name: packConfig.name,
6102
+ version: packConfig.version,
6103
+ port: typeof actualPort === "number" ? actualPort : port
6104
+ });
6105
+ } catch (err) {
6106
+ console.warn(" [Registry] Could not register pack:", err);
6107
+ }
6108
+ if (!daemonRun) {
5786
6109
  const cmd = process.platform === "darwin" ? `open ${url}` : process.platform === "win32" ? `start ${url}` : `xdg-open ${url}`;
5787
6110
  exec(cmd, (err) => {
5788
6111
  if (err) console.warn(` Could not open browser: ${err.message}`);
@@ -5790,9 +6113,11 @@ async function startServer(options) {
5790
6113
  }
5791
6114
  });
5792
6115
  process.on("SIGINT", () => {
6116
+ deregister(canonicalRootDir, process.pid);
5793
6117
  void lifecycle.requestShutdown("signal");
5794
6118
  });
5795
6119
  process.on("SIGTERM", () => {
6120
+ deregister(canonicalRootDir, process.pid);
5796
6121
  void lifecycle.requestShutdown("signal");
5797
6122
  });
5798
6123
  await new Promise((resolve, reject) => {
@@ -5827,23 +6152,23 @@ function findMissingSkills(workDir, config) {
5827
6152
  });
5828
6153
  }
5829
6154
  function copyStartTemplates2(workDir) {
5830
- const templateDir = path12.resolve(
6155
+ const templateDir = path13.resolve(
5831
6156
  new URL("../templates", import.meta.url).pathname
5832
6157
  );
5833
6158
  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);
6159
+ const src = path13.join(templateDir, file);
6160
+ const dest = path13.join(workDir, file);
6161
+ if (fs14.existsSync(src)) {
6162
+ fs14.copyFileSync(src, dest);
5838
6163
  if (file === "start.sh") {
5839
- fs13.chmodSync(dest, 493);
6164
+ fs14.chmodSync(dest, 493);
5840
6165
  }
5841
6166
  }
5842
6167
  }
5843
6168
  }
5844
6169
  async function runCommand(directory) {
5845
- const workDir = directory ? path12.resolve(directory) : process.cwd();
5846
- fs13.mkdirSync(workDir, { recursive: true });
6170
+ const workDir = directory ? path13.resolve(directory) : process.cwd();
6171
+ fs14.mkdirSync(workDir, { recursive: true });
5847
6172
  if (!configExists(workDir)) {
5848
6173
  console.log(chalk4.blue("\n No skillpack.json found. Let's set one up.\n"));
5849
6174
  const { name, description } = await inquirer2.prompt([
@@ -5879,13 +6204,16 @@ async function runCommand(directory) {
5879
6204
  console.warn(chalk4.yellow(` Warning: Some skills could not be installed: ${err}`));
5880
6205
  }
5881
6206
  }
5882
- await startServer({ rootDir: workDir, firstRun: true });
6207
+ await startServer({
6208
+ rootDir: workDir,
6209
+ daemonRun: process.env.DAEMON_RUN === "1"
6210
+ });
5883
6211
  }
5884
6212
 
5885
6213
  // src/cli.ts
5886
- import fs14 from "fs";
6214
+ import fs15 from "fs";
5887
6215
  var packageJson = JSON.parse(
5888
- fs14.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
6216
+ fs15.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
5889
6217
  );
5890
6218
  var program = new Command();
5891
6219
  program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);