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.
- package/dist/assets/{TiptapBody-DS6BmNu7.js → TiptapBody-BPrQGByq.js} +1 -1
- package/dist/assets/{cssMode-Bz8SFtEY.js → cssMode-BqupsQhT.js} +1 -1
- package/dist/assets/{freemarker2-DgDtRkId.js → freemarker2-0gDz5OsZ.js} +1 -1
- package/dist/assets/{handlebars-Dihbt_Mk.js → handlebars-SmU_cMQ5.js} +1 -1
- package/dist/assets/{html-YEK2Ukg4.js → html-703DN3jh.js} +1 -1
- package/dist/assets/{htmlMode-Dg-MiMFK.js → htmlMode-DGXHhxM8.js} +1 -1
- package/dist/assets/{index-DJuzPa27.css → index-LyCQu6Cl.css} +1 -1
- package/dist/assets/{index-3WBXI5kq.js → index-z93ZHKU-.js} +617 -616
- package/dist/assets/{javascript-CQXKrXCl.js → javascript-CCqlYcRz.js} +1 -1
- package/dist/assets/{jsonMode-IRogHtiE.js → jsonMode-D1sLC2q8.js} +1 -1
- package/dist/assets/{liquid-BMmGufls.js → liquid-CyzWwa9e.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-CjGavzGi.js → lspLanguageFeatures-CgBUaMwZ.js} +1 -1
- package/dist/assets/{mdx-BBqtWGs6.js → mdx-iOgDcliU.js} +1 -1
- package/dist/assets/{python-Bqt0Xd-X.js → python-DW3A0NRm.js} +1 -1
- package/dist/assets/{razor-6-o8bJo5.js → razor-B9yGdjxE.js} +1 -1
- package/dist/assets/{tsMode-Cg0nI_Eq.js → tsMode-DNdyB0Pq.js} +1 -1
- package/dist/assets/{typescript-DiEzfesU.js → typescript-DDsE6PK5.js} +1 -1
- package/dist/assets/{xml--sJWdh5o.js → xml-CRN2mPLm.js} +1 -1
- package/dist/assets/{yaml-D3hKgUit.js → yaml-BwgW9kTw.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/main/hives.cjs +226 -0
- package/src/main/index.cjs +2 -0
- package/src/preload/api.d.ts +18 -0
- 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
|
+
};
|
package/src/main/index.cjs
CHANGED
|
@@ -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());
|
package/src/preload/api.d.ts
CHANGED
|
@@ -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>;
|
package/src/preload/index.cjs
CHANGED
|
@@ -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),
|