cloffice-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.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ import { doc, setDoc, serverTimestamp } from 'firebase/firestore';
3
+ import { SessionWatcher } from './watcher.js';
4
+ import { parseLine } from './parser.js';
5
+ import { initFirebase } from './firebase-init.js';
6
+ import { FirestoreSync } from './firestore-sync.js';
7
+ const ACTIVITY_LABELS = {
8
+ thinking: '🧠 Thinking',
9
+ responding: '💬 Responding',
10
+ waiting: '⏳ Waiting',
11
+ tool_use: '🔧 Tool Use',
12
+ user_prompt: '👤 User Prompt',
13
+ idle: '💤 Idle'
14
+ };
15
+ function parseArgs(argv) {
16
+ let token;
17
+ let name;
18
+ for (const arg of argv.slice(2)) {
19
+ if (arg.startsWith('--token=')) {
20
+ token = arg.slice('--token='.length);
21
+ }
22
+ else if (arg.startsWith('--name=')) {
23
+ name = arg.slice('--name='.length);
24
+ }
25
+ }
26
+ if (!token || !name)
27
+ return null;
28
+ return { token, name };
29
+ }
30
+ function decodeToken(token) {
31
+ const decoded = Buffer.from(token, 'base64').toString('utf-8');
32
+ const parts = decoded.split('\n');
33
+ if (parts.length !== 3)
34
+ throw new Error('Invalid token format');
35
+ return { email: parts[0], password: parts[1], roomId: parts[2] };
36
+ }
37
+ async function main() {
38
+ const args = parseArgs(process.argv);
39
+ if (!args) {
40
+ console.error('Usage: cloffice-bridge --token=TOKEN --name="Your Name"');
41
+ console.error('\nGet your command from the Cloffice web app (click "Copy bridge command").');
42
+ process.exit(1);
43
+ }
44
+ let email, password, roomId;
45
+ try {
46
+ ({ email, password, roomId } = decodeToken(args.token));
47
+ }
48
+ catch {
49
+ console.error('Invalid token. Get a new one from the Cloffice web app.');
50
+ process.exit(1);
51
+ }
52
+ const displayName = args.name;
53
+ console.log('Cloffice Bridge starting...\n');
54
+ console.log(`Display name: ${displayName}`);
55
+ console.log('Connecting to Firebase...');
56
+ const { db, uid } = await initFirebase({ email, password });
57
+ await setDoc(doc(db, 'rooms', roomId, 'members', uid), {
58
+ displayName,
59
+ role: 'member',
60
+ joinedAt: serverTimestamp()
61
+ });
62
+ const sync = new FirestoreSync(db, uid, displayName, roomId);
63
+ await sync.start();
64
+ await sync.cleanupStaleSessions().catch(() => { });
65
+ console.log(`Sync ready (room: ${roomId.slice(0, 8)}...).\n`);
66
+ const watcher = new SessionWatcher();
67
+ watcher.on('line', (raw) => {
68
+ const event = parseLine(raw.content);
69
+ if (!event)
70
+ return;
71
+ const { activity, tool, detail } = event.currentState;
72
+ const label = ACTIVITY_LABELS[activity] ?? activity;
73
+ const toolStr = tool ? ` → ${tool}` : '';
74
+ const detailStr = detail ? ` (${detail})` : '';
75
+ console.log(`[${event.project}] ${label}${toolStr}${detailStr}`);
76
+ sync.handleEvent(event);
77
+ });
78
+ watcher.on('error', (err) => {
79
+ console.error('Watcher error:', err.message);
80
+ });
81
+ const shutdown = async () => {
82
+ console.log('\nShutting down...');
83
+ await sync.shutdown();
84
+ await watcher.stop();
85
+ console.log('Goodbye!');
86
+ process.exit(0);
87
+ };
88
+ process.on('SIGINT', shutdown);
89
+ process.on('SIGTERM', shutdown);
90
+ await watcher.start();
91
+ }
92
+ main().catch((err) => {
93
+ console.error('Fatal error:', err);
94
+ process.exit(1);
95
+ });
96
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare function initFirebase(credentials?: {
2
+ email: string;
3
+ password: string;
4
+ }): Promise<{
5
+ auth: import("firebase/auth").Auth;
6
+ db: import("firebase/firestore").Firestore;
7
+ uid: string;
8
+ }>;
@@ -0,0 +1,26 @@
1
+ import { initializeApp } from 'firebase/app';
2
+ import { getAuth, signInAnonymously, signInWithEmailAndPassword } from 'firebase/auth';
3
+ import { getFirestore } from 'firebase/firestore';
4
+ const firebaseConfig = {
5
+ apiKey: 'AIzaSyBuruqIwZ2otkQqdjed_BzdIZCZRm6ZSaM',
6
+ authDomain: 'cloffice.firebaseapp.com',
7
+ projectId: 'cloffice',
8
+ storageBucket: 'cloffice.firebasestorage.app',
9
+ messagingSenderId: '305157374320',
10
+ appId: '1:305157374320:web:3261361135fae1eed78fb5'
11
+ };
12
+ export async function initFirebase(credentials) {
13
+ const app = initializeApp(firebaseConfig);
14
+ const auth = getAuth(app);
15
+ const db = getFirestore(app);
16
+ if (credentials) {
17
+ await signInWithEmailAndPassword(auth, credentials.email, credentials.password);
18
+ }
19
+ else {
20
+ await signInAnonymously(auth);
21
+ }
22
+ const uid = auth.currentUser.uid;
23
+ console.log(`Authenticated as ${uid}`);
24
+ return { auth, db, uid };
25
+ }
26
+ //# sourceMappingURL=firebase-init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firebase-init.js","sourceRoot":"","sources":["../src/firebase-init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,cAAc,GAAG;IACtB,MAAM,EAAE,yCAAyC;IACjD,UAAU,EAAE,0BAA0B;IACtC,SAAS,EAAE,UAAU;IACrB,aAAa,EAAE,8BAA8B;IAC7C,iBAAiB,EAAE,cAAc;IACjC,KAAK,EAAE,2CAA2C;CAClD,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,WAAiD;IACnF,MAAM,GAAG,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,EAAE,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAE7B,IAAI,WAAW,EAAE,CAAC;QACjB,MAAM,0BAA0B,CAAC,IAAI,EAAE,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IACjF,CAAC;SAAM,CAAC;QACP,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,WAAY,CAAC,GAAG,CAAC;IAClC,OAAO,CAAC,GAAG,CAAC,oBAAoB,GAAG,EAAE,CAAC,CAAC;IAEvC,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { Firestore } from 'firebase/firestore';
2
+ import type { ParsedEvent } from './parser.js';
3
+ export declare class FirestoreSync {
4
+ private db;
5
+ private uid;
6
+ private displayName;
7
+ private roomId;
8
+ private sessions;
9
+ private flushTimer;
10
+ constructor(db: Firestore, uid: string, displayName: string, roomId: string);
11
+ start(): Promise<void>;
12
+ handleEvent(event: ParsedEvent): void;
13
+ private createSession;
14
+ private flush;
15
+ shutdown(): Promise<void>;
16
+ cleanupStaleSessions(): Promise<void>;
17
+ }
@@ -0,0 +1,112 @@
1
+ import { doc, setDoc, updateDoc, serverTimestamp, collection, query, where, getDocs } from 'firebase/firestore';
2
+ export class FirestoreSync {
3
+ db;
4
+ uid;
5
+ displayName;
6
+ roomId;
7
+ sessions = new Map();
8
+ flushTimer = null;
9
+ constructor(db, uid, displayName, roomId) {
10
+ this.db = db;
11
+ this.uid = uid;
12
+ this.displayName = displayName;
13
+ this.roomId = roomId;
14
+ }
15
+ async start() {
16
+ await setDoc(doc(this.db, 'rooms', this.roomId, 'users', this.uid), {
17
+ displayName: this.displayName,
18
+ status: 'online',
19
+ lastSeen: serverTimestamp()
20
+ });
21
+ this.flushTimer = setInterval(() => this.flush(), 500);
22
+ }
23
+ handleEvent(event) {
24
+ const existing = this.sessions.get(event.sessionId);
25
+ if (existing) {
26
+ if (!existing.owned)
27
+ return;
28
+ existing.dirty = true;
29
+ existing.lastEvent = event;
30
+ }
31
+ else {
32
+ const state = { dirty: false, lastEvent: event, owned: true };
33
+ this.sessions.set(event.sessionId, state);
34
+ this.createSession(event, state).catch(() => { });
35
+ }
36
+ }
37
+ async createSession(event, state) {
38
+ try {
39
+ await setDoc(doc(this.db, 'rooms', this.roomId, 'sessions', event.sessionId), {
40
+ userId: this.uid,
41
+ project: event.project,
42
+ status: 'active',
43
+ currentState: event.currentState,
44
+ lastActivity: serverTimestamp(),
45
+ startedAt: serverTimestamp()
46
+ });
47
+ }
48
+ catch {
49
+ state.owned = false;
50
+ }
51
+ }
52
+ async flush() {
53
+ for (const [sessionId, state] of this.sessions) {
54
+ if (!state.dirty)
55
+ continue;
56
+ state.dirty = false;
57
+ try {
58
+ await updateDoc(doc(this.db, 'rooms', this.roomId, 'sessions', sessionId), {
59
+ currentState: state.lastEvent.currentState,
60
+ lastActivity: serverTimestamp()
61
+ });
62
+ }
63
+ catch {
64
+ // Session doc might not exist yet if create is still in flight
65
+ }
66
+ }
67
+ try {
68
+ await updateDoc(doc(this.db, 'rooms', this.roomId, 'users', this.uid), {
69
+ lastSeen: serverTimestamp()
70
+ });
71
+ }
72
+ catch {
73
+ // Ignore
74
+ }
75
+ }
76
+ async shutdown() {
77
+ if (this.flushTimer) {
78
+ clearInterval(this.flushTimer);
79
+ this.flushTimer = null;
80
+ }
81
+ const promises = [];
82
+ for (const sessionId of this.sessions.keys()) {
83
+ promises.push(updateDoc(doc(this.db, 'rooms', this.roomId, 'sessions', sessionId), {
84
+ status: 'ended',
85
+ currentState: { activity: 'idle', tool: null, detail: null },
86
+ lastActivity: serverTimestamp()
87
+ }).catch(() => { }));
88
+ }
89
+ promises.push(updateDoc(doc(this.db, 'rooms', this.roomId, 'users', this.uid), {
90
+ status: 'offline',
91
+ lastSeen: serverTimestamp()
92
+ }).catch(() => { }));
93
+ await Promise.all(promises);
94
+ }
95
+ async cleanupStaleSessions() {
96
+ const sessionsRef = collection(this.db, 'rooms', this.roomId, 'sessions');
97
+ const q = query(sessionsRef, where('userId', '==', this.uid), where('status', '==', 'active'));
98
+ try {
99
+ const snapshot = await getDocs(q);
100
+ const promises = snapshot.docs.map((d) => updateDoc(doc(this.db, 'rooms', this.roomId, 'sessions', d.id), {
101
+ status: 'ended',
102
+ currentState: { activity: 'idle', tool: null, detail: null },
103
+ lastActivity: serverTimestamp()
104
+ }).catch(() => { }));
105
+ await Promise.all(promises);
106
+ }
107
+ catch {
108
+ // Ignore cleanup errors
109
+ }
110
+ }
111
+ }
112
+ //# sourceMappingURL=firestore-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firestore-sync.js","sourceRoot":"","sources":["../src/firestore-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface SessionCurrentState {
2
+ activity: 'thinking' | 'responding' | 'waiting' | 'tool_use' | 'user_prompt' | 'idle';
3
+ tool: string | null;
4
+ detail: string | null;
5
+ }
6
+ export interface ParsedEvent {
7
+ sessionId: string;
8
+ project: string;
9
+ timestamp: string;
10
+ currentState: SessionCurrentState;
11
+ }
12
+ export declare function parseLine(raw: string): ParsedEvent | null;
package/dist/parser.js ADDED
@@ -0,0 +1,98 @@
1
+ import { basename } from 'node:path';
2
+ function safeBasename(filePath) {
3
+ if (typeof filePath !== 'string')
4
+ return null;
5
+ return basename(filePath);
6
+ }
7
+ function extractToolContext(name, input) {
8
+ switch (name) {
9
+ case 'Read':
10
+ case 'Write':
11
+ case 'Edit':
12
+ return { tool: name, detail: safeBasename(input.file_path) };
13
+ case 'Bash':
14
+ return { tool: 'Bash', detail: typeof input.description === 'string' ? input.description : null };
15
+ case 'Grep':
16
+ return { tool: 'Grep', detail: typeof input.pattern === 'string' ? input.pattern : null };
17
+ case 'Glob':
18
+ return { tool: 'Glob', detail: typeof input.pattern === 'string' ? input.pattern : null };
19
+ case 'Task':
20
+ return { tool: 'Task', detail: typeof input.subagent_type === 'string' ? input.subagent_type : null };
21
+ case 'WebSearch':
22
+ case 'WebFetch':
23
+ return { tool: name, detail: null };
24
+ default: {
25
+ // MCP tools - strip the mcp__ prefix for display
26
+ if (name.startsWith('mcp__')) {
27
+ const parts = name.split('__');
28
+ const shortName = parts[parts.length - 1] ?? name;
29
+ return { tool: shortName, detail: null };
30
+ }
31
+ return { tool: name, detail: null };
32
+ }
33
+ }
34
+ }
35
+ function projectFromCwd(cwd) {
36
+ if (!cwd)
37
+ return 'unknown';
38
+ return basename(cwd);
39
+ }
40
+ export function parseLine(raw) {
41
+ let line;
42
+ try {
43
+ line = JSON.parse(raw);
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ const sessionId = line.sessionId;
49
+ if (!sessionId)
50
+ return null;
51
+ const timestamp = line.timestamp ?? new Date().toISOString();
52
+ const project = projectFromCwd(line.cwd);
53
+ // User message = user is typing a prompt
54
+ if (line.type === 'user' && line.userType !== 'tool_result') {
55
+ return {
56
+ sessionId,
57
+ project,
58
+ timestamp,
59
+ currentState: { activity: 'user_prompt', tool: null, detail: null }
60
+ };
61
+ }
62
+ // Assistant message = Claude is responding
63
+ if (line.type === 'assistant' && line.message?.content) {
64
+ const content = line.message.content;
65
+ // Check for tool_use blocks
66
+ const toolUse = content.find((b) => b.type === 'tool_use');
67
+ if (toolUse?.name && toolUse?.input) {
68
+ const { tool, detail } = extractToolContext(toolUse.name, toolUse.input);
69
+ return {
70
+ sessionId,
71
+ project,
72
+ timestamp,
73
+ currentState: { activity: 'tool_use', tool, detail }
74
+ };
75
+ }
76
+ // Text-only response = Claude is responding with text
77
+ const hasText = content.some((b) => b.type === 'text' && b.text);
78
+ if (hasText) {
79
+ return {
80
+ sessionId,
81
+ project,
82
+ timestamp,
83
+ currentState: { activity: 'responding', tool: null, detail: null }
84
+ };
85
+ }
86
+ }
87
+ // Tool result coming back = Claude is thinking about the result
88
+ if (line.type === 'user' && line.userType === 'tool_result') {
89
+ return {
90
+ sessionId,
91
+ project,
92
+ timestamp,
93
+ currentState: { activity: 'thinking', tool: null, detail: null }
94
+ };
95
+ }
96
+ return null;
97
+ }
98
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { EventEmitter } from 'node:events';
2
+ export interface RawLine {
3
+ content: string;
4
+ filePath: string;
5
+ }
6
+ export declare class SessionWatcher extends EventEmitter {
7
+ private filePositions;
8
+ private claudeDir;
9
+ private watcher;
10
+ constructor();
11
+ start(): Promise<void>;
12
+ stop(): Promise<void>;
13
+ private handleFile;
14
+ }
@@ -0,0 +1,65 @@
1
+ import { watch } from 'chokidar';
2
+ import { open, stat } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { EventEmitter } from 'node:events';
6
+ export class SessionWatcher extends EventEmitter {
7
+ filePositions = new Map();
8
+ claudeDir;
9
+ watcher = null;
10
+ constructor() {
11
+ super();
12
+ this.claudeDir = join(homedir(), '.claude', 'projects');
13
+ }
14
+ async start() {
15
+ this.watcher = watch(this.claudeDir, {
16
+ persistent: true,
17
+ ignoreInitial: false,
18
+ depth: 5
19
+ });
20
+ const isJsonl = (p) => p.endsWith('.jsonl');
21
+ this.watcher.on('add', (filePath) => isJsonl(filePath) && this.handleFile(filePath, true));
22
+ this.watcher.on('change', (filePath) => isJsonl(filePath) && this.handleFile(filePath, false));
23
+ this.watcher.on('error', (err) => this.emit('error', err));
24
+ console.log(`Watching ${this.claudeDir} for JSONL files...`);
25
+ }
26
+ async stop() {
27
+ await this.watcher?.close();
28
+ this.watcher = null;
29
+ }
30
+ async handleFile(filePath, isNew) {
31
+ try {
32
+ const fileStat = await stat(filePath);
33
+ if (isNew) {
34
+ const tenMinutesAgo = Date.now() - 10 * 60 * 1000;
35
+ if (fileStat.mtimeMs < tenMinutesAgo) {
36
+ this.filePositions.set(filePath, fileStat.size);
37
+ return;
38
+ }
39
+ }
40
+ const position = this.filePositions.get(filePath) ?? 0;
41
+ if (fileStat.size <= position)
42
+ return;
43
+ // Only read the new bytes from the file
44
+ const bytesToRead = fileStat.size - position;
45
+ const buffer = Buffer.alloc(bytesToRead);
46
+ const fh = await open(filePath, 'r');
47
+ try {
48
+ await fh.read(buffer, 0, bytesToRead, position);
49
+ }
50
+ finally {
51
+ await fh.close();
52
+ }
53
+ this.filePositions.set(filePath, fileStat.size);
54
+ const newContent = buffer.toString('utf-8');
55
+ const lines = newContent.split('\n').filter((l) => l.trim());
56
+ for (const line of lines) {
57
+ this.emit('line', { content: line, filePath });
58
+ }
59
+ }
60
+ catch {
61
+ // File may have been deleted between event and read
62
+ }
63
+ }
64
+ }
65
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "cloffice-bridge",
3
+ "version": "0.1.0",
4
+ "description": "Bridge CLI that syncs local Claude Code sessions to a Cloffice room",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/csimpkins/cloffice"
10
+ },
11
+ "bin": {
12
+ "cloffice-bridge": "./dist/cli.js"
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "dependencies": {
21
+ "chokidar": "^4.0.0",
22
+ "firebase": "^11.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "tsx": "^4.0.0",
26
+ "typescript": "^5.9.3",
27
+ "@types/node": "^22"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "dev": "tsx src/cli.ts"
32
+ }
33
+ }