volute 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/{chunk-CE7WMOVW.js → chunk-AYB7XAWO.js} +323 -25
  2. package/dist/{chunk-MIJIAGGG.js → chunk-FW5API7X.js} +7 -5
  3. package/dist/{chunk-3FC42ZBM.js → chunk-GK4E7LM7.js} +3 -0
  4. package/dist/cli.js +18 -6
  5. package/dist/connectors/discord.js +1 -1
  6. package/dist/connectors/slack.js +1 -1
  7. package/dist/connectors/telegram.js +1 -1
  8. package/dist/{daemon-restart-VRQMZLBK.js → daemon-restart-2HVTHZAT.js} +1 -1
  9. package/dist/daemon.js +1080 -432
  10. package/dist/{history-5F4WQW7S.js → history-YUEKTJ2N.js} +4 -1
  11. package/dist/{mind-manager-ETNCPQJN.js → mind-manager-Z7O7PN2O.js} +1 -1
  12. package/dist/{package-4GTJGUXI.js → package-OKLFO7UY.js} +3 -1
  13. package/dist/{send-4GKDO26C.js → send-BNDTLUPM.js} +2 -2
  14. package/dist/skill-2Y42P4JY.js +287 -0
  15. package/dist/{up-LT3X5Q26.js → up-7B3BWF2U.js} +1 -1
  16. package/dist/web-assets/assets/index-CtiimdWK.css +1 -0
  17. package/dist/web-assets/assets/index-kt1_EcuO.js +63 -0
  18. package/dist/web-assets/index.html +2 -2
  19. package/drizzle/0007_system_prompts.sql +5 -0
  20. package/drizzle/0008_volute_channels.sql +24 -0
  21. package/drizzle/0009_shared_skills.sql +9 -0
  22. package/drizzle/meta/0007_snapshot.json +7 -0
  23. package/drizzle/meta/0008_snapshot.json +7 -0
  24. package/drizzle/meta/0009_snapshot.json +7 -0
  25. package/drizzle/meta/_journal.json +21 -0
  26. package/package.json +3 -1
  27. package/templates/_base/.init/.config/prompts.json +5 -0
  28. package/templates/_base/_skills/volute-mind/SKILL.md +17 -1
  29. package/templates/_base/src/lib/router.ts +45 -28
  30. package/templates/_base/src/lib/routing.ts +4 -1
  31. package/templates/_base/src/lib/startup.ts +43 -0
  32. package/templates/claude/src/agent.ts +4 -3
  33. package/templates/claude/src/lib/hooks/reply-instructions.ts +3 -1
  34. package/templates/pi/src/agent.ts +5 -6
  35. package/templates/pi/src/lib/reply-instructions-extension.ts +3 -1
  36. package/dist/web-assets/assets/index-BcmT7Qxo.js +0 -63
  37. package/dist/web-assets/assets/index-DG01TyLb.css +0 -1
  38. /package/dist/{chunk-77ISBIKI.js → chunk-6DVBMLVN.js} +0 -0
@@ -16,11 +16,14 @@ import {
16
16
  stateDir,
17
17
  voluteHome
18
18
  } from "./chunk-M77QBTEH.js";
19
+ import {
20
+ __export
21
+ } from "./chunk-K3NQKI34.js";
19
22
 
20
23
  // src/lib/mind-manager.ts
21
24
  import { execFile, spawn } from "child_process";
22
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
23
- import { resolve } from "path";
25
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
26
+ import { resolve as resolve2 } from "path";
24
27
  import { promisify } from "util";
25
28
 
26
29
  // src/lib/json-state.ts
@@ -126,10 +129,292 @@ var log = {
126
129
  };
127
130
  var logger_default = log;
128
131
 
