@cremini/skillpack 1.1.6 → 1.1.8-beta.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/README.md +19 -11
- package/dist/cli.js +998 -132
- package/dist/runtime/registry.js +244 -0
- package/package.json +4 -2
- package/templates/start.bat +27 -0
- package/templates/start.sh +30 -1
- package/web/js/api-key-dialog.js +3 -5
- package/web/js/chat-apps-dialog.js +4 -10
- package/web/js/settings.js +3 -8
package/dist/cli.js
CHANGED
|
@@ -85,6 +85,91 @@ var init_attachment_utils = __esm({
|
|
|
85
85
|
}
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
+
// src/runtime/config.ts
|
|
89
|
+
import fs8 from "fs";
|
|
90
|
+
import path8 from "path";
|
|
91
|
+
var ConfigManager, configManager;
|
|
92
|
+
var init_config = __esm({
|
|
93
|
+
"src/runtime/config.ts"() {
|
|
94
|
+
"use strict";
|
|
95
|
+
ConfigManager = class _ConfigManager {
|
|
96
|
+
static instance;
|
|
97
|
+
configData = {};
|
|
98
|
+
configPath = "";
|
|
99
|
+
constructor() {
|
|
100
|
+
}
|
|
101
|
+
static getInstance() {
|
|
102
|
+
if (!_ConfigManager.instance) {
|
|
103
|
+
_ConfigManager.instance = new _ConfigManager();
|
|
104
|
+
}
|
|
105
|
+
return _ConfigManager.instance;
|
|
106
|
+
}
|
|
107
|
+
load(rootDir) {
|
|
108
|
+
this.configPath = path8.join(rootDir, "data", "config.json");
|
|
109
|
+
if (fs8.existsSync(this.configPath)) {
|
|
110
|
+
try {
|
|
111
|
+
this.configData = JSON.parse(fs8.readFileSync(this.configPath, "utf-8"));
|
|
112
|
+
console.log(" Loaded config from data/config.json");
|
|
113
|
+
} catch (err) {
|
|
114
|
+
console.warn(" Warning: Failed to parse data/config.json:", err);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
let { apiKey = "", provider = "openai" } = this.configData;
|
|
118
|
+
if (!apiKey) {
|
|
119
|
+
if (process.env.OPENAI_API_KEY) {
|
|
120
|
+
apiKey = process.env.OPENAI_API_KEY;
|
|
121
|
+
provider = "openai";
|
|
122
|
+
} else if (process.env.ANTHROPIC_API_KEY) {
|
|
123
|
+
apiKey = process.env.ANTHROPIC_API_KEY;
|
|
124
|
+
provider = "anthropic";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
this.configData.apiKey = apiKey;
|
|
128
|
+
this.configData.provider = provider;
|
|
129
|
+
return this.configData;
|
|
130
|
+
}
|
|
131
|
+
getConfig() {
|
|
132
|
+
return this.configData;
|
|
133
|
+
}
|
|
134
|
+
save(rootDir, updates) {
|
|
135
|
+
const configDir = path8.join(rootDir, "data");
|
|
136
|
+
if (!this.configPath) {
|
|
137
|
+
this.configPath = path8.join(rootDir, "data", "config.json");
|
|
138
|
+
}
|
|
139
|
+
if (!fs8.existsSync(configDir)) {
|
|
140
|
+
fs8.mkdirSync(configDir, { recursive: true });
|
|
141
|
+
}
|
|
142
|
+
if (updates.apiKey !== void 0) this.configData.apiKey = updates.apiKey;
|
|
143
|
+
if (updates.provider !== void 0) this.configData.provider = updates.provider;
|
|
144
|
+
if (updates.adapters !== void 0) {
|
|
145
|
+
const merged = { ...this.configData.adapters || {} };
|
|
146
|
+
for (const [adapterKey, adapterVal] of Object.entries(updates.adapters)) {
|
|
147
|
+
if (adapterVal === null || adapterVal === void 0) {
|
|
148
|
+
delete merged[adapterKey];
|
|
149
|
+
} else {
|
|
150
|
+
merged[adapterKey] = adapterVal;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
this.configData.adapters = merged;
|
|
154
|
+
}
|
|
155
|
+
if (updates.scheduledJobs !== void 0) {
|
|
156
|
+
this.configData.scheduledJobs = updates.scheduledJobs;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
fs8.writeFileSync(
|
|
160
|
+
this.configPath,
|
|
161
|
+
JSON.stringify(this.configData, null, 2),
|
|
162
|
+
"utf-8"
|
|
163
|
+
);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.error("Failed to save config:", err);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
configManager = ConfigManager.getInstance();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
88
173
|
// src/runtime/adapters/markdown.ts
|
|
89
174
|
function unwrapMarkdownSourceBlocks(text) {
|
|
90
175
|
return text.replace(
|
|
@@ -204,7 +289,7 @@ var telegram_exports = {};
|
|
|
204
289
|
__export(telegram_exports, {
|
|
205
290
|
TelegramAdapter: () => TelegramAdapter
|
|
206
291
|
});
|
|
207
|
-
import
|
|
292
|
+
import fs11 from "fs";
|
|
208
293
|
import TelegramBot from "node-telegram-bot-api";
|
|
209
294
|
var COMMANDS2, MAX_MESSAGE_LENGTH, ACK_REACTION, TelegramAdapter;
|
|
210
295
|
var init_telegram = __esm({
|
|
@@ -256,6 +341,21 @@ var init_telegram = __esm({
|
|
|
256
341
|
console.log("[TelegramAdapter] Stopped");
|
|
257
342
|
}
|
|
258
343
|
// -------------------------------------------------------------------------
|
|
344
|
+
// MessageSender – proactive message sending
|
|
345
|
+
// -------------------------------------------------------------------------
|
|
346
|
+
/**
|
|
347
|
+
* Public method: send a message to a specific Telegram chat.
|
|
348
|
+
* channelId format: telegram-<chatId>
|
|
349
|
+
*/
|
|
350
|
+
async sendMessage(channelId, text) {
|
|
351
|
+
if (!this.bot) throw new Error("[Telegram] Bot not initialized");
|
|
352
|
+
const chatId = Number(channelId.replace("telegram-", ""));
|
|
353
|
+
if (isNaN(chatId)) {
|
|
354
|
+
throw new Error(`[Telegram] Invalid channelId: ${channelId}`);
|
|
355
|
+
}
|
|
356
|
+
await this.sendLongMessage(chatId, text);
|
|
357
|
+
}
|
|
358
|
+
// -------------------------------------------------------------------------
|
|
259
359
|
// Message handler
|
|
260
360
|
// -------------------------------------------------------------------------
|
|
261
361
|
async handleTelegramMessage(msg) {
|
|
@@ -499,7 +599,7 @@ var init_telegram = __esm({
|
|
|
499
599
|
async sendFileSafe(chatId, filePath, caption) {
|
|
500
600
|
if (!this.bot) return;
|
|
501
601
|
try {
|
|
502
|
-
if (!
|
|
602
|
+
if (!fs11.existsSync(filePath)) {
|
|
503
603
|
console.error(`[Telegram] File not found for sending: ${filePath}`);
|
|
504
604
|
return;
|
|
505
605
|
}
|
|
@@ -519,8 +619,8 @@ var slack_exports = {};
|
|
|
519
619
|
__export(slack_exports, {
|
|
520
620
|
SlackAdapter: () => SlackAdapter
|
|
521
621
|
});
|
|
522
|
-
import
|
|
523
|
-
import
|
|
622
|
+
import fs12 from "fs";
|
|
623
|
+
import path11 from "path";
|
|
524
624
|
import { App, LogLevel } from "@slack/bolt";
|
|
525
625
|
var INLINE_COMMANDS, SLASH_COMMANDS, MAX_MESSAGE_LENGTH2, ACK_REACTION2, SlackAdapter;
|
|
526
626
|
var init_slack = __esm({
|
|
@@ -578,6 +678,20 @@ var init_slack = __esm({
|
|
|
578
678
|
console.log("[SlackAdapter] Stopped");
|
|
579
679
|
}
|
|
580
680
|
// -------------------------------------------------------------------------
|
|
681
|
+
// MessageSender – proactive message sending
|
|
682
|
+
// -------------------------------------------------------------------------
|
|
683
|
+
/**
|
|
684
|
+
* Public method: send a message to a specific Slack channel/DM.
|
|
685
|
+
* channelId formats:
|
|
686
|
+
* - slack-dm-<teamId>-<channelId>
|
|
687
|
+
* - slack-thread-<teamId>-<channel>-<threadTs>
|
|
688
|
+
*/
|
|
689
|
+
async sendMessage(channelId, text) {
|
|
690
|
+
if (!this.app) throw new Error("[Slack] App not initialized");
|
|
691
|
+
const route = this.parseChannelId(channelId);
|
|
692
|
+
await this.sendLongMessage(this.app.client, route, text);
|
|
693
|
+
}
|
|
694
|
+
// -------------------------------------------------------------------------
|
|
581
695
|
// Listener registration
|
|
582
696
|
// -------------------------------------------------------------------------
|
|
583
697
|
registerListeners(app) {
|
|
@@ -916,6 +1030,30 @@ var init_slack = __esm({
|
|
|
916
1030
|
escapeRegExp(value) {
|
|
917
1031
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
918
1032
|
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Parse a skillpack channelId into a SlackRoute.
|
|
1035
|
+
* Supports:
|
|
1036
|
+
* slack-dm-<teamId>-<channelId> → { channel: <channelId> }
|
|
1037
|
+
* slack-thread-<teamId>-<ch>-<ts> → { channel: <ch>, threadTs: <ts> }
|
|
1038
|
+
*/
|
|
1039
|
+
parseChannelId(channelId) {
|
|
1040
|
+
if (channelId.startsWith("slack-thread-")) {
|
|
1041
|
+
const rest = channelId.replace("slack-thread-", "");
|
|
1042
|
+
const parts = rest.split("-");
|
|
1043
|
+
if (parts.length >= 3) {
|
|
1044
|
+
const threadTs = parts.slice(2).join("-");
|
|
1045
|
+
return { channel: parts[1], threadTs };
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if (channelId.startsWith("slack-dm-")) {
|
|
1049
|
+
const rest = channelId.replace("slack-dm-", "");
|
|
1050
|
+
const parts = rest.split("-");
|
|
1051
|
+
if (parts.length >= 2) {
|
|
1052
|
+
return { channel: parts.slice(1).join("-") };
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return { channel: channelId };
|
|
1056
|
+
}
|
|
919
1057
|
// -------------------------------------------------------------------------
|
|
920
1058
|
// Attachment extraction & sending
|
|
921
1059
|
// -------------------------------------------------------------------------
|
|
@@ -959,12 +1097,12 @@ var init_slack = __esm({
|
|
|
959
1097
|
*/
|
|
960
1098
|
async sendFileSafe(client, route, filePath, caption) {
|
|
961
1099
|
try {
|
|
962
|
-
if (!
|
|
1100
|
+
if (!fs12.existsSync(filePath)) {
|
|
963
1101
|
console.error(`[Slack] File not found for sending: ${filePath}`);
|
|
964
1102
|
return;
|
|
965
1103
|
}
|
|
966
|
-
const filename =
|
|
967
|
-
const fileContent =
|
|
1104
|
+
const filename = path11.basename(filePath);
|
|
1105
|
+
const fileContent = fs12.readFileSync(filePath);
|
|
968
1106
|
await client.files.uploadV2({
|
|
969
1107
|
channel_id: route.channel,
|
|
970
1108
|
thread_ts: route.threadTs,
|
|
@@ -981,6 +1119,357 @@ var init_slack = __esm({
|
|
|
981
1119
|
}
|
|
982
1120
|
});
|
|
983
1121
|
|
|
1122
|
+
// src/runtime/adapters/types.ts
|
|
1123
|
+
var types_exports = {};
|
|
1124
|
+
__export(types_exports, {
|
|
1125
|
+
isMessageSender: () => isMessageSender
|
|
1126
|
+
});
|
|
1127
|
+
function isMessageSender(adapter) {
|
|
1128
|
+
return typeof adapter.sendMessage === "function";
|
|
1129
|
+
}
|
|
1130
|
+
var init_types = __esm({
|
|
1131
|
+
"src/runtime/adapters/types.ts"() {
|
|
1132
|
+
"use strict";
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
// src/runtime/adapters/scheduler.ts
|
|
1137
|
+
var scheduler_exports = {};
|
|
1138
|
+
__export(scheduler_exports, {
|
|
1139
|
+
SchedulerAdapter: () => SchedulerAdapter
|
|
1140
|
+
});
|
|
1141
|
+
import cron from "node-cron";
|
|
1142
|
+
function isValidTimezone(tz) {
|
|
1143
|
+
try {
|
|
1144
|
+
Intl.DateTimeFormat(void 0, { timeZone: tz });
|
|
1145
|
+
return true;
|
|
1146
|
+
} catch {
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
function isValidJobName(name) {
|
|
1151
|
+
return VALID_JOB_NAME.test(name) && name.length <= 64;
|
|
1152
|
+
}
|
|
1153
|
+
var VALID_JOB_NAME, SchedulerAdapter;
|
|
1154
|
+
var init_scheduler = __esm({
|
|
1155
|
+
"src/runtime/adapters/scheduler.ts"() {
|
|
1156
|
+
"use strict";
|
|
1157
|
+
init_config();
|
|
1158
|
+
VALID_JOB_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
|
|
1159
|
+
SchedulerAdapter = class {
|
|
1160
|
+
name = "scheduler";
|
|
1161
|
+
agent;
|
|
1162
|
+
rootDir = "";
|
|
1163
|
+
notifyFn = async () => {
|
|
1164
|
+
};
|
|
1165
|
+
jobs = /* @__PURE__ */ new Map();
|
|
1166
|
+
async start(ctx) {
|
|
1167
|
+
this.agent = ctx.agent;
|
|
1168
|
+
this.rootDir = ctx.rootDir;
|
|
1169
|
+
this.notifyFn = ctx.notify || (async () => {
|
|
1170
|
+
});
|
|
1171
|
+
const config = configManager.getConfig();
|
|
1172
|
+
const jobConfigs = config.scheduledJobs || [];
|
|
1173
|
+
let scheduledCount = 0;
|
|
1174
|
+
let disabledCount = 0;
|
|
1175
|
+
for (const jc of jobConfigs) {
|
|
1176
|
+
const result = this.registerJob(jc);
|
|
1177
|
+
if (result.registered) {
|
|
1178
|
+
if (jc.enabled === false) {
|
|
1179
|
+
disabledCount++;
|
|
1180
|
+
} else {
|
|
1181
|
+
scheduledCount++;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
const parts = [];
|
|
1186
|
+
if (scheduledCount > 0) parts.push(`${scheduledCount} active`);
|
|
1187
|
+
if (disabledCount > 0) parts.push(`${disabledCount} disabled`);
|
|
1188
|
+
if (parts.length > 0) {
|
|
1189
|
+
console.log(`[SchedulerAdapter] Started with ${parts.join(", ")} job(s)`);
|
|
1190
|
+
} else {
|
|
1191
|
+
console.log("[SchedulerAdapter] Started (no jobs configured)");
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
// -------------------------------------------------------------------------
|
|
1195
|
+
// Core: register a job into the managed map
|
|
1196
|
+
// -------------------------------------------------------------------------
|
|
1197
|
+
/**
|
|
1198
|
+
* Register a job: validate, create cron task (if enabled), store in map.
|
|
1199
|
+
* Does NOT persist – callers decide when to persist.
|
|
1200
|
+
*/
|
|
1201
|
+
registerJob(jobConfig) {
|
|
1202
|
+
if (!isValidJobName(jobConfig.name)) {
|
|
1203
|
+
const msg = `[Scheduler] Invalid job name "${jobConfig.name}": must match ${VALID_JOB_NAME} and be \u226464 chars`;
|
|
1204
|
+
console.error(msg);
|
|
1205
|
+
return { registered: false, message: msg };
|
|
1206
|
+
}
|
|
1207
|
+
if (!cron.validate(jobConfig.cron)) {
|
|
1208
|
+
const msg = `[Scheduler] Invalid cron expression for job "${jobConfig.name}": ${jobConfig.cron}`;
|
|
1209
|
+
console.error(msg);
|
|
1210
|
+
return { registered: false, message: msg };
|
|
1211
|
+
}
|
|
1212
|
+
if (jobConfig.timezone && !isValidTimezone(jobConfig.timezone)) {
|
|
1213
|
+
const msg = `[Scheduler] Invalid timezone for job "${jobConfig.name}": ${jobConfig.timezone}`;
|
|
1214
|
+
console.error(msg);
|
|
1215
|
+
return { registered: false, message: msg };
|
|
1216
|
+
}
|
|
1217
|
+
this.removeFromMap(jobConfig.name);
|
|
1218
|
+
let task = null;
|
|
1219
|
+
if (jobConfig.enabled !== false) {
|
|
1220
|
+
task = cron.schedule(
|
|
1221
|
+
jobConfig.cron,
|
|
1222
|
+
() => {
|
|
1223
|
+
void this.runJob(jobConfig);
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
timezone: jobConfig.timezone
|
|
1227
|
+
}
|
|
1228
|
+
);
|
|
1229
|
+
console.log(
|
|
1230
|
+
`[Scheduler] Job "${jobConfig.name}" scheduled: ${jobConfig.cron}${jobConfig.timezone ? ` (${jobConfig.timezone})` : ""}`
|
|
1231
|
+
);
|
|
1232
|
+
} else {
|
|
1233
|
+
console.log(
|
|
1234
|
+
`[Scheduler] Job "${jobConfig.name}" registered (disabled)`
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
this.jobs.set(jobConfig.name, {
|
|
1238
|
+
config: jobConfig,
|
|
1239
|
+
task,
|
|
1240
|
+
running: false,
|
|
1241
|
+
notifyFailed: false
|
|
1242
|
+
});
|
|
1243
|
+
return { registered: true, message: "" };
|
|
1244
|
+
}
|
|
1245
|
+
// -------------------------------------------------------------------------
|
|
1246
|
+
// Job execution
|
|
1247
|
+
// -------------------------------------------------------------------------
|
|
1248
|
+
/**
|
|
1249
|
+
* Execute a scheduled job: call agent.handleMessage and push results.
|
|
1250
|
+
* Returns { text, notifyFailed } so callers can produce accurate status.
|
|
1251
|
+
*/
|
|
1252
|
+
async runJob(jobConfig) {
|
|
1253
|
+
const channelId = `scheduler-${jobConfig.name}`;
|
|
1254
|
+
const job = this.jobs.get(jobConfig.name);
|
|
1255
|
+
if (job?.running) {
|
|
1256
|
+
console.warn(
|
|
1257
|
+
`[Scheduler] Job "${jobConfig.name}" is already running, skipping this trigger`
|
|
1258
|
+
);
|
|
1259
|
+
return { text: "", notifyFailed: false };
|
|
1260
|
+
}
|
|
1261
|
+
if (job) job.running = true;
|
|
1262
|
+
console.log(`[Scheduler] Running job "${jobConfig.name}"`);
|
|
1263
|
+
let fullText = "";
|
|
1264
|
+
let agentFailed = false;
|
|
1265
|
+
const pendingFiles = [];
|
|
1266
|
+
const onEvent = (event) => {
|
|
1267
|
+
if (event.type === "text_delta") fullText += event.delta;
|
|
1268
|
+
if (event.type === "file_output") {
|
|
1269
|
+
pendingFiles.push({
|
|
1270
|
+
filePath: event.filePath,
|
|
1271
|
+
caption: event.caption
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
try {
|
|
1276
|
+
const result = await this.agent.handleMessage(
|
|
1277
|
+
channelId,
|
|
1278
|
+
jobConfig.prompt,
|
|
1279
|
+
onEvent
|
|
1280
|
+
);
|
|
1281
|
+
if (result.errorMessage) {
|
|
1282
|
+
fullText = `\u274C \u5B9A\u65F6\u4EFB\u52A1 "${jobConfig.name}" \u6267\u884C\u5931\u8D25\uFF1A${result.errorMessage}`;
|
|
1283
|
+
agentFailed = true;
|
|
1284
|
+
if (job) job.lastError = result.errorMessage;
|
|
1285
|
+
} else {
|
|
1286
|
+
if (job) job.lastError = void 0;
|
|
1287
|
+
}
|
|
1288
|
+
} catch (err) {
|
|
1289
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1290
|
+
fullText = `\u274C \u5B9A\u65F6\u4EFB\u52A1 "${jobConfig.name}" \u5F02\u5E38\uFF1A${errorMsg}`;
|
|
1291
|
+
agentFailed = true;
|
|
1292
|
+
if (job) job.lastError = errorMsg;
|
|
1293
|
+
}
|
|
1294
|
+
if (job) {
|
|
1295
|
+
job.lastRunAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1296
|
+
job.lastResult = fullText.slice(0, 200);
|
|
1297
|
+
}
|
|
1298
|
+
let notifyFailed = false;
|
|
1299
|
+
if (fullText.trim()) {
|
|
1300
|
+
try {
|
|
1301
|
+
await this.notifyFn(
|
|
1302
|
+
jobConfig.notify.adapter,
|
|
1303
|
+
jobConfig.notify.channelId,
|
|
1304
|
+
fullText
|
|
1305
|
+
);
|
|
1306
|
+
} catch (err) {
|
|
1307
|
+
notifyFailed = true;
|
|
1308
|
+
const notifyErr = err instanceof Error ? err.message : String(err);
|
|
1309
|
+
console.error(
|
|
1310
|
+
`[Scheduler] Failed to notify for job "${jobConfig.name}":`,
|
|
1311
|
+
err
|
|
1312
|
+
);
|
|
1313
|
+
if (job) {
|
|
1314
|
+
job.lastError = agentFailed ? `${job.lastError}; Notify also failed: ${notifyErr}` : `Notify failed: ${notifyErr}`;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
if (job) {
|
|
1319
|
+
job.running = false;
|
|
1320
|
+
job.notifyFailed = notifyFailed;
|
|
1321
|
+
}
|
|
1322
|
+
return { text: fullText, notifyFailed };
|
|
1323
|
+
}
|
|
1324
|
+
// -------------------------------------------------------------------------
|
|
1325
|
+
// Dynamic management API
|
|
1326
|
+
// -------------------------------------------------------------------------
|
|
1327
|
+
/**
|
|
1328
|
+
* Add a new job, persist to config.json.
|
|
1329
|
+
*/
|
|
1330
|
+
addJob(jobConfig) {
|
|
1331
|
+
if (this.jobs.has(jobConfig.name)) {
|
|
1332
|
+
return {
|
|
1333
|
+
success: false,
|
|
1334
|
+
message: `Job "${jobConfig.name}" already exists. Remove it first.`
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
const result = this.registerJob(jobConfig);
|
|
1338
|
+
if (!result.registered) {
|
|
1339
|
+
return { success: false, message: result.message };
|
|
1340
|
+
}
|
|
1341
|
+
this.persistJobs();
|
|
1342
|
+
const enabled = jobConfig.enabled !== false;
|
|
1343
|
+
return {
|
|
1344
|
+
success: true,
|
|
1345
|
+
message: enabled ? `Job "${jobConfig.name}" created and scheduled.` : `Job "${jobConfig.name}" created (disabled).`
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Remove a job and persist to config.json.
|
|
1350
|
+
*/
|
|
1351
|
+
removeJob(name) {
|
|
1352
|
+
if (!this.jobs.has(name)) {
|
|
1353
|
+
return { success: false, message: `Job "${name}" not found.` };
|
|
1354
|
+
}
|
|
1355
|
+
this.removeFromMap(name);
|
|
1356
|
+
this.persistJobs();
|
|
1357
|
+
return { success: true, message: `Job "${name}" removed.` };
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Enable or disable a job and persist.
|
|
1361
|
+
*/
|
|
1362
|
+
setEnabled(name, enabled) {
|
|
1363
|
+
const job = this.jobs.get(name);
|
|
1364
|
+
if (!job) {
|
|
1365
|
+
return { success: false, message: `Job "${name}" not found.` };
|
|
1366
|
+
}
|
|
1367
|
+
job.config.enabled = enabled;
|
|
1368
|
+
if (enabled && !job.task) {
|
|
1369
|
+
job.task = cron.schedule(
|
|
1370
|
+
job.config.cron,
|
|
1371
|
+
() => {
|
|
1372
|
+
void this.runJob(job.config);
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
timezone: job.config.timezone
|
|
1376
|
+
}
|
|
1377
|
+
);
|
|
1378
|
+
} else if (enabled && job.task) {
|
|
1379
|
+
job.task.start();
|
|
1380
|
+
} else if (!enabled && job.task) {
|
|
1381
|
+
job.task.stop();
|
|
1382
|
+
}
|
|
1383
|
+
this.persistJobs();
|
|
1384
|
+
return {
|
|
1385
|
+
success: true,
|
|
1386
|
+
message: `Job "${name}" ${enabled ? "enabled" : "disabled"}.`
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Manually trigger a job (runs immediately, ignoring cron schedule).
|
|
1391
|
+
*/
|
|
1392
|
+
async triggerJob(name) {
|
|
1393
|
+
const job = this.jobs.get(name);
|
|
1394
|
+
if (!job) {
|
|
1395
|
+
return { success: false, message: `Job "${name}" not found.` };
|
|
1396
|
+
}
|
|
1397
|
+
const { text, notifyFailed } = await this.runJob(job.config);
|
|
1398
|
+
if (!text) {
|
|
1399
|
+
return {
|
|
1400
|
+
success: true,
|
|
1401
|
+
message: `Job "${name}" triggered but produced no output.`
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
if (notifyFailed) {
|
|
1405
|
+
return {
|
|
1406
|
+
success: true,
|
|
1407
|
+
message: `Job "${name}" executed, but notification to ${job.config.notify.adapter} failed. Check logs.`
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
return {
|
|
1411
|
+
success: true,
|
|
1412
|
+
message: `Job "${name}" triggered. Result sent to ${job.config.notify.adapter}.`
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* List all jobs with their current status.
|
|
1417
|
+
*/
|
|
1418
|
+
listJobs() {
|
|
1419
|
+
const result = [];
|
|
1420
|
+
for (const [, job] of this.jobs) {
|
|
1421
|
+
result.push({
|
|
1422
|
+
name: job.config.name,
|
|
1423
|
+
cron: job.config.cron,
|
|
1424
|
+
prompt: job.config.prompt,
|
|
1425
|
+
notify: job.config.notify,
|
|
1426
|
+
enabled: job.config.enabled !== false,
|
|
1427
|
+
timezone: job.config.timezone,
|
|
1428
|
+
lastRunAt: job.lastRunAt,
|
|
1429
|
+
lastError: job.lastError,
|
|
1430
|
+
running: job.running,
|
|
1431
|
+
notifyFailed: job.notifyFailed
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
return result;
|
|
1435
|
+
}
|
|
1436
|
+
// -------------------------------------------------------------------------
|
|
1437
|
+
// Internal helpers
|
|
1438
|
+
// -------------------------------------------------------------------------
|
|
1439
|
+
/**
|
|
1440
|
+
* Stop the cron task and remove a job from the map (does NOT persist).
|
|
1441
|
+
*/
|
|
1442
|
+
removeFromMap(name) {
|
|
1443
|
+
const existing = this.jobs.get(name);
|
|
1444
|
+
if (existing) {
|
|
1445
|
+
existing.task?.stop();
|
|
1446
|
+
this.jobs.delete(name);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Persist all current jobs to data/config.json.
|
|
1451
|
+
*/
|
|
1452
|
+
persistJobs() {
|
|
1453
|
+
const configs = [];
|
|
1454
|
+
for (const [, job] of this.jobs) {
|
|
1455
|
+
configs.push(job.config);
|
|
1456
|
+
}
|
|
1457
|
+
configManager.save(this.rootDir, { scheduledJobs: configs });
|
|
1458
|
+
}
|
|
1459
|
+
// -------------------------------------------------------------------------
|
|
1460
|
+
// Lifecycle
|
|
1461
|
+
// -------------------------------------------------------------------------
|
|
1462
|
+
async stop() {
|
|
1463
|
+
for (const [, job] of this.jobs) {
|
|
1464
|
+
job.task?.stop();
|
|
1465
|
+
}
|
|
1466
|
+
this.jobs.clear();
|
|
1467
|
+
console.log("[SchedulerAdapter] All jobs stopped.");
|
|
1468
|
+
}
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
});
|
|
1472
|
+
|
|
984
1473
|
// src/cli.ts
|
|
985
1474
|
import { Command } from "commander";
|
|
986
1475
|
import chalk5 from "chalk";
|
|
@@ -1547,15 +2036,15 @@ async function interactiveCreate(workDir) {
|
|
|
1547
2036
|
}
|
|
1548
2037
|
|
|
1549
2038
|
// src/commands/run.ts
|
|
1550
|
-
import
|
|
1551
|
-
import
|
|
2039
|
+
import path13 from "path";
|
|
2040
|
+
import fs14 from "fs";
|
|
1552
2041
|
import inquirer2 from "inquirer";
|
|
1553
2042
|
import chalk4 from "chalk";
|
|
1554
2043
|
|
|
1555
2044
|
// src/runtime/server.ts
|
|
1556
2045
|
import express from "express";
|
|
1557
|
-
import
|
|
1558
|
-
import
|
|
2046
|
+
import path12 from "path";
|
|
2047
|
+
import fs13 from "fs";
|
|
1559
2048
|
import { fileURLToPath } from "url";
|
|
1560
2049
|
import { createServer } from "http";
|
|
1561
2050
|
import { exec } from "child_process";
|
|
@@ -4268,6 +4757,169 @@ function createSendFileTool(fileOutputCallbackRef) {
|
|
|
4268
4757
|
};
|
|
4269
4758
|
}
|
|
4270
4759
|
|
|
4760
|
+
// src/runtime/tools/manage-schedule-tool.ts
|
|
4761
|
+
var ManageScheduleParams = Type.Object({
|
|
4762
|
+
action: Type.Union(
|
|
4763
|
+
[
|
|
4764
|
+
Type.Literal("add"),
|
|
4765
|
+
Type.Literal("list"),
|
|
4766
|
+
Type.Literal("remove"),
|
|
4767
|
+
Type.Literal("trigger"),
|
|
4768
|
+
Type.Literal("enable"),
|
|
4769
|
+
Type.Literal("disable")
|
|
4770
|
+
],
|
|
4771
|
+
{ description: "The action to perform." }
|
|
4772
|
+
),
|
|
4773
|
+
name: Type.Optional(
|
|
4774
|
+
Type.String({
|
|
4775
|
+
description: "Unique name for the scheduled task. Required for add/remove/trigger/enable/disable."
|
|
4776
|
+
})
|
|
4777
|
+
),
|
|
4778
|
+
cron: Type.Optional(
|
|
4779
|
+
Type.String({
|
|
4780
|
+
description: "Cron expression (5 fields: minute hour day month weekday). Required for add."
|
|
4781
|
+
})
|
|
4782
|
+
),
|
|
4783
|
+
prompt: Type.Optional(
|
|
4784
|
+
Type.String({
|
|
4785
|
+
description: "The work prompt to execute when the task triggers. Required for add. Describe only what to do each run; do not repeat timing, cron, or 'every N minutes' instructions here."
|
|
4786
|
+
})
|
|
4787
|
+
),
|
|
4788
|
+
notifyAdapter: Type.Optional(
|
|
4789
|
+
Type.String({
|
|
4790
|
+
description: "Target adapter name for result notification: 'telegram' or 'slack'. Required for add."
|
|
4791
|
+
})
|
|
4792
|
+
),
|
|
4793
|
+
notifyChannelId: Type.Optional(
|
|
4794
|
+
Type.String({
|
|
4795
|
+
description: "Target channelId for result notification (e.g. 'telegram-123456'). Required for add."
|
|
4796
|
+
})
|
|
4797
|
+
),
|
|
4798
|
+
timezone: Type.Optional(
|
|
4799
|
+
Type.String({
|
|
4800
|
+
description: "Optional timezone for the cron schedule, e.g. 'Asia/Shanghai', 'America/New_York'."
|
|
4801
|
+
})
|
|
4802
|
+
)
|
|
4803
|
+
});
|
|
4804
|
+
function textResult(text) {
|
|
4805
|
+
return { content: [{ type: "text", text }], details: void 0 };
|
|
4806
|
+
}
|
|
4807
|
+
function createManageScheduleTool(schedulerRef, _rootDirRef) {
|
|
4808
|
+
return {
|
|
4809
|
+
name: "manage_scheduled_task",
|
|
4810
|
+
label: "Manage Scheduled Task",
|
|
4811
|
+
description: [
|
|
4812
|
+
"Manage scheduled tasks (cron jobs) that automatically execute prompts and push results to IM channels.",
|
|
4813
|
+
"",
|
|
4814
|
+
"Actions:",
|
|
4815
|
+
"- add: Create a new scheduled task. Requires: name, cron, prompt, notifyAdapter, notifyChannelId. The prompt must describe only the work for each run, not the schedule itself.",
|
|
4816
|
+
"- list: List all scheduled tasks with their status.",
|
|
4817
|
+
"- remove: Remove a scheduled task by name.",
|
|
4818
|
+
"- trigger: Manually trigger a scheduled task by name (runs immediately).",
|
|
4819
|
+
"- enable: Enable a disabled scheduled task.",
|
|
4820
|
+
"- disable: Disable a scheduled task without removing it.",
|
|
4821
|
+
"",
|
|
4822
|
+
"Cron expression format: '* * * * *' (minute hour day month weekday)",
|
|
4823
|
+
"Examples:",
|
|
4824
|
+
" '0 9 * * 1-5' = every weekday at 9:00 AM",
|
|
4825
|
+
" '0 18 * * 5' = every Friday at 6:00 PM",
|
|
4826
|
+
" '*/30 * * * *' = every 30 minutes",
|
|
4827
|
+
"",
|
|
4828
|
+
"notifyAdapter: 'telegram' or 'slack'",
|
|
4829
|
+
"notifyChannelId: the channel ID where result will be sent (e.g. 'telegram-123456')"
|
|
4830
|
+
].join("\n"),
|
|
4831
|
+
parameters: ManageScheduleParams,
|
|
4832
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
4833
|
+
const scheduler = schedulerRef.current;
|
|
4834
|
+
if (!scheduler) {
|
|
4835
|
+
return textResult(
|
|
4836
|
+
"Error: Scheduler is not available. The scheduled task system may not be initialized."
|
|
4837
|
+
);
|
|
4838
|
+
}
|
|
4839
|
+
switch (params.action) {
|
|
4840
|
+
case "list": {
|
|
4841
|
+
const jobs = scheduler.listJobs();
|
|
4842
|
+
if (jobs.length === 0) {
|
|
4843
|
+
return textResult("No scheduled tasks configured.");
|
|
4844
|
+
}
|
|
4845
|
+
const lines = jobs.map(
|
|
4846
|
+
(j) => `- **${j.name}**: \`${j.cron}\` \u2192 ${j.notify.adapter}:${j.notify.channelId} [${j.enabled ? "enabled" : "disabled"}]${j.running ? " (running)" : ""}${j.lastRunAt ? ` (last: ${j.lastRunAt})` : ""}`
|
|
4847
|
+
);
|
|
4848
|
+
return textResult(
|
|
4849
|
+
`Scheduled tasks (${jobs.length}):
|
|
4850
|
+
${lines.join("\n")}`
|
|
4851
|
+
);
|
|
4852
|
+
}
|
|
4853
|
+
case "add": {
|
|
4854
|
+
if (!params.name || !params.cron || !params.prompt) {
|
|
4855
|
+
return textResult(
|
|
4856
|
+
"Error: 'name', 'cron', and 'prompt' are required for adding a task."
|
|
4857
|
+
);
|
|
4858
|
+
}
|
|
4859
|
+
if (!params.notifyAdapter || !params.notifyChannelId) {
|
|
4860
|
+
return textResult(
|
|
4861
|
+
"Error: 'notifyAdapter' and 'notifyChannelId' are required for adding a task."
|
|
4862
|
+
);
|
|
4863
|
+
}
|
|
4864
|
+
const jobConfig = {
|
|
4865
|
+
name: params.name,
|
|
4866
|
+
cron: params.cron,
|
|
4867
|
+
prompt: params.prompt,
|
|
4868
|
+
notify: {
|
|
4869
|
+
adapter: params.notifyAdapter,
|
|
4870
|
+
channelId: params.notifyChannelId
|
|
4871
|
+
},
|
|
4872
|
+
enabled: true,
|
|
4873
|
+
timezone: params.timezone
|
|
4874
|
+
};
|
|
4875
|
+
const result = scheduler.addJob(jobConfig);
|
|
4876
|
+
return textResult(result.message);
|
|
4877
|
+
}
|
|
4878
|
+
case "remove": {
|
|
4879
|
+
if (!params.name) {
|
|
4880
|
+
return textResult(
|
|
4881
|
+
"Error: 'name' is required for removing a task."
|
|
4882
|
+
);
|
|
4883
|
+
}
|
|
4884
|
+
const result = scheduler.removeJob(params.name);
|
|
4885
|
+
return textResult(result.message);
|
|
4886
|
+
}
|
|
4887
|
+
case "trigger": {
|
|
4888
|
+
if (!params.name) {
|
|
4889
|
+
return textResult(
|
|
4890
|
+
"Error: 'name' is required for triggering a task."
|
|
4891
|
+
);
|
|
4892
|
+
}
|
|
4893
|
+
const result = await scheduler.triggerJob(params.name);
|
|
4894
|
+
return textResult(result.message);
|
|
4895
|
+
}
|
|
4896
|
+
case "enable": {
|
|
4897
|
+
if (!params.name) {
|
|
4898
|
+
return textResult(
|
|
4899
|
+
"Error: 'name' is required for enabling a task."
|
|
4900
|
+
);
|
|
4901
|
+
}
|
|
4902
|
+
const result = scheduler.setEnabled(params.name, true);
|
|
4903
|
+
return textResult(result.message);
|
|
4904
|
+
}
|
|
4905
|
+
case "disable": {
|
|
4906
|
+
if (!params.name) {
|
|
4907
|
+
return textResult(
|
|
4908
|
+
"Error: 'name' is required for disabling a task."
|
|
4909
|
+
);
|
|
4910
|
+
}
|
|
4911
|
+
const result = scheduler.setEnabled(params.name, false);
|
|
4912
|
+
return textResult(result.message);
|
|
4913
|
+
}
|
|
4914
|
+
default:
|
|
4915
|
+
return textResult(
|
|
4916
|
+
`Error: Unknown action '${params.action}'. Use: add, list, remove, trigger, enable, disable.`
|
|
4917
|
+
);
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
};
|
|
4921
|
+
}
|
|
4922
|
+
|
|
4271
4923
|
// src/runtime/agent.ts
|
|
4272
4924
|
var DEBUG = true;
|
|
4273
4925
|
var log = (...args) => DEBUG && console.log(...args);
|
|
@@ -4298,8 +4950,22 @@ var PackAgent = class {
|
|
|
4298
4950
|
current: null
|
|
4299
4951
|
};
|
|
4300
4952
|
sendFileToolDef = createSendFileTool(this.fileOutputCallbackRef);
|
|
4953
|
+
schedulerRef = { current: null };
|
|
4954
|
+
rootDirRef;
|
|
4955
|
+
scheduleToolDef;
|
|
4301
4956
|
constructor(options) {
|
|
4302
4957
|
this.options = options;
|
|
4958
|
+
this.rootDirRef = { current: options.rootDir };
|
|
4959
|
+
this.scheduleToolDef = createManageScheduleTool(
|
|
4960
|
+
this.schedulerRef,
|
|
4961
|
+
this.rootDirRef
|
|
4962
|
+
);
|
|
4963
|
+
}
|
|
4964
|
+
/**
|
|
4965
|
+
* Inject scheduler reference (called by server.ts after adapter init).
|
|
4966
|
+
*/
|
|
4967
|
+
setScheduler(scheduler) {
|
|
4968
|
+
this.schedulerRef.current = scheduler;
|
|
4303
4969
|
}
|
|
4304
4970
|
/**
|
|
4305
4971
|
* Lazily create (or return existing) session for a channel.
|
|
@@ -4350,7 +5016,7 @@ var PackAgent = class {
|
|
|
4350
5016
|
resourceLoader,
|
|
4351
5017
|
model,
|
|
4352
5018
|
tools,
|
|
4353
|
-
customTools: [this.sendFileToolDef]
|
|
5019
|
+
customTools: [this.sendFileToolDef, this.scheduleToolDef]
|
|
4354
5020
|
});
|
|
4355
5021
|
const channelSession = {
|
|
4356
5022
|
session,
|
|
@@ -4555,87 +5221,10 @@ ${text}`;
|
|
|
4555
5221
|
};
|
|
4556
5222
|
|
|
4557
5223
|
// src/runtime/adapters/web.ts
|
|
5224
|
+
init_config();
|
|
4558
5225
|
import fs9 from "fs";
|
|
4559
5226
|
import path9 from "path";
|
|
4560
5227
|
import { WebSocketServer } from "ws";
|
|
4561
|
-
|
|
4562
|
-
// src/runtime/config.ts
|
|
4563
|
-
import fs8 from "fs";
|
|
4564
|
-
import path8 from "path";
|
|
4565
|
-
var ConfigManager = class _ConfigManager {
|
|
4566
|
-
static instance;
|
|
4567
|
-
configData = {};
|
|
4568
|
-
configPath = "";
|
|
4569
|
-
constructor() {
|
|
4570
|
-
}
|
|
4571
|
-
static getInstance() {
|
|
4572
|
-
if (!_ConfigManager.instance) {
|
|
4573
|
-
_ConfigManager.instance = new _ConfigManager();
|
|
4574
|
-
}
|
|
4575
|
-
return _ConfigManager.instance;
|
|
4576
|
-
}
|
|
4577
|
-
load(rootDir) {
|
|
4578
|
-
this.configPath = path8.join(rootDir, "data", "config.json");
|
|
4579
|
-
if (fs8.existsSync(this.configPath)) {
|
|
4580
|
-
try {
|
|
4581
|
-
this.configData = JSON.parse(fs8.readFileSync(this.configPath, "utf-8"));
|
|
4582
|
-
console.log(" Loaded config from data/config.json");
|
|
4583
|
-
} catch (err) {
|
|
4584
|
-
console.warn(" Warning: Failed to parse data/config.json:", err);
|
|
4585
|
-
}
|
|
4586
|
-
}
|
|
4587
|
-
let { apiKey = "", provider = "openai" } = this.configData;
|
|
4588
|
-
if (!apiKey) {
|
|
4589
|
-
if (process.env.OPENAI_API_KEY) {
|
|
4590
|
-
apiKey = process.env.OPENAI_API_KEY;
|
|
4591
|
-
provider = "openai";
|
|
4592
|
-
} else if (process.env.ANTHROPIC_API_KEY) {
|
|
4593
|
-
apiKey = process.env.ANTHROPIC_API_KEY;
|
|
4594
|
-
provider = "anthropic";
|
|
4595
|
-
}
|
|
4596
|
-
}
|
|
4597
|
-
this.configData.apiKey = apiKey;
|
|
4598
|
-
this.configData.provider = provider;
|
|
4599
|
-
return this.configData;
|
|
4600
|
-
}
|
|
4601
|
-
getConfig() {
|
|
4602
|
-
return this.configData;
|
|
4603
|
-
}
|
|
4604
|
-
save(rootDir, updates) {
|
|
4605
|
-
const configDir = path8.join(rootDir, "data");
|
|
4606
|
-
if (!this.configPath) {
|
|
4607
|
-
this.configPath = path8.join(rootDir, "data", "config.json");
|
|
4608
|
-
}
|
|
4609
|
-
if (!fs8.existsSync(configDir)) {
|
|
4610
|
-
fs8.mkdirSync(configDir, { recursive: true });
|
|
4611
|
-
}
|
|
4612
|
-
if (updates.apiKey !== void 0) this.configData.apiKey = updates.apiKey;
|
|
4613
|
-
if (updates.provider !== void 0) this.configData.provider = updates.provider;
|
|
4614
|
-
if (updates.adapters !== void 0) {
|
|
4615
|
-
const merged = { ...this.configData.adapters || {} };
|
|
4616
|
-
for (const [adapterKey, adapterVal] of Object.entries(updates.adapters)) {
|
|
4617
|
-
if (adapterVal === null || adapterVal === void 0) {
|
|
4618
|
-
delete merged[adapterKey];
|
|
4619
|
-
} else {
|
|
4620
|
-
merged[adapterKey] = adapterVal;
|
|
4621
|
-
}
|
|
4622
|
-
}
|
|
4623
|
-
this.configData.adapters = merged;
|
|
4624
|
-
}
|
|
4625
|
-
try {
|
|
4626
|
-
fs8.writeFileSync(
|
|
4627
|
-
this.configPath,
|
|
4628
|
-
JSON.stringify(this.configData, null, 2),
|
|
4629
|
-
"utf-8"
|
|
4630
|
-
);
|
|
4631
|
-
} catch (err) {
|
|
4632
|
-
console.error("Failed to save config:", err);
|
|
4633
|
-
}
|
|
4634
|
-
}
|
|
4635
|
-
};
|
|
4636
|
-
var configManager = ConfigManager.getInstance();
|
|
4637
|
-
|
|
4638
|
-
// src/runtime/adapters/web.ts
|
|
4639
5228
|
function getPackConfig(rootDir) {
|
|
4640
5229
|
const raw = fs9.readFileSync(path9.join(rootDir, "skillpack.json"), "utf-8");
|
|
4641
5230
|
return JSON.parse(raw);
|
|
@@ -4680,8 +5269,7 @@ var WebAdapter = class {
|
|
|
4680
5269
|
hasApiKey: !!conf.apiKey,
|
|
4681
5270
|
apiKey: conf.apiKey || "",
|
|
4682
5271
|
provider: conf.provider || "openai",
|
|
4683
|
-
adapters: conf.adapters || {}
|
|
4684
|
-
runtimeControl: lifecycle.getRuntimeControl()
|
|
5272
|
+
adapters: conf.adapters || {}
|
|
4685
5273
|
});
|
|
4686
5274
|
});
|
|
4687
5275
|
app.get("/api/skills", (_req, res) => {
|
|
@@ -4710,22 +5298,12 @@ var WebAdapter = class {
|
|
|
4710
5298
|
success: true,
|
|
4711
5299
|
provider: newConf.provider,
|
|
4712
5300
|
adapters: newConf.adapters,
|
|
4713
|
-
requiresRestart
|
|
4714
|
-
runtimeControl: lifecycle.getRuntimeControl()
|
|
5301
|
+
requiresRestart
|
|
4715
5302
|
});
|
|
4716
5303
|
});
|
|
4717
5304
|
app.post("/api/runtime/restart", async (_req, res) => {
|
|
4718
|
-
const runtimeControl = lifecycle.getRuntimeControl();
|
|
4719
|
-
if (!runtimeControl.canManagedRestart) {
|
|
4720
|
-
res.status(409).json({
|
|
4721
|
-
success: false,
|
|
4722
|
-
message: "Managed restart is unavailable for this process.",
|
|
4723
|
-
runtimeControl
|
|
4724
|
-
});
|
|
4725
|
-
return;
|
|
4726
|
-
}
|
|
4727
5305
|
const result = await lifecycle.requestRestart("web");
|
|
4728
|
-
res.status(202).json(
|
|
5306
|
+
res.status(202).json(result);
|
|
4729
5307
|
});
|
|
4730
5308
|
app.delete("/api/chat", (_req, res) => {
|
|
4731
5309
|
res.json({ success: true });
|
|
@@ -4761,6 +5339,75 @@ var WebAdapter = class {
|
|
|
4761
5339
|
);
|
|
4762
5340
|
fs9.createReadStream(resolvedPath).pipe(res);
|
|
4763
5341
|
});
|
|
5342
|
+
const getScheduler = () => {
|
|
5343
|
+
const schedulerAdapter = ctx.adapterMap?.get("scheduler");
|
|
5344
|
+
if (!schedulerAdapter) return null;
|
|
5345
|
+
return schedulerAdapter;
|
|
5346
|
+
};
|
|
5347
|
+
app.get("/api/scheduler/jobs", (_req, res) => {
|
|
5348
|
+
const scheduler = getScheduler();
|
|
5349
|
+
if (!scheduler) {
|
|
5350
|
+
res.json([]);
|
|
5351
|
+
return;
|
|
5352
|
+
}
|
|
5353
|
+
res.json(scheduler.listJobs());
|
|
5354
|
+
});
|
|
5355
|
+
app.post("/api/scheduler/jobs", (req, res) => {
|
|
5356
|
+
const scheduler = getScheduler();
|
|
5357
|
+
if (!scheduler) {
|
|
5358
|
+
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
5359
|
+
return;
|
|
5360
|
+
}
|
|
5361
|
+
const { name, cron: cronExpr, prompt, notify, enabled, timezone } = req.body;
|
|
5362
|
+
if (!name || !cronExpr || !prompt || !notify?.adapter || !notify?.channelId) {
|
|
5363
|
+
res.status(400).json({
|
|
5364
|
+
success: false,
|
|
5365
|
+
message: "Required fields: name, cron, prompt, notify.adapter, notify.channelId"
|
|
5366
|
+
});
|
|
5367
|
+
return;
|
|
5368
|
+
}
|
|
5369
|
+
const result = scheduler.addJob({
|
|
5370
|
+
name,
|
|
5371
|
+
cron: cronExpr,
|
|
5372
|
+
prompt,
|
|
5373
|
+
notify,
|
|
5374
|
+
enabled: enabled !== false,
|
|
5375
|
+
timezone
|
|
5376
|
+
});
|
|
5377
|
+
res.json(result);
|
|
5378
|
+
});
|
|
5379
|
+
app.delete("/api/scheduler/jobs/:name", (req, res) => {
|
|
5380
|
+
const scheduler = getScheduler();
|
|
5381
|
+
if (!scheduler) {
|
|
5382
|
+
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
5383
|
+
return;
|
|
5384
|
+
}
|
|
5385
|
+
const result = scheduler.removeJob(req.params.name);
|
|
5386
|
+
res.json(result);
|
|
5387
|
+
});
|
|
5388
|
+
app.post("/api/scheduler/jobs/:name/trigger", async (req, res) => {
|
|
5389
|
+
const scheduler = getScheduler();
|
|
5390
|
+
if (!scheduler) {
|
|
5391
|
+
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
5392
|
+
return;
|
|
5393
|
+
}
|
|
5394
|
+
const result = await scheduler.triggerJob(req.params.name);
|
|
5395
|
+
res.json(result);
|
|
5396
|
+
});
|
|
5397
|
+
app.patch("/api/scheduler/jobs/:name", (req, res) => {
|
|
5398
|
+
const scheduler = getScheduler();
|
|
5399
|
+
if (!scheduler) {
|
|
5400
|
+
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
5401
|
+
return;
|
|
5402
|
+
}
|
|
5403
|
+
const { enabled } = req.body;
|
|
5404
|
+
if (typeof enabled !== "boolean") {
|
|
5405
|
+
res.status(400).json({ success: false, message: "Field 'enabled' (boolean) is required" });
|
|
5406
|
+
return;
|
|
5407
|
+
}
|
|
5408
|
+
const result = scheduler.setEnabled(req.params.name, enabled);
|
|
5409
|
+
res.json(result);
|
|
5410
|
+
});
|
|
4764
5411
|
this.wss = new WebSocketServer({ noServer: true });
|
|
4765
5412
|
server.on("upgrade", (request, socket, head) => {
|
|
4766
5413
|
if (request.url?.startsWith("/api/chat")) {
|
|
@@ -4841,33 +5488,25 @@ var WebAdapter = class {
|
|
|
4841
5488
|
}
|
|
4842
5489
|
};
|
|
4843
5490
|
|
|
5491
|
+
// src/runtime/server.ts
|
|
5492
|
+
init_config();
|
|
5493
|
+
|
|
4844
5494
|
// src/runtime/lifecycle.ts
|
|
4845
5495
|
var SHUTDOWN_EXIT_CODE = 64;
|
|
4846
5496
|
var RESTART_EXIT_CODE = 75;
|
|
4847
5497
|
var STOP_TIMEOUT_MS = 3e3;
|
|
4848
|
-
function detectProcessManager() {
|
|
4849
|
-
return process.env.PACK_ROOT ? "wrapper" : "none";
|
|
4850
|
-
}
|
|
4851
5498
|
var Lifecycle = class {
|
|
4852
5499
|
server;
|
|
4853
5500
|
exitFn;
|
|
4854
|
-
processManager;
|
|
4855
5501
|
adapters = [];
|
|
4856
5502
|
stopReason = null;
|
|
4857
5503
|
constructor(server, exitFn = (code) => process.exit(code)) {
|
|
4858
5504
|
this.server = server;
|
|
4859
5505
|
this.exitFn = exitFn;
|
|
4860
|
-
this.processManager = detectProcessManager();
|
|
4861
5506
|
}
|
|
4862
5507
|
registerAdapters(adapters) {
|
|
4863
5508
|
this.adapters = adapters;
|
|
4864
5509
|
}
|
|
4865
|
-
getRuntimeControl() {
|
|
4866
|
-
return {
|
|
4867
|
-
canManagedRestart: this.processManager === "wrapper",
|
|
4868
|
-
processManager: this.processManager
|
|
4869
|
-
};
|
|
4870
|
-
}
|
|
4871
5510
|
async requestRestart(trigger) {
|
|
4872
5511
|
return this.requestStop("restart", trigger);
|
|
4873
5512
|
}
|
|
@@ -4923,25 +5562,197 @@ var Lifecycle = class {
|
|
|
4923
5562
|
}
|
|
4924
5563
|
};
|
|
4925
5564
|
|
|
5565
|
+
// src/runtime/registry.ts
|
|
5566
|
+
import crypto from "crypto";
|
|
5567
|
+
import fs10 from "fs";
|
|
5568
|
+
import os from "os";
|
|
5569
|
+
import path10 from "path";
|
|
5570
|
+
var SKILLPACK_HOME = path10.join(os.homedir(), ".skillpack");
|
|
5571
|
+
var LEGACY_REGISTRY_FILE = path10.join(SKILLPACK_HOME, "registry.json");
|
|
5572
|
+
var REGISTRY_DIR = path10.join(SKILLPACK_HOME, "registry.d");
|
|
5573
|
+
var migrationChecked = false;
|
|
5574
|
+
function ensureHomeDir() {
|
|
5575
|
+
if (!fs10.existsSync(SKILLPACK_HOME)) {
|
|
5576
|
+
fs10.mkdirSync(SKILLPACK_HOME, { recursive: true });
|
|
5577
|
+
}
|
|
5578
|
+
}
|
|
5579
|
+
function ensureRegistryDir() {
|
|
5580
|
+
ensureHomeDir();
|
|
5581
|
+
if (!fs10.existsSync(REGISTRY_DIR)) {
|
|
5582
|
+
fs10.mkdirSync(REGISTRY_DIR, { recursive: true });
|
|
5583
|
+
}
|
|
5584
|
+
}
|
|
5585
|
+
function canonicalizeDir(dir) {
|
|
5586
|
+
const resolved = path10.resolve(dir);
|
|
5587
|
+
try {
|
|
5588
|
+
return fs10.realpathSync(resolved);
|
|
5589
|
+
} catch {
|
|
5590
|
+
return resolved;
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
function hashDir(dir) {
|
|
5594
|
+
return crypto.createHash("md5").update(canonicalizeDir(dir)).digest("hex");
|
|
5595
|
+
}
|
|
5596
|
+
function getEntryPathForCanonicalDir(dir) {
|
|
5597
|
+
return path10.join(REGISTRY_DIR, `${hashDir(dir)}.json`);
|
|
5598
|
+
}
|
|
5599
|
+
function getEntryPath(dir) {
|
|
5600
|
+
ensureRegistryReady();
|
|
5601
|
+
return getEntryPathForCanonicalDir(canonicalizeDir(dir));
|
|
5602
|
+
}
|
|
5603
|
+
function listEntryFiles() {
|
|
5604
|
+
ensureRegistryReady();
|
|
5605
|
+
return fs10.readdirSync(REGISTRY_DIR).filter((file) => file.endsWith(".json")).sort().map((file) => path10.join(REGISTRY_DIR, file));
|
|
5606
|
+
}
|
|
5607
|
+
function readEntryFile(filePath) {
|
|
5608
|
+
try {
|
|
5609
|
+
const raw = fs10.readFileSync(filePath, "utf-8");
|
|
5610
|
+
const data = JSON.parse(raw);
|
|
5611
|
+
if (typeof data?.dir !== "string" || typeof data?.name !== "string" || typeof data?.version !== "string" || typeof data?.port !== "number" || typeof data?.pid !== "number" && data?.pid !== null || data?.status !== "running" && data?.status !== "stopped") {
|
|
5612
|
+
return null;
|
|
5613
|
+
}
|
|
5614
|
+
return {
|
|
5615
|
+
dir: canonicalizeDir(data.dir),
|
|
5616
|
+
name: data.name,
|
|
5617
|
+
version: data.version,
|
|
5618
|
+
port: data.port,
|
|
5619
|
+
pid: data.pid,
|
|
5620
|
+
status: data.status,
|
|
5621
|
+
startedAt: data.startedAt,
|
|
5622
|
+
stoppedAt: data.stoppedAt,
|
|
5623
|
+
updatedAt: data.updatedAt
|
|
5624
|
+
};
|
|
5625
|
+
} catch {
|
|
5626
|
+
return null;
|
|
5627
|
+
}
|
|
5628
|
+
}
|
|
5629
|
+
function createTmpPath(entryPath) {
|
|
5630
|
+
const suffix = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
|
|
5631
|
+
return `${entryPath}.tmp.${suffix}`;
|
|
5632
|
+
}
|
|
5633
|
+
function writeEntryFile(entry) {
|
|
5634
|
+
ensureRegistryReady();
|
|
5635
|
+
const normalized = {
|
|
5636
|
+
...entry,
|
|
5637
|
+
dir: canonicalizeDir(entry.dir),
|
|
5638
|
+
updatedAt: entry.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
5639
|
+
};
|
|
5640
|
+
const entryPath = getEntryPathForCanonicalDir(normalized.dir);
|
|
5641
|
+
const tmpPath = createTmpPath(entryPath);
|
|
5642
|
+
fs10.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), "utf-8");
|
|
5643
|
+
fs10.renameSync(tmpPath, entryPath);
|
|
5644
|
+
}
|
|
5645
|
+
function migrateLegacyRegistryIfNeeded() {
|
|
5646
|
+
if (migrationChecked) {
|
|
5647
|
+
return;
|
|
5648
|
+
}
|
|
5649
|
+
migrationChecked = true;
|
|
5650
|
+
ensureRegistryDir();
|
|
5651
|
+
if (!fs10.existsSync(LEGACY_REGISTRY_FILE)) {
|
|
5652
|
+
return;
|
|
5653
|
+
}
|
|
5654
|
+
if (listEntryFiles().length > 0) {
|
|
5655
|
+
return;
|
|
5656
|
+
}
|
|
5657
|
+
try {
|
|
5658
|
+
const raw = fs10.readFileSync(LEGACY_REGISTRY_FILE, "utf-8");
|
|
5659
|
+
const data = JSON.parse(raw);
|
|
5660
|
+
const packs = Array.isArray(data?.packs) ? data.packs : [];
|
|
5661
|
+
for (const pack of packs) {
|
|
5662
|
+
try {
|
|
5663
|
+
writeEntryFile({
|
|
5664
|
+
...pack,
|
|
5665
|
+
dir: canonicalizeDir(pack.dir),
|
|
5666
|
+
updatedAt: pack.updatedAt ?? pack.stoppedAt ?? pack.startedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
5667
|
+
});
|
|
5668
|
+
} catch {
|
|
5669
|
+
}
|
|
5670
|
+
}
|
|
5671
|
+
fs10.renameSync(LEGACY_REGISTRY_FILE, `${LEGACY_REGISTRY_FILE}.legacy`);
|
|
5672
|
+
} catch (err) {
|
|
5673
|
+
console.warn(" [Registry] Failed to migrate legacy registry.json:", err);
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
function ensureRegistryReady() {
|
|
5677
|
+
ensureRegistryDir();
|
|
5678
|
+
migrateLegacyRegistryIfNeeded();
|
|
5679
|
+
}
|
|
5680
|
+
function readEntry(dir) {
|
|
5681
|
+
ensureRegistryReady();
|
|
5682
|
+
return readEntryFile(getEntryPath(dir));
|
|
5683
|
+
}
|
|
5684
|
+
function register(opts) {
|
|
5685
|
+
try {
|
|
5686
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5687
|
+
const entry = {
|
|
5688
|
+
dir: canonicalizeDir(opts.dir),
|
|
5689
|
+
name: opts.name,
|
|
5690
|
+
version: opts.version,
|
|
5691
|
+
port: opts.port,
|
|
5692
|
+
pid: process.pid,
|
|
5693
|
+
status: "running",
|
|
5694
|
+
startedAt: now,
|
|
5695
|
+
updatedAt: now
|
|
5696
|
+
};
|
|
5697
|
+
writeEntryFile(entry);
|
|
5698
|
+
console.log(` [Registry] Registered "${opts.name}" (pid ${process.pid})`);
|
|
5699
|
+
} catch (err) {
|
|
5700
|
+
console.warn(" [Registry] Failed to register:", err);
|
|
5701
|
+
}
|
|
5702
|
+
}
|
|
5703
|
+
function deregister(dir, pid) {
|
|
5704
|
+
try {
|
|
5705
|
+
const entry = readEntry(dir);
|
|
5706
|
+
if (!entry || entry.pid !== pid) {
|
|
5707
|
+
return;
|
|
5708
|
+
}
|
|
5709
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5710
|
+
writeEntryFile({
|
|
5711
|
+
...entry,
|
|
5712
|
+
pid: null,
|
|
5713
|
+
status: "stopped",
|
|
5714
|
+
stoppedAt: now,
|
|
5715
|
+
updatedAt: now
|
|
5716
|
+
});
|
|
5717
|
+
console.log(` [Registry] Deregistered "${entry.name}"`);
|
|
5718
|
+
} catch (err) {
|
|
5719
|
+
console.warn(" [Registry] Failed to deregister:", err);
|
|
5720
|
+
}
|
|
5721
|
+
}
|
|
5722
|
+
|
|
4926
5723
|
// src/runtime/server.ts
|
|
4927
|
-
var __dirname =
|
|
5724
|
+
var __dirname = path12.dirname(fileURLToPath(import.meta.url));
|
|
4928
5725
|
async function startServer(options) {
|
|
4929
5726
|
const {
|
|
4930
5727
|
rootDir,
|
|
4931
5728
|
host = process.env.HOST || "127.0.0.1",
|
|
4932
5729
|
port = Number(process.env.PORT) || 26313,
|
|
4933
|
-
|
|
5730
|
+
daemonRun = false
|
|
4934
5731
|
} = options;
|
|
4935
5732
|
const dataConfig = configManager.load(rootDir);
|
|
4936
5733
|
const apiKey = dataConfig.apiKey || "";
|
|
4937
5734
|
const provider = dataConfig.provider || "openai";
|
|
5735
|
+
const canonicalRootDir = canonicalizeDir(rootDir);
|
|
5736
|
+
const packConfig = loadConfig(canonicalRootDir);
|
|
4938
5737
|
const modelId = provider === "anthropic" ? "claude-opus-4-6" : "gpt-5.4";
|
|
4939
|
-
const packageRoot =
|
|
4940
|
-
const webDir =
|
|
5738
|
+
const packageRoot = path12.resolve(__dirname, "..");
|
|
5739
|
+
const webDir = fs13.existsSync(path12.join(rootDir, "web")) ? path12.join(rootDir, "web") : path12.join(packageRoot, "web");
|
|
4941
5740
|
const app = express();
|
|
4942
5741
|
app.use(express.json());
|
|
4943
5742
|
app.use(express.static(webDir));
|
|
4944
5743
|
const server = createServer(app);
|
|
5744
|
+
app.get("/api/health", (_req, res) => {
|
|
5745
|
+
const address = server.address();
|
|
5746
|
+
const actualPort = typeof address === "string" ? port : address?.port ?? port;
|
|
5747
|
+
res.json({
|
|
5748
|
+
status: "ok",
|
|
5749
|
+
dir: canonicalRootDir,
|
|
5750
|
+
name: packConfig.name,
|
|
5751
|
+
version: packConfig.version,
|
|
5752
|
+
port: actualPort,
|
|
5753
|
+
pid: process.pid
|
|
5754
|
+
});
|
|
5755
|
+
});
|
|
4945
5756
|
const lifecycle = new Lifecycle(server);
|
|
4946
5757
|
const agent = new PackAgent({
|
|
4947
5758
|
apiKey,
|
|
@@ -4951,9 +5762,11 @@ async function startServer(options) {
|
|
|
4951
5762
|
lifecycleHandler: lifecycle
|
|
4952
5763
|
});
|
|
4953
5764
|
const adapters = [];
|
|
5765
|
+
const adapterMap = /* @__PURE__ */ new Map();
|
|
4954
5766
|
const webAdapter = new WebAdapter();
|
|
4955
|
-
await webAdapter.start({ agent, server, app, rootDir, lifecycle });
|
|
5767
|
+
await webAdapter.start({ agent, server, app, rootDir, lifecycle, adapterMap });
|
|
4956
5768
|
adapters.push(webAdapter);
|
|
5769
|
+
adapterMap.set(webAdapter.name, webAdapter);
|
|
4957
5770
|
if (dataConfig.adapters?.telegram?.token) {
|
|
4958
5771
|
try {
|
|
4959
5772
|
const { TelegramAdapter: TelegramAdapter2 } = await Promise.resolve().then(() => (init_telegram(), telegram_exports));
|
|
@@ -4962,6 +5775,7 @@ async function startServer(options) {
|
|
|
4962
5775
|
});
|
|
4963
5776
|
await telegramAdapter.start({ agent, server, app, rootDir, lifecycle });
|
|
4964
5777
|
adapters.push(telegramAdapter);
|
|
5778
|
+
adapterMap.set(telegramAdapter.name, telegramAdapter);
|
|
4965
5779
|
} catch (err) {
|
|
4966
5780
|
console.error("[Telegram] Failed to start:", err);
|
|
4967
5781
|
}
|
|
@@ -4981,11 +5795,48 @@ async function startServer(options) {
|
|
|
4981
5795
|
});
|
|
4982
5796
|
await slackAdapter.start({ agent, server, app, rootDir, lifecycle });
|
|
4983
5797
|
adapters.push(slackAdapter);
|
|
5798
|
+
adapterMap.set(slackAdapter.name, slackAdapter);
|
|
4984
5799
|
} catch (err) {
|
|
4985
5800
|
console.error("[Slack] Failed to start:", err);
|
|
4986
5801
|
}
|
|
4987
5802
|
}
|
|
4988
5803
|
}
|
|
5804
|
+
const { isMessageSender: isMessageSender2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
5805
|
+
const notifyFn = async (adapterName, channelId, text) => {
|
|
5806
|
+
const adapter = adapterMap.get(adapterName);
|
|
5807
|
+
if (!adapter || !isMessageSender2(adapter)) {
|
|
5808
|
+
console.warn(
|
|
5809
|
+
`[Scheduler] Target adapter "${adapterName}" not found or doesn't support sendMessage`
|
|
5810
|
+
);
|
|
5811
|
+
return;
|
|
5812
|
+
}
|
|
5813
|
+
await adapter.sendMessage(channelId, text);
|
|
5814
|
+
};
|
|
5815
|
+
const scheduledJobs = dataConfig.scheduledJobs || [];
|
|
5816
|
+
let schedulerAdapter = null;
|
|
5817
|
+
try {
|
|
5818
|
+
const { SchedulerAdapter: SchedulerAdapter2 } = await Promise.resolve().then(() => (init_scheduler(), scheduler_exports));
|
|
5819
|
+
schedulerAdapter = new SchedulerAdapter2();
|
|
5820
|
+
await schedulerAdapter.start({
|
|
5821
|
+
agent,
|
|
5822
|
+
server,
|
|
5823
|
+
app,
|
|
5824
|
+
rootDir,
|
|
5825
|
+
lifecycle,
|
|
5826
|
+
notify: notifyFn,
|
|
5827
|
+
adapterMap
|
|
5828
|
+
});
|
|
5829
|
+
adapters.push(schedulerAdapter);
|
|
5830
|
+
adapterMap.set(schedulerAdapter.name, schedulerAdapter);
|
|
5831
|
+
if (scheduledJobs.length > 0) {
|
|
5832
|
+
console.log(`[Server] Scheduler started with ${scheduledJobs.length} job(s)`);
|
|
5833
|
+
}
|
|
5834
|
+
} catch (err) {
|
|
5835
|
+
console.error("[Scheduler] Failed to start:", err);
|
|
5836
|
+
}
|
|
5837
|
+
if (schedulerAdapter) {
|
|
5838
|
+
agent.setScheduler(schedulerAdapter);
|
|
5839
|
+
}
|
|
4989
5840
|
lifecycle.registerAdapters(adapters);
|
|
4990
5841
|
server.once("listening", () => {
|
|
4991
5842
|
const address = server.address();
|
|
@@ -4995,7 +5846,17 @@ async function startServer(options) {
|
|
|
4995
5846
|
Skills Pack Server`);
|
|
4996
5847
|
console.log(` Running at ${url}
|
|
4997
5848
|
`);
|
|
4998
|
-
|
|
5849
|
+
try {
|
|
5850
|
+
register({
|
|
5851
|
+
dir: canonicalRootDir,
|
|
5852
|
+
name: packConfig.name,
|
|
5853
|
+
version: packConfig.version,
|
|
5854
|
+
port: typeof actualPort === "number" ? actualPort : port
|
|
5855
|
+
});
|
|
5856
|
+
} catch (err) {
|
|
5857
|
+
console.warn(" [Registry] Could not register pack:", err);
|
|
5858
|
+
}
|
|
5859
|
+
if (!daemonRun) {
|
|
4999
5860
|
const cmd = process.platform === "darwin" ? `open ${url}` : process.platform === "win32" ? `start ${url}` : `xdg-open ${url}`;
|
|
5000
5861
|
exec(cmd, (err) => {
|
|
5001
5862
|
if (err) console.warn(` Could not open browser: ${err.message}`);
|
|
@@ -5003,9 +5864,11 @@ async function startServer(options) {
|
|
|
5003
5864
|
}
|
|
5004
5865
|
});
|
|
5005
5866
|
process.on("SIGINT", () => {
|
|
5867
|
+
deregister(canonicalRootDir, process.pid);
|
|
5006
5868
|
void lifecycle.requestShutdown("signal");
|
|
5007
5869
|
});
|
|
5008
5870
|
process.on("SIGTERM", () => {
|
|
5871
|
+
deregister(canonicalRootDir, process.pid);
|
|
5009
5872
|
void lifecycle.requestShutdown("signal");
|
|
5010
5873
|
});
|
|
5011
5874
|
await new Promise((resolve, reject) => {
|
|
@@ -5040,23 +5903,23 @@ function findMissingSkills(workDir, config) {
|
|
|
5040
5903
|
});
|
|
5041
5904
|
}
|
|
5042
5905
|
function copyStartTemplates2(workDir) {
|
|
5043
|
-
const templateDir =
|
|
5906
|
+
const templateDir = path13.resolve(
|
|
5044
5907
|
new URL("../templates", import.meta.url).pathname
|
|
5045
5908
|
);
|
|
5046
5909
|
for (const file of ["start.sh", "start.bat"]) {
|
|
5047
|
-
const src =
|
|
5048
|
-
const dest =
|
|
5049
|
-
if (
|
|
5050
|
-
|
|
5910
|
+
const src = path13.join(templateDir, file);
|
|
5911
|
+
const dest = path13.join(workDir, file);
|
|
5912
|
+
if (fs14.existsSync(src)) {
|
|
5913
|
+
fs14.copyFileSync(src, dest);
|
|
5051
5914
|
if (file === "start.sh") {
|
|
5052
|
-
|
|
5915
|
+
fs14.chmodSync(dest, 493);
|
|
5053
5916
|
}
|
|
5054
5917
|
}
|
|
5055
5918
|
}
|
|
5056
5919
|
}
|
|
5057
5920
|
async function runCommand(directory) {
|
|
5058
|
-
const workDir = directory ?
|
|
5059
|
-
|
|
5921
|
+
const workDir = directory ? path13.resolve(directory) : process.cwd();
|
|
5922
|
+
fs14.mkdirSync(workDir, { recursive: true });
|
|
5060
5923
|
if (!configExists(workDir)) {
|
|
5061
5924
|
console.log(chalk4.blue("\n No skillpack.json found. Let's set one up.\n"));
|
|
5062
5925
|
const { name, description } = await inquirer2.prompt([
|
|
@@ -5092,13 +5955,16 @@ async function runCommand(directory) {
|
|
|
5092
5955
|
console.warn(chalk4.yellow(` Warning: Some skills could not be installed: ${err}`));
|
|
5093
5956
|
}
|
|
5094
5957
|
}
|
|
5095
|
-
await startServer({
|
|
5958
|
+
await startServer({
|
|
5959
|
+
rootDir: workDir,
|
|
5960
|
+
daemonRun: process.env.DAEMON_RUN === "1"
|
|
5961
|
+
});
|
|
5096
5962
|
}
|
|
5097
5963
|
|
|
5098
5964
|
// src/cli.ts
|
|
5099
|
-
import
|
|
5965
|
+
import fs15 from "fs";
|
|
5100
5966
|
var packageJson = JSON.parse(
|
|
5101
|
-
|
|
5967
|
+
fs15.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
|
|
5102
5968
|
);
|
|
5103
5969
|
var program = new Command();
|
|
5104
5970
|
program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);
|