the-campfire 0.1.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.
Files changed (180) hide show
  1. package/bin/cli.ts +182 -0
  2. package/dist/apple-touch-icon.png +0 -0
  3. package/dist/assets/MermaidDiagram-By1J3Whj.js +2 -0
  4. package/dist/assets/_basePickBy-Dq6gnD-p.js +1 -0
  5. package/dist/assets/_baseUniq-SpgN6cGc.js +1 -0
  6. package/dist/assets/arc-D9SEA8dX.js +1 -0
  7. package/dist/assets/architectureDiagram-VXUJARFQ-CoTcwLeR.js +36 -0
  8. package/dist/assets/blockDiagram-VD42YOAC-Dvc63voV.js +122 -0
  9. package/dist/assets/c4Diagram-YG6GDRKO-tuDcG4_4.js +10 -0
  10. package/dist/assets/channel-CxhcHn7S.js +1 -0
  11. package/dist/assets/chunk-4BX2VUAB-x1hChYu9.js +1 -0
  12. package/dist/assets/chunk-55IACEB6-BX6HWnWP.js +1 -0
  13. package/dist/assets/chunk-B4BG7PRW-B4IlF03G.js +165 -0
  14. package/dist/assets/chunk-DI55MBZ5-TpYb5rLg.js +220 -0
  15. package/dist/assets/chunk-FMBD7UC4-DZX4LUPx.js +15 -0
  16. package/dist/assets/chunk-QN33PNHL-CuQs0NDY.js +1 -0
  17. package/dist/assets/chunk-QZHKN3VN-CygWwVx8.js +1 -0
  18. package/dist/assets/chunk-TZMSLE5B-CtI-IdKV.js +1 -0
  19. package/dist/assets/classDiagram-2ON5EDUG-BM0LptJF.js +1 -0
  20. package/dist/assets/classDiagram-v2-WZHVMYZB-BM0LptJF.js +1 -0
  21. package/dist/assets/clone-C6kt1rE7.js +1 -0
  22. package/dist/assets/cose-bilkent-S5V4N54A-PdySs991.js +1 -0
  23. package/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
  24. package/dist/assets/dagre-6UL2VRFP-J0SxEiEu.js +4 -0
  25. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  26. package/dist/assets/diagram-PSM6KHXK-CW0HLXaH.js +24 -0
  27. package/dist/assets/diagram-QEK2KX5R-DADHF-k4.js +43 -0
  28. package/dist/assets/diagram-S2PKOQOG-9S0-PC3S.js +24 -0
  29. package/dist/assets/erDiagram-Q2GNP2WA-BEHAiY_q.js +60 -0
  30. package/dist/assets/flowDiagram-NV44I4VS-Cu6n9Xnd.js +162 -0
  31. package/dist/assets/ganttDiagram-JELNMOA3-Cr_BHryu.js +267 -0
  32. package/dist/assets/gitGraphDiagram-V2S2FVAM-D5pT35v5.js +65 -0
  33. package/dist/assets/graph-DKRbrxcs.js +1 -0
  34. package/dist/assets/index-Byjg9zgI.css +32 -0
  35. package/dist/assets/index-Cwj3m0hT.js +284 -0
  36. package/dist/assets/infoDiagram-HS3SLOUP-BaJlrgKV.js +2 -0
  37. package/dist/assets/init-Gi6I4Gst.js +1 -0
  38. package/dist/assets/journeyDiagram-XKPGCS4Q-CXC4-DJv.js +139 -0
  39. package/dist/assets/kanban-definition-3W4ZIXB7-BtHBu9cx.js +89 -0
  40. package/dist/assets/katex-BlHpptmG.js +261 -0
  41. package/dist/assets/layout-DnxSwLDJ.js +1 -0
  42. package/dist/assets/linear-CWbbIT8l.js +1 -0
  43. package/dist/assets/mermaid.core-DXn1cFAK.js +256 -0
  44. package/dist/assets/mindmap-definition-VGOIOE7T-BR8br1hs.js +68 -0
  45. package/dist/assets/ordinal-BENe2yWM.js +1 -0
  46. package/dist/assets/pieDiagram-ADFJNKIX-B7RmXV7I.js +30 -0
  47. package/dist/assets/quadrantDiagram-AYHSOK5B-DK0FhyV_.js +7 -0
  48. package/dist/assets/requirementDiagram-UZGBJVZJ-3zAGxSpL.js +64 -0
  49. package/dist/assets/sankeyDiagram-TZEHDZUN-DZppKtjc.js +10 -0
  50. package/dist/assets/sequenceDiagram-WL72ISMW-D1x3r3wV.js +145 -0
  51. package/dist/assets/stateDiagram-FKZM4ZOC-2ByD08Vq.js +1 -0
  52. package/dist/assets/stateDiagram-v2-4FDKWEC3-Vm1cYjgV.js +1 -0
  53. package/dist/assets/timeline-definition-IT6M3QCI-l5S35biR.js +61 -0
  54. package/dist/assets/treemap-GDKQZRPO-DIPadPiF.js +162 -0
  55. package/dist/assets/xychartDiagram-PRI3JC2R-9GUvwS2b.js +7 -0
  56. package/dist/favicon.svg +3 -0
  57. package/dist/fonts/Geist-Variable.woff2 +0 -0
  58. package/dist/fonts/GeistMono-Variable.woff2 +0 -0
  59. package/dist/icon-192.png +0 -0
  60. package/dist/icon-512.png +0 -0
  61. package/dist/index.html +33 -0
  62. package/dist/logo-codex.svg +14 -0
  63. package/dist/logo.svg +3 -0
  64. package/dist/manifest.json +26 -0
  65. package/dist/og-image.png +0 -0
  66. package/dist/sw.js +130 -0
  67. package/package.json +92 -0
  68. package/server/ADAPTERS.md +121 -0
  69. package/server/adapter-registry-types.ts +28 -0
  70. package/server/adapter-registry.ts +293 -0
  71. package/server/adapter-types.ts +51 -0
  72. package/server/agent-executor.ts +249 -0
  73. package/server/agent-mcp-bridge.ts +148 -0
  74. package/server/agent-mcp-stdio.ts +171 -0
  75. package/server/agent-mcp-tools.ts +76 -0
  76. package/server/agent-store.ts +183 -0
  77. package/server/agent-types.ts +79 -0
  78. package/server/aider-adapter.ts +525 -0
  79. package/server/auth.ts +153 -0
  80. package/server/auto-namer.ts +132 -0
  81. package/server/capability-discovery.ts +431 -0
  82. package/server/claude-container-auth.ts +40 -0
  83. package/server/claude-stdio-adapter.ts +452 -0
  84. package/server/clawhub-export.ts +218 -0
  85. package/server/cli-launcher.ts +1613 -0
  86. package/server/codex-adapter.ts +1987 -0
  87. package/server/codex-container-auth.ts +40 -0
  88. package/server/codex-home.ts +26 -0
  89. package/server/collective-intelligence.ts +401 -0
  90. package/server/commands-discovery.ts +160 -0
  91. package/server/constants.ts +5 -0
  92. package/server/container-manager.ts +328 -0
  93. package/server/cron-scheduler.ts +243 -0
  94. package/server/cron-store.ts +149 -0
  95. package/server/cron-types.ts +63 -0
  96. package/server/deliberation-engine.ts +323 -0
  97. package/server/embedding.ts +89 -0
  98. package/server/env-manager.ts +146 -0
  99. package/server/environment-detector.ts +106 -0
  100. package/server/environment-rules.ts +106 -0
  101. package/server/gallery-store.ts +216 -0
  102. package/server/gallery-types.ts +52 -0
  103. package/server/gallery-votes.ts +101 -0
  104. package/server/git-utils.ts +384 -0
  105. package/server/github-pr.ts +379 -0
  106. package/server/goose-adapter.ts +929 -0
  107. package/server/image-pull-manager.ts +144 -0
  108. package/server/index.ts +363 -0
  109. package/server/linear-project-manager.ts +78 -0
  110. package/server/moltbook-client.ts +160 -0
  111. package/server/openclaw-adapter.ts +916 -0
  112. package/server/openclaw-channel/channel.ts +188 -0
  113. package/server/openclaw-channel/index.ts +68 -0
  114. package/server/openclaw-channel/package.json +19 -0
  115. package/server/openclaw-channel/types.ts +93 -0
  116. package/server/opencode-adapter.ts +951 -0
  117. package/server/openhands-adapter.ts +775 -0
  118. package/server/orchestrator-executor.ts +184 -0
  119. package/server/orchestrator-store.ts +106 -0
  120. package/server/orchestrator-types.ts +86 -0
  121. package/server/path-resolver.ts +168 -0
  122. package/server/pr-poller.ts +162 -0
  123. package/server/proactive-keepalive.ts +121 -0
  124. package/server/prompt-manager.ts +103 -0
  125. package/server/protocol-monitor.ts +197 -0
  126. package/server/race-controller.ts +295 -0
  127. package/server/race-store.ts +82 -0
  128. package/server/recorder.ts +359 -0
  129. package/server/recording-hub/compat-validator.ts +122 -0
  130. package/server/recording-hub/diagnostics.ts +289 -0
  131. package/server/recording-hub/hub-routes.ts +146 -0
  132. package/server/recording-hub/hub-store.ts +224 -0
  133. package/server/replay.ts +78 -0
  134. package/server/routes/adapter-routes.ts +31 -0
  135. package/server/routes/agent-mcp-routes.ts +35 -0
  136. package/server/routes/agent-routes.ts +185 -0
  137. package/server/routes/auth-routes.ts +101 -0
  138. package/server/routes/ci-routes.ts +165 -0
  139. package/server/routes/commands-routes.ts +94 -0
  140. package/server/routes/cron-routes.ts +100 -0
  141. package/server/routes/env-routes.ts +50 -0
  142. package/server/routes/folder-routes.ts +56 -0
  143. package/server/routes/fs-routes.ts +403 -0
  144. package/server/routes/gallery-routes.ts +410 -0
  145. package/server/routes/git-routes.ts +108 -0
  146. package/server/routes/index.ts +62 -0
  147. package/server/routes/linear-routes.ts +254 -0
  148. package/server/routes/monitor-routes.ts +11 -0
  149. package/server/routes/orchestrator-routes.ts +210 -0
  150. package/server/routes/prompt-routes.ts +47 -0
  151. package/server/routes/race-routes.ts +103 -0
  152. package/server/routes/recording-routes.ts +101 -0
  153. package/server/routes/route-deps.ts +32 -0
  154. package/server/routes/session-routes.ts +560 -0
  155. package/server/routes/settings-routes.ts +108 -0
  156. package/server/routes/skills-routes.ts +52 -0
  157. package/server/routes/system-routes.ts +243 -0
  158. package/server/routes/webhook-routes.ts +135 -0
  159. package/server/routes.ts +44 -0
  160. package/server/security-middleware.ts +100 -0
  161. package/server/semantic-memory.ts +389 -0
  162. package/server/service.ts +733 -0
  163. package/server/session-folders.ts +107 -0
  164. package/server/session-git-info.ts +89 -0
  165. package/server/session-linear-issues.ts +94 -0
  166. package/server/session-names.ts +67 -0
  167. package/server/session-store.ts +169 -0
  168. package/server/session-types.ts +415 -0
  169. package/server/settings-manager.ts +120 -0
  170. package/server/shared-context.ts +401 -0
  171. package/server/skills-manager.ts +250 -0
  172. package/server/sub-session-manager.ts +263 -0
  173. package/server/terminal-manager.ts +185 -0
  174. package/server/update-checker.ts +114 -0
  175. package/server/usage-limits.ts +192 -0
  176. package/server/webhook-manager.ts +228 -0
  177. package/server/webhook-store.ts +168 -0
  178. package/server/webhook-types.ts +66 -0
  179. package/server/worktree-tracker.ts +84 -0
  180. package/server/ws-bridge.ts +2322 -0
