claude-code-session-manager 0.12.0 → 0.12.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.
Files changed (25) hide show
  1. package/dist/assets/{TiptapBody-DS6BmNu7.js → TiptapBody-BPrQGByq.js} +1 -1
  2. package/dist/assets/{cssMode-Bz8SFtEY.js → cssMode-BqupsQhT.js} +1 -1
  3. package/dist/assets/{freemarker2-DgDtRkId.js → freemarker2-0gDz5OsZ.js} +1 -1
  4. package/dist/assets/{handlebars-Dihbt_Mk.js → handlebars-SmU_cMQ5.js} +1 -1
  5. package/dist/assets/{html-YEK2Ukg4.js → html-703DN3jh.js} +1 -1
  6. package/dist/assets/{htmlMode-Dg-MiMFK.js → htmlMode-DGXHhxM8.js} +1 -1
  7. package/dist/assets/{index-DJuzPa27.css → index-LyCQu6Cl.css} +1 -1
  8. package/dist/assets/{index-3WBXI5kq.js → index-z93ZHKU-.js} +617 -616
  9. package/dist/assets/{javascript-CQXKrXCl.js → javascript-CCqlYcRz.js} +1 -1
  10. package/dist/assets/{jsonMode-IRogHtiE.js → jsonMode-D1sLC2q8.js} +1 -1
  11. package/dist/assets/{liquid-BMmGufls.js → liquid-CyzWwa9e.js} +1 -1
  12. package/dist/assets/{lspLanguageFeatures-CjGavzGi.js → lspLanguageFeatures-CgBUaMwZ.js} +1 -1
  13. package/dist/assets/{mdx-BBqtWGs6.js → mdx-iOgDcliU.js} +1 -1
  14. package/dist/assets/{python-Bqt0Xd-X.js → python-DW3A0NRm.js} +1 -1
  15. package/dist/assets/{razor-6-o8bJo5.js → razor-B9yGdjxE.js} +1 -1
  16. package/dist/assets/{tsMode-Cg0nI_Eq.js → tsMode-DNdyB0Pq.js} +1 -1
  17. package/dist/assets/{typescript-DiEzfesU.js → typescript-DDsE6PK5.js} +1 -1
  18. package/dist/assets/{xml--sJWdh5o.js → xml-CRN2mPLm.js} +1 -1
  19. package/dist/assets/{yaml-D3hKgUit.js → yaml-BwgW9kTw.js} +1 -1
  20. package/dist/index.html +2 -2
  21. package/package.json +1 -1
  22. package/src/main/hives.cjs +226 -0
  23. package/src/main/index.cjs +2 -0
  24. package/src/preload/api.d.ts +18 -0
  25. package/src/preload/index.cjs +6 -0
