@fronx/use-claude-code 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 (58) hide show
  1. package/README.md +273 -0
  2. package/dist/client/connection.d.ts +101 -0
  3. package/dist/client/connection.d.ts.map +1 -0
  4. package/dist/client/connection.js +379 -0
  5. package/dist/client/connection.js.map +1 -0
  6. package/dist/client/event-emitter.d.ts +32 -0
  7. package/dist/client/event-emitter.d.ts.map +1 -0
  8. package/dist/client/event-emitter.js +72 -0
  9. package/dist/client/event-emitter.js.map +1 -0
  10. package/dist/client/index.d.ts +30 -0
  11. package/dist/client/index.d.ts.map +1 -0
  12. package/dist/client/index.js +29 -0
  13. package/dist/client/index.js.map +1 -0
  14. package/dist/client/state-machine.d.ts +61 -0
  15. package/dist/client/state-machine.d.ts.map +1 -0
  16. package/dist/client/state-machine.js +292 -0
  17. package/dist/client/state-machine.js.map +1 -0
  18. package/dist/client/stream-processor.d.ts +46 -0
  19. package/dist/client/stream-processor.d.ts.map +1 -0
  20. package/dist/client/stream-processor.js +173 -0
  21. package/dist/client/stream-processor.js.map +1 -0
  22. package/dist/react/hook.d.ts +55 -0
  23. package/dist/react/hook.d.ts.map +1 -0
  24. package/dist/react/hook.js +132 -0
  25. package/dist/react/hook.js.map +1 -0
  26. package/dist/react/index.d.ts +49 -0
  27. package/dist/react/index.d.ts.map +1 -0
  28. package/dist/react/index.js +48 -0
  29. package/dist/react/index.js.map +1 -0
  30. package/dist/server/claude-spawn.d.ts +52 -0
  31. package/dist/server/claude-spawn.d.ts.map +1 -0
  32. package/dist/server/claude-spawn.js +147 -0
  33. package/dist/server/claude-spawn.js.map +1 -0
  34. package/dist/server/conversation.d.ts +113 -0
  35. package/dist/server/conversation.d.ts.map +1 -0
  36. package/dist/server/conversation.js +520 -0
  37. package/dist/server/conversation.js.map +1 -0
  38. package/dist/server/index.d.ts +35 -0
  39. package/dist/server/index.d.ts.map +1 -0
  40. package/dist/server/index.js +39 -0
  41. package/dist/server/index.js.map +1 -0
  42. package/dist/server/sse-emitter.d.ts +42 -0
  43. package/dist/server/sse-emitter.d.ts.map +1 -0
  44. package/dist/server/sse-emitter.js +56 -0
  45. package/dist/server/sse-emitter.js.map +1 -0
  46. package/dist/shared/index.d.ts +3 -0
  47. package/dist/shared/index.d.ts.map +1 -0
  48. package/dist/shared/index.js +3 -0
  49. package/dist/shared/index.js.map +1 -0
  50. package/dist/shared/tool-utils.d.ts +25 -0
  51. package/dist/shared/tool-utils.d.ts.map +1 -0
  52. package/dist/shared/tool-utils.js +49 -0
  53. package/dist/shared/tool-utils.js.map +1 -0
  54. package/dist/shared/types.d.ts +82 -0
  55. package/dist/shared/types.d.ts.map +1 -0
  56. package/dist/shared/types.js +5 -0
  57. package/dist/shared/types.js.map +1 -0
  58. package/package.json +40 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * React hook for Claude Code conversation management.
