opencode-swarm-plugin 0.36.1 → 0.37.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # opencode-swarm-plugin
2
2
 
3
+ ## 0.37.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`66b5795`](https://github.com/joelhooks/swarm-tools/commit/66b57951e2c114702c663b98829d5f7626607a16) Thanks [@joelhooks](https://github.com/joelhooks)! - ## 🐝 `swarm cells` - Query Your Hive Like a Pro
8
+
9
+ New CLI command AND plugin tool for querying cells directly from the database.
10
+
11
+ ### CLI: `swarm cells`
12
+
13
+ ```bash
14
+ swarm cells # List all cells (table format)
15
+ swarm cells --status open # Filter by status
16
+ swarm cells --type bug # Filter by type
17
+ swarm cells --ready # Next unblocked cell
18
+ swarm cells mjkmd # Partial ID lookup
19
+ swarm cells --json # Raw JSON for scripting
20
+ ```
21
+
22
+ **Replaces:** The awkward `swarm tool hive_query --json '{"status":"open"}'` pattern.
23
+
24
+ ### Plugin Tool: `hive_cells`
25
+
26
+ ```typescript
27
+ // Agents can now query cells directly
28
+ hive_cells({ status: "open", type: "task" });
29
+ hive_cells({ id: "mjkmd" }); // Partial ID works!
30
+ hive_cells({ ready: true }); // Next unblocked
31
+ ```
32
+
33
+ **Why this matters:**
34
+
35
+ - Reads from DATABASE (fast, indexed) not JSONL files
36
+ - Partial ID resolution built-in
37
+ - Consistent JSON array output
38
+ - Rich descriptions encourage agentic use
39
+
40
+ ### Also Fixed
41
+
42
+ - `swarm_review_feedback` tests updated for coordinator-driven retry architecture
43
+ - 425 tests passing
44
+
3
45
  ## 0.36.1
4
46
 
5
47
  ### Patch Changes
package/bin/swarm.test.ts CHANGED
@@ -197,6 +197,112 @@ READ-ONLY research agent. Never modifies code - only gathers intel and stores fi
197
197
  // Log Command Tests (TDD)
198
198
  // ============================================================================
199
199
 