@@ -0,0 +1,226 @@
1
+ /**
2
+ * hives.cjs — pre-baked subagent swarm templates ("Hives").
3
+ *
4
+ * A Hive is a named collection of subagent roles + an optional default plan,
5
+ * launchable as one unit. Concept ported from ClaudeCodeUnleashed; our shape
6
+ * is `{ slug, name, description, roles: [{ label, prompt }], defaultPlan? }`
7
+ * (renderer launches by configuring Orchestrator with those roles).
8
+ *
9
+ * Storage: `~/.claude/session-manager/hives/<slug>.json`
10
+ * - slug must match SLUG_RE: /^[a-z0-9-_]{1,64}$/
11
+ * - up to 32 roles per hive
12
+ * - per-field byte caps mirrored in the inline zod schemas below
13
+ *
14
+ * IPC namespace:
15
+ * - hives:list -> { hives: Hive[], error: string | null }
16
+ * - hives:get -> { hive: Hive | null, error: string | null }
17
+ * - hives:save -> { ok: boolean, error: string | null }
18
+ * - hives:delete -> { ok: boolean, error: string | null }
19
+ *
20
+ * All mutations go through config.cjs::writeJson (atomic tmp+rename) and
21
+ * config.cjs::validatePath (allowedRoots = home dir). Never raw fs.writeFile.
22
+ *
23
+ * Default hives (Code review / Build feature / Bug hunt) ship in the renderer
24
+ * (src/renderer/lib/defaultHives.ts) and are NOT writable to disk — they exist
25
+ * only as in-memory starter examples so a fresh install has content. The IPC
26
+ * layer only sees user-saved hives.
27
+ */
28
+
29
+ 'use strict';
30
+
31
+ const { ipcMain } = require('electron');
32
+ const fsp = require('node:fs/promises');
33
+ const path = require('node:path');
34
+ const os = require('node:os');
35
+ const { z } = require('zod');
36
+ const config = require('./config.cjs');
37
+
38
+ // ──────────────────────────────────────────── caps
39
+ const SLUG_RE = /^[a-z0-9-_]{1,64}$/;
40
+ const MAX_NAME_LEN = 128;
41
+ const MAX_DESC_LEN = 2048;
42
+ const MAX_LABEL_LEN = 128;
43
+ const MAX_PROMPT_LEN = 16 * 1024;
44
+ const MAX_PLAN_LEN = 8 * 1024;
45
+ const MAX_ROLES = 32;
46
+
47
+ // ──────────────────────────────────────────── inline zod schemas
48
+ const hiveRoleSchema = z.object({
49
+ label: z.string().min(1).max(MAX_LABEL_LEN),
50
+ prompt: z.string().min(1).max(MAX_PROMPT_LEN),
51
+ }).strict();
52
+
53
+ const hiveSchema = z.object({
54
+ slug: z.string().regex(SLUG_RE),
55
+ name: z.string().min(1).max(MAX_NAME_LEN),
56
+ description: z.string().max(MAX_DESC_LEN).default(''),
57
+ roles: z.array(hiveRoleSchema).min(1).max(MAX_ROLES),
58
+ defaultPlan: z.string().max(MAX_PLAN_LEN).optional(),
59
+ }).strict();
60
+
61
+ const slugPayload = z.object({
62
+ slug: z.string().regex(SLUG_RE),
63
+ }).strict();
64
+
65
+ const savePayload = z.object({
66
+ slug: z.string().regex(SLUG_RE),
67
+ hive: hiveSchema,
68
+ }).strict();
69
+
70
+ // ──────────────────────────────────────────── paths
71
+ function rootDir() {
72
+ return path.join(os.homedir(), '.claude', 'session-manager', 'hives');
73
+ }
74
+
75
+ function hivePath(slug) {
76
+ if (!SLUG_RE.test(slug)) {
77
+ throw new Error(`invalid hive slug (must match ${SLUG_RE.source})`);
78
+ }
79
+ return path.join(rootDir(), `${slug}.json`);
80
+ }
81
+
82
+ async function ensureRoot() {
83
+ await fsp.mkdir(rootDir(), { recursive: true });
84
+ }
85
+
86
+ // ──────────────────────────────────────────── core ops
87
+ async function listHives() {
88
+ try {
89
+ await ensureRoot();
90
+ const r = await config.listDir(rootDir(), { filesOnly: true });
91
+ if (!r.ok) return { hives: [], error: r.error };
92
+ const slugs = r.entries
93
+ .filter((e) => e.name.endsWith('.json'))
94
+ .map((e) => e.name.replace(/\.json$/, ''))
95
+ .filter((s) => SLUG_RE.test(s));
96
+ // Load each in parallel. Skip any that fail to parse cleanly.
97
+ const hives = await Promise.all(
98
+ slugs.map(async (slug) => {
99
+ try {
100
+ const got = await readHive(slug);
101
+ return got.hive;
102
+ } catch {
103
+ return null;
104
+ }
105
+ }),
106
+ );
107
+ return {
108
+ hives: hives.filter((h) => h !== null).sort((a, b) => a.slug.localeCompare(b.slug)),
109
+ error: null,
110
+ };
111
+ } catch (e) {
112
+ return { hives: [], error: e.message };
113
+ }
114
+ }
115
+
116
+ async function readHive(slug) {
117
+ const abs = hivePath(slug);
118
+ const r = await config.readJson(abs);
119
+ if (!r || !r.exists) return { hive: null, error: null };
120
+ if (r.parseError) return { hive: null, error: `parse error: ${r.parseError}` };
121
+ // Re-validate stored shape so a hand-edited file can't smuggle in bad data.
122
+ const parsed = hiveSchema.safeParse(r.data);
123
+ if (!parsed.success) {
124
+ return { hive: null, error: `invalid hive on disk: ${parsed.error.issues[0]?.message ?? 'unknown'}` };
125
+ }
126
+ // Trust the file's own slug only if it matches the filename; otherwise force
127
+ // them to agree (filename wins — that's the storage key).
128
+ const hive = { ...parsed.data, slug };
129
+ return { hive, error: null };
130
+ }
131
+
132
+ async function getHive(slug) {
133
+ try {
134
+ return await readHive(slug);
135
+ } catch (e) {
136
+ return { hive: null, error: e.message };
137
+ }
138
+ }
139
+
140
+ async function saveHive(slug, hive) {
141
+ try {
142
+ // Body slug must match path slug. Reject mismatches loudly so the renderer
143
+ // can't accidentally overwrite the wrong file by tampering with `slug`.
144
+ if (hive.slug !== slug) {
145
+ return { ok: false, error: `slug mismatch: payload slug "${hive.slug}" != path "${slug}"` };
146
+ }
147
+ await ensureRoot();
148
+ const abs = hivePath(slug);
149
+ const out = {
150
+ ...hive,
151
+ // Strip undefined for clean JSON on disk.
152
+ description: hive.description ?? '',
153
+ };
154
+ if (out.defaultPlan === undefined || out.defaultPlan === '') delete out.defaultPlan;
155
+ const result = await config.writeJson(abs, out);
156
+ if (!result || !result.ok) {
157
+ return { ok: false, error: result?.error ?? 'write failed' };
158
+ }
159
+ return { ok: true, error: null };
160
+ } catch (e) {
161
+ return { ok: false, error: e.message };
162
+ }
163
+ }
164
+
165
+ async function deleteHive(slug) {
166
+ try {
167
+ const abs = hivePath(slug);
168
+ let real;
169
+ try {
170
+ real = config.validatePath(abs);
171
+ if (typeof config.validateWrite === 'function') {
172
+ config.validateWrite(real);
173
+ }
174
+ } catch (e) {
175
+ return { ok: false, error: e.message };
176
+ }
177
+ try {
178
+ await fsp.unlink(real);
179
+ } catch (e) {
180
+ if (e.code === 'ENOENT') {
181
+ // Treat missing as success — idempotent delete, same as fs unlink ENOENT.
182
+ return { ok: true, error: null };
183
+ }
184
+ return { ok: false, error: e.message };
185
+ }
186
+ return { ok: true, error: null };
187
+ } catch (e) {
188
+ return { ok: false, error: e.message };
189
+ }
190
+ }
191
+
192
+ // ──────────────────────────────────────────── IPC
193
+ function validated(schema, handler) {
194
+ return (_event, payload) => {
195
+ const parsed = schema.parse(payload);
196
+ return handler(parsed);
197
+ };
198
+ }
199
+
200
+ function registerHiveHandlers() {
201
+ ipcMain.handle('hives:list', () => listHives());
202
+ ipcMain.handle(
203
+ 'hives:get',
204
+ validated(slugPayload, ({ slug }) => getHive(slug)),
205
+ );
206
+ ipcMain.handle(
207
+ 'hives:save',
208
+ validated(savePayload, ({ slug, hive }) => saveHive(slug, hive)),
209
+ );
210
+ ipcMain.handle(
211
+ 'hives:delete',
212
+ validated(slugPayload, ({ slug }) => deleteHive(slug)),
213
+ );
214
+ }
215
+
216
+ module.exports = {
217
+ registerHiveHandlers,
218
+ // exported for tests
219
+ rootDir,
220
+ hivePath,
221
+ SLUG_RE,
222
+ listHives,
223
+ getHive,
224
+ saveHive,
225
+ deleteHive,
226
+ };
@@ -32,6 +32,7 @@ const { registerProjectSkillsHandlers } = require('./projectSkills.cjs');
32
32
  const filesIpc = require('./files.cjs');