3
+ *
4
+ * Thin wrapper over ClaudeConnection that converts events to React state updates.
5
+ */
6
+ import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
7
+ import { ClaudeConnection } from '../client/connection.js';
8
+ /**
9
+ * Hook for managing a Claude conversation in React.
10
+ *
11
+ * @example
12
+ * function Chat({ projectId }) {
13
+ * const {
14
+ * messages,
15
+ * status,
16
+ * isStreaming,
17
+ * streamingSegments,
18
+ * hasPersistedSession,
19
+ * start,
20
+ * send,
21
+ * stop,
22
+ * clear,
23
+ * resume,
24
+ * } = useClaudeConversation({
25
+ * baseUrl: '/api/claude',
26
+ * conversationId: projectId,
27
+ * onResult: () => console.log('Response complete'),
28
+ * });
29
+ *
30
+ * // Render messages, input, etc.
31
+ * }
32
+ */
33
+ export function useClaudeConversation(options) {
34
+ const { baseUrl, conversationId, autoConnect = true, reconnect, onResult } = options;
35
+ // State
36
+ const [messages, setMessages] = useState([]);
37
+ const [status, setStatus] = useState('checking');
38
+ const [isStreaming, setIsStreaming] = useState(false);
39
+ const [streamingSegments, setStreamingSegments] = useState([]);
40
+ const [hasPersistedSession, setHasPersistedSession] = useState(false);
41
+ const [lastError, setLastError] = useState(null);
42
+ // Store callback in ref to avoid triggering effect re-runs when parent re-renders
43
+ const onResultRef = useRef(onResult);
44
+ onResultRef.current = onResult;
45
+ // Connection instance - recreate when conversationId changes
46
+ const connectionRef = useRef(null);
47
+ // Create connection config - memoize to avoid unnecessary recreation
48
+ const connectionConfig = useMemo(() => ({
49
+ baseUrl,
50
+ conversationId,
51
+ reconnect,
52
+ }), [baseUrl, conversationId, reconnect]);
53
+ // Effect to manage connection lifecycle
54
+ useEffect(() => {
55
+ // Create new connection
56
+ const connection = new ClaudeConnection(connectionConfig);
57
+ connectionRef.current = connection;
58
+ // Subscribe to state changes
59
+ const handleStateChange = () => {
60
+ setMessages(connection.messages);
61
+ setStatus(connection.status);
62
+ setIsStreaming(connection.isStreaming);
63
+ setStreamingSegments(connection.streamingSegments);
64
+ setHasPersistedSession(connection.hasPersistedSession);
65
+ setLastError(connection.lastError);
66
+ };
67
+ const handleResult = () => {
68
+ onResultRef.current?.();
69
+ };
70
+ connection.on('stateChange', handleStateChange);
71
+ connection.on('result', handleResult);
72
+ // Connect if autoConnect is true
73
+ if (autoConnect) {
74
+ connection.connect();
75
+ }
76
+ // Cleanup on unmount or when conversationId changes
77
+ return () => {
78
+ connection.off('stateChange', handleStateChange);
79
+ connection.off('result', handleResult);
80
+ connection.disconnect();
81
+ connectionRef.current = null;
82
+ };
83
+ }, [connectionConfig, autoConnect]);
84
+ // Action methods - wrapped in useCallback to maintain stable references
85
+ const start = useCallback(async (initialMessage) => {
86
+ if (connectionRef.current) {
87
+ await connectionRef.current.start(initialMessage);
88
+ }
89
+ }, []);
90
+ const send = useCallback(async (message) => {
91
+ if (connectionRef.current) {
92
+ await connectionRef.current.send(message);
93
+ }
94
+ }, []);
95
+ const stop = useCallback(async () => {
96
+ if (connectionRef.current) {
97
+ await connectionRef.current.stop();
98
+ }
99
+ }, []);
100
+ const clear = useCallback(async () => {
101
+ if (connectionRef.current) {
102
+ await connectionRef.current.clear();
103
+ }
104
+ }, []);
105
+ const resume = useCallback(async () => {
106
+ if (connectionRef.current) {
107
+ await connectionRef.current.resume();
108
+ }
109
+ }, []);
110
+ const prewarm = useCallback(async () => {
111
+ if (connectionRef.current) {
112
+ await connectionRef.current.prewarm();
113
+ }
114
+ }, []);
115
+ return {
116
+ // State
117
+ messages,
118
+ status,
119
+ isStreaming,
120
+ streamingSegments,
121
+ hasPersistedSession,
122
+ lastError,
123
+ // Actions
124
+ start,
125
+ send,
126
+ stop,
127
+ clear,
128
+ resume,
129
+ prewarm,
130
+ };
131
+ }
132
+ //# sourceMappingURL=hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../../src/react/hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * React hooks for Claude Code integration
3
+ *
4
+ * @example
5
+ * import { useClaudeConversation } from '@anthropic/claude-code-lib/react';
6
+ *
7
+ * function Chat({ projectId }) {
8
+ * const {
9
+ * messages,
10
+ * status,
11
+ * isStreaming,
12
+ * streamingSegments,
13
+ * hasPersistedSession,
14
+ * start,
15
+ * send,
16
+ * stop,
17
+ * clear,
18
+ * resume,
19
+ * } = useClaudeConversation({
20
+ * baseUrl: '/api/claude',
21
+ * conversationId: projectId,
22
+ * onResult: () => console.log('Response complete'),
23
+ * });
24
+ *
25
+ * return (
26
+ * <div>
27
+ * {messages.map(msg => (
28
+ * <div key={msg.id}>{msg.content}</div>
29
+ * ))}
30
+ *
31
+ * {streamingSegments.map((seg, i) =>
32
+ * seg.type === 'text'
33
+ * ? <div key={i}>{seg.content}</div>
34
+ * : <div key={i}>Running: {seg.name}</div>
35
+ * )}
36
+ *
37
+ * {status === 'disconnected' && hasPersistedSession && (
38
+ * <button onClick={resume}>Resume</button>
39
+ * )}
40
+ * </div>
41
+ * );
42
+ * }
43
+ */
44
+ export * from '../shared/index.js';
45
+ export type { ConnectionStatus, ChatState } from '../client/state-machine.js';
46
+ export type { ClaudeConnectionConfig, ClaudeConnectionEvents } from '../client/connection.js';
47
+ export { useClaudeConversation } from './hook.js';
48
+ export type { UseClaudeConversationOptions, UseClaudeConversationReturn } from './hook.js';
49
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,cAAc,oBAAoB,CAAC;AAGnC,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,YAAY,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAG9F,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAClD,YAAY,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * React hooks for Claude Code integration
3
+ *
4
+ * @example
5
+ * import { useClaudeConversation } from '@anthropic/claude-code-lib/react';
6
+ *
7
+ * function Chat({ projectId }) {
8
+ * const {
9
+ * messages,
10
+ * status,
11
+ * isStreaming,
12
+ * streamingSegments,
13
+ * hasPersistedSession,
14
+ * start,
15
+ * send,
16
+ * stop,
17
+ * clear,
18
+ * resume,
19
+ * } = useClaudeConversation({
20
+ * baseUrl: '/api/claude',
21
+ * conversationId: projectId,
22
+ * onResult: () => console.log('Response complete'),
23
+ * });
24
+ *
25
+ * return (
26
+ * <div>
27
+ * {messages.map(msg => (
28
+ * <div key={msg.id}>{msg.content}</div>
29
+ * ))}
30
+ *
31
+ * {streamingSegments.map((seg, i) =>
32
+ * seg.type === 'text'
33
+ * ? <div key={i}>{seg.content}</div>
34
+ * : <div key={i}>Running: {seg.name}</div>
35
+ * )}
36
+ *
37
+ * {status === 'disconnected' && hasPersistedSession && (
38
+ * <button onClick={resume}>Resume</button>
39
+ * )}
40
+ * </div>
41
+ * );
42
+ * }
43
+ */
44
+ // Re-export shared types and utilities
45
+ export * from '../shared/index.js';
46
+ // React-specific exports
47
+ export { useClaudeConversation } from './hook.js';
48
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,uCAAuC;AACvC,cAAc,oBAAoB,CAAC;AAMnC,yBAAyB;AACzB,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Claude CLI subprocess spawning utilities.
3
+ *
4
+ * Handles spawning Claude CLI in both one-shot (-p) mode and conversational mode
5
+ * with stream-json input/output for multi-turn interactions.
6
+ */
7
+ import { ChildProcess } from 'child_process';
8
+ export interface ClaudeSpawnOptions {
9
+ prompt: string;
10
+ cwd: string;
11
+ claudePath?: string;
12
+ allowedTools?: string[];
13
+ onStdoutLine: (line: string, parsed: unknown | null) => void;
14
+ onStderr: (content: string) => void;
15
+ onError: (err: Error) => void;
16
+ onClose: (code: number | null, signal: string | null) => void;
17
+ }
18
+ export interface ClaudeProcess {
19
+ process: ChildProcess;
20
+ kill: () => void;
21
+ }
22
+ /**
23
+ * Spawn Claude CLI with a single prompt (one-shot mode).
24
+ * Returns a handle to kill the process if needed.
25
+ */
26
+ export declare function spawnClaude(options: ClaudeSpawnOptions): ClaudeProcess;
27
+ export interface ClaudeConversationOptions {
28
+ cwd: string;
29
+ systemPrompt: string;
30
+ claudePath?: string;
31
+ allowedTools?: string[];
32
+ resumeSessionId?: string;
33
+ onMessage: (data: unknown) => void;
34
+ onError: (err: Error) => void;
35
+ onClose: (code: number | null) => void;
36
+ }
37
+ export interface ClaudeConversation {
38
+ process: ChildProcess;
39
+ sendMessage: (text: string) => void;
40
+ interrupt: () => void;
41
+ kill: () => void;
42
+ }
43
+ /**
44
+ * Spawn Claude CLI in conversational mode for multi-turn interactions.
45
+ *
46
+ * Uses --input-format stream-json and --output-format stream-json to allow
47
+ * sending multiple messages via stdin and receiving responses via stdout.
48
+ *
49
+ * Key difference from spawnClaude: stdin stays open for sending messages.
50
+ */
51
+ export declare function spawnClaudeConversation(options: ClaudeConversationOptions): ClaudeConversation;
52
+ //# sourceMappingURL=claude-spawn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-spawn.d.ts","sourceRoot":"","sources":["../../src/server/claude-spawn.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAS,YAAY,EAAE,MAAM,eAAe,CAAC;AAEpD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7D,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAC/D;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,aAAa,CAgEtE;AAMD,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,YAAY,CAAC;IACtB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,yBAAyB,GACjC,kBAAkB,CAkGpB"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Claude CLI subprocess spawning utilities.
3
+ *
4
+ * Handles spawning Claude CLI in both one-shot (-p) mode and conversational mode
5
+ * with stream-json input/output for multi-turn interactions.
6
+ */
7
+ import { spawn } from 'child_process';
8
+ /**
9
+ * Spawn Claude CLI with a single prompt (one-shot mode).
10
+ * Returns a handle to kill the process if needed.
11
+ */
12
+ export function spawnClaude(options) {
13
+ const { prompt, cwd, claudePath = 'claude', allowedTools = ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep'], onStdoutLine, onStderr, onError, onClose, } = options;
14
+ const args = [
15
+ '-p',
16
+ prompt,
17
+ '--allowedTools',
18
+ allowedTools.join(','),
19
+ '--output-format',
20
+ 'stream-json',
21
+ '--verbose',
22
+ '--dangerously-skip-permissions',
23
+ ];
24
+ const proc = spawn(claudePath, args, {
25
+ cwd,
26
+ env: { ...process.env },
27
+ stdio: ['pipe', 'pipe', 'pipe'],
28
+ });
29
+ // Close stdin immediately - Claude doesn't need input for -p mode
30
+ proc.stdin.end();
31
+ proc.on('error', onError);
32
+ let buffer = '';
33
+ proc.stdout.on('data', (data) => {
34
+ buffer += data.toString();
35
+ const lines = buffer.split('\n');
36
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
37
+ for (const line of lines) {
38
+ if (line.trim()) {
39
+ let parsed = null;
40
+ try {
41
+ parsed = JSON.parse(line);
42
+ }
43
+ catch {
44
+ // Not JSON
45
+ }
46
+ onStdoutLine(line, parsed);
47
+ }
48
+ }
49
+ });
50
+ proc.stderr.on('data', (data) => {
51
+ onStderr(data.toString());
52
+ });
53
+ proc.on('close', onClose);
54
+ return {
55
+ process: proc,
56
+ kill: () => proc.kill(),
57
+ };
58
+ }
59
+ /**
60
+ * Spawn Claude CLI in conversational mode for multi-turn interactions.
61
+ *
62
+ * Uses --input-format stream-json and --output-format stream-json to allow
63
+ * sending multiple messages via stdin and receiving responses via stdout.
64
+ *
65
+ * Key difference from spawnClaude: stdin stays open for sending messages.
66
+ */
67
+ export function spawnClaudeConversation(options) {
68
+ const { cwd, systemPrompt, claudePath = 'claude', allowedTools = ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep'], resumeSessionId, onMessage, onError, onClose, } = options;
69
+ const args = [
70
+ '-p',
71
+ '--input-format',
72
+ 'stream-json',
73
+ '--output-format',
74
+ 'stream-json',
75
+ '--system-prompt',
76
+ systemPrompt,
77
+ '--allowedTools',
78
+ allowedTools.join(','),
79
+ '--dangerously-skip-permissions',
80
+ '--verbose',
81
+ ];
82
+ // Add --resume flag if resuming a previous session
83
+ if (resumeSessionId) {
84
+ args.push('--resume', resumeSessionId);
85
+ }
86
+ const proc = spawn(claudePath, args, {
87
+ cwd,
88
+ env: { ...process.env },
89
+ stdio: ['pipe', 'pipe', 'pipe'],
90
+ });
91
+ // Don't close stdin - we'll send messages through it
92
+ proc.on('error', onError);
93
+ let buffer = '';
94
+ proc.stdout.on('data', (data) => {
95
+ buffer += data.toString();
96
+ const lines = buffer.split('\n');
97
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
98
+ for (const line of lines) {
99
+ if (line.trim()) {
100
+ try {
101
+ const parsed = JSON.parse(line);
102
+ onMessage(parsed);
103
+ }
104
+ catch {
105
+ // Non-JSON line, ignore
106
+ }
107
+ }
108
+ }
109
+ });
110
+ proc.stderr.on('data', (data) => {
111
+ // Log stderr for debugging but don't treat as error
112
+ console.error('[claude-spawn stderr]:', data.toString());
113
+ });
114
+ proc.on('close', (code) => {
115
+ onClose(code);
116
+ });
117
+ /**
118
+ * Send a user message to the Claude process.
119
+ *
120
+ * The stream-json input format expects newline-delimited JSON objects.
121
+ * Format: { type: 'user', message: { role: 'user', content: [...] } }
122
+ */
123
+ function sendMessage(text) {
124
+ const message = JSON.stringify({
125
+ type: 'user',
126
+ message: {
127
+ role: 'user',
128
+ content: [{ type: 'text', text }],
129
+ },
130
+ });
131
+ proc.stdin.write(message + '\n');
132
+ }
133
+ return {
134
+ process: proc,
135
+ sendMessage,
136
+ interrupt: () => {
137
+ // Send SIGINT (Ctrl+C) to pause generation without killing the process
138
+ // This matches Claude Code's Esc behavior
139
+ proc.kill('SIGINT');
140
+ },
141
+ kill: () => {
142
+ proc.stdin.end();
143
+ proc.kill();
144
+ },
145
+ };
146
+ }
147
+ //# sourceMappingURL=claude-spawn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-spawn.js","sourceRoot":"","sources":["../../src/server/claude-spawn.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Claude conversation lifecycle management.
3
+ *
4
+ * The ClaudeManager class handles:
5
+ * - Spawning and tracking Claude CLI conversations
6
+ * - Session persistence to the filesystem
7
+ * - Resuming conversations with --resume flag
8
+ * - Prewarming (spawning without an initial message)
9
+ * - SSE client subscription management
10
+ */
11
+ import { ServerResponse } from 'http';
12
+ import { Message } from '../shared/types.js';
13
+ export interface ClaudeManagerConfig {
14
+ sessionDir: string;
15
+ claudePath?: string;
16
+ defaultAllowedTools?: string[];
17
+ autoPrewarm?: boolean;
18
+ }
19
+ export interface StartOptions {
20
+ systemPrompt: string | {
21
+ file: string;
22
+ };
23
+ cwd: string;
24
+ allowedTools?: string[];
25
+ initialMessage?: string;
26
+ }
27
+ export type ConversationStatus = 'starting' | 'ready' | 'streaming' | 'stopped';
28
+ export interface Conversation {
29
+ readonly id: string;
30
+ readonly sessionId: string;
31
+ readonly claudeSessionId: string | null;
32
+ readonly status: ConversationStatus;
33
+ readonly messages: Message[];
34
+ readonly isPrewarmed: boolean;
35
+ send(message: string): void;
36
+ stop(): void;
37
+ subscribe(res: ServerResponse): void;
38
+ unsubscribe(res: ServerResponse): void;
39
+ }
40
+ export declare class ClaudeManager {
41
+ private config;
42
+ private conversations;
43
+ constructor(config: ClaudeManagerConfig);
44
+ /**
45
+ * Start a new conversation.
46
+ *
47
+ * If a prewarmed conversation exists for this ID, it will be reused.
48
+ * Otherwise, a new Claude process is spawned.
49
+ */
50
+ start(id: string, options: StartOptions): Conversation;
51
+ /**
52
+ * Get an active conversation by ID.
53
+ */
54
+ get(id: string): Conversation | undefined;
55
+ /**
56
+ * Resume a conversation from a persisted session.
57
+ *
58
+ * Loads the session from disk and spawns Claude with --resume flag.
59
+ */
60
+ resume(id: string, options: StartOptions): Conversation;
61
+ /**
62
+ * Stop a conversation's current response.
63
+ *
64
+ * Sends SIGINT to pause generation without killing the process.
65
+ */
66
+ stop(id: string): void;
67
+ /**
68
+ * Clear a conversation completely.
69
+ *
70
+ * Kills the process, deletes the persisted session, and optionally
71
+ * prewarms a new conversation.
72
+ */
73
+ clear(id: string): void;
74
+ /**
75
+ * Prewarm a conversation by spawning Claude without sending a message.
76
+ *
77
+ * This makes the first response faster since Claude is already running.
78
+ */
79
+ prewarm(id: string, options: StartOptions): void;
80
+ /**
81
+ * Check if a persisted session exists for an ID.
82
+ */
83
+ hasPersistedSession(id: string): boolean;
84
+ /**
85
+ * Get status information for a conversation.
86
+ */
87
+ getStatus(id: string): {
88
+ active: boolean;
89
+ messageCount: number;
90
+ isStreaming: boolean;
91
+ sessionId: string | null;
92
+ claudeSessionId: string | null;
93
+ hasPersistedSession: boolean;
94
+ };
95
+ /**
96
+ * Clear all conversations (for testing or shutdown).
97
+ */
98
+ clearAll(): void;
99
+ private spawnClaude;
100
+ private spawnClaudePrewarmed;
101
+ private activatePrewarmed;
102
+ private handleClaudeMessage;
103
+ private handleClaudeError;
104
+ private handleClaudeClose;
105
+ private createUserMessage;
106
+ private broadcastToConversation;
107
+ private createConversationHandle;
108
+ private getSessionPath;
109
+ private saveSession;
110
+ private loadSession;
111
+ private deleteSession;
112
+ }
113
+ //# sourceMappingURL=conversation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/server/conversation.ts"],"names":[],"mappings":"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"}