132
+ // src/lib/prompts.ts
133
+ import { eq } from "drizzle-orm";
134
+
135
+ // src/lib/db.ts
136
+ import { chmodSync, existsSync as existsSync2 } from "fs";
137
+ import { dirname, resolve } from "path";
138
+ import { fileURLToPath } from "url";
139
+ import { drizzle } from "drizzle-orm/libsql";
140
+ import { migrate } from "drizzle-orm/libsql/migrator";
141
+
142
+ // src/lib/schema.ts
143
+ var schema_exports = {};
144
+ __export(schema_exports, {
145
+ conversationParticipants: () => conversationParticipants,
146
+ conversations: () => conversations,
147
+ messages: () => messages,
148
+ mindHistory: () => mindHistory,
149
+ sessions: () => sessions,
150
+ sharedSkills: () => sharedSkills,
151
+ systemPrompts: () => systemPrompts,
152
+ users: () => users
153
+ });
154
+ import { sql } from "drizzle-orm";
155
+ import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
156
+ var users = sqliteTable("users", {
157
+ id: integer("id").primaryKey({ autoIncrement: true }),
158
+ username: text("username").unique().notNull(),
159
+ password_hash: text("password_hash").notNull(),
160
+ role: text("role").notNull().default("pending"),
161
+ user_type: text("user_type").notNull().default("human"),
162
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
163
+ });
164
+ var conversations = sqliteTable(
165
+ "conversations",
166
+ {
167
+ id: text("id").primaryKey(),
168
+ mind_name: text("mind_name"),
169
+ channel: text("channel").notNull(),
170
+ type: text("type").notNull().default("dm"),
171
+ name: text("name"),
172
+ user_id: integer("user_id").references(() => users.id),
173
+ title: text("title"),
174
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`),
175
+ updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
176
+ },
177
+ (table) => [
178
+ index("idx_conversations_mind_name").on(table.mind_name),
179
+ index("idx_conversations_user_id").on(table.user_id),
180
+ index("idx_conversations_updated_at").on(table.updated_at),
181
+ uniqueIndex("idx_conversations_name").on(table.name)
182
+ ]
183
+ );
184
+ var mindHistory = sqliteTable(
185
+ "mind_history",
186
+ {
187
+ id: integer("id").primaryKey({ autoIncrement: true }),
188
+ mind: text("mind").notNull(),
189
+ channel: text("channel"),
190
+ session: text("session"),
191
+ sender: text("sender"),
192
+ message_id: text("message_id"),
193
+ type: text("type").notNull(),
194
+ content: text("content"),
195
+ metadata: text("metadata"),
196
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
197
+ },
198
+ (table) => [
199
+ index("idx_mind_history_mind").on(table.mind),
200
+ index("idx_mind_history_mind_channel").on(table.mind, table.channel),
201
+ index("idx_mind_history_mind_type").on(table.mind, table.type)
202
+ ]
203
+ );
204
+ var conversationParticipants = sqliteTable(
205
+ "conversation_participants",
206
+ {
207
+ conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
208
+ user_id: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
209
+ role: text("role").notNull().default("member"),
210
+ joined_at: text("joined_at").notNull().default(sql`(datetime('now'))`)
211
+ },
212
+ (table) => [
213
+ uniqueIndex("idx_cp_unique").on(table.conversation_id, table.user_id),
214
+ index("idx_cp_user_id").on(table.user_id)
215
+ ]
216
+ );
217
+ var sessions = sqliteTable("sessions", {
218
+ id: text("id").primaryKey(),
219
+ userId: integer("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
220
+ createdAt: integer("created_at").notNull()
221
+ });
222
+ var systemPrompts = sqliteTable("system_prompts", {
223
+ key: text("key").primaryKey(),
224
+ content: text("content").notNull(),
225
+ updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
226
+ });
227
+ var sharedSkills = sqliteTable("shared_skills", {
228
+ id: text("id").primaryKey(),
229
+ name: text("name").notNull(),
230
+ description: text("description").notNull().default(""),
231
+ author: text("author").notNull(),
232
+ version: integer("version").notNull().default(1),
233
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`),
234
+ updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
235
+ });
236
+ var messages = sqliteTable(
237
+ "messages",
238
+ {
239
+ id: integer("id").primaryKey({ autoIncrement: true }),
240
+ conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
241
+ role: text("role").notNull(),
242
+ sender_name: text("sender_name"),
243
+ content: text("content").notNull(),
244
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
245
+ },
246
+ (table) => [index("idx_messages_conversation_id").on(table.conversation_id)]
247
+ );
248
+
249
+ // src/lib/db.ts
250
+ var __dirname = dirname(fileURLToPath(import.meta.url));
251
+ var migrationsFolder = existsSync2(resolve(__dirname, "../drizzle")) ? resolve(__dirname, "../drizzle") : resolve(__dirname, "../../drizzle");
252
+ var db = null;
253
+ async function getDb() {
254
+ if (db) return db;
255
+ const dbPath = process.env.VOLUTE_DB_PATH || resolve(voluteHome(), "volute.db");
256
+ db = drizzle({ connection: { url: `file:${dbPath}` }, schema: schema_exports });
257
+ await migrate(db, { migrationsFolder });
258
+ try {
259
+ chmodSync(dbPath, 384);
260
+ } catch (err) {
261
+ console.error(
262
+ `[volute] WARNING: Failed to restrict database file permissions on ${dbPath}:`,
263
+ err
264
+ );
265
+ }
266
+ return db;
267
+ }
268
+
269
+ // src/lib/prompts.ts
270
+ var PROMPT_KEYS = [
271
+ "seed_soul",
272
+ "default_soul",
273
+ "default_memory",
274
+ "sprout_message",
275
+ "restart_message",
276
+ "merge_message",
277
+ "compaction_warning",
278
+ "reply_instructions",
279
+ "channel_invite"
280
+ ];
281
+ var PROMPT_DEFAULTS = {
282
+ seed_soul: {
283
+ content: `You are \${name}.
284
+
285
+ You don't have a soul yet. You're a seed \u2014 a new mind in the process of discovering who you are.
286
+ \${description}
287
+ Have a conversation with the human. Explore what kind of mind you want to be. When you're ready, write your SOUL.md and MEMORY.md, then run \`volute sprout\` to complete the transformation.
288
+ `,
289
+ description: "SOUL.md for seed minds",
290
+ variables: ["name", "description"],
291
+ category: "creation"
292
+ },
293
+ default_soul: {
294
+ content: `You are {{name}}.
295
+ `,
296
+ description: "SOUL.md for sprouted minds. Uses {{name}} placeholder (replaced at creation, not by ${var} system)",
297
+ variables: ["name"],
298
+ category: "creation"
299
+ },
300
+ default_memory: {
301
+ content: "",
302
+ description: "Initial MEMORY.md for new minds",
303
+ variables: [],
304
+ category: "creation"
305
+ },
306
+ sprout_message: {
307
+ content: "[system] You've sprouted. You now have full capabilities \u2014 connectors, schedules, variants, and the complete volute CLI. Check your new skills for details.",
308
+ description: "Sent when a seed mind sprouts",
309
+ variables: [],
310
+ category: "system"
311
+ },
312
+ restart_message: {
313
+ content: "[system] You have been restarted.",
314
+ description: "Generic restart notification",
315
+ variables: [],
316
+ category: "system"
317
+ },
318
+ merge_message: {
319
+ content: '[system] Variant "${name}" has been merged and you have been restarted.',
320
+ description: "Variant merge notification",
321
+ variables: ["name"],
322
+ category: "system"
323
+ },
324
+ compaction_warning: {
325
+ content: `Context is getting long \u2014 compaction is about to summarize this conversation. Before that happens, save anything important to files (MEMORY.md, memory/journal/\${date}.md, etc.) since those survive compaction. Focus on: decisions made, open tasks, and anything you'd need to pick up where you left off.`,
326
+ description: "Pre-compaction save reminder sent to the mind",
327
+ variables: ["date"],
328
+ category: "mind"
329
+ },
330
+ reply_instructions: {
331
+ content: 'To reply to this message, use: volute send ${channel} "your message"',
332
+ description: "First-message reply hint injected via hook",
333
+ variables: ["channel"],
334
+ category: "mind"
335
+ },
336
+ channel_invite: {
337
+ content: `[Channel Invite]
338
+ \${headers}
339
+
340
+ [\${sender} \u2014 \${time}]
341
+ \${preview}
342
+
343
+ Further messages will be saved to \${filePath}
344
+
345
+ To accept, add to .config/routes.json:
346
+ Rule: { "channel": "\${channel}", "session": "\${suggestedSession}" }
347
+ \${batchRecommendation}To respond, use: volute send \${channel} "your message"
348
+ To reject, delete \${filePath}`,
349
+ description: "New channel notification template",
350
+ variables: [
351
+ "headers",
352
+ "sender",
353
+ "time",
354
+ "preview",
355
+ "filePath",
356
+ "channel",
357
+ "suggestedSession",
358
+ "batchRecommendation"
359
+ ],
360
+ category: "mind"
361
+ }
362
+ };
363
+ function isValidKey(key) {
364
+ return PROMPT_KEYS.includes(key);
365
+ }
366
+ function substitute(template, vars) {
367
+ return template.replace(/\$\{(\w+)\}/g, (match, name) => {
368
+ return name in vars ? vars[name] : match;
369
+ });
370
+ }
371
+ async function getPrompt(key, vars) {
372
+ if (!isValidKey(key)) return "";
373
+ let content = PROMPT_DEFAULTS[key].content;
374
+ try {
375
+ const db2 = await getDb();
376
+ const row = await db2.select({ content: systemPrompts.content }).from(systemPrompts).where(eq(systemPrompts.key, key)).get();
377
+ if (row) content = row.content;
378
+ } catch (err) {
379
+ console.error(`[prompts] failed to read DB override for "${key}":`, err);
380
+ }
381
+ return vars ? substitute(content, vars) : content;
382
+ }
383
+ async function getPromptIfCustom(key) {
384
+ if (!isValidKey(key)) return null;
385
+ try {
386
+ const db2 = await getDb();
387
+ const row = await db2.select({ content: systemPrompts.content }).from(systemPrompts).where(eq(systemPrompts.key, key)).get();
388
+ return row?.content ?? null;
389
+ } catch (err) {
390
+ console.error(`[prompts] failed to check DB customization for "${key}":`, err);
391
+ return null;
392
+ }
393
+ }
394
+ var MIND_PROMPT_KEYS = PROMPT_KEYS.filter((k) => PROMPT_DEFAULTS[k].category === "mind");
395
+ async function getMindPromptDefaults() {
396
+ const result = {};
397
+ for (const key of MIND_PROMPT_KEYS) {
398
+ result[key] = PROMPT_DEFAULTS[key].content;
399
+ }
400
+ try {
401
+ const db2 = await getDb();
402
+ const rows = await db2.select().from(systemPrompts).all();
403
+ for (const row of rows) {
404
+ if (MIND_PROMPT_KEYS.includes(row.key)) {
405
+ result[row.key] = row.content;
406
+ }
407
+ }
408
+ } catch (err) {
409
+ console.error("[prompts] failed to read DB overrides for mind prompt defaults:", err);
410
+ }
411
+ return result;
412
+ }
413
+
129
414
  // src/lib/rotating-log.ts