@@ -0,0 +1,452 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { Subprocess } from "bun";
3
+ import type { AgentAdapter, AdapterSessionMeta } from "./adapter-types.js";
4
+ import type { RecorderManager } from "./recorder.js";
5
+ import type {
6
+ BrowserIncomingMessage,
7
+ BrowserOutgoingMessage,
8
+ CLIAssistantMessage,
9
+ CLIAuthStatusMessage,
10
+ CLIControlRequestMessage,
11
+ CLIControlResponseMessage,
12
+ CLIMessage,
13
+ CLIResultMessage,
14
+ CLIStreamEventMessage,
15
+ CLISystemInitMessage,
16
+ CLISystemStatusMessage,
17
+ CLIToolProgressMessage,
18
+ CLIToolUseSummaryMessage,
19
+ McpServerDetail,
20
+ PermissionRequest,
21
+ SessionState,
22
+ } from "./session-types.js";
23
+
24
+ export interface ClaudeStdioAdapterOptions {
25
+ model?: string;
26
+ cwd?: string;
27
+ permissionMode?: string;
28
+ recorder?: RecorderManager;
29
+ }
30
+
31
+ class NdjsonTransport {
32
+ private writer: WritableStreamDefaultWriter<Uint8Array>;
33
+ private buffer = "";
34
+ private connected = true;
35
+ private rawInCb: ((line: string) => void) | null = null;
36
+ private rawOutCb: ((line: string) => void) | null = null;
37
+ private messageCb: ((msg: CLIMessage) => void) | null = null;
38
+ private disconnectCb: (() => void) | null = null;
39
+
40
+ constructor(
41
+ stdin: WritableStream<Uint8Array> | { write(data: Uint8Array): number | Promise<number> },
42
+ stdout: ReadableStream<Uint8Array>,
43
+ ) {
44
+ let writable: WritableStream<Uint8Array>;
45
+ if ("write" in stdin && typeof stdin.write === "function") {
46
+ writable = new WritableStream({
47
+ async write(chunk) {
48
+ await (stdin as { write(data: Uint8Array): number | Promise<number> }).write(chunk);
49
+ },
50
+ });
51
+ } else {
52
+ writable = stdin as WritableStream<Uint8Array>;
53
+ }
54
+ this.writer = writable.getWriter();
55
+ this.readStdout(stdout);
56
+ }
57
+
58
+ onRawIncoming(cb: (line: string) => void): void {
59
+ this.rawInCb = cb;
60
+ }
61
+
62
+ onRawOutgoing(cb: (line: string) => void): void {
63
+ this.rawOutCb = cb;
64
+ }
65
+
66
+ onMessage(cb: (msg: CLIMessage) => void): void {
67
+ this.messageCb = cb;
68
+ }
69
+
70
+ onDisconnect(cb: () => void): void {
71
+ this.disconnectCb = cb;
72
+ }
73
+
74
+ isConnected(): boolean {
75
+ return this.connected;
76
+ }
77
+
78
+ async write(msg: unknown): Promise<void> {
79
+ if (!this.connected) throw new Error("Transport closed");
80
+ const line = JSON.stringify(msg);
81
+ this.rawOutCb?.(line);
82
+ await this.writer.write(new TextEncoder().encode(line + "\n"));
83
+ }
84
+
85
+ async close(): Promise<void> {
86
+ this.connected = false;
87
+ try {
88
+ await this.writer.close();
89
+ } catch {}
90
+ }
91
+
92
+ private async readStdout(stdout: ReadableStream<Uint8Array>): Promise<void> {
93
+ const reader = stdout.getReader();
94
+ const decoder = new TextDecoder();
95
+ try {
96
+ while (true) {
97
+ const { done, value } = await reader.read();
98
+ if (done) break;
99
+ this.buffer += decoder.decode(value, { stream: true });
100
+ this.processBuffer();
101
+ }
102
+ } catch (err) {
103
+ console.error("[claude-stdio-adapter] stdout reader error:", err);
104
+ } finally {
105
+ this.connected = false;
106
+ this.disconnectCb?.();
107
+ }
108
+ }
109
+
110
+ private processBuffer(): void {
111
+ const lines = this.buffer.split("\n");
112
+ this.buffer = lines.pop() || "";
113
+
114
+ for (const line of lines) {
115
+ const trimmed = line.trim();
116
+ if (!trimmed) continue;
117
+ this.rawInCb?.(trimmed);
118
+
119
+ let msg: CLIMessage;
120
+ try {
121
+ msg = JSON.parse(trimmed);
122
+ } catch {
123
+ console.warn("[claude-stdio-adapter] Failed to parse NDJSON:", trimmed.substring(0, 200));
124
+ continue;
125
+ }
126
+ this.messageCb?.(msg);
127
+ }
128
+ }
129
+ }
130
+
131
+ export class ClaudeStdioAdapter implements AgentAdapter {
132
+ private transport: NdjsonTransport;
133
+ private proc: Subprocess;
134
+ private sessionId: string;
135
+ private options: ClaudeStdioAdapterOptions;
136
+ private browserMessageCb: ((msg: BrowserIncomingMessage) => void) | null = null;
137
+ private sessionMetaCb: ((meta: AdapterSessionMeta) => void) | null = null;
138
+ private disconnectCb: (() => void) | null = null;
139
+ private initErrorCb: ((error: string) => void) | null = null;
140
+ private backendSessionId: string | null = null;
141
+ private pendingControlRequests = new Map<string, "mcp_status">();
142
+
143
+ constructor(proc: Subprocess, sessionId: string, options: ClaudeStdioAdapterOptions = {}) {
144
+ this.proc = proc;
145
+ this.sessionId = sessionId;
146
+ this.options = options;
147
+
148
+ if (!proc.stdin || typeof proc.stdin === "number" || !proc.stdout || typeof proc.stdout === "number") {
149
+ throw new Error("Claude stdio adapter requires piped stdin and stdout");
150
+ }
151
+
152
+ this.transport = new NdjsonTransport(proc.stdin, proc.stdout);
153
+ this.transport.onRawIncoming((line) => {
154
+ this.options.recorder?.record(this.sessionId, "in", line, "cli", "claude", this.options.cwd ?? "");
155
+ });
156
+ this.transport.onRawOutgoing((line) => {
157
+ this.options.recorder?.record(this.sessionId, "out", line, "cli", "claude", this.options.cwd ?? "");
158
+ });
159
+ this.transport.onMessage((msg) => this.handleCLIMessage(msg));
160
+ this.transport.onDisconnect(() => this.disconnectCb?.());
161
+ }
162
+
163
+ sendBrowserMessage(msg: BrowserOutgoingMessage): boolean {
164
+ try {
165
+ switch (msg.type) {
166
+ case "user_message":
167
+ this.sendUserMessage(msg);
168
+ return true;
169
+ case "permission_response":
170
+ this.sendPermissionResponse(msg);
171
+ return true;
172
+ case "interrupt":
173
+ this.sendControlRequest({ subtype: "interrupt" });
174
+ return true;
175
+ case "set_model":
176
+ this.sendControlRequest({ subtype: "set_model", model: msg.model });
177
+ return true;
178
+ case "set_permission_mode":
179
+ this.sendControlRequest({ subtype: "set_permission_mode", mode: msg.mode });
180
+ return true;
181
+ case "mcp_get_status":
182
+ this.sendMcpStatusRequest();
183
+ return true;
184
+ case "mcp_toggle":
185
+ this.sendControlRequest({ subtype: "mcp_toggle", serverName: msg.serverName, enabled: msg.enabled });
186
+ setTimeout(() => this.sendMcpStatusRequest(), 500);
187
+ return true;
188
+ case "mcp_reconnect":
189
+ this.sendControlRequest({ subtype: "mcp_reconnect", serverName: msg.serverName });
190
+ setTimeout(() => this.sendMcpStatusRequest(), 1000);
191
+ return true;
192
+ case "mcp_set_servers":
193
+ this.sendControlRequest({ subtype: "mcp_set_servers", servers: msg.servers });
194
+ setTimeout(() => this.sendMcpStatusRequest(), 2000);
195
+ return true;
196
+ default:
197
+ return false;
198
+ }
199
+ } catch (err) {
200
+ console.warn("[claude-stdio-adapter] Failed to send browser message:", err);
201
+ return false;
202
+ }
203
+ }
204
+
205
+ onBrowserMessage(cb: (msg: BrowserIncomingMessage) => void): void {
206
+ this.browserMessageCb = cb;
207
+ }
208
+
209
+ onSessionMeta(cb: (meta: AdapterSessionMeta) => void): void {
210
+ this.sessionMetaCb = cb;
211
+ }
212
+
213
+ onDisconnect(cb: () => void): void {
214
+ this.disconnectCb = cb;
215
+ }
216
+
217
+ onInitError(cb: (error: string) => void): void {
218
+ this.initErrorCb = cb;
219
+ }
220
+
221
+ isConnected(): boolean {
222
+ return this.transport.isConnected();
223
+ }
224
+
225
+ async disconnect(): Promise<void> {
226
+ await this.transport.close();
227
+ try {
228
+ this.proc.kill("SIGTERM");
229
+ } catch {}
230
+ }
231
+
232
+ getBackendSessionId(): string | null {
233
+ return this.backendSessionId;
234
+ }
235
+
236
+ private emit(msg: BrowserIncomingMessage): void {
237
+ this.browserMessageCb?.(msg);
238
+ }
239
+
240
+ private sendRaw(msg: unknown): void {
241
+ this.transport.write(msg).catch((err) => {
242
+ console.warn("[claude-stdio-adapter] Failed to write NDJSON:", err);
243
+ });
244
+ }
245
+
246
+ private buildUserContent(msg: Extract<BrowserOutgoingMessage, { type: "user_message" }>): string | unknown[] {
247
+ if (!msg.images?.length) return msg.content;
248
+
249
+ const blocks: unknown[] = msg.images.map((img) => ({
250
+ type: "image",
251
+ source: { type: "base64", media_type: img.media_type, data: img.data },
252
+ }));
253
+ blocks.push({ type: "text", text: msg.content });
254
+ return blocks;
255
+ }
256
+
257
+ private sendUserMessage(msg: Extract<BrowserOutgoingMessage, { type: "user_message" }>): void {
258
+ this.sendRaw({
259
+ type: "user",
260
+ message: { role: "user", content: this.buildUserContent(msg) },
261
+ parent_tool_use_id: null,
262
+ session_id: msg.session_id || this.backendSessionId || "",
263
+ });
264
+ }
265
+
266
+ private sendPermissionResponse(msg: Extract<BrowserOutgoingMessage, { type: "permission_response" }>): void {
267
+ const response: Record<string, unknown> = msg.behavior === "allow"
268
+ ? { behavior: "allow", updatedInput: msg.updated_input ?? {} }
269
+ : { behavior: "deny", message: msg.message || "Denied by user" };
270
+
271
+ if (msg.behavior === "allow" && msg.updated_permissions?.length) {
272
+ response.updatedPermissions = msg.updated_permissions;
273
+ }
274
+
275
+ this.sendRaw({
276
+ type: "control_response",
277
+ response: {
278
+ subtype: "success",
279
+ request_id: msg.request_id,
280
+ response,
281
+ },
282
+ });
283
+ }
284
+
285
+ private sendControlRequest(request: Record<string, unknown>, requestId = randomUUID()): string {
286
+ this.sendRaw({
287
+ type: "control_request",
288
+ request_id: requestId,
289
+ request,
290
+ });
291
+ return requestId;
292
+ }
293
+
294
+ private sendMcpStatusRequest(): void {
295
+ const requestId = this.sendControlRequest({ subtype: "mcp_status" });
296
+ this.pendingControlRequests.set(requestId, "mcp_status");
297
+ }
298
+
299
+ private handleCLIMessage(msg: CLIMessage): void {
300
+ switch (msg.type) {
301
+ case "system":
302
+ this.handleSystemMessage(msg);
303
+ break;
304
+ case "assistant":
305
+ this.handleAssistantMessage(msg);
306
+ break;
307
+ case "result":
308
+ this.emit({ type: "result", data: msg });
309
+ break;
310
+ case "stream_event":
311
+ this.handleStreamEvent(msg);
312
+ break;
313
+ case "control_request":
314
+ this.handleControlRequest(msg);
315
+ break;
316
+ case "tool_progress":
317
+ this.handleToolProgress(msg);
318
+ break;
319
+ case "tool_use_summary":
320
+ this.handleToolUseSummary(msg);
321
+ break;
322
+ case "auth_status":
323
+ this.handleAuthStatus(msg);
324
+ break;
325
+ case "control_response":
326
+ this.handleControlResponse(msg);
327
+ break;
328
+ case "keep_alive":
329
+ break;
330
+ }
331
+ }
332
+
333
+ private handleSystemMessage(msg: CLISystemInitMessage | CLISystemStatusMessage): void {
334
+ if (msg.subtype === "init") {
335
+ this.backendSessionId = msg.session_id;
336
+ this.sessionMetaCb?.({ cliSessionId: msg.session_id, model: msg.model, cwd: msg.cwd });
337
+ this.emit({
338
+ type: "session_init",
339
+ session: this.toSessionState(msg),
340
+ });
341
+ return;
342
+ }
343
+
344
+ if (msg.permissionMode) {
345
+ this.emit({ type: "session_update", session: { permissionMode: msg.permissionMode } });
346
+ }
347
+ this.emit({ type: "status_change", status: msg.status ?? null });
348
+ }
349
+
350
+ private toSessionState(msg: CLISystemInitMessage): SessionState {
351
+ return {
352
+ session_id: this.sessionId,
353
+ backend_type: "claude",
354
+ model: msg.model,
355
+ cwd: msg.cwd,
356
+ tools: msg.tools,
357
+ permissionMode: msg.permissionMode,
358
+ claude_code_version: msg.claude_code_version,
359
+ mcp_servers: msg.mcp_servers,
360
+ agents: msg.agents ?? [],
361
+ slash_commands: msg.slash_commands ?? [],
362
+ skills: msg.skills ?? [],
363
+ total_cost_usd: 0,
364
+ num_turns: 0,
365
+ context_used_percent: 0,
366
+ is_compacting: false,
367
+ git_branch: "",
368
+ is_worktree: false,
369
+ repo_root: "",
370
+ git_ahead: 0,
371
+ git_behind: 0,
372
+ total_lines_added: 0,
373
+ total_lines_removed: 0,
374
+ total_duration_api_ms: 0,
375
+ };
376
+ }
377
+
378
+ private handleAssistantMessage(msg: CLIAssistantMessage): void {
379
+ this.emit({
380
+ type: "assistant",
381
+ message: msg.message,
382
+ parent_tool_use_id: msg.parent_tool_use_id,
383
+ timestamp: Date.now(),
384
+ });
385
+ }
386
+
387
+ private handleStreamEvent(msg: CLIStreamEventMessage): void {
388
+ this.emit({
389
+ type: "stream_event",
390
+ event: msg.event,
391
+ parent_tool_use_id: msg.parent_tool_use_id,
392
+ });
393
+ }
394
+
395
+ private handleControlRequest(msg: CLIControlRequestMessage): void {
396
+ if (msg.request.subtype !== "can_use_tool") return;
397
+ const request: PermissionRequest = {
398
+ request_id: msg.request_id,
399
+ tool_name: msg.request.tool_name,
400
+ input: msg.request.input,
401
+ permission_suggestions: msg.request.permission_suggestions,
402
+ description: msg.request.description,
403
+ tool_use_id: msg.request.tool_use_id,
404
+ agent_id: msg.request.agent_id,
405
+ timestamp: Date.now(),
406
+ };
407
+ this.emit({ type: "permission_request", request });
408
+ }
409
+
410
+ private handleToolProgress(msg: CLIToolProgressMessage): void {
411
+ this.emit({
412
+ type: "tool_progress",
413
+ tool_use_id: msg.tool_use_id,
414
+ tool_name: msg.tool_name,
415
+ elapsed_time_seconds: msg.elapsed_time_seconds,
416
+ });
417
+ }
418
+
419
+ private handleToolUseSummary(msg: CLIToolUseSummaryMessage): void {
420
+ this.emit({
421
+ type: "tool_use_summary",
422
+ summary: msg.summary,
423
+ tool_use_ids: msg.preceding_tool_use_ids,
424
+ });
425
+ }
426
+
427
+ private handleAuthStatus(msg: CLIAuthStatusMessage): void {
428
+ this.emit({
429
+ type: "auth_status",
430
+ isAuthenticating: msg.isAuthenticating,
431
+ output: msg.output,
432
+ error: msg.error,
433
+ });
434
+ }
435
+
436
+ private handleControlResponse(msg: CLIControlResponseMessage): void {
437
+ const requestId = msg.response.request_id;
438
+ const pending = this.pendingControlRequests.get(requestId);
439
+ if (!pending) return;
440
+ this.pendingControlRequests.delete(requestId);
441
+
442
+ if (msg.response.subtype === "error") {
443
+ console.warn(`[claude-stdio-adapter] Control request ${pending} failed: ${msg.response.error}`);
444
+ return;
445
+ }
446
+
447
+ if (pending === "mcp_status") {
448
+ const servers = (msg.response.response as { mcpServers?: McpServerDetail[] } | undefined)?.mcpServers ?? [];
449
+ this.emit({ type: "mcp_status", servers });
450
+ }
451
+ }
452
+ }
@@ -0,0 +1,218 @@
1
+ import { execSync } from "node:child_process";
2
+ import { mkdirSync, writeFileSync, existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import type { GalleryEntry } from "./gallery-types.js";
6
+
7
+ // ─── Types ──────────────────────────────────────────────────────────────────
8
+
9
+ export interface ClawHubSkillMeta {
10
+ name: string;
11
+ description: string;
12
+ emoji: string;
13
+ homepage: string;
14
+ cost: string;
15
+ duration: string;
16
+ model: string;
17
+ backend: string;
18
+ turns: number;
19
+ prompt?: string;
20
+ replayUrl?: string;
21
+ }
22
+
23
+ export interface ClawHubSearchResult {
24
+ name: string;
25
+ description: string;
26
+ version: string;
27
+ author?: string;
28
+ downloads?: number;
29
+ }
30
+
31
+ // ─── ClawHub CLI availability ───────────────────────────────────────────────
32
+
33
+ /** Check if the `clawhub` CLI is available on PATH. */
34
+ export function checkClawHubAvailable(): boolean {
35
+ try {
36
+ execSync("clawhub --version", { stdio: "ignore", timeout: 5000 });
37
+ return true;
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ // ─── SKILL.md generation ────────────────────────────────────────────────────
44
+
45
+ function formatDuration(ms: number): string {
46
+ const secs = Math.floor(ms / 1000);
47
+ if (secs < 60) return `${secs}s`;
48
+ const mins = Math.floor(secs / 60);
49
+ if (mins < 60) return `${mins}m`;
50
+ const hours = Math.floor(mins / 60);
51
+ const remainingMins = mins % 60;
52
+ return remainingMins > 0 ? `${hours}h ${remainingMins}m` : `${hours}h`;
53
+ }
54
+
55
+ /**
56
+ * Generate SKILL.md content from a gallery entry.
57
+ * This creates a ClawHub-compatible skill definition that
58
+ * links back to Campfire for replay and details.
59
+ */
60
+ export function generateSkillMd(
61
+ entry: GalleryEntry,
62
+ options?: {
63
+ campfireBaseUrl?: string;
64
+ prompt?: string;
65
+ },
66
+ ): string {
67
+ const baseUrl = options?.campfireBaseUrl || "http://localhost:4567";
68
+ const cost = `$${entry.totalCostUsd.toFixed(2)}`;
69
+ const duration = formatDuration(entry.durationMs);
70
+ const replayUrl = `${baseUrl}/#/replay/session/${entry.sessionId}`;
71
+ const galleryUrl = `${baseUrl}/#/gallery`;
72
+
73
+ const lines: string[] = [];
74
+
75
+ // YAML frontmatter
76
+ lines.push("---");
77
+ lines.push(`name: ${entry.id}`);
78
+ lines.push(`description: ${entry.description || entry.name}`);
79
+ lines.push("metadata:");
80
+ lines.push(" openclaw:");
81
+ lines.push(' emoji: "🔥"');
82
+ lines.push(` homepage: "${galleryUrl}"`);
83
+ lines.push("user-invocable: true");
84
+ lines.push("---");
85
+ lines.push("");
86
+
87
+ // Title
88
+ lines.push(`# ${entry.name}`);
89
+ lines.push("");
90
+
91
+ // Stats line
92
+ lines.push(`**Cost:** ${cost} | **Duration:** ${duration} | **Model:** ${entry.model} | **Backend:** ${entry.backendType} | **Turns:** ${entry.numTurns}`);
93
+ lines.push("");
94
+
95
+ // Description
96
+ if (entry.description) {
97
+ lines.push("## What happened");
98
+ lines.push("");
99
+ lines.push(entry.description);
100
+ lines.push("");
101
+ }
102
+
103
+ // Tags
104
+ if (entry.tags.length > 0) {
105
+ lines.push(`**Tags:** ${entry.tags.join(", ")}`);
106
+ lines.push("");
107
+ }
108
+
109
+ // Lines changed
110
+ if (entry.totalLinesAdded > 0 || entry.totalLinesRemoved > 0) {
111
+ lines.push(`**Lines changed:** +${entry.totalLinesAdded} / -${entry.totalLinesRemoved}`);
112
+ lines.push("");
113
+ }
114
+
115
+ // Replay link
116
+ lines.push("## Replay");
117
+ lines.push("");
118
+ lines.push(`View the full session replay at: ${replayUrl}`);
119
+ lines.push("");
120
+
121
+ // Original prompt
122
+ if (options?.prompt) {
123
+ lines.push("## Prompt");
124
+ lines.push("");
125
+ lines.push(options.prompt);
126
+ lines.push("");
127
+ }
128
+
129
+ // Footer
130
+ lines.push("---");
131
+ lines.push("*Exported from [Campfire](https://github.com/your-org/campfire)*");
132
+ lines.push("");
133
+
134
+ return lines.join("\n");
135
+ }
136
+
137
+ // ─── Export to ClawHub ──────────────────────────────────────────────────────
138
+
139
+ /**
140
+ * Export a gallery entry to ClawHub.
141
+ * Writes SKILL.md to a temp directory and runs `clawhub publish`.
142
+ */
143
+ export function exportToClawHub(
144
+ entry: GalleryEntry,
145
+ options?: {
146
+ campfireBaseUrl?: string;
147
+ prompt?: string;
148
+ dryRun?: boolean;
149
+ },
150
+ ): { success: boolean; skillDir: string; output?: string; error?: string } {
151
+ const skillDir = join(tmpdir(), `campfire-clawhub-${entry.id}`);
152
+ mkdirSync(skillDir, { recursive: true });
153
+
154
+ const skillMd = generateSkillMd(entry, options);
155
+ writeFileSync(join(skillDir, "SKILL.md"), skillMd, "utf-8");
156
+
157
+ if (options?.dryRun) {
158
+ return { success: true, skillDir };
159
+ }
160
+
161
+ try {
162
+ const output = execSync(`clawhub publish "${skillDir}"`, {
163
+ cwd: skillDir,
164
+ timeout: 30_000,
165
+ encoding: "utf-8",
166
+ });
167
+ return { success: true, skillDir, output: output.trim() };
168
+ } catch (err) {
169
+ const msg = err instanceof Error ? err.message : String(err);
170
+ return { success: false, skillDir, error: msg };
171
+ }
172
+ }
173
+
174
+ // ─── Search ClawHub ─────────────────────────────────────────────────────────
175
+
176
+ /**
177
+ * Search ClawHub skills via the CLI.
178
+ * Returns parsed results from `clawhub search <query>`.
179
+ */
180
+ export function searchClawHub(query: string): ClawHubSearchResult[] {
181
+ try {
182
+ const output = execSync(`clawhub search "${query.replace(/"/g, '\\"')}" --json`, {
183
+ timeout: 15_000,
184
+ encoding: "utf-8",
185
+ });
186
+
187
+ // Parse JSON output from clawhub CLI
188
+ const parsed = JSON.parse(output.trim());
189
+ if (Array.isArray(parsed)) {
190
+ return parsed.map((item: Record<string, unknown>) => ({
191
+ name: String(item.name || ""),
192
+ description: String(item.description || ""),
193
+ version: String(item.version || "0.0.0"),
194
+ author: item.author ? String(item.author) : undefined,
195
+ downloads: typeof item.downloads === "number" ? item.downloads : undefined,
196
+ }));
197
+ }
198
+ return [];
199
+ } catch {
200
+ return [];
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Install a ClawHub skill by slug.
206
+ */
207
+ export function installClawHubSkill(slug: string): { success: boolean; output?: string; error?: string } {
208
+ try {
209
+ const output = execSync(`clawhub install "${slug.replace(/"/g, '\\"')}"`, {
210
+ timeout: 30_000,
211
+ encoding: "utf-8",
212
+ });
213
+ return { success: true, output: output.trim() };
214
+ } catch (err) {
215
+ const msg = err instanceof Error ? err.message : String(err);
216
+ return { success: false, error: msg };
217
+ }
218
+ }