ftown-bridge 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.
@@ -0,0 +1,20 @@
1
+ import type { Command, CommandResponse, Session } from './types.js';
2
+ type TerminalInputHandler = (sessionId: string, data: string) => void;
3
+ type TerminalResizeHandler = (sessionId: string, cols: number, rows: number) => void;
4
+ type CommandHandler = (command: Command) => void;
5
+ export declare class CentrifugoClient {
6
+ private readonly client;
7
+ private readonly subscriptions;
8
+ constructor(url: string, token: string);
9
+ connect(): void;
10
+ disconnect(): void;
11
+ subscribeToSessions(userId: string): void;
12
+ publishSessionUpdate(userId: string, session: Session): Promise<void>;
13
+ publishTerminalData(userId: string, sessionId: string, data: string): Promise<void>;
14
+ subscribeToTerminalInput(userId: string, sessionId: string, onInput: TerminalInputHandler, onResize: TerminalResizeHandler): void;
15
+ subscribeToCommands(userId: string, handler: CommandHandler): void;
16
+ joinBridgesChannel(userId: string, bridgeId: string): void;
17
+ publishHookEvent(userId: string, sessionId: string, event: Record<string, unknown>): Promise<void>;
18
+ publishCommandResponse(userId: string, response: CommandResponse): Promise<void>;
19
+ }
20
+ export {};
@@ -0,0 +1,172 @@
1
+ import { Centrifuge } from 'centrifuge';
2
+ import WebSocket from 'ws';
3
+ import { hostname } from 'node:os';
4
+ export class CentrifugoClient {
5
+ client;
6
+ subscriptions = new Map();
7
+ constructor(url, token) {
8
+ this.client = new Centrifuge(url, {
9
+ token,
10
+ websocket: WebSocket,
11
+ });
12
+ this.client.on('connecting', (ctx) => {
13
+ console.log(`[Centrifugo] Connecting: ${ctx.reason}`);
14
+ });
15
+ this.client.on('connected', (ctx) => {
16
+ console.log(`[Centrifugo] Connected to ${ctx.transport}`);
17
+ });
18
+ this.client.on('disconnected', (ctx) => {
19
+ console.log(`[Centrifugo] Disconnected: code=${ctx.code} reason=${ctx.reason}`);
20
+ });
21
+ this.client.on('error', (ctx) => {
22
+ console.error(`[Centrifugo] Error:`, ctx.error);
23
+ });
24
+ }
25
+ connect() {
26
+ this.client.connect();
27
+ }
28
+ disconnect() {
29
+ for (const [channel, sub] of this.subscriptions) {
30
+ sub.unsubscribe();
31
+ this.subscriptions.delete(channel);
32
+ }
33
+ this.client.disconnect();
34
+ }
35
+ subscribeToSessions(userId) {
36
+ const channel = `sessions:updates#${userId}`;
37
+ const sub = this.client.newSubscription(channel);
38
+ sub.subscribe();
39
+ this.subscriptions.set(channel, sub);
40
+ }
41
+ async publishSessionUpdate(userId, session) {
42
+ const channel = `sessions:updates#${userId}`;
43
+ try {
44
+ await this.client.publish(channel, {
45
+ type: 'session_update',
46
+ session,
47
+ timestamp: new Date().toISOString(),
48
+ });
49
+ }
50
+ catch (err) {
51
+ console.error(`[Centrifugo] Failed to publish session update to ${channel}:`, err);
52
+ throw err;
53
+ }
54
+ }
55
+ async publishTerminalData(userId, sessionId, data) {
56
+ const channel = `terminal:${sessionId}#${userId}`;
57
+ if (!this.subscriptions.has(channel)) {
58
+ const sub = this.client.newSubscription(channel);
59
+ sub.subscribe();
60
+ this.subscriptions.set(channel, sub);
61
+ }
62
+ try {
63
+ await this.client.publish(channel, { type: 'output', data });
64
+ }
65
+ catch (err) {
66
+ console.error(`[Centrifugo] Failed to publish terminal data to ${channel}:`, err);
67
+ }
68
+ }
69
+ subscribeToTerminalInput(userId, sessionId, onInput, onResize) {
70
+ const channel = `terminal-input:${sessionId}#${userId}`;
71
+ if (this.subscriptions.has(channel)) {
72
+ return;
73
+ }
74
+ const sub = this.client.newSubscription(channel);
75
+ sub.on('publication', (ctx) => {
76
+ const msg = ctx.data;
77
+ if (msg.type === 'input' && msg.data !== undefined) {
78
+ onInput(sessionId, msg.data);
79
+ }
80
+ if (msg.type === 'resize' && msg.cols !== undefined && msg.rows !== undefined) {
81
+ onResize(sessionId, msg.cols, msg.rows);
82
+ }
83
+ });
84
+ sub.subscribe();
85
+ this.subscriptions.set(channel, sub);
86
+ }
87
+ subscribeToCommands(userId, handler) {
88
+ const channel = `commands#${userId}`;
89
+ const existingSub = this.subscriptions.get(channel);
90
+ if (existingSub) {
91
+ existingSub.unsubscribe();
92
+ this.subscriptions.delete(channel);
93
+ }
94
+ const sub = this.client.newSubscription(channel);
95
+ sub.on('publication', (ctx) => {
96
+ const data = ctx.data;
97
+ if (data.type === 'command_response') {
98
+ return;
99
+ }
100
+ const command = data;
101
+ if (!command.type || !command.requestId) {
102
+ return;
103
+ }
104
+ handler(command);
105
+ });
106
+ sub.on('subscribing', (ctx) => {
107
+ console.log(`[Centrifugo] Subscribing to ${channel}: ${ctx.reason}`);
108
+ });
109
+ sub.on('subscribed', (ctx) => {
110
+ console.log(`[Centrifugo] Subscribed to ${channel}, recoverable=${ctx.recoverable}`);
111
+ });
112
+ sub.on('error', (ctx) => {
113
+ console.error(`[Centrifugo] Subscription error on ${channel}:`, ctx.error);
114
+ });
115
+ sub.on('unsubscribed', (ctx) => {
116
+ console.log(`[Centrifugo] Unsubscribed from ${channel}: ${ctx.reason}`);
117
+ });
118
+ sub.subscribe();
119
+ this.subscriptions.set(channel, sub);
120
+ }
121
+ joinBridgesChannel(userId, bridgeId) {
122
+ const channel = `bridges#${userId}`;
123
+ const presenceInfo = {
124
+ bridgeId,
125
+ hostname: hostname(),
126
+ connectedAt: new Date().toISOString(),
127
+ };
128
+ const sub = this.client.newSubscription(channel, {
129
+ data: presenceInfo,
130
+ });
131
+ sub.on('subscribed', () => {
132
+ console.log(`[Centrifugo] Joined bridges channel as ${bridgeId} (${presenceInfo.hostname})`);
133
+ });
134
+ sub.on('error', (ctx) => {
135
+ console.error(`[Centrifugo] Bridges channel error:`, ctx.error);
136
+ });
137
+ sub.subscribe();
138
+ this.subscriptions.set(channel, sub);
139
+ }
140
+ async publishHookEvent(userId, sessionId, event) {
141
+ const channel = `events:${sessionId}#${userId}`;
142
+ if (!this.subscriptions.has(channel)) {
143
+ const sub = this.client.newSubscription(channel);
144
+ this.subscriptions.set(channel, sub);
145
+ await new Promise((resolve) => {
146
+ sub.on('subscribed', () => resolve());
147
+ sub.subscribe();
148
+ });
149
+ }
150
+ try {
151
+ await this.client.publish(channel, event);
152
+ }
153
+ catch (err) {
154
+ console.error(`[Centrifugo] Failed to publish hook event to ${channel}:`, err);
155
+ }
156
+ }
157
+ async publishCommandResponse(userId, response) {
158
+ const channel = `commands#${userId}`;
159
+ try {
160
+ await this.client.publish(channel, {
161
+ type: 'command_response',
162
+ response,
163
+ timestamp: new Date().toISOString(),
164
+ });
165
+ }
166
+ catch (err) {
167
+ console.error(`[Centrifugo] Failed to publish command response to ${channel}:`, err);
168
+ throw err;
169
+ }
170
+ }
171
+ }
172
+ //# sourceMappingURL=centrifugo-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"centrifugo-client.js","sourceRoot":"","sources":["../src/centrifugo-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { ShellType } from './types.js';
3
+ export interface ProcessRunnerEvents {
4
+ data: [string, string];
5
+ complete: [string];
6
+ error: [string, Error];
7
+ }
8
+ interface RunOptions {
9
+ model?: string;
10
+ workingDir?: string;
11
+ cols?: number;
12
+ rows?: number;
13
+ shellType?: ShellType;
14
+ hookPort?: number;
15
+ resumeSessionId?: string;
16
+ }
17
+ export declare class ProcessRunner extends EventEmitter<ProcessRunnerEvents> {
18
+ private readonly activeProcesses;
19
+ run(sessionId: string, prompt: string, options?: RunOptions): void;
20
+ write(sessionId: string, data: string): boolean;
21
+ resize(sessionId: string, cols: number, rows: number): boolean;
22
+ stop(sessionId: string): boolean;
23
+ stopAll(): void;
24
+ isRunning(sessionId: string): boolean;
25
+ }
26
+ export {};
@@ -0,0 +1,120 @@
1
+ import * as pty from 'node-pty';
2
+ import { EventEmitter } from 'node:events';
3
+ export class ProcessRunner extends EventEmitter {
4
+ activeProcesses = new Map();
5
+ run(sessionId, prompt, options = {}) {
6
+ const cwd = options.workingDir ?? process.cwd();
7
+ const cols = options.cols ?? 120;
8
+ const rows = options.rows ?? 40;
9
+ const shellType = options.shellType ?? 'claude';
10
+ let proc;
11
+ if (shellType === 'shell') {
12
+ console.log(`[ProcessRunner] Spawning interactive shell in ${cwd}`);
13
+ try {
14
+ proc = pty.spawn('/bin/zsh', ['-l'], {
15
+ name: 'xterm-256color',
16
+ cols,
17
+ rows,
18
+ cwd,
19
+ env: { ...process.env, TERM: 'xterm-256color' },
20
+ });
21
+ console.log(`[ProcessRunner] Shell process spawned, pid: ${proc.pid}`);
22
+ }
23
+ catch (err) {
24
+ console.error(`[ProcessRunner] Failed to spawn shell:`, err);
25
+ this.emit('error', sessionId, err instanceof Error ? err : new Error(String(err)));
26
+ return;
27
+ }
28
+ }
29
+ else {
30
+ const args = ['--dangerously-skip-permissions'];
31
+ if (options.resumeSessionId) {
32
+ args.push('--resume', options.resumeSessionId);
33
+ }
34
+ const env = { ...process.env, TERM: 'xterm-256color' };
35
+ if (options.hookPort) {
36
+ env.FTOWN_HOOK_PORT = String(options.hookPort);
37
+ env.FTOWN_SESSION_ID = sessionId;
38
+ }
39
+ const claudePath = process.env.CLAUDE_PATH ?? 'claude';
40
+ const shellCmd = [claudePath, ...args].map((a) => a.includes(' ') ? `"${a}"` : a).join(' ');
41
+ console.log(`[ProcessRunner] Spawning claude: ${shellCmd} in ${cwd}`);
42
+ try {
43
+ proc = pty.spawn('/bin/zsh', ['-l', '-c', shellCmd], {
44
+ name: 'xterm-256color',
45
+ cols,
46
+ rows,
47
+ cwd,
48
+ env,
49
+ });
50
+ console.log(`[ProcessRunner] Claude process spawned, pid: ${proc.pid}`);
51
+ }
52
+ catch (err) {
53
+ console.error(`[ProcessRunner] Failed to spawn claude:`, err);
54
+ this.emit('error', sessionId, err instanceof Error ? err : new Error(String(err)));
55
+ return;
56
+ }
57
+ }
58
+ this.activeProcesses.set(sessionId, proc);
59
+ proc.onData((data) => {
60
+ this.emit('data', sessionId, data);
61
+ });
62
+ proc.onExit(({ exitCode, signal }) => {
63
+ console.log(`[ProcessRunner] Process exited, code: ${exitCode}, signal: ${signal}`);
64
+ this.activeProcesses.delete(sessionId);
65
+ if (exitCode === 0 || exitCode === null || exitCode === undefined) {
66
+ this.emit('complete', sessionId);
67
+ }
68
+ else {
69
+ this.emit('error', sessionId, new Error(`Process exited with code ${exitCode}`));
70
+ }
71
+ });
72
+ if (shellType === 'claude' && !options.resumeSessionId) {
73
+ setTimeout(() => {
74
+ if (this.activeProcesses.has(sessionId)) {
75
+ console.log(`[ProcessRunner] Sending prompt to session ${sessionId}`);
76
+ proc.write(prompt + '\r');
77
+ }
78
+ }, 2000);
79
+ }
80
+ }
81
+ write(sessionId, data) {
82
+ const proc = this.activeProcesses.get(sessionId);
83
+ if (!proc) {
84
+ return false;
85
+ }
86
+ proc.write(data);
87
+ return true;
88
+ }
89
+ resize(sessionId, cols, rows) {
90
+ const proc = this.activeProcesses.get(sessionId);
91
+ if (!proc) {
92
+ return false;
93
+ }
94
+ proc.resize(cols, rows);
95
+ return true;
96
+ }
97
+ stop(sessionId) {
98
+ const proc = this.activeProcesses.get(sessionId);
99
+ if (!proc) {
100
+ return false;
101
+ }
102
+ proc.kill();
103
+ setTimeout(() => {
104
+ if (this.activeProcesses.has(sessionId)) {
105
+ proc.kill('SIGKILL');
106
+ this.activeProcesses.delete(sessionId);
107
+ }
108
+ }, 5000);
109
+ return true;
110
+ }
111
+ stopAll() {
112
+ for (const [sessionId] of this.activeProcesses) {
113
+ this.stop(sessionId);
114
+ }
115
+ }
116
+ isRunning(sessionId) {
117
+ return this.activeProcesses.has(sessionId);
118
+ }
119
+ }
120
+ //# sourceMappingURL=claude-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-runner.js","sourceRoot":"","sources":["../src/claude-runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { EventEmitter } from 'node:events';
2
+ export interface ClaudeHookPayload {
3
+ session_id: string;
4
+ ftown_session_id?: string;
5
+ transcript_path: string;
6
+ cwd: string;
7
+ hook_event_name: string;
8
+ tool_name?: string;
9
+ tool_input?: Record<string, unknown>;
10
+ }
11
+ export interface HookEvent {
12
+ sessionId: string;
13
+ claudeSessionId: string;
14
+ eventName: string;
15
+ data: Record<string, unknown>;
16
+ }
17
+ interface HookServerEvents {
18
+ event: [HookEvent];
19
+ }
20
+ export declare class HookServer extends EventEmitter<HookServerEvents> {
21
+ private server;
22
+ start(): Promise<number>;
23
+ stop(): void;
24
+ private handleRequest;
25
+ }
26
+ export {};
@@ -0,0 +1,79 @@
1
+ import { createServer } from 'node:http';
2
+ import { EventEmitter } from 'node:events';
3
+ export class HookServer extends EventEmitter {
4
+ server = null;
5
+ async start() {
6
+ return new Promise((resolve, reject) => {
7
+ const server = createServer((req, res) => {
8
+ this.handleRequest(req, res);
9
+ });
10
+ server.on('error', (err) => {
11
+ console.error('[HookServer] Server error:', err.message);
12
+ });
13
+ server.listen(0, '127.0.0.1', () => {
14
+ const address = server.address();
15
+ if (!address || typeof address === 'string') {
16
+ reject(new Error('Failed to get server address'));
17
+ return;
18
+ }
19
+ this.server = server;
20
+ console.log(`[HookServer] Listening on port ${address.port}`);
21
+ resolve(address.port);
22
+ });
23
+ });
24
+ }
25
+ stop() {
26
+ if (this.server) {
27
+ this.server.close();
28
+ this.server = null;
29
+ }
30
+ }
31
+ handleRequest(req, res) {
32
+ if (req.method !== 'POST' || req.url !== '/hook') {
33
+ res.writeHead(404);
34
+ res.end();
35
+ return;
36
+ }
37
+ const chunks = [];
38
+ req.on('data', (chunk) => {
39
+ chunks.push(chunk);
40
+ });
41
+ req.on('end', () => {
42
+ try {
43
+ const body = Buffer.concat(chunks).toString('utf-8');
44
+ const payload = JSON.parse(body);
45
+ if (!payload.ftown_session_id) {
46
+ res.writeHead(200);
47
+ res.end('{"ok":true}');
48
+ return;
49
+ }
50
+ console.log(`[HookServer] Received ${payload.hook_event_name} for ftown session ${payload.ftown_session_id}`);
51
+ const hookEvent = {
52
+ sessionId: payload.ftown_session_id,
53
+ claudeSessionId: payload.session_id,
54
+ eventName: payload.hook_event_name,
55
+ data: {
56
+ cwd: payload.cwd,
57
+ transcript_path: payload.transcript_path,
58
+ ...(payload.tool_name ? { tool_name: payload.tool_name } : {}),
59
+ ...(payload.tool_input ? { tool_input: payload.tool_input } : {}),
60
+ },
61
+ };
62
+ this.emit('event', hookEvent);
63
+ res.writeHead(200, { 'Content-Type': 'application/json' });
64
+ res.end('{"ok":true}');
65
+ }
66
+ catch (err) {
67
+ console.error('[HookServer] Failed to parse hook payload:', err instanceof Error ? err.message : String(err));
68
+ res.writeHead(400);
69
+ res.end();
70
+ }
71
+ });
72
+ req.on('error', (err) => {
73
+ console.error('[HookServer] Request error:', err.message);
74
+ res.writeHead(500);
75
+ res.end();
76
+ });
77
+ }
78
+ }
79
+ //# sourceMappingURL=hook-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-server.js","sourceRoot":"","sources":["../src/hook-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,444 @@
1
+ #!/usr/bin/env node
2
+ import { Command as Commander } from 'commander';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import { resolve, join, dirname } from 'node:path';
5
+ import { hostname as osHostname, homedir } from 'node:os';
6
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { readFile } from 'node:fs/promises';
9
+ import { CentrifugoClient } from './centrifugo-client.js';
10
+ import { ProcessRunner } from './claude-runner.js';
11
+ import { SessionStore } from './session-store.js';
12
+ import { HookServer } from './hook-server.js';
13
+ async function fetchBridgeToken(apiUrl, authToken, bridgeId) {
14
+ const res = await fetch(`${apiUrl}/api/auth/bridge`, {
15
+ method: 'POST',
16
+ headers: { 'Content-Type': 'application/json' },
17
+ body: JSON.stringify({
18
+ token: authToken,
19
+ bridgeId,
20
+ hostname: osHostname(),
21
+ }),
22
+ });
23
+ if (!res.ok) {
24
+ const body = await res.text();
25
+ throw new Error(`Bridge auth failed (${res.status}): ${body}`);
26
+ }
27
+ return res.json();
28
+ }
29
+ const __filename = fileURLToPath(import.meta.url);
30
+ const __dirname = dirname(__filename);
31
+ function installGlobalHooks() {
32
+ const hookScript = resolve(join(__dirname, '..', 'hooks', 'notify.sh'));
33
+ const claudeDir = join(homedir(), '.claude');
34
+ const settingsPath = join(claudeDir, 'settings.json');
35
+ const hookEntry = { matcher: '', hooks: [{ type: 'command', command: hookScript, async: true }] };
36
+ const ftownHooks = {
37
+ UserPromptSubmit: [hookEntry],
38
+ Stop: [hookEntry],
39
+ PreToolUse: [hookEntry],
40
+ PostToolUse: [hookEntry],
41
+ Notification: [hookEntry],
42
+ };
43
+ let settings = {};
44
+ try {
45
+ settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
46
+ }
47
+ catch {
48
+ // file doesn't exist or invalid json
49
+ }
50
+ const existingHooks = (settings.hooks ?? {});
51
+ settings.hooks = { ...existingHooks, ...ftownHooks };
52
+ mkdirSync(claudeDir, { recursive: true });
53
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
54
+ console.log(`[Bridge] Installed global hooks at ${settingsPath}`);
55
+ }
56
+ const program = new Commander();
57
+ program
58
+ .name('ftown-bridge')
59
+ .description('Claude Code orchestrator bridge for Centrifugo')
60
+ .requiredOption('--token <jwt>', 'Auth token (JWT signed with Centrifugo secret)')
61
+ .requiredOption('--api-url <url>', 'ftown UI API URL (e.g. https://ftown.vercel.app)')
62
+ .option('--data-dir <path>', 'Directory for session data', './data')
63
+ .option('--bridge-id <id>', 'Bridge instance ID')
64
+ .action(async (opts) => {
65
+ const bridgeId = opts.bridgeId ?? uuidv4();
66
+ const dataDir = resolve(opts.dataDir);
67
+ console.log('[Bridge] Authenticating with API...');
68
+ const auth = await fetchBridgeToken(opts.apiUrl, opts.token, bridgeId);
69
+ const userId = auth.userId;
70
+ const centrifugoUrl = auth.centrifugoUrl;
71
+ console.log('========================================');
72
+ console.log(' ftown-bridge starting');
73
+ console.log(` Bridge ID: ${bridgeId}`);
74
+ console.log(` User ID: ${userId}`);
75
+ console.log(` Centrifugo URL: ${centrifugoUrl}`);
76
+ console.log(` Data dir: ${dataDir}`);
77
+ console.log('========================================');
78
+ installGlobalHooks();
79
+ const store = new SessionStore(dataDir);
80
+ // Mark any previously "running" sessions as "error" (they died with the old bridge)
81
+ const staleSessiones = await store.listSessions();
82
+ for (const s of staleSessiones) {
83
+ if (s.status === 'running' || s.status === 'pending') {
84
+ s.status = 'error';
85
+ s.updatedAt = new Date().toISOString();
86
+ await store.saveSession(s);
87
+ console.log(`[Bridge] Marked stale session ${s.id} as error`);
88
+ }
89
+ }
90
+ const runner = new ProcessRunner();
91
+ const centrifugo = new CentrifugoClient(centrifugoUrl, auth.token);
92
+ const hookServer = new HookServer();
93
+ const hookPort = await hookServer.start();
94
+ console.log(`[Bridge] Hook server started on port ${hookPort}`);
95
+ const outputBuffers = new Map();
96
+ const flushTimers = new Map();
97
+ const FLUSH_INTERVAL_MS = 16;
98
+ const MAX_BUFFER_BYTES = 32_000;
99
+ function flushBuffer(sessionId) {
100
+ const buf = outputBuffers.get(sessionId);
101
+ if (!buf)
102
+ return;
103
+ outputBuffers.delete(sessionId);
104
+ const timer = flushTimers.get(sessionId);
105
+ if (timer)
106
+ clearTimeout(timer);
107
+ flushTimers.delete(sessionId);
108
+ store.appendTerminalData(sessionId, buf).catch((err) => {
109
+ console.error(`[Bridge] Failed to store terminal data for ${sessionId}:`, err);
110
+ });
111
+ centrifugo.publishTerminalData(userId, sessionId, buf).catch((err) => {
112
+ console.error(`[Bridge] Failed to publish terminal data for ${sessionId}:`, err);
113
+ });
114
+ }
115
+ runner.on('data', (sessionId, data) => {
116
+ const existing = outputBuffers.get(sessionId) ?? '';
117
+ outputBuffers.set(sessionId, existing + data);
118
+ if ((existing.length + data.length) >= MAX_BUFFER_BYTES) {
119
+ flushBuffer(sessionId);
120
+ }
121
+ else if (!flushTimers.has(sessionId)) {
122
+ flushTimers.set(sessionId, setTimeout(() => flushBuffer(sessionId), FLUSH_INTERVAL_MS));
123
+ }
124
+ });
125
+ runner.on('complete', async (sessionId) => {
126
+ flushBuffer(sessionId);
127
+ try {
128
+ const session = await store.loadSession(sessionId);
129
+ if (session) {
130
+ session.status = 'completed';
131
+ session.updatedAt = new Date().toISOString();
132
+ await store.saveSession(session);
133
+ await centrifugo.publishSessionUpdate(userId, session);
134
+ }
135
+ console.log(`[Bridge] Session ${sessionId} completed`);
136
+ }
137
+ catch (err) {
138
+ console.error(`[Bridge] Failed to handle completion for session ${sessionId}:`, err);
139
+ }
140
+ });
141
+ runner.on('error', async (sessionId, error) => {
142
+ flushBuffer(sessionId);
143
+ try {
144
+ const session = await store.loadSession(sessionId);
145
+ if (session) {
146
+ session.status = 'error';
147
+ session.updatedAt = new Date().toISOString();
148
+ await store.saveSession(session);
149
+ await centrifugo.publishSessionUpdate(userId, session);
150
+ }
151
+ console.error(`[Bridge] Session ${sessionId} error:`, error.message);
152
+ }
153
+ catch (err) {
154
+ console.error(`[Bridge] Failed to handle error for session ${sessionId}:`, err);
155
+ }
156
+ });
157
+ async function parseTranscriptUsage(transcriptPath) {
158
+ try {
159
+ const content = await readFile(transcriptPath, 'utf-8');
160
+ const lines = content.trim().split('\n');
161
+ let inputTokens = 0;
162
+ let outputTokens = 0;
163
+ for (const line of lines) {
164
+ if (!line.trim())
165
+ continue;
166
+ try {
167
+ const entry = JSON.parse(line);
168
+ if (entry.type === 'assistant' && entry.message?.usage) {
169
+ inputTokens += entry.message.usage.input_tokens ?? 0;
170
+ outputTokens += entry.message.usage.output_tokens ?? 0;
171
+ }
172
+ }
173
+ catch {
174
+ // skip malformed lines
175
+ }
176
+ }
177
+ if (inputTokens === 0 && outputTokens === 0)
178
+ return undefined;
179
+ return { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens };
180
+ }
181
+ catch {
182
+ return undefined;
183
+ }
184
+ }
185
+ hookServer.on('event', (hookEvent) => {
186
+ (async () => {
187
+ if (hookEvent.claudeSessionId) {
188
+ const session = await store.loadSession(hookEvent.sessionId);
189
+ if (session && !session.claudeSessionId) {
190
+ session.claudeSessionId = hookEvent.claudeSessionId;
191
+ await store.saveSession(session);
192
+ }
193
+ }
194
+ const eventData = {
195
+ type: 'hook_event',
196
+ eventName: hookEvent.eventName,
197
+ data: hookEvent.data,
198
+ };
199
+ if (hookEvent.eventName === 'Stop') {
200
+ const transcriptPath = hookEvent.data.transcript_path;
201
+ if (transcriptPath) {
202
+ const usage = await parseTranscriptUsage(transcriptPath);
203
+ if (usage) {
204
+ eventData.usage = usage;
205
+ }
206
+ }
207
+ }
208
+ await centrifugo.publishHookEvent(userId, hookEvent.sessionId, eventData);
209
+ })().catch((err) => {
210
+ console.error('[Bridge] Failed to handle hook event:', err);
211
+ });
212
+ });
213
+ async function handleCommand(command) {
214
+ console.log(`[Bridge] Received command: ${command.type} (requestId: ${command.requestId})`);
215
+ let response;
216
+ try {
217
+ switch (command.type) {
218
+ case 'create_session': {
219
+ const payload = command.payload;
220
+ if (payload.bridgeId && payload.bridgeId !== bridgeId) {
221
+ return;
222
+ }
223
+ if (!payload.prompt && payload.shellType !== 'shell') {
224
+ response = { requestId: command.requestId, success: false, error: 'Missing prompt' };
225
+ break;
226
+ }
227
+ const sessionId = uuidv4();
228
+ const session = {
229
+ id: sessionId,
230
+ name: payload.name ?? (payload.shellType === 'shell' ? 'Shell' : payload.prompt.slice(0, 80)),
231
+ prompt: payload.prompt ?? '',
232
+ status: 'running',
233
+ bridgeId,
234
+ createdAt: new Date().toISOString(),
235
+ updatedAt: new Date().toISOString(),
236
+ model: payload.model,
237
+ workingDir: payload.workingDir,
238
+ shellType: payload.shellType,
239
+ };
240
+ await store.saveSession(session);
241
+ await centrifugo.publishSessionUpdate(userId, session);
242
+ runner.run(sessionId, payload.prompt, {
243
+ model: payload.model,
244
+ workingDir: payload.workingDir,
245
+ shellType: payload.shellType,
246
+ hookPort,
247
+ });
248
+ // Subscribe to terminal input from UI for this session
249
+ centrifugo.subscribeToTerminalInput(userId, sessionId, (sid, data) => { runner.write(sid, data); }, (sid, cols, rows) => { runner.resize(sid, cols, rows); });
250
+ response = { requestId: command.requestId, success: true, data: { session } };
251
+ break;
252
+ }
253
+ case 'stop_session': {
254
+ const payload = command.payload;
255
+ if (!payload.sessionId) {
256
+ response = { requestId: command.requestId, success: false, error: 'Missing sessionId' };
257
+ break;
258
+ }
259
+ const stopped = runner.stop(payload.sessionId);
260
+ if (stopped) {
261
+ const session = await store.loadSession(payload.sessionId);
262
+ if (session) {
263
+ session.status = 'completed';
264
+ session.updatedAt = new Date().toISOString();
265
+ await store.saveSession(session);
266
+ await centrifugo.publishSessionUpdate(userId, session);
267
+ }
268
+ }
269
+ response = { requestId: command.requestId, success: true, data: { stopped } };
270
+ break;
271
+ }
272
+ case 'list_sessions': {
273
+ const sessions = await store.listSessions();
274
+ response = { requestId: command.requestId, success: true, data: { sessions } };
275
+ break;
276
+ }
277
+ case 'get_history': {
278
+ const payload = command.payload;
279
+ if (!payload.sessionId) {
280
+ response = { requestId: command.requestId, success: false, error: 'Missing sessionId' };
281
+ break;
282
+ }
283
+ const session = await store.loadSession(payload.sessionId);
284
+ response = { requestId: command.requestId, success: true, data: { session } };
285
+ break;
286
+ }
287
+ case 'retry_session': {
288
+ const payload = command.payload;
289
+ if (payload.bridgeId && payload.bridgeId !== bridgeId) {
290
+ return;
291
+ }
292
+ if (!payload.sessionId) {
293
+ response = { requestId: command.requestId, success: false, error: 'Missing sessionId' };
294
+ break;
295
+ }
296
+ const existingSession = await store.loadSession(payload.sessionId);
297
+ if (!existingSession) {
298
+ response = { requestId: command.requestId, success: false, error: 'Session not found' };
299
+ break;
300
+ }
301
+ if (existingSession.status === 'running') {
302
+ response = { requestId: command.requestId, success: false, error: 'Session is already running' };
303
+ break;
304
+ }
305
+ existingSession.status = 'running';
306
+ existingSession.updatedAt = new Date().toISOString();
307
+ await store.saveSession(existingSession);
308
+ await centrifugo.publishSessionUpdate(userId, existingSession);
309
+ runner.run(existingSession.id, existingSession.prompt, {
310
+ model: existingSession.model,
311
+ workingDir: existingSession.workingDir,
312
+ shellType: existingSession.shellType,
313
+ hookPort,
314
+ });
315
+ centrifugo.subscribeToTerminalInput(userId, existingSession.id, (sid, data) => { runner.write(sid, data); }, (sid, cols, rows) => { runner.resize(sid, cols, rows); });
316
+ response = { requestId: command.requestId, success: true, data: { session: existingSession } };
317
+ break;
318
+ }
319
+ case 'resume_session': {
320
+ const payload = command.payload;
321
+ if (!payload.sessionId) {
322
+ response = { requestId: command.requestId, success: false, error: 'Missing sessionId' };
323
+ break;
324
+ }
325
+ const sessionToResume = await store.loadSession(payload.sessionId);
326
+ if (!sessionToResume) {
327
+ response = { requestId: command.requestId, success: false, error: 'Session not found on this bridge' };
328
+ break;
329
+ }
330
+ if (runner.isRunning(payload.sessionId)) {
331
+ response = { requestId: command.requestId, success: false, error: 'Session is already running' };
332
+ break;
333
+ }
334
+ sessionToResume.status = 'running';
335
+ sessionToResume.updatedAt = new Date().toISOString();
336
+ await store.saveSession(sessionToResume);
337
+ await centrifugo.publishSessionUpdate(userId, sessionToResume);
338
+ if (sessionToResume.claudeSessionId) {
339
+ runner.run(sessionToResume.id, sessionToResume.prompt, {
340
+ model: sessionToResume.model,
341
+ workingDir: sessionToResume.workingDir,
342
+ shellType: sessionToResume.shellType,
343
+ hookPort,
344
+ resumeSessionId: sessionToResume.claudeSessionId,
345
+ });
346
+ }
347
+ else {
348
+ runner.run(sessionToResume.id, sessionToResume.prompt, {
349
+ model: sessionToResume.model,
350
+ workingDir: sessionToResume.workingDir,
351
+ shellType: sessionToResume.shellType,
352
+ hookPort,
353
+ });
354
+ }
355
+ centrifugo.subscribeToTerminalInput(userId, sessionToResume.id, (sid, data) => { runner.write(sid, data); }, (sid, cols, rows) => { runner.resize(sid, cols, rows); });
356
+ response = { requestId: command.requestId, success: true, data: { session: sessionToResume } };
357
+ break;
358
+ }
359
+ case 'rename_session': {
360
+ const payload = command.payload;
361
+ if (!payload.sessionId || !payload.name) {
362
+ response = { requestId: command.requestId, success: false, error: 'Missing sessionId or name' };
363
+ break;
364
+ }
365
+ const sessionToRename = await store.loadSession(payload.sessionId);
366
+ if (!sessionToRename) {
367
+ response = { requestId: command.requestId, success: false, error: 'Session not found' };
368
+ break;
369
+ }
370
+ sessionToRename.name = payload.name;
371
+ sessionToRename.updatedAt = new Date().toISOString();
372
+ await store.saveSession(sessionToRename);
373
+ await centrifugo.publishSessionUpdate(userId, sessionToRename);
374
+ response = { requestId: command.requestId, success: true, data: { session: sessionToRename } };
375
+ break;
376
+ }
377
+ case 'remove_session': {
378
+ const payload = command.payload;
379
+ if (!payload.sessionId) {
380
+ response = { requestId: command.requestId, success: false, error: 'Missing sessionId' };
381
+ break;
382
+ }
383
+ runner.stop(payload.sessionId);
384
+ const sessionToRemove = await store.loadSession(payload.sessionId);
385
+ await store.deleteSession(payload.sessionId);
386
+ if (sessionToRemove) {
387
+ const removedSession = {
388
+ ...sessionToRemove,
389
+ status: 'removed',
390
+ updatedAt: new Date().toISOString(),
391
+ };
392
+ await centrifugo.publishSessionUpdate(userId, removedSession);
393
+ }
394
+ response = { requestId: command.requestId, success: true, data: { removed: true } };
395
+ break;
396
+ }
397
+ default: {
398
+ response = {
399
+ requestId: command.requestId,
400
+ success: false,
401
+ error: `Unknown command type: ${command.type}`,
402
+ };
403
+ }
404
+ }
405
+ }
406
+ catch (err) {
407
+ const errorMessage = err instanceof Error ? err.message : String(err);
408
+ response = { requestId: command.requestId, success: false, error: errorMessage };
409
+ }
410
+ try {
411
+ await centrifugo.publishCommandResponse(userId, response);
412
+ }
413
+ catch (err) {
414
+ console.error(`[Bridge] Failed to publish command response:`, err);
415
+ }
416
+ }
417
+ centrifugo.connect();
418
+ centrifugo.joinBridgesChannel(userId, bridgeId);
419
+ centrifugo.subscribeToSessions(userId);
420
+ let ready = false;
421
+ centrifugo.subscribeToCommands(userId, (command) => {
422
+ if (!ready)
423
+ return;
424
+ handleCommand(command).catch((err) => {
425
+ console.error(`[Bridge] Unhandled error in command handler:`, err);
426
+ });
427
+ });
428
+ // Ignore replayed history — only process commands arriving after subscribe
429
+ setTimeout(() => {
430
+ ready = true;
431
+ console.log('[Bridge] Ready and listening for commands');
432
+ }, 2000);
433
+ const shutdown = () => {
434
+ console.log('\n[Bridge] Shutting down...');
435
+ hookServer.stop();
436
+ runner.stopAll();
437
+ centrifugo.disconnect();
438
+ process.exit(0);
439
+ };
440
+ process.on('SIGINT', shutdown);
441
+ process.on('SIGTERM', shutdown);
442
+ });
443
+ program.parse();
444
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Session } from './types.js';
2
+ export declare class SessionStore {
3
+ private readonly sessionsDir;
4
+ private readonly writeLocks;
5
+ constructor(dataDir: string);
6
+ private sessionDir;
7
+ private sessionFilePath;
8
+ private terminalLogPath;
9
+ saveSession(session: Session): Promise<void>;
10
+ loadSession(sessionId: string): Promise<Session | null>;
11
+ listSessions(): Promise<Session[]>;
12
+ appendTerminalData(sessionId: string, data: string): Promise<void>;
13
+ deleteSession(sessionId: string): Promise<void>;
14
+ loadTerminalLog(sessionId: string): Promise<string>;
15
+ }
@@ -0,0 +1,71 @@
1
+ import { readFile, writeFile, mkdir, readdir, appendFile, rm } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { existsSync } from 'node:fs';
4
+ export class SessionStore {
5
+ sessionsDir;
6
+ writeLocks = new Map();
7
+ constructor(dataDir) {
8
+ this.sessionsDir = join(dataDir, 'sessions');
9
+ }
10
+ sessionDir(sessionId) {
11
+ return join(this.sessionsDir, sessionId);
12
+ }
13
+ sessionFilePath(sessionId) {
14
+ return join(this.sessionDir(sessionId), 'session.json');
15
+ }
16
+ terminalLogPath(sessionId) {
17
+ return join(this.sessionDir(sessionId), 'terminal.log');
18
+ }
19
+ async saveSession(session) {
20
+ const dir = this.sessionDir(session.id);
21
+ await mkdir(dir, { recursive: true });
22
+ await writeFile(this.sessionFilePath(session.id), JSON.stringify(session, null, 2), 'utf-8');
23
+ }
24
+ async loadSession(sessionId) {
25
+ const filePath = this.sessionFilePath(sessionId);
26
+ if (!existsSync(filePath)) {
27
+ return null;
28
+ }
29
+ const data = await readFile(filePath, 'utf-8');
30
+ return JSON.parse(data);
31
+ }
32
+ async listSessions() {
33
+ if (!existsSync(this.sessionsDir)) {
34
+ return [];
35
+ }
36
+ const entries = await readdir(this.sessionsDir, { withFileTypes: true });
37
+ const sessions = [];
38
+ for (const entry of entries) {
39
+ if (entry.isDirectory()) {
40
+ const session = await this.loadSession(entry.name);
41
+ if (session) {
42
+ sessions.push(session);
43
+ }
44
+ }
45
+ }
46
+ return sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
47
+ }
48
+ async appendTerminalData(sessionId, data) {
49
+ const dir = this.sessionDir(sessionId);
50
+ await mkdir(dir, { recursive: true });
51
+ const filePath = this.terminalLogPath(sessionId);
52
+ const prevLock = this.writeLocks.get(sessionId) ?? Promise.resolve();
53
+ const newLock = prevLock.then(() => appendFile(filePath, data, 'utf-8'));
54
+ this.writeLocks.set(sessionId, newLock);
55
+ await newLock;
56
+ }
57
+ async deleteSession(sessionId) {
58
+ const dir = this.sessionDir(sessionId);
59
+ if (existsSync(dir)) {
60
+ await rm(dir, { recursive: true, force: true });
61
+ }
62
+ }
63
+ async loadTerminalLog(sessionId) {
64
+ const filePath = this.terminalLogPath(sessionId);
65
+ if (!existsSync(filePath)) {
66
+ return '';
67
+ }
68
+ return readFile(filePath, 'utf-8');
69
+ }
70
+ }
71
+ //# sourceMappingURL=session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ export type ShellType = 'claude' | 'shell';
2
+ export interface Session {
3
+ id: string;
4
+ name: string;
5
+ prompt: string;
6
+ status: SessionStatus;
7
+ bridgeId: string;
8
+ createdAt: string;
9
+ updatedAt: string;
10
+ model?: string;
11
+ workingDir?: string;
12
+ shellType?: ShellType;
13
+ claudeSessionId?: string;
14
+ }
15
+ export type SessionStatus = 'pending' | 'running' | 'completed' | 'error';
16
+ export interface SessionMessage {
17
+ sessionId: string;
18
+ type: SessionMessageType;
19
+ content: string;
20
+ timestamp: string;
21
+ toolName?: string;
22
+ raw?: ClaudeStreamEvent;
23
+ }
24
+ export type SessionMessageType = 'assistant' | 'user' | 'system' | 'tool_use' | 'tool_result';
25
+ export interface Command {
26
+ type: CommandType;
27
+ payload: CommandPayload;
28
+ requestId: string;
29
+ }
30
+ export type CommandType = 'create_session' | 'stop_session' | 'list_sessions' | 'get_history' | 'retry_session' | 'send_message' | 'rename_session' | 'remove_session' | 'resume_session';
31
+ export interface CreateSessionPayload {
32
+ prompt: string;
33
+ name?: string;
34
+ model?: string;
35
+ workingDir?: string;
36
+ bridgeId?: string;
37
+ shellType?: ShellType;
38
+ }
39
+ export interface StopSessionPayload {
40
+ sessionId: string;
41
+ }
42
+ export interface GetHistoryPayload {
43
+ sessionId: string;
44
+ }
45
+ export interface RetrySessionPayload {
46
+ sessionId: string;
47
+ bridgeId?: string;
48
+ }
49
+ export interface SendMessagePayload {
50
+ sessionId: string;
51
+ message: string;
52
+ }
53
+ export interface RenameSessionPayload {
54
+ sessionId: string;
55
+ name: string;
56
+ }
57
+ export interface RemoveSessionPayload {
58
+ sessionId: string;
59
+ }
60
+ export interface ResumeSessionPayload {
61
+ sessionId: string;
62
+ bridgeId?: string;
63
+ }
64
+ export type CommandPayload = CreateSessionPayload | StopSessionPayload | GetHistoryPayload | RetrySessionPayload | SendMessagePayload | RenameSessionPayload | RemoveSessionPayload | ResumeSessionPayload | Record<string, unknown>;
65
+ export interface CommandResponse {
66
+ requestId: string;
67
+ success: boolean;
68
+ data?: unknown;
69
+ error?: string;
70
+ }
71
+ export interface ClaudeStreamEvent {
72
+ type: string;
73
+ subtype?: string;
74
+ content_block?: {
75
+ type: string;
76
+ text?: string;
77
+ name?: string;
78
+ id?: string;
79
+ input?: Record<string, unknown>;
80
+ };
81
+ delta?: {
82
+ type: string;
83
+ text?: string;
84
+ partial_json?: string;
85
+ };
86
+ index?: number;
87
+ message?: {
88
+ id: string;
89
+ role: string;
90
+ model: string;
91
+ stop_reason?: string;
92
+ };
93
+ tool_name?: string;
94
+ result?: string;
95
+ duration_ms?: number;
96
+ duration_api_ms?: number;
97
+ is_error?: boolean;
98
+ num_turns?: number;
99
+ session_id?: string;
100
+ cost_usd?: number;
101
+ }
102
+ export interface BridgeConfig {
103
+ token: string;
104
+ centrifugoUrl: string;
105
+ dataDir: string;
106
+ bridgeId: string;
107
+ userId: string;
108
+ }
109
+ export interface BridgePresenceInfo {
110
+ bridgeId: string;
111
+ hostname: string;
112
+ connectedAt: string;
113
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+ INPUT=$(cat)
3
+ PORT="${FTOWN_HOOK_PORT}"
4
+ SESSION_ID="${FTOWN_SESSION_ID}"
5
+ if [ -z "$PORT" ] || [ -z "$SESSION_ID" ]; then
6
+ exit 0
7
+ fi
8
+ PAYLOAD=$(echo "$INPUT" | jq -c --arg sid "$SESSION_ID" '. + {ftown_session_id: $sid}')
9
+ curl -s -X POST "http://localhost:${PORT}/hook" \
10
+ -H "Content-Type: application/json" \
11
+ -d "$PAYLOAD" > /dev/null 2>&1
12
+ exit 0
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "ftown-bridge",
3
+ "version": "0.1.0",
4
+ "description": "CLI bridge for ftown — connects local Claude Code sessions to the ftown orchestrator",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "ftown-bridge": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "hooks"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "prepublishOnly": "npm run build",
17
+ "start": "tsx src/index.ts",
18
+ "dev": "tsx watch src/index.ts"
19
+ },
20
+ "engines": {
21
+ "node": ">=22"
22
+ },
23
+ "keywords": [
24
+ "claude",
25
+ "claude-code",
26
+ "orchestrator",
27
+ "terminal",
28
+ "bridge"
29
+ ],
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/fmktech/ftown.git",
34
+ "directory": "bridge"
35
+ },
36
+ "dependencies": {
37
+ "centrifuge": "^5.2.2",
38
+ "commander": "^13.1.0",
39
+ "node-pty": "^1.2.0-beta.12",
40
+ "uuid": "^11.1.0",
41
+ "ws": "^8.18.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.13.0",
45
+ "@types/uuid": "^10.0.0",
46
+ "@types/ws": "^8.5.14",
47
+ "tsx": "^4.19.0",
48
+ "typescript": "^5.7.0"
49
+ }
50
+ }