@cmdop/bot 2026.2.26 → 2026.3.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/index.js CHANGED
@@ -3,6 +3,7 @@ import { Bot } from 'grammy';
3
3
  import { Client, GatewayIntentBits, Events, REST, Routes } from 'discord.js';
4
4
  import { App, Assistant } from '@slack/bolt';
5
5
  import { CMDOPClient } from '@cmdop/node';
6
+ import { createConsola } from 'consola';
6
7
 
7
8
  var __create = Object.create;
8
9
  var __defProp = Object.defineProperty;
@@ -159,6 +160,95 @@ var init_errors = __esm({
159
160
  }
160
161
  });
161
162
 
163
+ // src/models/command.ts
164
+ var command_exports = {};
165
+ __export(command_exports, {
166
+ ParsedCommandSchema: () => ParsedCommandSchema,
167
+ parseCommand: () => parseCommand
168
+ });
169
+ function parseCommand(text) {
170
+ const trimmed = text.trim();
171
+ const match = /^[/!](\w+)(?:\s+(.*))?$/s.exec(trimmed);
172
+ if (!match) return null;
173
+ const name = (match[1] ?? "").toLowerCase();
174
+ const rest = (match[2] ?? "").trim();
175
+ const args = rest ? rest.split(/\s+/) : [];
176
+ return ParsedCommandSchema.parse({ name, args, rawText: trimmed });
177
+ }
178
+ var ParsedCommandSchema;
179
+ var init_command = __esm({
180
+ "src/models/command.ts"() {
181
+ ParsedCommandSchema = z.object({
182
+ name: z.string().min(1),
183
+ args: z.array(z.string()),
184
+ rawText: z.string()
185
+ });
186
+ }
187
+ });
188
+
189
+ // src/core/base-channel.ts
190
+ var BaseChannel;
191
+ var init_base_channel = __esm({
192
+ "src/core/base-channel.ts"() {
193
+ init_command();
194
+ init_errors();
195
+ BaseChannel = class {
196
+ constructor(id, name, permissions, dispatcher, logger) {
197
+ this.id = id;
198
+ this.name = name;
199
+ this.permissions = permissions;
200
+ this.dispatcher = dispatcher;
201
+ this.logger = logger;
202
+ }
203
+ /**
204
+ * Process an incoming message: parse command → check permission → dispatch → send result.
205
+ * Plain text (non-command) is routed to the agent handler automatically.
206
+ * Channels call this from their platform event handler.
207
+ */
208
+ async processMessage(msg) {
209
+ const parsed = parseCommand(msg.text);
210
+ const ctx = parsed ? {
211
+ userId: msg.userId,
212
+ command: parsed.name,
213
+ args: parsed.args,
214
+ channelId: msg.channelId,
215
+ message: msg
216
+ } : {
217
+ userId: msg.userId,
218
+ command: "agent",
219
+ args: [msg.text],
220
+ channelId: msg.channelId,
221
+ message: msg
222
+ };
223
+ let result;
224
+ try {
225
+ result = await this.dispatcher.dispatch(ctx);
226
+ } catch (err2) {
227
+ const botErr = err2 instanceof BotError ? err2 : new BotError("Unexpected error", { cause: err2 instanceof Error ? err2 : void 0 });
228
+ result = { ok: false, error: botErr };
229
+ }
230
+ if (result.ok) {
231
+ await this.send(msg.userId, result.value);
232
+ } else {
233
+ await this.send(msg.userId, {
234
+ type: "error",
235
+ message: this.formatErrorMessage(result.error)
236
+ });
237
+ }
238
+ }
239
+ formatErrorMessage(error) {
240
+ if (error instanceof CommandNotFoundError) {
241
+ return `Unknown command. Type /help for available commands.`;
242
+ }
243
+ return error.message;
244
+ }
245
+ logEvent(event, meta = {}) {
246
+ this.logger.info(`[${this.name}] ${event}`, { channel: this.id, ...meta });
247
+ }
248
+ };
249
+ }
250
+ });
251
+
162
252
  // ../../node_modules/.pnpm/bottleneck@2.19.5/node_modules/bottleneck/lib/parser.js
