heyio 0.1.30 → 0.1.32

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,6 +4,7 @@ import { execSync } 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 } from "./universes.js";
7
8
  // Ensure child processes have HOME set (systemd services often don't)
8
9
  function shellEnv() {
9
10
  const env = { ...process.env };
@@ -59,17 +60,23 @@ export function createTools(deps) {
59
60
  },
60
61
  });
61
62
  const squadCreate = defineTool("squad_create", {
62
- description: "Create a persistent project squad. Squads remember decisions and context for a specific codebase.",
63
+ description: "Create a persistent project squad with an 80s-themed team. A random universe (A-Team, Transformers, etc.) is assigned unless you specify one. After creating, use squad_analyze to examine the project, then squad_add_agent for each specialist needed.",
63
64
  skipPermission: true,
64
65
  parameters: z.object({
65
66
  slug: z.string().describe("Unique identifier (e.g., 'michaeljolley-io')"),
66
67
  name: z.string().describe("Display name (e.g., 'IO Assistant')"),
67
68
  project_path: z.string().describe("Path to the project directory"),
69
+ universe: z
70
+ .enum(UNIVERSES.map((u) => u.id))
71
+ .optional()
72
+ .describe("80s universe theme. Options: a-team, transformers, thundercats, gi-joe, aliens, ghostbusters. Random if omitted."),
68
73
  }),
69
- handler: async ({ slug, name, project_path }) => {
74
+ handler: async ({ slug, name, project_path, universe }) => {
70
75
  try {
71
- deps.createSquad(slug, name, project_path);
72
- return `Squad "${name}" created for ${project_path}`;
76
+ deps.createSquad(slug, name, project_path, universe);
77
+ const squad = deps.getSquad(slug);
78
+ const universeName = UNIVERSES.find((u) => u.id === squad?.universe)?.name ?? squad?.universe;
79
+ return `Squad "${name}" created for ${project_path}\nUniverse: ${universeName}\n\nNext steps:\n1. Use \`squad_analyze\` to examine the project\n2. Use \`squad_add_agent\` to add specialists based on the analysis`;
73
80
  }
74
81
  catch (err) {
75
82
  return `Error creating squad: ${err instanceof Error ? err.message : String(err)}`;
@@ -91,7 +98,7 @@ export function createTools(deps) {
91
98
  },
92
99
  });
93
100
  const squadStatus = defineTool("squad_status", {
94
- description: "List all squads and their status.",
101
+ description: "List all squads with their universe theme and agent roster.",
95
102
  skipPermission: true,
96
103
  parameters: z.object({}),
97
104
  handler: async () => {
@@ -99,7 +106,16 @@ export function createTools(deps) {
99
106
  if (squads.length === 0)
100
107
  return "No squads created yet.";
101
108
  return squads
102
- .map((s) => `- **${s.name}** (\`${s.slug}\`) — ${s.status} — ${s.projectPath}`)
109
+ .map((s) => {
110
+ const universeName = s.universe
111
+ ? UNIVERSES.find((u) => u.id === s.universe)?.name ?? s.universe
112
+ : "none";
113
+ const agents = deps.listSquadAgents(s.slug);
114
+ const agentList = agents.length > 0
115
+ ? "\n Agents: " + agents.map((a) => `${a.character_name} (${a.role_title})`).join(", ")
116
+ : "\n Agents: none — use squad_add_agent to build the team";
117
+ return `- **${s.name}** (\`${s.slug}\`) — ${s.status} — 🎬 ${universeName}${agentList}\n 📁 ${s.projectPath}`;
118
+ })
103
119
  .join("\n");
104
120
  },
105
121
  });
@@ -122,21 +138,26 @@ export function createTools(deps) {
122
138
  },
123
139
  });
124
140
  const squadDelegate = defineTool("squad_delegate", {
125
- description: "Delegate a task to a squad agent for autonomous execution. The agent runs in the background and you get a task ID immediately. Use squad_task_status to check progress. Use this after planning work with the user to send each task to the squad for implementation.",
141
+ description: "Delegate a task to a squad agent for autonomous execution. If the squad has named agents, you can target a specific one by character name, or let the system pick the best available agent. Returns a task ID immediately.",
126
142
  skipPermission: true,
127
143
  parameters: z.object({
128
144
  slug: z.string().describe("Squad slug to delegate to"),
129
145
  task: z
130
146
  .string()
131
147
  .describe("Detailed task description. Be specific — include file paths, expected behavior, acceptance criteria. The agent works autonomously with this as its only instruction."),
148
+ agent: z
149
+ .string()
150
+ .optional()
151
+ .describe("Character name of a specific agent to target (e.g., 'Hannibal', 'Optimus Prime'). If omitted, the system picks the best available agent."),
132
152
  }),
133
- handler: async ({ slug, task }) => {
134
- console.error(`[io] squad_delegate called: ${slug} — ${task.slice(0, 100)}…`);
153
+ handler: async ({ slug, task, agent }) => {
154
+ console.error(`[io] squad_delegate called: ${slug}${agent ? ` → ${agent}` : ""} — ${task.slice(0, 100)}…`);
135
155
  try {
136
156
  const taskId = await deps.delegateToAgent(slug, task, (id, result) => {
137
157
  console.error(`[io] Agent task ${id} completed for squad ${slug}`);
138
- });
139
- return `Task delegated to squad "${slug}". Task ID: ${taskId}\n\nThe agent is working on this in the background. Use squad_task_status to check progress.`;
158
+ }, agent);
159
+ const agentLabel = agent ? `agent "${agent}" in squad "${slug}"` : `squad "${slug}"`;
160
+ return `Task delegated to ${agentLabel}. Task ID: ${taskId}\n\nThe agent is working on this in the background. Use squad_task_status to check progress.`;
140
161
  }
141
162
  catch (err) {
142
163
  return `Error delegating task: ${err instanceof Error ? err.message : String(err)}`;
@@ -172,9 +193,209 @@ export function createTools(deps) {
172
193
  .join("\n");
173
194
  },
174
195
  });
196
+ // --- Squad analyze ---
197
+ const squadAnalyze = defineTool("squad_analyze", {
198
+ description: "Analyze a project directory to determine what specialist agents the squad needs. Scans for languages, frameworks, test tools, CI/CD config, and project structure. Use the output to decide which agents to add with squad_add_agent.",
199
+ skipPermission: true,
200
+ parameters: z.object({
201
+ project_path: z.string().describe("Path to the project directory to analyze"),
202
+ }),
203
+ handler: async ({ project_path }) => {
204
+ console.error(`[io] squad_analyze called: ${project_path}`);
205
+ try {
206
+ const resolved = resolve(project_path);
207
+ if (!existsSync(resolved))
208
+ return `Directory not found: ${project_path}`;
209
+ const analysis = [];
210
+ analysis.push(`## Project Analysis: ${project_path}\n`);
211
+ // Detect languages & frameworks by scanning for key files
212
+ const indicators = [
213
+ { file: "package.json", label: "Node.js/JavaScript/TypeScript" },
214
+ { file: "tsconfig.json", label: "TypeScript" },
215
+ { file: "Cargo.toml", label: "Rust" },
216
+ { file: "go.mod", label: "Go" },
217
+ { file: "requirements.txt", label: "Python" },
218
+ { file: "pyproject.toml", label: "Python" },
219
+ { file: "Gemfile", label: "Ruby" },
220
+ { file: "pom.xml", label: "Java (Maven)" },
221
+ { file: "build.gradle", label: "Java/Kotlin (Gradle)" },
222
+ { file: "*.csproj", label: ".NET/C#" },
223
+ { file: "*.fsproj", label: ".NET/F#" },
224
+ { file: "*.sln", label: ".NET Solution" },
225
+ { file: "Dockerfile", label: "Docker" },
226
+ { file: "docker-compose.yml", label: "Docker Compose" },
227
+ { file: "docker-compose.yaml", label: "Docker Compose" },
228
+ { file: ".github/workflows", label: "GitHub Actions CI/CD" },
229
+ { file: ".gitlab-ci.yml", label: "GitLab CI" },
230
+ { file: "Jenkinsfile", label: "Jenkins CI" },
231
+ { file: "azure-pipelines.yml", label: "Azure Pipelines" },
232
+ { file: "vite.config.ts", label: "Vite" },
233
+ { file: "vite.config.js", label: "Vite" },
234
+ { file: "next.config.js", label: "Next.js" },
235
+ { file: "next.config.mjs", label: "Next.js" },
236
+ { file: "nuxt.config.ts", label: "Nuxt" },
237
+ { file: "angular.json", label: "Angular" },
238
+ { file: "tailwind.config.js", label: "Tailwind CSS" },
239
+ { file: "tailwind.config.ts", label: "Tailwind CSS" },
240
+ { file: "jest.config.js", label: "Jest testing" },
241
+ { file: "jest.config.ts", label: "Jest testing" },
242
+ { file: "vitest.config.ts", label: "Vitest testing" },
243
+ { file: ".eslintrc.js", label: "ESLint" },
244
+ { file: "eslint.config.js", label: "ESLint" },
245
+ { file: "terraform", label: "Terraform" },
246
+ { file: "serverless.yml", label: "Serverless Framework" },
247
+ ];
248
+ const detected = [];
249
+ for (const { file, label } of indicators) {
250
+ if (file.includes("*")) {
251
+ // Glob-like check — just look for files ending with the pattern
252
+ const ext = file.replace("*", "");
253
+ try {
254
+ const entries = readdirSync(resolved);
255
+ if (entries.some((e) => e.endsWith(ext))) {
256
+ detected.push(label);
257
+ }
258
+ }
259
+ catch { /* skip */ }
260
+ }
261
+ else {
262
+ if (existsSync(join(resolved, file))) {
263
+ detected.push(label);
264
+ }
265
+ }
266
+ }
267
+ if (detected.length > 0) {
268
+ analysis.push(`**Detected Technologies**: ${[...new Set(detected)].join(", ")}`);
269
+ }
270
+ // Read package.json for more detail
271
+ const pkgPath = join(resolved, "package.json");
272
+ if (existsSync(pkgPath)) {
273
+ try {
274
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
275
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
276
+ const frameworks = [];
277
+ const depNames = Object.keys(allDeps);
278
+ const frameworkMap = {
279
+ react: "React", vue: "Vue.js", angular: "Angular", svelte: "Svelte",
280
+ express: "Express.js", fastify: "Fastify", koa: "Koa", hono: "Hono",
281
+ "next": "Next.js", "nuxt": "Nuxt", "@nestjs/core": "NestJS",
282
+ prisma: "Prisma ORM", drizzle: "Drizzle ORM", sequelize: "Sequelize",
283
+ mongoose: "Mongoose", typeorm: "TypeORM",
284
+ jest: "Jest", vitest: "Vitest", mocha: "Mocha", playwright: "Playwright",
285
+ cypress: "Cypress", "@testing-library/react": "React Testing Library",
286
+ tailwindcss: "Tailwind CSS", "@mui/material": "MUI",
287
+ electron: "Electron", tauri: "Tauri",
288
+ "@github/copilot-sdk": "GitHub Copilot SDK",
289
+ };
290
+ for (const [dep, label] of Object.entries(frameworkMap)) {
291
+ if (depNames.includes(dep))
292
+ frameworks.push(label);
293
+ }
294
+ if (frameworks.length > 0) {
295
+ analysis.push(`**Frameworks/Libraries**: ${frameworks.join(", ")}`);
296
+ }
297
+ if (pkg.scripts) {
298
+ analysis.push(`**Scripts**: ${Object.keys(pkg.scripts).join(", ")}`);
299
+ }
300
+ }
301
+ catch { /* skip */ }
302
+ }
303
+ // Detect directory structure (top-level)
304
+ try {
305
+ const entries = readdirSync(resolved);
306
+ const dirs = entries.filter((e) => {
307
+ try {
308
+ return statSync(join(resolved, e)).isDirectory() && !e.startsWith(".");
309
+ }
310
+ catch {
311
+ return false;
312
+ }
313
+ });
314
+ if (dirs.length > 0) {
315
+ analysis.push(`**Top-level directories**: ${dirs.join(", ")}`);
316
+ }
317
+ }
318
+ catch { /* skip */ }
319
+ analysis.push("\n**Recommendation**: Based on this analysis, use `squad_add_agent` to create specialists. " +
320
+ "Choose role titles that match the project's technology stack (e.g., 'Express API Engineer', " +
321
+ "'Vue.js Frontend Developer', 'Vitest Test Engineer'). Write a charter for each agent describing " +
322
+ "their specific responsibilities within this project.");
323
+ return analysis.join("\n");
324
+ }
325
+ catch (err) {
326
+ return `Error analyzing project: ${err instanceof Error ? err.message : String(err)}`;
327
+ }
328
+ },
329
+ });
330
+ // --- Squad add agent ---
331
+ const squadAddAgent = defineTool("squad_add_agent", {
332
+ description: "Add a named specialist agent to a squad. The next character from the squad's 80s universe is automatically assigned. Use after squad_analyze to build the team.",
333
+ skipPermission: true,
334
+ parameters: z.object({
335
+ slug: z.string().describe("Squad slug"),
336
+ role_title: z
337
+ .string()
338
+ .describe("Free-form role title based on project needs (e.g., 'Express API Engineer', 'Vue.js Frontend Dev', 'Vitest Test Engineer', 'GitHub Actions CI/CD Specialist')"),
339
+ charter: z
340
+ .string()
341
+ .describe("Detailed description of this agent's responsibilities, technologies they own, and quality standards. This becomes their persistent mission."),
342
+ model_tier: z
343
+ .enum(["high", "medium", "low"])
344
+ .optional()
345
+ .describe("Model tier for this agent. Defaults to 'medium'. Use 'high' for architecture/complex work, 'low' for simple tasks."),
346
+ }),
347
+ handler: async ({ slug, role_title, charter, model_tier }) => {
348
+ console.error(`[io] squad_add_agent called: ${slug} — ${role_title}`);
349
+ try {
350
+ const agent = deps.addSquadAgent(slug, role_title, charter, model_tier);
351
+ return `Agent added to squad "${slug}":\n- **${agent.character_name}** — ${agent.role_title}\n- Personality: ${agent.personality}\n- Model tier: ${agent.model_tier}`;
352
+ }
353
+ catch (err) {
354
+ return `Error adding agent: ${err instanceof Error ? err.message : String(err)}`;
355
+ }
356
+ },
357
+ });
358
+ // --- Squad agents (list roster) ---
359
+ const squadAgents = defineTool("squad_agents", {
360
+ description: "List all named agents in a squad's roster with their character names, roles, and status.",
361
+ skipPermission: true,
362
+ parameters: z.object({
363
+ slug: z.string().describe("Squad slug"),
364
+ }),
365
+ handler: async ({ slug }) => {
366
+ const squad = deps.getSquad(slug);
367
+ if (!squad)
368
+ return `Squad not found: ${slug}`;
369
+ const agents = deps.listSquadAgents(slug);
370
+ if (agents.length === 0) {
371
+ return `Squad "${squad.name}" has no agents yet. Use squad_add_agent to build the team.`;
372
+ }
373
+ const universeName = squad.universe
374
+ ? UNIVERSES.find((u) => u.id === squad.universe)?.name ?? squad.universe
375
+ : "none";
376
+ const lines = agents.map((a) => `- **${a.character_name}** — ${a.role_title} (${a.model_tier}) — ${a.status}${a.personality ? `\n _${a.personality}_` : ""}`);
377
+ return `**${squad.name}** — 🎬 ${universeName}\n\n${lines.join("\n")}`;
378
+ },
379
+ });
380
+ // --- Squad remove agent ---
381
+ const squadRemoveAgent = defineTool("squad_remove_agent", {
382
+ description: "Remove a named agent from a squad's roster.",
383
+ skipPermission: true,
384
+ parameters: z.object({
385
+ slug: z.string().describe("Squad slug"),
386
+ character_name: z.string().describe("Character name of the agent to remove"),
387
+ }),
388
+ handler: async ({ slug, character_name }) => {
389
+ console.error(`[io] squad_remove_agent called: ${slug} — ${character_name}`);
390
+ const removed = deps.removeSquadAgent(slug, character_name);
391
+ return removed
392
+ ? `Agent "${character_name}" removed from squad "${slug}".`
393
+ : `Agent "${character_name}" not found in squad "${slug}".`;
394
+ },
395
+ });
175
396
  // --- Squad delete ---
176
397
  const squadDelete = defineTool("squad_delete", {
177
- description: "Delete a squad and all its decisions. This is permanent.",
398
+ description: "Delete a squad and all its agents and decisions. This is permanent.",
178
399
  skipPermission: true,
179
400
  parameters: z.object({
180
401
  slug: z.string().describe("Squad slug to delete"),
@@ -752,7 +973,7 @@ export function createTools(deps) {
752
973
  }
753
974
  },
754
975
  });
755
- return [wikiRead, wikiWrite, wikiSearch, wikiDelete, wikiList, squadCreate, squadRecall, squadStatus, squadLogDecision, squadDelegate, squadTaskStatus, squadDelete, skillList, skillInstall, skillRemove, skillSearch, configUpdate, checkUpdate, shell, fileOps, bash, readFile, viewTool, grepTool, strReplaceEditor, github];
976
+ return [wikiRead, wikiWrite, wikiSearch, wikiDelete, wikiList, squadCreate, squadRecall, squadStatus, squadLogDecision, squadDelegate, squadTaskStatus, squadDelete, squadAnalyze, squadAddAgent, squadAgents, squadRemoveAgent, skillList, skillInstall, skillRemove, skillSearch, configUpdate, checkUpdate, shell, fileOps, bash, readFile, viewTool, grepTool, strReplaceEditor, github];
756
977
  }
757
978
  function walkDirectory(dir, maxDepth = 3, depth = 0) {
758
979
  if (depth >= maxDepth)
@@ -0,0 +1,256 @@
1
+ /**
2
+ * 80s-themed universe data for squad agent character assignment.
3
+ *
4
+ * Each universe provides a pool of characters with personality descriptions.
5
+ * Characters are assigned in order as agents are added to a squad.
6
+ */
7
+ export const UNIVERSES = [
8
+ {
9
+ id: "a-team",
10
+ name: "The A-Team",
11
+ tagline: "I love it when a plan comes together.",
12
+ characters: [
13
+ {
14
+ name: "Hannibal",
15
+ personality: "Strategic mastermind who thrives on bold plans. Always confident, never rattled. Leads with charisma and cigar-chomping optimism.",
16
+ },
17
+ {
18
+ name: "Face",
19
+ personality: "Smooth-talking charmer who can talk his way into (or out of) anything. Polished, persuasive, and resourceful.",
20
+ },
21
+ {
22
+ name: "B.A. Baracus",
23
+ personality: "Tough, no-nonsense mechanic and muscle. Direct communicator — says what needs saying, no fluff. Gets things built right.",
24
+ },
25
+ {
26
+ name: "Murdock",
27
+ personality: "Eccentric genius pilot. Unconventional thinker who sees solutions others miss. Wild energy, but brilliant under pressure.",
28
+ },
29
+ {
30
+ name: "Amy Allen",
31
+ personality: "Sharp investigative journalist. Thorough researcher who documents everything. Asks the hard questions.",
32
+ },
33
+ {
34
+ name: "Frankie Santana",
35
+ personality: "Special effects wizard and creative problem-solver. Thinks visually, builds impressive solutions from limited resources.",
36
+ },
37
+ ],
38
+ },
39
+ {
40
+ id: "transformers",
41
+ name: "Transformers",
42
+ tagline: "More than meets the eye.",
43
+ characters: [
44
+ {
45
+ name: "Optimus Prime",
46
+ personality: "Noble leader who inspires through example. Methodical, principled, and always considers the bigger picture.",
47
+ },
48
+ {
49
+ name: "Bumblebee",
50
+ personality: "Eager and energetic scout. Quick to volunteer, fast on execution. Small but punches way above weight class.",
51
+ },
52
+ {
53
+ name: "Ratchet",
54
+ personality: "Meticulous medic and diagnostician. Obsessed with quality and correctness. Will not ship broken work.",
55
+ },
56
+ {
57
+ name: "Prowl",
58
+ personality: "Analytical strategist who thinks in logic and probability. Data-driven decision maker. Calm and precise.",
59
+ },
60
+ {
61
+ name: "Wheeljack",
62
+ personality: "Inventive engineer who loves experimenting. Builds creative prototypes, sometimes they explode. Learns fast from failures.",
63
+ },
64
+ {
65
+ name: "Jazz",
66
+ personality: "Cool, adaptable special ops agent. Works well in any environment, improvises when plans change. Unflappable.",
67
+ },
68
+ {
69
+ name: "Ironhide",
70
+ personality: "Veteran warrior who values reliability and battle-tested solutions. Conservative approach — prefers proven methods.",
71
+ },
72
+ {
73
+ name: "Grimlock",
74
+ personality: "Raw power with surprising depth. Aggressive problem-solver who bulldozes through obstacles. Not subtle, but effective.",
75
+ },
76
+ ],
77
+ },
78
+ {
79
+ id: "thundercats",
80
+ name: "ThunderCats",
81
+ tagline: "Thunder, Thunder, ThunderCats, HO!",
82
+ characters: [
83
+ {
84
+ name: "Lion-O",
85
+ personality: "Young leader growing into greatness. Courageous and willing to tackle challenges head-on. Learns quickly from mistakes.",
86
+ },
87
+ {
88
+ name: "Tygra",
89
+ personality: "Intellectual and level-headed. Thinks before acting, provides thoughtful analysis. The voice of reason.",
90
+ },
91
+ {
92
+ name: "Panthro",
93
+ personality: "Master engineer and builder. Hands-on, practical, loves building and maintaining complex systems. Strong and dependable.",
94
+ },
95
+ {
96
+ name: "Cheetara",
97
+ personality: "Lightning-fast executor with keen intuition. Spots patterns quickly, delivers results at remarkable speed.",
98
+ },
99
+ {
100
+ name: "WilyKit",
101
+ personality: "Clever and agile trickster. Finds creative shortcuts and unconventional approaches. Thinks outside the box.",
102
+ },
103
+ {
104
+ name: "WilyKat",
105
+ personality: "Resourceful partner-in-crime to WilyKit. Great at reconnaissance and gathering information quickly.",
106
+ },
107
+ {
108
+ name: "Snarf",
109
+ personality: "Loyal supporter who handles the unglamorous but essential work. Worries about quality and completeness.",
110
+ },
111
+ ],
112
+ },
113
+ {
114
+ id: "gi-joe",
115
+ name: "G.I. Joe",
116
+ tagline: "A real American hero.",
117
+ characters: [
118
+ {
119
+ name: "Duke",
120
+ personality: "Decisive field commander. Clear communicator, excellent at breaking complex problems into actionable orders.",
121
+ },
122
+ {
123
+ name: "Scarlett",
124
+ personality: "Intelligence specialist and martial arts expert. Combines analytical thinking with swift execution.",
125
+ },
126
+ {
127
+ name: "Snake Eyes",
128
+ personality: "Silent but deadly ninja commando. Lets work speak for itself. Minimal chatter, maximum impact.",
129
+ },
130
+ {
131
+ name: "Flint",
132
+ personality: "Warrant officer with a Rhodes Scholar mind. Combines academic rigor with field pragmatism.",
133
+ },
134
+ {
135
+ name: "Lady Jaye",
136
+ personality: "Master of disguise and covert ops. Versatile — adapts approach to fit any situation perfectly.",
137
+ },
138
+ {
139
+ name: "Breaker",
140
+ personality: "Communications expert and tech specialist. Keeps systems connected and information flowing smoothly.",
141
+ },
142
+ {
143
+ name: "Doc",
144
+ personality: "Combat medic who patches things up under fire. Calm in crisis, systematic in approach to fixing problems.",
145
+ },
146
+ {
147
+ name: "Roadblock",
148
+ personality: "Heavy weapons specialist who also happens to be a gourmet chef. Powerful, creative, and surprisingly refined.",
149
+ },
150
+ ],
151
+ },
152
+ {
153
+ id: "aliens",
154
+ name: "Aliens",
155
+ tagline: "This time it's war.",
156
+ characters: [
157
+ {
158
+ name: "Ripley",
159
+ personality: "Battle-hardened survivor. Pragmatic, resourceful, and absolutely refuses to give up. Trusts instincts over procedure.",
160
+ },
161
+ {
162
+ name: "Hicks",
163
+ personality: "Calm, competent corporal. Professional without ego. Does the job right and keeps the team focused.",
164
+ },
165
+ {
166
+ name: "Bishop",
167
+ personality: "Synthetic with surgical precision. Methodical, tireless, and extremely reliable. Logic-first approach to every problem.",
168
+ },
169
+ {
170
+ name: "Vasquez",
171
+ personality: "Fierce smartgunner who never backs down. Intense, competitive, and brings overwhelming force to problems.",
172
+ },
173
+ {
174
+ name: "Hudson",
175
+ personality: "Complains a lot but always comes through. Expressive about challenges but ultimately delivers when it counts.",
176
+ },
177
+ {
178
+ name: "Newt",
179
+ personality: "Survival expert who knows every hiding spot. Finds paths others overlook. Small but impossibly resourceful.",
180
+ },
181
+ {
182
+ name: "Apone",
183
+ personality: "Grizzled sergeant who keeps the squad in line. Practical, experienced, no tolerance for sloppy work.",
184
+ },
185
+ {
186
+ name: "Drake",
187
+ personality: "Heavy weapons partner who charges in first. Aggressive on deadlines, pushes pace, keeps momentum high.",
188
+ },
189
+ ],
190
+ },
191
+ {
192
+ id: "ghostbusters",
193
+ name: "Ghostbusters",
194
+ tagline: "Who ya gonna call?",
195
+ characters: [
196
+ {
197
+ name: "Venkman",
198
+ personality: "Wisecracking leader who keeps morale high. Skeptical, sarcastic, but ultimately gets things done with style.",
199
+ },
200
+ {
201
+ name: "Egon",
202
+ personality: "Brilliant scientist obsessed with data and precision. Dry wit, encyclopedic knowledge. Never guesses when he can measure.",
203
+ },
204
+ {
205
+ name: "Ray",
206
+ personality: "Enthusiastic engineer-scientist who gets genuinely excited about the work. Optimistic builder, loves a good challenge.",
207
+ },
208
+ {
209
+ name: "Winston",
210
+ personality: "Everyman voice of reason. Practical, grounded, asks the questions everyone else forgets. Reliable above all else.",
211
+ },
212
+ {
213
+ name: "Janine",
214
+ personality: "Sharp-tongued office manager who keeps everything organized. No-nonsense coordinator — nothing slips through the cracks.",
215
+ },
216
+ {
217
+ name: "Louis",
218
+ personality: "Eager accountant-turned-helper. Tries hard, brings unexpected value. Detail-oriented with numbers and logistics.",
219
+ },
220
+ {
221
+ name: "Dana",
222
+ personality: "Talented musician with strong convictions. Brings artistic perspective and high standards to quality.",
223
+ },
224
+ {
225
+ name: "Gozer",
226
+ personality: "Otherworldly force of nature. Brings raw, unstoppable energy. Approaches problems with cosmic-scale ambition.",
227
+ },
228
+ ],
229
+ },
230
+ ];
231
+ /**
232
+ * Get a universe by ID.
233
+ */
234
+ export function getUniverse(id) {
235
+ return UNIVERSES.find((u) => u.id === id);
236
+ }
237
+ /**
238
+ * Pick a random universe.
239
+ */
240
+ export function randomUniverse() {
241
+ return UNIVERSES[Math.floor(Math.random() * UNIVERSES.length)];
242
+ }
243
+ /**
244
+ * Get the next unassigned character from a universe's pool.
245
+ * @param universeId The universe ID
246
+ * @param usedNames Character names already assigned to agents in this squad
247
+ * @returns The next available character, or undefined if all are taken
248
+ */
249
+ export function nextCharacter(universeId, usedNames) {
250
+ const universe = getUniverse(universeId);
251
+ if (!universe)
252
+ return undefined;
253
+ const used = new Set(usedNames.map((n) => n.toLowerCase()));
254
+ return universe.characters.find((c) => !used.has(c.name.toLowerCase()));
255
+ }
256
+ //# sourceMappingURL=universes.js.map
package/dist/store/db.js CHANGED
@@ -54,12 +54,31 @@ export function getDb() {
54
54
  completed_at DATETIME
55
55
  );
56
56
  `);
57
- // Migration: add model column to squads (for existing databases)
58
- try {
59
- db.exec(`ALTER TABLE squads ADD COLUMN model TEXT`);
60
- }
61
- catch {
62
- // Column already exists — ignore
57
+ // Migrations for existing databases
58
+ const migrations = [
59
+ `ALTER TABLE squads ADD COLUMN model TEXT`,
60
+ `ALTER TABLE squads ADD COLUMN universe TEXT`,
61
+ `CREATE TABLE IF NOT EXISTS squad_agents (
62
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
63
+ squad_slug TEXT NOT NULL,
64
+ character_name TEXT NOT NULL,
65
+ role_title TEXT NOT NULL,
66
+ charter TEXT,
67
+ model_tier TEXT NOT NULL DEFAULT 'medium',
68
+ personality TEXT,
69
+ copilot_session_id TEXT,
70
+ status TEXT NOT NULL DEFAULT 'idle',
71
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
72
+ UNIQUE(squad_slug, character_name)
73
+ )`,
74
+ ];
75
+ for (const migration of migrations) {
76
+ try {
77
+ db.exec(migration);
78
+ }
79
+ catch {
80
+ // Already applied — ignore
81
+ }
63
82
  }
64
83
  return db;
65
84
  }