200
+ // ============================================================================
201
+ // Cells Command Tests (TDD)
202
+ // ============================================================================
203
+
204
+ /**
205
+ * Format cells as table output
206
+ */
207
+ function formatCellsTable(cells: Array<{
208
+ id: string;
209
+ title: string;
210
+ status: string;
211
+ priority: number;
212
+ }>): string {
213
+ if (cells.length === 0) {
214
+ return "No cells found";
215
+ }
216
+
217
+ const rows = cells.map(c => ({
218
+ id: c.id,
219
+ title: c.title.length > 50 ? c.title.slice(0, 47) + "..." : c.title,
220
+ status: c.status,
221
+ priority: String(c.priority),
222
+ }));
223
+
224
+ // Calculate column widths
225
+ const widths = {
226
+ id: Math.max(2, ...rows.map(r => r.id.length)),
227
+ title: Math.max(5, ...rows.map(r => r.title.length)),
228
+ status: Math.max(6, ...rows.map(r => r.status.length)),
229
+ priority: Math.max(8, ...rows.map(r => r.priority.length)),
230
+ };
231
+
232
+ // Build header
233
+ const header = [
234
+ "ID".padEnd(widths.id),
235
+ "TITLE".padEnd(widths.title),
236
+ "STATUS".padEnd(widths.status),
237
+ "PRIORITY".padEnd(widths.priority),
238
+ ].join(" ");
239
+
240
+ const separator = "-".repeat(header.length);
241
+
242
+ // Build rows
243
+ const bodyRows = rows.map(r =>
244
+ [
245
+ r.id.padEnd(widths.id),
246
+ r.title.padEnd(widths.title),
247
+ r.status.padEnd(widths.status),
248
+ r.priority.padEnd(widths.priority),
249
+ ].join(" ")
250
+ );
251
+
252
+ return [header, separator, ...bodyRows].join("\n");
253
+ }
254
+
255
+ describe("Cells command", () => {
256
+ describe("formatCellsTable", () => {
257
+ test("formats cells as table with id, title, status, priority", () => {
258
+ const cells = [
259
+ {
260
+ id: "test-abc123-xyz",
261
+ title: "Fix bug",
262
+ status: "open",
263
+ priority: 0,
264
+ type: "bug",
265
+ created_at: 1234567890,
266
+ updated_at: 1234567890,
267
+ },
268
+ {
269
+ id: "test-def456-abc",
270
+ title: "Add feature",
271
+ status: "in_progress",
272
+ priority: 2,
273
+ type: "feature",
274
+ created_at: 1234567890,
275
+ updated_at: 1234567890,
276
+ },
277
+ ];
278
+
279
+ const table = formatCellsTable(cells);
280
+
281
+ // Should contain headers
282
+ expect(table).toContain("ID");
283
+ expect(table).toContain("TITLE");
284
+ expect(table).toContain("STATUS");
285
+ expect(table).toContain("PRIORITY");
286
+
287
+ // Should contain cell data
288
+ expect(table).toContain("test-abc123-xyz");
289
+ expect(table).toContain("Fix bug");
290
+ expect(table).toContain("open");
291
+ expect(table).toContain("0");
292
+
293
+ expect(table).toContain("test-def456-abc");
294
+ expect(table).toContain("Add feature");
295
+ expect(table).toContain("in_progress");
296
+ expect(table).toContain("2");
297
+ });
298
+
299
+ test("returns 'No cells found' for empty array", () => {
300
+ const table = formatCellsTable([]);
301
+ expect(table).toBe("No cells found");
302
+ });
303
+ });
304
+ });
305
+
200
306
  describe("Log command helpers", () => {
201
307
  let testDir: string;
202
308
 
package/bin/swarm.ts CHANGED
@@ -2722,6 +2722,7 @@ ${cyan("Commands:")}
2722
2722
  swarm config Show paths to generated config files
2723
2723
  swarm agents Update AGENTS.md with skill awareness
2724
2724
  swarm migrate Migrate PGlite database to libSQL
2725
+ swarm cells List or get cells from database (replaces 'swarm tool hive_query')
2725
2726
  swarm log View swarm logs with filtering
2726
2727
  swarm update Update to latest version
2727
2728
  swarm version Show version and banner
@@ -2733,6 +2734,14 @@ ${cyan("Tool Execution:")}
2733
2734
  swarm tool <name> Execute tool with no args
2734
2735
  swarm tool <name> --json '<args>' Execute tool with JSON args
2735
2736
 
2737
+ ${cyan("Cell Management:")}
2738
+ swarm cells List cells from database (default: 20 most recent)
2739
+ swarm cells <id> Get single cell by ID or partial hash
2740
+ swarm cells --status <status> Filter by status (open, in_progress, closed, blocked)
2741
+ swarm cells --type <type> Filter by type (task, bug, feature, epic, chore)
2742
+ swarm cells --ready Show next ready (unblocked) cell
2743
+ swarm cells --json Raw JSON output (array, no wrapper)
2744
+
2736
2745
  ${cyan("Log Viewing:")}
2737
2746
  swarm log Tail recent logs (last 50 lines)
2738
2747
  swarm log <module> Filter by module (e.g., compaction)
@@ -3245,6 +3254,173 @@ function readLogFiles(dir: string): LogEntry[] {
3245
3254
  return entries;
3246
3255
  }
3247
3256
 
3257
+ /**
3258
+ * Format cells as table output
3259
+ */
3260
+ function formatCellsTable(cells: Array<{
3261
+ id: string;
3262
+ title: string;
3263
+ status: string;
3264
+ priority: number;
3265
+ }>): string {
3266
+ if (cells.length === 0) {
3267
+ return "No cells found";
3268
+ }
3269
+
3270
+ const rows = cells.map(c => ({
3271
+ id: c.id,
3272
+ title: c.title.length > 50 ? c.title.slice(0, 47) + "..." : c.title,
3273
+ status: c.status,
3274
+ priority: String(c.priority),
3275
+ }));
3276
+
3277
+ // Calculate column widths
3278
+ const widths = {
3279
+ id: Math.max(2, ...rows.map(r => r.id.length)),
3280
+ title: Math.max(5, ...rows.map(r => r.title.length)),
3281
+ status: Math.max(6, ...rows.map(r => r.status.length)),
3282
+ priority: Math.max(8, ...rows.map(r => r.priority.length)),
3283
+ };
3284
+
3285
+ // Build header
3286
+ const header = [
3287
+ "ID".padEnd(widths.id),
3288
+ "TITLE".padEnd(widths.title),
3289
+ "STATUS".padEnd(widths.status),
3290
+ "PRIORITY".padEnd(widths.priority),
3291
+ ].join(" ");
3292
+
3293
+ const separator = "-".repeat(header.length);
3294
+
3295
+ // Build rows
3296
+ const bodyRows = rows.map(r =>
3297
+ [
3298
+ r.id.padEnd(widths.id),
3299
+ r.title.padEnd(widths.title),
3300
+ r.status.padEnd(widths.status),
3301
+ r.priority.padEnd(widths.priority),
3302
+ ].join(" ")
3303
+ );
3304
+
3305
+ return [header, separator, ...bodyRows].join("\n");
3306
+ }
3307
+
3308
+ /**
3309
+ * List or get cells from database
3310
+ */
3311
+ async function cells() {
3312
+ const args = process.argv.slice(3);
3313
+
3314
+ // Parse arguments
3315
+ let cellId: string | null = null;
3316
+ let statusFilter: string | null = null;
3317
+ let typeFilter: string | null = null;
3318
+ let readyOnly = false;
3319
+ let jsonOutput = false;
3320
+
3321
+ for (let i = 0; i < args.length; i++) {
3322
+ const arg = args[i];
3323
+
3324
+ if (arg === "--status" && i + 1 < args.length) {
3325
+ statusFilter = args[++i];
3326
+ if (!["open", "in_progress", "closed", "blocked"].includes(statusFilter)) {
3327
+ p.log.error(`Invalid status: ${statusFilter}`);
3328
+ p.log.message(dim(" Valid statuses: open, in_progress, closed, blocked"));
3329
+ process.exit(1);
3330
+ }
3331
+ } else if (arg === "--type" && i + 1 < args.length) {
3332
+ typeFilter = args[++i];
3333
+ if (!["task", "bug", "feature", "epic", "chore"].includes(typeFilter)) {
3334
+ p.log.error(`Invalid type: ${typeFilter}`);
3335
+ p.log.message(dim(" Valid types: task, bug, feature, epic, chore"));
3336
+ process.exit(1);
3337
+ }
3338
+ } else if (arg === "--ready") {
3339
+ readyOnly = true;
3340
+ } else if (arg === "--json") {
3341
+ jsonOutput = true;
3342
+ } else if (!arg.startsWith("--") && !arg.startsWith("-")) {
3343
+ // Positional arg = cell ID (full or partial)
3344
+ cellId = arg;
3345
+ }
3346
+ }
3347
+
3348
+ // Get adapter using swarm-mail
3349
+ const projectPath = process.cwd();
3350
+ const { getSwarmMailLibSQL, createHiveAdapter, resolvePartialId } = await import("swarm-mail");
3351
+
3352
+ try {
3353
+ const swarmMail = await getSwarmMailLibSQL(projectPath);
3354
+ const db = await swarmMail.getDatabase();
3355
+ const adapter = createHiveAdapter(db, projectPath);
3356
+
3357
+ // Run migrations to ensure schema exists
3358
+ await adapter.runMigrations();
3359
+
3360
+ // If cell ID provided, get single cell
3361
+ if (cellId) {
3362
+ // Resolve partial ID to full ID
3363
+ const fullId = await resolvePartialId(adapter, projectPath, cellId) || cellId;
3364
+ const cell = await adapter.getCell(projectPath, fullId);
3365
+
3366
+ if (!cell) {
3367
+ p.log.error(`Cell not found: ${cellId}`);
3368
+ process.exit(1);
3369
+ }
3370
+
3371
+ if (jsonOutput) {
3372
+ console.log(JSON.stringify([cell], null, 2));
3373
+ } else {
3374
+ const table = formatCellsTable([{
3375
+ id: cell.id,
3376
+ title: cell.title,
3377
+ status: cell.status,
3378
+ priority: cell.priority,
3379
+ }]);
3380
+ console.log(table);
3381
+ }
3382
+ return;
3383
+ }
3384
+
3385
+ // Otherwise query cells
3386
+ let cells: Array<{ id: string; title: string; status: string; priority: number }>;
3387
+
3388
+ if (readyOnly) {
3389
+ const readyCell = await adapter.getNextReadyCell(projectPath);
3390
+ cells = readyCell ? [{
3391
+ id: readyCell.id,
3392
+ title: readyCell.title,
3393
+ status: readyCell.status,
3394
+ priority: readyCell.priority,
3395
+ }] : [];
3396
+ } else {
3397
+ const queriedCells = await adapter.queryCells(projectPath, {
3398
+ status: statusFilter as any || undefined,
3399
+ type: typeFilter as any || undefined,
3400
+ limit: 20,
3401
+ });
3402
+
3403
+ cells = queriedCells.map(c => ({
3404
+ id: c.id,
3405
+ title: c.title,
3406
+ status: c.status,
3407
+ priority: c.priority,
3408
+ }));
3409
+ }
3410
+
3411
+ if (jsonOutput) {
3412
+ console.log(JSON.stringify(cells, null, 2));
3413
+ } else {
3414
+ const table = formatCellsTable(cells);
3415
+ console.log(table);
3416
+ }
3417
+ } catch (error) {
3418
+ const message = error instanceof Error ? error.message : String(error);
3419
+ p.log.error(`Failed to query cells: ${message}`);
3420
+ process.exit(1);
3421
+ }
3422
+ }
3423
+
3248
3424
  async function logs() {
3249
3425
  const args = process.argv.slice(3);
3250
3426
 
@@ -3614,6 +3790,9 @@ switch (command) {
3614
3790
  case "db":
3615
3791
  await db();
3616
3792
  break;
3793
+ case "cells":
3794
+ await cells();
3795
+ break;
3617
3796
  case "log":
3618
3797
  case "logs":
3619
3798
  await logs();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm-plugin",
3
- "version": "0.36.1",
3
+ "version": "0.37.0",
4
4
  "description": "Multi-agent swarm coordination for OpenCode with learning capabilities, beads integration, and Agent Mail",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1895,6 +1895,154 @@ describe("beads integration", () => {
1895
1895
  });
1896
1896
  });
1897
1897
 
1898
+ describe("hive_cells", () => {
1899
+ let testCellId: string;
1900
+
1901
+ beforeEach(async () => {
1902
+ // Create a test cell for hive_cells tests
1903
+ const result = await hive_create.execute(
1904
+ { title: "Cells tool test", type: "task" },
1905
+ mockContext,
1906
+ );
1907
+ const cell = parseResponse<Cell>(result);
1908
+ testCellId = cell.id;
1909
+ createdBeadIds.push(testCellId);
1910
+ });
1911
+
1912
+ it("lists all cells with no filters", async () => {
1913
+ const { hive_cells } = await import("./hive");
1914
+
1915
+ const result = await hive_cells.execute({}, mockContext);
1916
+ const cells = parseResponse<Cell[]>(result);
1917
+
1918
+ expect(Array.isArray(cells)).toBe(true);
1919
+ expect(cells.length).toBeGreaterThan(0);
1920
+ });
1921
+
1922
+ it("filters by status", async () => {
1923
+ const { hive_cells } = await import("./hive");
1924
+
1925
+ const result = await hive_cells.execute({ status: "open" }, mockContext);
1926
+ const cells = parseResponse<Cell[]>(result);
1927
+
1928
+ expect(Array.isArray(cells)).toBe(true);
1929
+ expect(cells.every((c) => c.status === "open")).toBe(true);
1930
+ });
1931
+
1932
+ it("filters by type", async () => {
1933
+ const { hive_cells } = await import("./hive");
1934
+
1935
+ // Create a bug cell
1936
+ const bugResult = await hive_create.execute(
1937
+ { title: "Bug for cells test", type: "bug" },
1938
+ mockContext,
1939
+ );
1940
+ const bug = parseResponse<Cell>(bugResult);
1941
+ createdBeadIds.push(bug.id);
1942
+
1943
+ const result = await hive_cells.execute({ type: "bug" }, mockContext);
1944
+ const cells = parseResponse<Cell[]>(result);
1945
+
1946
+ expect(Array.isArray(cells)).toBe(true);
1947
+ expect(cells.every((c) => c.issue_type === "bug")).toBe(true);
1948
+ });
1949
+
1950
+ it("returns next ready cell when ready=true", async () => {
1951
+ const { hive_cells } = await import("./hive");
1952
+
1953
+ const result = await hive_cells.execute({ ready: true }, mockContext);
1954
+ const cells = parseResponse<Cell[]>(result);
1955
+
1956
+ expect(Array.isArray(cells)).toBe(true);
1957
+ // Should return 0 or 1 cells (the next ready one)
1958
+ expect(cells.length).toBeLessThanOrEqual(1);
1959
+ if (cells.length === 1) {
1960
+ expect(["open", "in_progress"]).toContain(cells[0].status);
1961
+ }
1962
+ });
1963
+
1964
+ it("looks up cell by partial ID", async () => {
1965
+ const { hive_cells } = await import("./hive");
1966
+
1967
+ // Extract hash from full ID (6-char segment before the last hyphen)
1968
+ const lastHyphenIndex = testCellId.lastIndexOf("-");
1969
+ const beforeLast = testCellId.substring(0, lastHyphenIndex);
1970
+ const secondLastHyphenIndex = beforeLast.lastIndexOf("-");
1971
+ const hash = testCellId.substring(secondLastHyphenIndex + 1, lastHyphenIndex);
1972
+
1973
+ // Use last 6 chars of hash (or full hash if short)
1974
+ const shortHash = hash.substring(Math.max(0, hash.length - 6));
1975
+
1976
+ try {
1977
+ const result = await hive_cells.execute({ id: shortHash }, mockContext);
1978
+ const cells = parseResponse<Cell[]>(result);
1979
+
1980
+ // Should return exactly one cell matching the ID
1981
+ expect(cells).toHaveLength(1);
1982
+ expect(cells[0].id).toBe(testCellId);
1983
+ } catch (error) {
1984
+ // If ambiguous, verify error message is helpful
1985
+ if (error instanceof Error && error.message.includes("Ambiguous")) {
1986
+ expect(error.message).toMatch(/ambiguous.*multiple/i);
1987
+ expect(error.message).toContain(shortHash);
1988
+ } else {
1989
+ throw error;
1990
+ }
1991
+ }
1992
+ });
1993
+
1994
+ it("looks up cell by full ID", async () => {
1995
+ const { hive_cells } = await import("./hive");
1996
+
1997
+ const result = await hive_cells.execute({ id: testCellId }, mockContext);
1998
+ const cells = parseResponse<Cell[]>(result);
1999
+
2000
+ expect(cells).toHaveLength(1);
2001
+ expect(cells[0].id).toBe(testCellId);
2002
+ expect(cells[0].title).toBe("Cells tool test");
2003
+ });
2004
+
2005
+ it("throws error for non-existent ID", async () => {
2006
+ const { hive_cells } = await import("./hive");
2007
+
2008
+ await expect(
2009
+ hive_cells.execute({ id: "nonexistent999" }, mockContext),
2010
+ ).rejects.toThrow(/not found|no cell|nonexistent999/i);
2011
+ });
2012
+
2013
+ it("respects limit parameter", async () => {
2014
+ const { hive_cells } = await import("./hive");
2015
+
2016
+ const result = await hive_cells.execute({ limit: 2 }, mockContext);
2017
+ const cells = parseResponse<Cell[]>(result);
2018
+
2019
+ expect(cells.length).toBeLessThanOrEqual(2);
2020
+ });
2021
+
2022
+ it("combines filters (status + type + limit)", async () => {
2023
+ const { hive_cells } = await import("./hive");
2024
+
2025
+ // Create some task cells
2026
+ for (let i = 0; i < 3; i++) {
2027
+ const r = await hive_create.execute(
2028
+ { title: `Task ${i}`, type: "task" },
2029
+ mockContext,
2030
+ );
2031
+ const c = parseResponse<Cell>(r);
2032
+ createdBeadIds.push(c.id);
2033
+ }
2034
+
2035
+ const result = await hive_cells.execute(
2036
+ { status: "open", type: "task", limit: 2 },
2037
+ mockContext,
2038
+ );
2039
+ const cells = parseResponse<Cell[]>(result);
2040
+
2041
+ expect(cells.length).toBeLessThanOrEqual(2);
2042
+ expect(cells.every((c) => c.status === "open" && c.issue_type === "task")).toBe(true);
2043
+ });
2044
+ });
2045
+
1898
2046
  describe("bigint to Date conversion", () => {
1899
2047
  it("should handle PGLite bigint timestamps correctly in hive_query", async () => {
1900
2048
  const { mkdirSync, rmSync } = await import("node:fs");
package/src/hive.ts CHANGED
@@ -1092,6 +1092,94 @@ export const hive_ready = tool({
1092
1092
  },
1093
1093
  });
1094
1094
 
1095
+ /**
1096
+ * Query cells from the hive database with flexible filtering
1097
+ */
1098
+ export const hive_cells = tool({
1099
+ description: `Query cells from the hive database with flexible filtering.
1100
+
1101
+ USE THIS TOOL TO:
1102
+ - List all open cells: hive_cells()
1103
+ - Find cells by status: hive_cells({ status: "in_progress" })
1104
+ - Find cells by type: hive_cells({ type: "bug" })
1105
+ - Get a specific cell by partial ID: hive_cells({ id: "mjkmd" })
1106
+ - Get the next ready (unblocked) cell: hive_cells({ ready: true })
1107
+ - Combine filters: hive_cells({ status: "open", type: "task" })
1108
+
1109
+ RETURNS: Array of cells with id, title, status, priority, type, parent_id, created_at, updated_at
1110
+
1111
+ PREFER THIS OVER hive_query when you need to:
1112
+ - See what work is available
1113
+ - Check status of multiple cells
1114
+ - Find cells matching criteria
1115
+ - Look up a cell by partial ID`,
1116
+ args: {
1117
+ id: tool.schema.string().optional().describe("Partial or full cell ID to look up"),
1118
+ status: tool.schema.enum(["open", "in_progress", "blocked", "closed"]).optional().describe("Filter by status"),
1119
+ type: tool.schema.enum(["task", "bug", "feature", "epic", "chore"]).optional().describe("Filter by type"),
1120
+ ready: tool.schema.boolean().optional().describe("If true, return only the next unblocked cell"),
1121
+ limit: tool.schema.number().optional().describe("Max cells to return (default 20)"),
1122
+ },
1123
+ async execute(args, ctx) {
1124
+ const projectKey = getHiveWorkingDirectory();
1125
+ const adapter = await getHiveAdapter(projectKey);
1126
+
1127
+ try {
1128
+ // If specific ID requested, resolve and return single cell
1129
+ if (args.id) {
1130
+ const fullId = await resolvePartialId(adapter, projectKey, args.id) || args.id;
1131
+ const cell = await adapter.getCell(projectKey, fullId);
1132
+ if (!cell) {
1133
+ throw new HiveError(`No cell found matching ID '${args.id}'`, "hive_cells");
1134
+ }
1135
+ const formatted = formatCellForOutput(cell);
1136
+ return JSON.stringify([formatted], null, 2);
1137
+ }
1138
+
1139
+ // If ready flag, return next unblocked cell
1140
+ if (args.ready) {
1141
+ const ready = await adapter.getNextReadyCell(projectKey);
1142
+ if (!ready) {
1143
+ return JSON.stringify([], null, 2);
1144
+ }
1145
+ const formatted = formatCellForOutput(ready);
1146
+ return JSON.stringify([formatted], null, 2);
1147
+ }
1148
+
1149
+ // Query with filters
1150
+ const cells = await adapter.queryCells(projectKey, {
1151
+ status: args.status,
1152
+ type: args.type,
1153
+ limit: args.limit || 20,
1154
+ });
1155
+
1156
+ const formatted = cells.map(c => formatCellForOutput(c));
1157
+ return JSON.stringify(formatted, null, 2);
1158
+ } catch (error) {
1159
+ const message = error instanceof Error ? error.message : String(error);
1160
+
1161
+ // Provide helpful error messages
1162
+ if (message.includes("Ambiguous hash")) {
1163
+ throw new HiveError(
1164
+ `Ambiguous ID '${args.id}': multiple cells match. Please provide more characters.`,
1165
+ "hive_cells",
1166
+ );
1167
+ }
1168
+ if (message.includes("Bead not found") || message.includes("Cell not found")) {
1169
+ throw new HiveError(
1170
+ `No cell found matching ID '${args.id || "unknown"}'`,
1171
+ "hive_cells",
1172
+ );
1173
+ }
1174
+
1175
+ throw new HiveError(
1176
+ `Failed to query cells: ${message}`,
1177
+ "hive_cells",
1178
+ );
1179
+ }
1180
+ },
1181
+ });
1182
+
1095
1183
  /**
1096
1184
  * Sync hive to git and push
1097
1185
  */
@@ -1345,6 +1433,7 @@ export const hiveTools = {
1345
1433
  hive_close,
1346
1434
  hive_start,
1347
1435
  hive_ready,
1436
+ hive_cells,
1348
1437
  hive_sync,
1349
1438
  hive_link_thread,
1350
1439
  };