@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 +508 -180
- package/dist/runtime/registry.js +244 -0
- package/package.json +2 -2
- package/web/index.html +29 -62
- package/web/js/api-key-dialog.js +189 -36
- package/web/js/chat-apps-dialog.js +4 -10
- package/web/js/main.js +0 -2
- package/web/styles.css +65 -27
- package/web/js/settings.js +0 -205
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/
|
|
12
|
+
// src/runtime/config.ts
|
|
13
13
|
import fs5 from "fs";
|
|
14
14
|
import path5 from "path";
|
|
15
|
-
|
|
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 =
|
|
109
|
-
if (
|
|
65
|
+
this.configPath = path5.join(rootDir, "data", "config.json");
|
|
66
|
+
if (fs5.existsSync(this.configPath)) {
|
|
110
67
|
try {
|
|
111
|
-
this.configData = JSON.parse(
|
|
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 =
|
|
96
|
+
const configDir = path5.join(rootDir, "data");
|
|
137
97
|
if (!this.configPath) {
|
|
138
|
-
this.configPath =
|
|
98
|
+
this.configPath = path5.join(rootDir, "data", "config.json");
|
|
139
99
|
}
|
|
140
|
-
if (!
|
|
141
|
-
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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
|
|
628
|
-
import
|
|
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 (!
|
|
1197
|
+
if (!fs12.existsSync(filePath)) {
|
|
1107
1198
|
console.error(`[Slack] File not found for sending: ${filePath}`);
|
|
1108
1199
|
return;
|
|
1109
1200
|
}
|
|
1110
|
-
const filename =
|
|
1111
|
-
const fileContent =
|
|
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
|
|
2047
|
-
import
|
|
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
|
|
2054
|
-
import
|
|
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
|
|
2062
|
-
import
|
|
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
|
|
2075
|
-
import
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
4935
|
-
const skillDir =
|
|
4936
|
-
const skillPath =
|
|
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
|
-
|
|
4940
|
-
for (const entry of
|
|
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 =
|
|
4945
|
-
const destPath =
|
|
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 =
|
|
4955
|
-
|
|
5046
|
+
const content = fs8.readFileSync(srcPath, "utf-8");
|
|
5047
|
+
fs8.writeFileSync(destPath, renderTemplate(content), "utf-8");
|
|
4956
5048
|
continue;
|
|
4957
5049
|
}
|
|
4958
|
-
|
|
5050
|
+
fs8.copyFileSync(srcPath, destPath);
|
|
4959
5051
|
}
|
|
4960
5052
|
};
|
|
4961
|
-
if (!
|
|
5053
|
+
if (!fs8.existsSync(skillDir)) {
|
|
4962
5054
|
copyDir(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR, skillDir);
|
|
4963
5055
|
}
|
|
4964
|
-
if (!
|
|
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 {
|
|
5043
|
-
const authStorage =
|
|
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 =
|
|
5163
|
+
const sessionDir = path8.resolve(
|
|
5054
5164
|
rootDir,
|
|
5055
5165
|
"data",
|
|
5056
5166
|
"sessions",
|
|
5057
5167
|
channelId
|
|
5058
5168
|
);
|
|
5059
|
-
|
|
5169
|
+
fs8.mkdirSync(sessionDir, { recursive: true });
|
|
5060
5170
|
const sessionManager = SessionManager.continueRecent(rootDir, sessionDir);
|
|
5061
5171
|
log(`[PackAgent] Session dir: ${sessionDir}`);
|
|
5062
|
-
const workspaceDir =
|
|
5172
|
+
const workspaceDir = path8.resolve(
|
|
5063
5173
|
rootDir,
|
|
5064
5174
|
"data",
|
|
5065
5175
|
"workspaces",
|
|
5066
5176
|
channelId
|
|
5067
5177
|
);
|
|
5068
|
-
|
|
5178
|
+
fs8.mkdirSync(workspaceDir, { recursive: true });
|
|
5069
5179
|
log(`[PackAgent] Workspace dir: ${workspaceDir}`);
|
|
5070
|
-
const skillsPath =
|
|
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 =
|
|
5253
|
-
if (
|
|
5254
|
-
|
|
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:
|
|
5463
|
+
provider: currentProvider2,
|
|
5351
5464
|
baseUrl: conf.baseUrl || "",
|
|
5352
5465
|
adapters: conf.adapters || {},
|
|
5353
|
-
|
|
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
|
-
|
|
5380
|
-
const
|
|
5493
|
+
agent.updateAuth(currentProvider, apiKey);
|
|
5494
|
+
const afterConfig = configManager.getConfig();
|
|
5495
|
+
const requiresRestart = getRuntimeConfigSignature(beforeConfig) !== getRuntimeConfigSignature(afterConfig);
|
|
5381
5496
|
res.json({
|
|
5382
|
-
|
|
5383
|
-
|
|
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/
|
|
5391
|
-
const
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
5523
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
5685
|
-
const packageRoot =
|
|
5686
|
-
const webDir =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
5835
|
-
const dest =
|
|
5836
|
-
if (
|
|
5837
|
-
|
|
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
|
-
|
|
6164
|
+
fs14.chmodSync(dest, 493);
|
|
5840
6165
|
}
|
|
5841
6166
|
}
|
|
5842
6167
|
}
|
|
5843
6168
|
}
|
|
5844
6169
|
async function runCommand(directory) {
|
|
5845
|
-
const workDir = directory ?
|
|
5846
|
-
|
|
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({
|
|
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
|
|
6214
|
+
import fs15 from "fs";
|
|
5887
6215
|
var packageJson = JSON.parse(
|
|
5888
|
-
|
|
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);
|