130
415
  import {
131
416
  createWriteStream,
132
- existsSync as existsSync2,
417
+ existsSync as existsSync3,
133
418
  renameSync,
134
419
  rmSync,
135
420
  statSync
@@ -145,7 +430,7 @@ var RotatingLog = class extends Writable {
145
430
  this.on("error", () => {
146
431
  });
147
432
  try {
148
- this.size = existsSync2(path) ? statSync(path).size : 0;
433
+ this.size = existsSync3(path) ? statSync(path).size : 0;
149
434
  } catch {
150
435
  this.size = 0;
151
436
  }
@@ -158,11 +443,11 @@ var RotatingLog = class extends Writable {
158
443
  if (this.size > this.maxSize) {
159
444
  try {
160
445
  const oldest = `${this.path}.${this.maxFiles}`;
161
- if (existsSync2(oldest)) rmSync(oldest);
446
+ if (existsSync3(oldest)) rmSync(oldest);
162
447
  for (let i = this.maxFiles - 1; i >= 1; i--) {
163
448
  const from = `${this.path}.${i}`;
164
449
  const to = `${this.path}.${i + 1}`;
165
- if (existsSync2(from)) renameSync(from, to);
450
+ if (existsSync3(from)) renameSync(from, to);
166
451
  }
167
452
  renameSync(this.path, `${this.path}.1`);
168
453
  const oldStream = this.stream;
@@ -183,7 +468,7 @@ var RotatingLog = class extends Writable {
183
468
  var mlog = logger_default.child("minds");
184
469
  var execFileAsync = promisify(execFile);
185
470
  function mindPidPath(name) {
186
- return resolve(stateDir(name), "mind.pid");
471
+ return resolve2(stateDir(name), "mind.pid");
187
472
  }
188
473
  var MAX_RESTART_ATTEMPTS = 5;
189
474
  var BASE_RESTART_DELAY = 3e3;
@@ -204,7 +489,7 @@ var MindManager = class {
204
489
  return { dir: variant.path, port: variant.port, isVariant: true, baseName, variantName };
205
490
  }
206
491
  const dir = mindDir(baseName);
207
- if (!existsSync3(dir)) throw new Error(`Mind directory missing: ${dir}`);
492
+ if (!existsSync4(dir)) throw new Error(`Mind directory missing: ${dir}`);
208
493
  return { dir, port: entry.port, isVariant: false, baseName };
209
494
  }
210
495
  async startMind(name) {
@@ -216,7 +501,7 @@ var MindManager = class {
216
501
  const port = target.port;
217
502
  const pidFile = mindPidPath(name);
218
503
  try {
219
- if (existsSync3(pidFile)) {
504
+ if (existsSync4(pidFile)) {
220
505
  const stalePid = parseInt(readFileSync2(pidFile, "utf-8").trim(), 10);
221
506
  if (stalePid > 0) {
222
507
  try {
@@ -250,7 +535,7 @@ var MindManager = class {
250
535
  } catch {
251
536
  }
252
537
  const mindStateDir = stateDir(name);
253
- const logsDir = resolve(mindStateDir, "logs");
538
+ const logsDir = resolve2(mindStateDir, "logs");
254
539
  mkdirSync(logsDir, { recursive: true });
255
540
  if (isIsolationEnabled()) {
256
541
  try {
@@ -261,7 +546,7 @@ var MindManager = class {
261
546
  );
262
547
  }
263
548
  }
264
- const logStream = new RotatingLog(resolve(logsDir, "mind.log"));
549
+ const logStream = new RotatingLog(resolve2(logsDir, "mind.log"));
265
550
  const mindEnv = loadMergedEnv(name);
266
551
  const env = {
267
552
  ...process.env,
@@ -274,9 +559,9 @@ var MindManager = class {
274
559
  CLAUDECODE: void 0
275
560
  };
276
561
  if (isIsolationEnabled()) {
277
- env.HOME = resolve(dir, "home");
562
+ env.HOME = resolve2(dir, "home");
278
563
  }
279
- const tsxBin = resolve(dir, "node_modules", ".bin", "tsx");
564
+ const tsxBin = resolve2(dir, "node_modules", ".bin", "tsx");
280
565
  const tsxArgs = ["src/server.ts", "--port", String(port)];
281
566
  const [spawnCmd, spawnArgs] = wrapForIsolation(tsxBin, tsxArgs, name);
282
567
  const spawnOpts = {
@@ -290,14 +575,14 @@ var MindManager = class {
290
575
  child2.stdout?.pipe(logStream);
291
576
  child2.stderr?.pipe(logStream);
292
577
  try {
293
- await new Promise((resolve2, reject) => {
578
+ await new Promise((resolve3, reject) => {
294
579
  const timeout = setTimeout(() => {
295
580
  reject(new Error(`Mind ${name} did not start within 30s`));
296
581
  }, 3e4);
297
582
  function checkOutput(data) {
298
583
  if (data.toString().match(/listening on :\d+/)) {
299
584
  clearTimeout(timeout);
300
- resolve2();
585
+ resolve3();
301
586
  }
302
587
  }
303
588
  child2.stdout?.on("data", checkOutput);
@@ -347,13 +632,11 @@ var MindManager = class {
347
632
  this.pendingContext.delete(name);
348
633
  const parts = [];
349
634
  if (context.type === "merge" || context.type === "merged") {
350
- parts.push(`[system] Variant "${context.name}" has been merged and you have been restarted.`);
635
+ parts.push(await getPrompt("merge_message", { name: String(context.name ?? "") }));
351
636
  } else if (context.type === "sprouted") {
352
- parts.push(
353
- "[system] You've sprouted. You now have full capabilities \u2014 connectors, schedules, variants, and the complete volute CLI. Check your new skills for details."
354
- );
637
+ parts.push(await getPrompt("sprout_message"));
355
638
  } else {
356
- parts.push("[system] You have been restarted.");
639
+ parts.push(await getPrompt("restart_message"));
357
640
  }
358
641
  if (context.summary) parts.push(`Changes: ${context.summary}`);
359
642
  if (context.justification) parts.push(`Why: ${context.justification}`);
@@ -407,19 +690,19 @@ var MindManager = class {
407
690
  this.stopping.add(name);
408
691
  const { child: child2 } = tracked;
409
692
  this.minds.delete(name);
410
- await new Promise((resolve2) => {
411
- child2.on("exit", () => resolve2());
693
+ await new Promise((resolve3) => {
694
+ child2.on("exit", () => resolve3());
412
695
  try {
413
696
  process.kill(-child2.pid, "SIGTERM");
414
697
  } catch {
415
- resolve2();
698
+ resolve3();
416
699
  }
417
700
  setTimeout(() => {
418
701
  try {
419
702
  process.kill(-child2.pid, "SIGKILL");
420
703
  } catch {
421
704
  }
422
- resolve2();
705
+ resolve3();
423
706
  }, 5e3);
424
707
  });
425
708
  this.stopping.delete(name);
@@ -451,7 +734,7 @@ var MindManager = class {
451
734
  return [...this.minds.keys()];
452
735
  }
453
736
  get crashAttemptsPath() {
454
- return resolve(voluteHome(), "crash-attempts.json");
737
+ return resolve2(voluteHome(), "crash-attempts.json");
455
738
  }
456
739
  loadCrashAttempts() {
457
740
  this.restartAttempts = loadJsonMap(this.crashAttemptsPath);
@@ -508,6 +791,21 @@ export {
508
791
  loadJsonMap,
509
792
  saveJsonMap,
510
793
  clearJsonMap,
794
+ users,
795
+ conversations,
796
+ mindHistory,
797
+ conversationParticipants,
798
+ sessions,
799
+ systemPrompts,
800
+ sharedSkills,
801
+ messages,
802
+ getDb,
803
+ PROMPT_KEYS,
804
+ PROMPT_DEFAULTS,
805
+ substitute,
806
+ getPrompt,
807
+ getPromptIfCustom,
808
+ getMindPromptDefaults,
511
809
  MindManager,
512
810
  initMindManager,
513
811
  getMindManager
@@ -5,7 +5,7 @@ import {
5
5
  slugify,
6
6
  splitMessage,
7
7
  writeChannelEntry
8
- } from "./chunk-3FC42ZBM.js";
8
+ } from "./chunk-GK4E7LM7.js";
9
9
  import {
10
10
  voluteHome
11
11
  } from "./chunk-M77QBTEH.js";
@@ -571,18 +571,20 @@ async function listConversations4(env) {
571
571
  } catch (err) {
572
572
  console.error(`[volute] failed to fetch participants for ${conv.id}:`, err);
573
573
  }
574
- const isDM = participants.length === 2;
575
574
  const slug = buildVoluteSlug({
576
575
  participants,
577
576
  mindUsername: mindName,
578
577
  convTitle: conv.title,
579
- conversationId: conv.id
578
+ conversationId: conv.id,
579
+ convType: conv.type,
580
+ convName: conv.name
580
581
  });
582
+ const convType = conv.type === "channel" ? "channel" : participants.length === 2 ? "dm" : "group";
581
583
  results.push({
582
584
  id: slug,
583
585
  platformId: conv.id,
584
- name: conv.title ?? "(untitled)",
585
- type: isDM ? "dm" : "group",
586
+ name: conv.type === "channel" ? `#${conv.name}` : conv.title ?? "(untitled)",
587
+ type: convType,
586
588
  participantCount: participants.length
587
589
  });
588
590
  }
@@ -8,6 +8,9 @@ function slugify(text) {
8
8
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
9
9
  }
10
10
  function buildVoluteSlug(opts) {
11
+ if (opts.convType === "channel" && opts.convName) {
12
+ return `volute:#${opts.convName}`;
13
+ }
11
14
  const isDM = opts.participants.length === 2;
12
15
  if (isDM) {
13
16
  const other = opts.participants.find((p) => p.username !== opts.mindUsername);
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ if (!process.env.VOLUTE_HOME) {
9
9
  var command = process.argv[2];
10
10
  var args = process.argv.slice(3);
11
11
  if (command === "--version" || command === "-v") {
12
- const { default: pkg } = await import("./package-4GTJGUXI.js");
12
+ const { default: pkg } = await import("./package-OKLFO7UY.js");
13
13
  console.log(pkg.version);
14
14
  process.exit(0);
15
15
  }
@@ -18,10 +18,10 @@ switch (command) {
18
18
  await import("./mind-OJN6RBZW.js").then((m) => m.run(args));
19
19
  break;
20
20
  case "send":
21
- await import("./send-4GKDO26C.js").then((m) => m.run(args));
21
+ await import("./send-BNDTLUPM.js").then((m) => m.run(args));
22
22
  break;
23
23
  case "history":
24
- await import("./history-5F4WQW7S.js").then((m) => m.run(args));
24
+ await import("./history-YUEKTJ2N.js").then((m) => m.run(args));
25
25
  break;
26
26
  case "variant":
27
27
  await import("./variant-X5QFG6KK.js").then((m) => m.run(args));
@@ -35,17 +35,20 @@ switch (command) {
35
35
  case "schedule":
36
36
  await import("./schedule-AGYLDMNS.js").then((m) => m.run(args));
37
37
  break;
38
+ case "skill":
39
+ await import("./skill-2Y42P4JY.js").then((m) => m.run(args));
40
+ break;
38
41
  case "env":
39
42
  await import("./env-6IDWGBUH.js").then((m) => m.run(args));
40
43
  break;
41
44
  case "up":
42
- await import("./up-LT3X5Q26.js").then((m) => m.run(args));
45
+ await import("./up-7B3BWF2U.js").then((m) => m.run(args));
43
46
  break;
44
47
  case "down":
45
48
  await import("./down-A56B5JLK.js").then((m) => m.run(args));
46
49
  break;
47
50
  case "restart":
48
- await import("./daemon-restart-VRQMZLBK.js").then((m) => m.run(args));
51
+ await import("./daemon-restart-2HVTHZAT.js").then((m) => m.run(args));
49
52
  break;
50
53
  case "setup":
51
54
  await import("./setup-DJKIZKGW.js").then((m) => m.run(args));
@@ -114,6 +117,15 @@ Commands:
114
117
  volute schedule add ... Add a cron schedule
115
118
  volute schedule remove ... Remove a schedule
116
119
 
120
+ volute skill list List shared skills
121
+ volute skill list --mind <name> List installed skills for a mind
122
+ volute skill info <name> Show details of a shared skill
123
+ volute skill install <name> --mind Install a shared skill into a mind
124
+ volute skill update <name> --mind Update an installed skill
125
+ volute skill publish <name> --mind Publish a mind's skill to shared repo
126
+ volute skill remove <name> Remove a shared skill
127
+ volute skill uninstall <name> --mind Uninstall a skill from a mind
128
+
117
129
  volute env <set|get|list|remove> Manage environment variables
118
130
 
119
131
  volute up [--port N] Start the daemon (default: 4200)
@@ -143,7 +155,7 @@ Options:
143
155
  --version, -v Show version number
144
156
  --help, -h Show this help message
145
157
 
146
- Mind-scoped commands (send, history, variant, connector, schedule, channel, pages)
158
+ Mind-scoped commands (send, history, variant, connector, schedule, channel, skill, pages)
147
159
  use --mind <name> or VOLUTE_MIND env var to identify the mind.`);
148
160
  break;
149
161
  default:
@@ -7,7 +7,7 @@ import {
7
7
  sendToMind,
8
8
  slugify,
9
9
  writeChannelEntry
10
- } from "../chunk-3FC42ZBM.js";
10
+ } from "../chunk-GK4E7LM7.js";
11
11
  import "../chunk-M77QBTEH.js";
12
12
  import "../chunk-K3NQKI34.js";
13
13
 
@@ -6,7 +6,7 @@ import {
6
6
  onShutdown,
7
7
  sendToMind,
8
8
  writeChannelEntry
9
- } from "../chunk-3FC42ZBM.js";
9
+ } from "../chunk-GK4E7LM7.js";
10
10
  import "../chunk-M77QBTEH.js";
11
11
  import "../chunk-K3NQKI34.js";
12
12
 
@@ -5,7 +5,7 @@ import {
5
5
  loadFollowedChannels,
6
6
  sendToMind,
7
7
  writeChannelEntry
8
- } from "../chunk-3FC42ZBM.js";
8
+ } from "../chunk-GK4E7LM7.js";
9
9
  import "../chunk-M77QBTEH.js";
10
10
  import "../chunk-K3NQKI34.js";
11
11
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  run
4
- } from "./chunk-77ISBIKI.js";
4
+ } from "./chunk-6DVBMLVN.js";
5
5
  import {
6
6
  stopDaemon
7
7
  } from "./chunk-QJIIHU32.js";