163
253
  var require_parser = __commonJS({
164
254
  "../../node_modules/.pnpm/bottleneck@2.19.5/node_modules/bottleneck/lib/parser.js"(exports2) {
@@ -3009,9 +3099,9 @@ var require_lib = __commonJS({
3009
3099
  }
3010
3100
  });
3011
3101
 
3012
- // ../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.0/node_modules/@grammyjs/transformer-throttler/dist/deps.node.js
3102
+ // ../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.1/node_modules/@grammyjs/transformer-throttler/dist/deps.node.js
3013
3103
  var require_deps_node = __commonJS({
3014
- "../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.0/node_modules/@grammyjs/transformer-throttler/dist/deps.node.js"(exports2) {
3104
+ "../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.1/node_modules/@grammyjs/transformer-throttler/dist/deps.node.js"(exports2) {
3015
3105
  var __importDefault = exports2 && exports2.__importDefault || function(mod2) {
3016
3106
  return mod2 && mod2.__esModule ? mod2 : { "default": mod2 };
3017
3107
  };
@@ -3024,9 +3114,9 @@ var require_deps_node = __commonJS({
3024
3114
  }
3025
3115
  });
3026
3116
 
3027
- // ../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.0/node_modules/@grammyjs/transformer-throttler/dist/mod.js
3117
+ // ../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.1/node_modules/@grammyjs/transformer-throttler/dist/mod.js
3028
3118
  var require_mod = __commonJS({
3029
- "../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.0/node_modules/@grammyjs/transformer-throttler/dist/mod.js"(exports2) {
3119
+ "../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.1/node_modules/@grammyjs/transformer-throttler/dist/mod.js"(exports2) {
3030
3120
  Object.defineProperty(exports2, "__esModule", { value: true });
3031
3121
  exports2.bypassThrottler = exports2.apiThrottler = exports2.BottleneckStrategy = void 0;
3032
3122
  var deps_node_js_1 = require_deps_node();
@@ -3089,90 +3179,10 @@ var require_mod = __commonJS({
3089
3179
  }
3090
3180
  });
3091
3181
 
3092
- // src/models/command.ts
3093
- var command_exports = {};
3094
- __export(command_exports, {
3095
- ParsedCommandSchema: () => ParsedCommandSchema,
3096
- parseCommand: () => parseCommand
3097
- });
3098
- function parseCommand(text) {
3099
- const trimmed = text.trim();
3100
- const match = /^[/!](\w+)(?:\s+(.*))?$/s.exec(trimmed);
3101
- if (!match) return null;
3102
- const name = (match[1] ?? "").toLowerCase();
3103
- const rest = (match[2] ?? "").trim();
3104
- const args = rest ? rest.split(/\s+/) : [];
3105
- return ParsedCommandSchema.parse({ name, args, rawText: trimmed });
3106
- }
3107
- var ParsedCommandSchema;
3108
- var init_command = __esm({
3109
- "src/models/command.ts"() {
3110
- ParsedCommandSchema = z.object({
3111
- name: z.string().min(1),
3112
- args: z.array(z.string()),
3113
- rawText: z.string()
3114
- });
3115
- }
3116
- });
3117
-
3118
- // src/core/base-channel.ts
3119
- var BaseChannel;
3120
- var init_base_channel = __esm({
3121
- "src/core/base-channel.ts"() {
3122
- init_command();
3123
- init_errors();
3124
- BaseChannel = class {
3125
- constructor(id, name, permissions, dispatcher, logger) {
3126
- this.id = id;
3127
- this.name = name;
3128
- this.permissions = permissions;
3129
- this.dispatcher = dispatcher;
3130
- this.logger = logger;
3131
- }
3132
- /**
3133
- * Process an incoming message: parse command → check permission → dispatch → send result.
3134
- * Channels call this from their platform event handler.
3135
- */
3136
- async processMessage(msg) {
3137
- const parsed = parseCommand(msg.text);
3138
- if (!parsed) return;
3139
- const ctx = {
3140
- userId: msg.userId,
3141
- command: parsed.name,
3142
- args: parsed.args,
3143
- channelId: msg.channelId,
3144
- message: msg
3145
- };
3146
- let result;
3147
- try {
3148
- result = await this.dispatcher.dispatch(ctx);
3149
- } catch (err2) {
3150
- const botErr = err2 instanceof BotError ? err2 : new BotError("Unexpected error", { cause: err2 instanceof Error ? err2 : void 0 });
3151
- result = { ok: false, error: botErr };
3152
- }
3153
- if (result.ok) {
3154
- await this.send(msg.userId, result.value);
3155
- } else {
3156
- await this.send(msg.userId, {
3157
- type: "error",
3158
- message: this.formatErrorMessage(result.error)
3159
- });
3160
- }
3161
- }
3162
- formatErrorMessage(error) {
3163
- if (error instanceof CommandNotFoundError) {
3164
- return `Unknown command. Type /help for available commands.`;
3165
- }
3166
- return error.message;
3167
- }
3168
- logEvent(event, meta = {}) {
3169
- this.logger.info(`[${this.name}] ${event}`, { channel: this.id, ...meta });
3170
- }
3171
- };
3172
- }
3173
- });
3174
-
3175
3182
  // src/channels/telegram/formatter.ts
3183
+ function escapeHtml(text) {
3184
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3185
+ }
3176
3186
  function formatBytes(bytes) {
3177
3187
  if (bytes < 1024) return `${bytes}B`;
3178
3188
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}K`;
@@ -3242,6 +3252,51 @@ ${this.formatText(error.message)}`;
3242
3252
  const sizeStr = size !== void 0 && !isDir ? ` \\(${this.formatText(formatBytes(size))}\\)` : "";
3243
3253
  return `${icon} \`${this.escapeInline(name)}\`${sizeStr}`;
3244
3254
  }
3255
+ // ─── HTML formatting (preferred — avoids MarkdownV2 escaping hell) ────────
3256
+ /**
3257
+ * Escape HTML special chars, then convert basic markdown to HTML tags.
3258
+ * Handles: **bold**, `code`, ```code blocks```, _italic_
3259
+ */
3260
+ formatTextHtml(text) {
3261
+ let html = escapeHtml(text);
3262
+ html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_m, lang, code) => {
3263
+ const cls = lang ? ` class="language-${lang}"` : "";
3264
+ return `<pre><code${cls}>${code}</code></pre>`;
3265
+ });
3266
+ html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
3267
+ html = html.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
3268
+ html = html.replace(/__(.+?)__/g, "<b>$1</b>");
3269
+ html = html.replace(/(?<!\w)\*([^*]+?)\*(?!\w)/g, "<i>$1</i>");
3270
+ html = html.replace(/(?<!\w)_([^_]+?)_(?!\w)/g, "<i>$1</i>");
3271
+ return html;
3272
+ }
3273
+ /**
3274
+ * Format code block as HTML <pre>.
3275
+ */
3276
+ formatCodeHtml(code, language) {
3277
+ const escaped = escapeHtml(code);
3278
+ const cls = language ? ` class="language-${language}"` : "";
3279
+ const wrapped = `<pre><code${cls}>${escaped}</code></pre>`;
3280
+ if (wrapped.length <= TELEGRAM_MAX_MESSAGE_LENGTH) return wrapped;
3281
+ const overhead = 40 + (language?.length ?? 0);
3282
+ const maxCode = TELEGRAM_MAX_MESSAGE_LENGTH - overhead;
3283
+ return `<pre><code${cls}>${escaped.slice(0, maxCode)}
3284
+ \u2026(truncated)</code></pre>`;
3285
+ }
3286
+ /**
3287
+ * Format error message as HTML.
3288
+ */
3289
+ formatErrorHtml(message) {
3290
+ return `\u274C <b>Error:</b> ${escapeHtml(message)}`;
3291
+ }
3292
+ /**
3293
+ * Format file entry as HTML.
3294
+ */
3295
+ formatFileEntryHtml(name, isDir, size) {
3296
+ const icon = isDir ? "\u{1F4C1}" : "\u{1F4C4}";
3297
+ const sizeStr = size !== void 0 && !isDir ? ` (${formatBytes(size)})` : "";
3298
+ return `${icon} <code>${escapeHtml(name)}</code>${sizeStr}`;
3299
+ }
3245
3300
  // Escape text that appears inside inline code (backtick context)
3246
3301
  escapeInline(text) {
3247
3302
  return text.replace(/`/g, "'");
@@ -3280,8 +3335,18 @@ var init_channel = __esm({
3280
3335
  bot.on("message:text", async (ctx) => {
3281
3336
  const msg = this.normalizeContext(ctx);
3282
3337
  if (!msg) return;
3283
- for (const h3 of this.messageHandlers) await h3(msg);
3284
- await this.processMessage(msg);
3338
+ const typingInterval = setInterval(() => {
3339
+ ctx.api.sendChatAction(ctx.chat.id, "typing").catch(() => {
3340
+ });
3341
+ }, 4e3);
3342
+ await ctx.api.sendChatAction(ctx.chat.id, "typing").catch(() => {
3343
+ });
3344
+ try {
3345
+ for (const h3 of this.messageHandlers) await h3(msg);
3346
+ await this.processMessage(msg);
3347
+ } finally {
3348
+ clearInterval(typingInterval);
3349
+ }
3285
3350
  });
3286
3351
  bot.on("callback_query:data", async (ctx) => {
3287
3352
  await ctx.answerCallbackQuery();
@@ -3329,20 +3394,18 @@ var init_channel = __esm({
3329
3394
  try {
3330
3395
  switch (message.type) {
3331
3396
  case "text": {
3332
- const text = this.truncate(this.formatter.formatText(message.text));
3333
- await this.bot.api.sendMessage(chatId, text, { parse_mode: "MarkdownV2" });
3397
+ const text = this.truncate(this.formatter.formatTextHtml(message.text));
3398
+ await this.bot.api.sendMessage(chatId, text, { parse_mode: "HTML" });
3334
3399
  break;
3335
3400
  }
3336
3401
  case "code": {
3337
- const code = this.formatter.formatCode(message.code, message.language);
3338
- await this.bot.api.sendMessage(chatId, code, { parse_mode: "MarkdownV2" });
3402
+ const code = this.formatter.formatCodeHtml(message.code, message.language);
3403
+ await this.bot.api.sendMessage(chatId, code, { parse_mode: "HTML" });
3339
3404
  break;
3340
3405
  }
3341
3406
  case "error": {
3342
- const errText = this.formatter.formatError(
3343
- Object.assign(new Error(message.message), { code: "BOT_ERROR", context: {}, toLog: () => ({}) })
3344
- );
3345
- await this.bot.api.sendMessage(chatId, errText, { parse_mode: "MarkdownV2" });
3407
+ const errText = this.formatter.formatErrorHtml(message.message);
3408
+ await this.bot.api.sendMessage(chatId, errText, { parse_mode: "HTML" });
3346
3409
  break;
3347
3410
  }
3348
3411
  }
@@ -3369,7 +3432,7 @@ var init_channel = __esm({
3369
3432
  }
3370
3433
  truncate(text) {
3371
3434
  if (text.length <= this.maxLength) return text;
3372
- return text.slice(0, this.maxLength - 20) + "\n\\.\\.\\. *\\(truncated\\)*";
3435
+ return text.slice(0, this.maxLength - 20) + "\n... <i>(truncated)</i>";
3373
3436
  }
3374
3437
  };
3375
3438
  }
@@ -16291,7 +16354,7 @@ var init_channel3 = __esm({
16291
16354
  { title: "Get help", message: "/help" }
16292
16355
  ]
16293
16356
  });
16294
- await say("Hello! Use `/exec`, `/agent`, `/files`, or `/help`.");
16357
+ await say("Hello! Use `/exec`, `/agent`, `/skills`, `/files`, or `/help`.");
16295
16358
  },
16296
16359
  threadContextChanged: async ({ saveThreadContext }) => {
16297
16360
  await saveThreadContext();
@@ -16578,21 +16641,34 @@ var PermissionManager = class {
16578
16641
  return this.identityMap;
16579
16642
  }
16580
16643
  };
16581
-
16582
- // src/core/logger.ts
16583
- var LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
16644
+ var CONSOLA_LEVELS = {
16645
+ debug: 4,
16646
+ info: 3,
16647
+ warn: 2,
16648
+ error: 1
16649
+ };
16584
16650
  function createLogger(level = "info") {
16585
- const minLevel = LEVELS[level];
16586
- function log(lvl, msg, meta = {}) {
16587
- if (LEVELS[lvl] < minLevel) return;
16588
- const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level: lvl, msg, ...meta };
16589
- process.stderr.write(JSON.stringify(entry) + "\n");
16590
- }
16651
+ const consola = createConsola({
16652
+ level: CONSOLA_LEVELS[level],
16653
+ formatOptions: {
16654
+ date: true,
16655
+ colors: true
16656
+ }
16657
+ }).withTag("bot");
16591
16658
  return {
16592
- debug: (msg, meta) => log("debug", msg, meta ?? {}),
16593
- info: (msg, meta) => log("info", msg, meta ?? {}),
16594
- warn: (msg, meta) => log("warn", msg, meta ?? {}),
16595
- error: (msg, meta) => log("error", msg, meta ?? {})
16659
+ consola,
16660
+ debug(msg, meta) {
16661
+ meta ? consola.debug(msg, meta) : consola.debug(msg);
16662
+ },
16663
+ info(msg, meta) {
16664
+ meta ? consola.info(msg, meta) : consola.info(msg);
16665
+ },
16666
+ warn(msg, meta) {
16667
+ meta ? consola.warn(msg, meta) : consola.warn(msg);
16668
+ },
16669
+ error(msg, meta) {
16670
+ meta ? consola.error(msg, meta) : consola.error(msg);
16671
+ }
16596
16672
  };
16597
16673
  }
16598
16674
 
@@ -16699,7 +16775,21 @@ var AgentHandler = class extends BaseHandler {
16699
16775
  const text = result.text.length > this.maxOutput ? result.text.slice(0, this.maxOutput) + "\n...(truncated)" : result.text;
16700
16776
  return ok({ type: "text", text });
16701
16777
  } catch (e3) {
16702
- return err(new CMDOPError("Agent execution failed", e3 instanceof Error ? e3 : void 0));
16778
+ const errMsg = e3 instanceof Error ? e3.message : String(e3);
16779
+ this.logger.error("Agent execution failed", { error: errMsg });
16780
+ if (errMsg.includes("session_id") || errMsg.includes("No active session")) {
16781
+ return err(new CMDOPError("Machine is offline or CMDOP agent is not running.\nhttps://cmdop.com/downloads/"));
16782
+ }
16783
+ if (errMsg.includes("context canceled") || errMsg.includes("CANCELLED")) {
16784
+ return err(new CMDOPError("Request was interrupted. Please try again."));
16785
+ }
16786
+ if (errMsg.includes("DEADLINE_EXCEEDED") || errMsg.includes("timeout")) {
16787
+ return err(new CMDOPError("Request timed out. The task may be too complex \u2014 try a simpler prompt."));
16788
+ }
16789
+ if (errMsg.includes("UNAVAILABLE") || errMsg.includes("Connection refused")) {
16790
+ return err(new CMDOPError("Server unavailable. Check your connection and try again."));
16791
+ }
16792
+ return err(new CMDOPError(`Agent error: ${errMsg}`, e3 instanceof Error ? e3 : void 0));
16703
16793
  }
16704
16794
  }
16705
16795
  };
@@ -16796,6 +16886,103 @@ ${lines.join("\n\n")}`;
16796
16886
  }
16797
16887
  };
16798
16888
 
16889
+ // src/handlers/skills.ts
16890
+ init_errors();
16891
+ var SkillsHandler = class extends BaseHandler {
16892
+ name = "skills";
16893
+ description = "List, show, or run skills on the remote machine";
16894
+ usage = "/skills list | /skills show <name> | /skills run <name> <prompt>";
16895
+ requiredPermission = "EXECUTE";
16896
+ maxOutput;
16897
+ constructor(client, logger, config = {}) {
16898
+ super(client, logger);
16899
+ this.maxOutput = config.maxOutputLength ?? 4e3;
16900
+ }
16901
+ async handle(ctx) {
16902
+ const subcommand = ctx.args[0]?.toLowerCase();
16903
+ if (!subcommand) {
16904
+ return err(new CommandArgsError("skills", "Subcommand required. Usage: /skills list | /skills show <name> | /skills run <name> <prompt>"));
16905
+ }
16906
+ try {
16907
+ if (ctx.machine) {
16908
+ await this.client.skills.setMachine(ctx.machine);
16909
+ }
16910
+ switch (subcommand) {
16911
+ case "list":
16912
+ return await this.handleList();
16913
+ case "show":
16914
+ return await this.handleShow(ctx);
16915
+ case "run":
16916
+ return await this.handleRun(ctx);
16917
+ default:
16918
+ return err(new CommandArgsError("skills", `Unknown subcommand "${subcommand}". Usage: /skills list | /skills show <name> | /skills run <name> <prompt>`));
16919
+ }
16920
+ } catch (e3) {
16921
+ const errMsg = e3 instanceof Error ? e3.message : String(e3);
16922
+ this.logger.error("Skills operation failed", { error: errMsg });
16923
+ if (errMsg.includes("session_id") || errMsg.includes("No active session")) {
16924
+ return err(new CMDOPError("Machine is offline or CMDOP agent is not running.\nhttps://cmdop.com/downloads/"));
16925
+ }
16926
+ if (errMsg.includes("DEADLINE_EXCEEDED") || errMsg.includes("timeout")) {
16927
+ return err(new CMDOPError("Request timed out. The skill may be too complex \u2014 try a simpler prompt."));
16928
+ }
16929
+ if (errMsg.includes("UNAVAILABLE") || errMsg.includes("Connection refused")) {
16930
+ return err(new CMDOPError("Server unavailable. Check your connection and try again."));
16931
+ }
16932
+ return err(new CMDOPError(`Skills error: ${errMsg}`, e3 instanceof Error ? e3 : void 0));
16933
+ }
16934
+ }
16935
+ async handleList() {
16936
+ const skills = await this.client.skills.list();
16937
+ if (skills.length === 0) {
16938
+ return ok({ type: "text", text: "No skills installed." });
16939
+ }
16940
+ const lines = skills.map(
16941
+ (s4) => s4.description ? `${s4.name} \u2014 ${s4.description}` : s4.name
16942
+ );
16943
+ return ok({ type: "text", text: lines.join("\n") });
16944
+ }
16945
+ async handleShow(ctx) {
16946
+ const name = ctx.args[1];
16947
+ if (!name) {
16948
+ return err(new CommandArgsError("skills", "Skill name required. Usage: /skills show <name>"));
16949
+ }
16950
+ const detail = await this.client.skills.show(name);
16951
+ if (!detail.found) {
16952
+ return err(new CMDOPError(detail.error ?? `Skill "${name}" not found`));
16953
+ }
16954
+ const parts = [];
16955
+ if (detail.info) {
16956
+ parts.push(`Skill: ${detail.info.name}`);
16957
+ if (detail.info.description) parts.push(`Description: ${detail.info.description}`);
16958
+ if (detail.info.author) parts.push(`Author: ${detail.info.author}`);
16959
+ if (detail.info.version) parts.push(`Version: ${detail.info.version}`);
16960
+ if (detail.info.origin) parts.push(`Origin: ${detail.info.origin}`);
16961
+ }
16962
+ if (detail.source) parts.push(`Source: ${detail.source}`);
16963
+ if (detail.content) {
16964
+ const preview = detail.content.length > this.maxOutput ? detail.content.slice(0, this.maxOutput) + "\n...(truncated)" : detail.content;
16965
+ parts.push(`
16966
+ System prompt:
16967
+ ${preview}`);
16968
+ }
16969
+ return ok({ type: "code", code: parts.join("\n") });
16970
+ }
16971
+ async handleRun(ctx) {
16972
+ const name = ctx.args[1];
16973
+ if (!name) {
16974
+ return err(new CommandArgsError("skills", "Skill name and prompt required. Usage: /skills run <name> <prompt>"));
16975
+ }
16976
+ const prompt = ctx.args.slice(2).join(" ").trim();
16977
+ if (!prompt) {
16978
+ return err(new CommandArgsError("skills", "Prompt required. Usage: /skills run <name> <prompt>"));
16979
+ }
16980
+ const result = await this.client.skills.run(name, prompt);
16981
+ const code = result.text.length > this.maxOutput ? result.text.slice(0, this.maxOutput) + "\n...(truncated)" : result.text;
16982
+ return ok({ type: "code", code });
16983
+ }
16984
+ };
16985
+
16799
16986
  // src/config.ts
16800
16987
  init_errors();
16801
16988
  var BotSettingsSchema = z.object({
@@ -16820,6 +17007,63 @@ function loadSettings(env = process.env) {
16820
17007
  return result.data;
16821
17008
  }
16822
17009
 
17010
+ // src/channels/demo/channel.ts
17011
+ init_base_channel();
17012
+ var DemoChannel = class extends BaseChannel {
17013
+ messageHandlers = [];
17014
+ onOutput;
17015
+ constructor(permissions, dispatcher, logger, options2 = {}) {
17016
+ super("demo", "Demo", permissions, dispatcher, logger);
17017
+ this.onOutput = options2.onOutput ?? ((text) => process.stdout.write(text + "\n"));
17018
+ }
17019
+ async start() {
17020
+ this.logEvent("started");
17021
+ }
17022
+ async stop() {
17023
+ this.messageHandlers = [];
17024
+ this.logEvent("stopped");
17025
+ }
17026
+ send(_userId, message) {
17027
+ const text = formatOutgoing(message);
17028
+ this.onOutput(text, message);
17029
+ return Promise.resolve();
17030
+ }
17031
+ onMessage(handler) {
17032
+ this.messageHandlers.push(handler);
17033
+ }
17034
+ /**
17035
+ * Inject a message as if it came from a real platform user.
17036
+ * Primarily used in tests and CLI demos.
17037
+ */
17038
+ async injectMessage(input) {
17039
+ const msg = {
17040
+ id: `demo-${Date.now()}`,
17041
+ userId: input.userId ?? "demo-user",
17042
+ channelId: this.id,
17043
+ text: input.text,
17044
+ timestamp: /* @__PURE__ */ new Date(),
17045
+ attachments: []
17046
+ };
17047
+ for (const handler of this.messageHandlers) {
17048
+ await handler(msg);
17049
+ }
17050
+ await this.processMessage(msg);
17051
+ }
17052
+ };
17053
+ function formatOutgoing(message) {
17054
+ switch (message.type) {
17055
+ case "text":
17056
+ return message.text;
17057
+ case "code":
17058
+ return `\`\`\`${message.language ?? ""}
17059
+ ${message.code}
17060
+ \`\`\``;
17061
+ case "error":
17062
+ return `\u274C ${message.message}${message.hint ? `
17063
+ \u{1F4A1} ${message.hint}` : ""}`;
17064
+ }
17065
+ }
17066
+
16823
17067
  // src/hub.ts
16824
17068
  var IntegrationHub = class _IntegrationHub {
16825
17069
  constructor(_client, _logger, _settings, permissions, dispatcher, channelStartMode) {
@@ -16845,11 +17089,12 @@ var IntegrationHub = class _IntegrationHub {
16845
17089
  Object.assign(settings2, options2.settings ?? {});
16846
17090
  if (options2.defaultMachine) settings2.defaultMachine = options2.defaultMachine;
16847
17091
  const logger = options2.logger ?? createLogger(settings2.logLevel);
16848
- const client = options2.apiKey ? await CMDOPClient.remote(options2.apiKey) : await CMDOPClient.local();
17092
+ const client = options2.apiKey ? CMDOPClient.remote(options2.apiKey) : CMDOPClient.local();
16849
17093
  const store = options2.permissionStore ?? new InMemoryPermissionStore();
16850
17094
  const identityMap = new IdentityMap();
16851
17095
  const permissions = new PermissionManager(store, {
16852
17096
  adminUsers: [...options2.adminUsers ?? [], ...settings2.allowedUsers],
17097
+ defaultLevel: options2.defaultPermission,
16853
17098
  identityMap
16854
17099
  });
16855
17100
  const dispatcher = new MessageDispatcher(permissions, logger);
@@ -16857,6 +17102,7 @@ var IntegrationHub = class _IntegrationHub {
16857
17102
  const hub = new _IntegrationHub(client, logger, settings2, permissions, dispatcher, mode);
16858
17103
  hub.registerHandler(new TerminalHandler(client, logger, { maxOutputLength: settings2.maxOutputLength }));
16859
17104
  hub.registerHandler(new AgentHandler(client, logger, { maxOutputLength: settings2.maxOutputLength }));
17105
+ hub.registerHandler(new SkillsHandler(client, logger, { maxOutputLength: settings2.maxOutputLength }));
16860
17106
  hub.registerHandler(new FilesHandler(client, logger));
16861
17107
  hub.registerHandler(new HelpHandler(client, logger, {
16862
17108
  getCommands: () => dispatcher.getCommandList()
@@ -16896,6 +17142,15 @@ var IntegrationHub = class _IntegrationHub {
16896
17142
  const channel = new SlackChannel2(options2, this._permissions, this._dispatcher, this._logger);
16897
17143
  return this.registerChannel(channel);
16898
17144
  }
17145
+ /**
17146
+ * Create and register a DemoChannel for CLI testing.
17147
+ * No external dependencies — messages are injected via the returned channel's `injectMessage()`.
17148
+ */
17149
+ addDemo(options2) {
17150
+ const channel = new DemoChannel(this._permissions, this._dispatcher, this._logger, options2);
17151
+ this.registerChannel(channel);
17152
+ return channel;
17153
+ }
16899
17154
  registerHandler(handler) {
16900
17155
  this._dispatcher.register(handler);
16901
17156
  return this;
@@ -16904,6 +17159,17 @@ var IntegrationHub = class _IntegrationHub {
16904
17159
  async start() {
16905
17160
  if (this.started) return;
16906
17161
  this.started = true;
17162
+ if (this._settings.defaultMachine) {
17163
+ try {
17164
+ await this._client.setMachine(this._settings.defaultMachine);
17165
+ this._logger.info("Machine routing initialized", { machine: this._settings.defaultMachine });
17166
+ } catch (err2) {
17167
+ this._logger.error("Failed to initialize machine routing", {
17168
+ machine: this._settings.defaultMachine,
17169
+ err: err2 instanceof Error ? err2.message : String(err2)
17170
+ });
17171
+ }
17172
+ }
16907
17173
  if (this.channelStartMode === "strict") {
16908
17174
  await Promise.all([...this.channels.values()].map(async (ch) => {
16909
17175
  await ch.start();
@@ -17166,63 +17432,6 @@ var SlackStream = class _SlackStream {
17166
17432
  }
17167
17433
  };
17168
17434
 
17169
- // src/channels/demo/channel.ts
17170
- init_base_channel();
17171
- var DemoChannel = class extends BaseChannel {
17172
- messageHandlers = [];
17173
- onOutput;
17174
- constructor(permissions, dispatcher, logger, options2 = {}) {
17175
- super("demo", "Demo", permissions, dispatcher, logger);
17176
- this.onOutput = options2.onOutput ?? ((text) => process.stdout.write(text + "\n"));
17177
- }
17178
- async start() {
17179
- this.logEvent("started");
17180
- }
17181
- async stop() {
17182
- this.messageHandlers = [];
17183
- this.logEvent("stopped");
17184
- }
17185
- send(_userId, message) {
17186
- const text = formatOutgoing(message);
17187
- this.onOutput(text, message);
17188
- return Promise.resolve();
17189
- }
17190
- onMessage(handler) {
17191
- this.messageHandlers.push(handler);
17192
- }
17193
- /**
17194
- * Inject a message as if it came from a real platform user.
17195
- * Primarily used in tests and CLI demos.
17196
- */
17197
- async injectMessage(input) {
17198
- const msg = {
17199
- id: `demo-${Date.now()}`,
17200
- userId: input.userId ?? "demo-user",
17201
- channelId: this.id,
17202
- text: input.text,
17203
- timestamp: /* @__PURE__ */ new Date(),
17204
- attachments: []
17205
- };
17206
- for (const handler of this.messageHandlers) {
17207
- await handler(msg);
17208
- }
17209
- await this.processMessage(msg);
17210
- }
17211
- };
17212
- function formatOutgoing(message) {
17213
- switch (message.type) {
17214
- case "text":
17215
- return message.text;
17216
- case "code":
17217
- return `\`\`\`${message.language ?? ""}
17218
- ${message.code}
17219
- \`\`\``;
17220
- case "error":
17221
- return `\u274C ${message.message}${message.hint ? `
17222
- \u{1F4A1} ${message.hint}` : ""}`;
17223
- }
17224
- }
17225
-
17226
17435
  // src/channels/telegram/index.ts
17227
17436
  init_channel();
17228
17437
  init_formatter();