33
33
  const searchIpc = require('./search.cjs');
34
34
  const repoAnalyzer = require('./repoAnalyzer.cjs');
35
+ const hivesIpc = require('./hives.cjs');
35
36
  const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
36
37
  const { assertCwdInsideHome } = require('./lib/insideHome.cjs');
37
38
 
@@ -646,6 +647,7 @@ registerProjectSkillsHandlers();
646
647
  filesIpc.registerFilesHandlers();
647
648
  searchIpc.registerSearchHandlers();
648
649
  repoAnalyzer.register(ipcMain);
650
+ hivesIpc.registerHiveHandlers();
649
651
 
650
652
  // OTEL telemetry export (opt-in via ~/.config/session-manager/otel.json).
651
653
  ipcMain.handle('otel:get-config', async () => otelSettings.load());
@@ -458,6 +458,18 @@ export interface RepoAnalyzeResult {
458
458
  }
459
459
  export interface RepoAnalyzeError { ok: false; error: string }
460
460
 
461
+ export interface HiveRole { label: string; prompt: string }
462
+ export interface Hive {
463
+ slug: string;
464
+ name: string;
465
+ description: string;
466
+ roles: HiveRole[];
467
+ defaultPlan?: string;
468
+ }
469
+ export interface HiveListResult { hives: Hive[]; error: string | null }
470
+ export interface HiveGetResult { hive: Hive | null; error: string | null }
471
+ export interface HiveMutationResult { ok: boolean; error: string | null }
472
+
461
473
  export interface WatcherInfo {
462
474
  watcherId: string;
463
475
  tabId: string;
@@ -836,6 +848,12 @@ export interface SessionManagerAPI {
836
848
  repo: {
837
849
  analyze: (cwd: string) => Promise<RepoAnalyzeResult | RepoAnalyzeError>;
838
850
  };
851
+ hives: {
852
+ list: () => Promise<HiveListResult>;
853
+ get: (slug: string) => Promise<HiveGetResult>;
854
+ save: (slug: string, hive: Hive) => Promise<HiveMutationResult>;
855
+ delete: (slug: string) => Promise<HiveMutationResult>;
856
+ };
839
857
  history: {
840
858
  aggregate: (req?: HistoryAggregateRequest) => Promise<HistoryAggregateResult>;
841
859
  listConversations: () => Promise<ListConversationsResult>;
@@ -170,6 +170,12 @@ contextBridge.exposeInMainWorld('api', {
170
170
  repo: {
171
171
  analyze: (cwd) => ipcRenderer.invoke('repo:analyze', { cwd }),
172
172
  },
173
+ hives: {
174
+ list: () => ipcRenderer.invoke('hives:list'),
175
+ get: (slug) => ipcRenderer.invoke('hives:get', { slug }),
176
+ save: (slug, hive) => ipcRenderer.invoke('hives:save', { slug, hive }),
177
+ delete: (slug) => ipcRenderer.invoke('hives:delete', { slug }),
178
+ },
173
179
  schedule: {
174
180
  state: () => ipcRenderer.invoke('schedule:state'),
175
181
  setConfig: (partial) => ipcRenderer.invoke('schedule:set-config', partial),