heyio 0.23.0 → 0.24.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.
@@ -4,7 +4,7 @@ import { execSync, execFileSync } from "child_process";
4
4
  import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, mkdirSync } from "fs";
5
5
  import { join, dirname, resolve } from "path";
6
6
  import { homedir } from "os";
7
- import { UNIVERSES, getOrCreateUniverse } from "./universes.js";
7
+ import { UNIVERSES, getOrCreateUniverse, generateUniverseRoster } from "./universes.js";
8
8
  import { createFeedEntry } from "../store/feed.js";
9
9
  import { validateCron, nextRun } from "./cron.js";
10
10
  import { createIoSchedule, deleteIoSchedule, getIoSchedule, listIoSchedules, setIoScheduleEnabled, updateIoScheduleNextRun, } from "../store/io-schedules.js";
@@ -300,6 +300,9 @@ export function createTools(deps) {
300
300
  }),
301
301
  handler: async ({ slug, name, project_path, universe }) => {
302
302
  try {
303
+ if (universe) {
304
+ await generateUniverseRoster(universe);
305
+ }
303
306
  deps.createSquad(slug, name, project_path, universe);
304
307
  const squad = deps.getSquad(slug);
305
308
  const universeName = squad?.universe ? getOrCreateUniverse(squad.universe).name : "random";
@@ -378,6 +378,69 @@ export function getOrCreateUniverse(input) {
378
378
  UNIVERSES.push(custom);
379
379
  return custom;
380
380
  }
381
+ /**
382
+ * Generate a universe roster using the LLM for unknown universes.
383
+ * Falls back to archetype characters if the LLM call fails.
384
+ * For known/well-known universes, returns them directly without an LLM call.
385
+ */
386
+ export async function generateUniverseRoster(input) {
387
+ // First try synchronous resolution
388
+ const existing = getOrCreateUniverse(input);
389
+ // If it resolved to something other than archetypes, use it
390
+ const isArchetype = existing.characters[0]?.name === "Commander";
391
+ if (!isArchetype)
392
+ return existing;
393
+ // Use LLM to generate real characters for the unknown universe
394
+ let session;
395
+ try {
396
+ const { getClient } = await import("./client.js");
397
+ const { approveAll } = await import("@github/copilot-sdk");
398
+ const client = await getClient();
399
+ session = await client.createSession({
400
+ systemMessage: { mode: "replace", content: "You are a pop-culture expert. Generate character rosters for fictional universes. Respond ONLY with valid JSON, no markdown fencing." },
401
+ onPermissionRequest: approveAll,
402
+ });
403
+ const prompt = `Generate a roster of 8 characters from the universe "${input}". For each character provide their canonical name and a one-sentence personality description suitable for a software engineering team role.
404
+
405
+ Return ONLY a JSON object with this exact shape:
406
+ {"name":"<Universe Display Name>","tagline":"<iconic catchphrase or tagline>","characters":[{"name":"<Character Name>","personality":"<1-sentence personality description>"}]}
407
+
408
+ Use well-known, iconic characters from this universe. The personality should reflect the actual character's traits.`;
409
+ const response = await session.sendAndWait({ prompt }, 30_000);
410
+ const rawContent = response?.data?.content ?? "";
411
+ const jsonStr = rawContent.replace(/```json?\n?/g, "").replace(/```/g, "").trim();
412
+ const parsed = JSON.parse(jsonStr);
413
+ if (parsed?.characters?.length > 0) {
414
+ const slug = slugify(input);
415
+ const universe = {
416
+ id: slug,
417
+ name: parsed.name || input,
418
+ tagline: parsed.tagline || `Welcome to ${input}.`,
419
+ characters: parsed.characters.slice(0, 8).map((c) => ({
420
+ name: String(c.name),
421
+ personality: String(c.personality),
422
+ })),
423
+ };
424
+ // Replace the archetype entry with the real one
425
+ const idx = UNIVERSES.findIndex((u) => u.id === slug);
426
+ if (idx >= 0)
427
+ UNIVERSES.splice(idx, 1);
428
+ UNIVERSES.push(universe);
429
+ return universe;
430
+ }
431
+ }
432
+ catch (err) {
433
+ console.error(`[io] Failed to generate universe roster for "${input}":`, err);
434
+ }
435
+ finally {
436
+ try {
437
+ await session?.destroy();
438
+ }
439
+ catch { /* best-effort cleanup */ }
440
+ }
441
+ // LLM failed — return the archetype fallback (already registered)
442
+ return existing;
443
+ }
381
444
  /**
382
445
  * Get a universe by ID.
383
446
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heyio",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "IO — a personal AI assistant built on the GitHub Copilot SDK",
5
5
  "bin": {
6
6
  "io": "dist/index.js"