thetacog-mcp 1.0.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/.workflow/bucket-builder.html +823 -0
- package/.workflow/bucket-discoverer.html +928 -0
- package/.workflow/bucket-experimenter.html +802 -0
- package/.workflow/bucket-operator.html +951 -0
- package/.workflow/bucket-strategist.html +970 -0
- package/.workflow/bucket-teacher.html +873 -0
- package/.workflow/cognitive-dashboard-enhanced.html +731 -0
- package/.workflow/cognitive-dashboard.html +296 -0
- package/CHANGELOG.md +44 -0
- package/README.md +181 -0
- package/package.json +57 -0
- package/postinstall.js +80 -0
- package/server.js +1031 -0
package/server.js
ADDED
|
@@ -0,0 +1,1031 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ThetaCog MCP Server v0.1.0 - Cognitive Room Detection & Switching
|
|
5
|
+
*
|
|
6
|
+
* MODE MANAGEMENT, NOT TASK MANAGEMENT.
|
|
7
|
+
*
|
|
8
|
+
* This MCP server enables Claude to:
|
|
9
|
+
* 1. DETECT which cognitive room you should be in based on conversation
|
|
10
|
+
* 2. STATUS check your current room context and input streams
|
|
11
|
+
* 3. SWITCH between rooms with context preservation
|
|
12
|
+
*
|
|
13
|
+
* ARCHITECTURE:
|
|
14
|
+
* - HTML files with embedded JSON = self-contained rooms (git-tracked)
|
|
15
|
+
* - SQLite = optional session state and switch history
|
|
16
|
+
* - Supabase = optional multi-tenant sync (same as CRM pattern)
|
|
17
|
+
*
|
|
18
|
+
* The HTML files ARE the rooms. JSON inside them. Claude Flow MCP talks
|
|
19
|
+
* to the same SQLite when available, but rooms work without it.
|
|
20
|
+
*
|
|
21
|
+
* ROOM ARCHETYPES:
|
|
22
|
+
* - Builder (๐จ Blue, Tactical) - Ship, don't theorize
|
|
23
|
+
* - Architect (๐ Indigo, Strategic) - See the whole war before fighting
|
|
24
|
+
* - Operator (๐ฉ Green, Strategic) - Close, don't explore
|
|
25
|
+
* - Vault (๐ Red, Foundational) - Protect the irreversible
|
|
26
|
+
* - Voice (๐ค Purple, Tactical) - Test messaging variants
|
|
27
|
+
* - Laboratory (๐งช Cyan, Tactical) - Break things safely
|
|
28
|
+
*
|
|
29
|
+
* TERMINAL MAPPING (macOS default):
|
|
30
|
+
* - iTerm2 โ Builder
|
|
31
|
+
* - VS Code โ Architect
|
|
32
|
+
* - Kitty โ Operator
|
|
33
|
+
* - WezTerm โ Vault
|
|
34
|
+
* - Terminal โ Voice
|
|
35
|
+
* - Cursor โ Laboratory
|
|
36
|
+
*
|
|
37
|
+
* v0.1.0 Changes:
|
|
38
|
+
* - Initial release with 3 core tools: detect, status, switch
|
|
39
|
+
* - SQLite optional (works in memory-only mode)
|
|
40
|
+
* - Graceful shutdown handlers (SIGINT, SIGTERM, SIGHUP)
|
|
41
|
+
* - Install subcommand for easy registration
|
|
42
|
+
*
|
|
43
|
+
* For parallel founders who think in parallel.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
47
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
48
|
+
import {
|
|
49
|
+
CallToolRequestSchema,
|
|
50
|
+
ListToolsRequestSchema,
|
|
51
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
52
|
+
import fs from 'fs';
|
|
53
|
+
import path from 'path';
|
|
54
|
+
import { fileURLToPath } from 'url';
|
|
55
|
+
import { execSync } from 'child_process';
|
|
56
|
+
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// INSTALL SUBCOMMAND (copied from CRM pattern)
|
|
59
|
+
// ============================================================================
|
|
60
|
+
|
|
61
|
+
if (process.argv[2] === 'install') {
|
|
62
|
+
console.error('๐ง Installing ThetaCog MCP Server...');
|
|
63
|
+
console.error('');
|
|
64
|
+
console.error('โน๏ธ Mode management, not task management.');
|
|
65
|
+
console.error(' - Detects which cognitive room you should be in');
|
|
66
|
+
console.error(' - Switches context with memory palace anchoring');
|
|
67
|
+
console.error(' - SQLite optional (works in memory-only mode)');
|
|
68
|
+
console.error('');
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
console.error('๐ Registering MCP server with Claude Code...');
|
|
72
|
+
try {
|
|
73
|
+
execSync('claude mcp add thetacog thetacog-mcp', { stdio: 'pipe' });
|
|
74
|
+
console.error('โ
MCP server registered!');
|
|
75
|
+
} catch (addError) {
|
|
76
|
+
const errorMsg = addError.message || '';
|
|
77
|
+
if (errorMsg.includes('already exists')) {
|
|
78
|
+
console.error('โ
MCP server already registered!');
|
|
79
|
+
} else {
|
|
80
|
+
throw addError;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
console.error('');
|
|
84
|
+
console.error('๐ Installation complete!');
|
|
85
|
+
console.error('');
|
|
86
|
+
console.error('๐ Next steps:');
|
|
87
|
+
console.error(' 1. Restart Claude Code (Cmd+Q โ reopen)');
|
|
88
|
+
console.error(' 2. Test with: "What room should I be in?"');
|
|
89
|
+
console.error(' 3. Or: "Switch to architect mode"');
|
|
90
|
+
console.error('');
|
|
91
|
+
console.error('๐ Tools available:');
|
|
92
|
+
console.error(' - thetacog-detect: Analyze conversation for room signals');
|
|
93
|
+
console.error(' - thetacog-status: Get current room context');
|
|
94
|
+
console.error(' - thetacog-switch: Switch to a different room');
|
|
95
|
+
console.error('');
|
|
96
|
+
process.exit(0);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('โ Failed to register MCP server:', error.message);
|
|
99
|
+
console.error(' Try running manually: claude mcp add thetacog thetacog-mcp');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// CONFIGURATION
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
109
|
+
const __dirname = path.dirname(__filename);
|
|
110
|
+
|
|
111
|
+
// Room detection signals - add more patterns here to improve detection
|
|
112
|
+
// Claude Flow can edit this section to add domain-specific signals
|
|
113
|
+
const DETECTION_SIGNALS = {
|
|
114
|
+
builder: {
|
|
115
|
+
keywords: ['ship', 'deploy', 'build', 'fix', 'implement', 'code', 'done', 'finish', 'deadline', 'demo'],
|
|
116
|
+
confidence: 0.85,
|
|
117
|
+
emoji: '๐จ',
|
|
118
|
+
color: '#3b82f6',
|
|
119
|
+
tier: 'tactical'
|
|
120
|
+
},
|
|
121
|
+
architect: {
|
|
122
|
+
keywords: ['strategy', 'roadmap', 'bigger picture', 'sequence', 'prioritize', 'architecture', 'plan', 'vision', 'Q1', 'Q2', 'Q3', 'Q4'],
|
|
123
|
+
confidence: 0.90,
|
|
124
|
+
emoji: '๐',
|
|
125
|
+
color: '#4f46e5',
|
|
126
|
+
tier: 'strategic'
|
|
127
|
+
},
|
|
128
|
+
operator: {
|
|
129
|
+
keywords: ['close', 'deal', 'revenue', 'prospect', 'sales', 'meeting', 'pipeline', 'convert', 'customer'],
|
|
130
|
+
confidence: 0.88,
|
|
131
|
+
emoji: '๐ฉ',
|
|
132
|
+
color: '#22c55e',
|
|
133
|
+
tier: 'strategic'
|
|
134
|
+
},
|
|
135
|
+
vault: {
|
|
136
|
+
keywords: ['patent', 'IP', 'legal', 'protect', 'trademark', 'confidential', 'irreversible', 'prove', 'validate'],
|
|
137
|
+
confidence: 0.92,
|
|
138
|
+
emoji: '๐',
|
|
139
|
+
color: '#ef4444',
|
|
140
|
+
tier: 'foundational'
|
|
141
|
+
},
|
|
142
|
+
voice: {
|
|
143
|
+
keywords: ['message', 'copy', 'blog', 'content', 'docs', 'documentation', 'explain', 'teach', 'write'],
|
|
144
|
+
confidence: 0.80,
|
|
145
|
+
emoji: '๐ค',
|
|
146
|
+
color: '#a855f7',
|
|
147
|
+
tier: 'tactical'
|
|
148
|
+
},
|
|
149
|
+
laboratory: {
|
|
150
|
+
keywords: ['experiment', 'prototype', 'test', 'try', 'explore', 'break', 'hack', 'spike', 'POC'],
|
|
151
|
+
confidence: 0.82,
|
|
152
|
+
emoji: '๐งช',
|
|
153
|
+
color: '#06b6d4',
|
|
154
|
+
tier: 'tactical'
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Terminal to room mapping (macOS)
|
|
159
|
+
// Claude Flow can edit this to add Windows/Linux terminals
|
|
160
|
+
const TERMINALS_MAC = {
|
|
161
|
+
'iTerm': { app: 'iTerm.app', room: 'builder', html: 'iterm2-builder.html' },
|
|
162
|
+
'VS Code': { app: 'Visual Studio Code.app', room: 'architect', html: 'vscode-architect.html' },
|
|
163
|
+
'Kitty': { app: 'kitty.app', room: 'operator', html: 'kitty-operator.html' },
|
|
164
|
+
'WezTerm': { app: 'WezTerm.app', room: 'vault', html: 'wezterm-vault.html' },
|
|
165
|
+
'Terminal': { app: 'Terminal.app', room: 'voice', html: 'terminal-voice.html' },
|
|
166
|
+
'Cursor': { app: 'Cursor.app', room: 'laboratory', html: 'cursor-laboratory.html' },
|
|
167
|
+
'Alacritty': { app: 'Alacritty.app', room: 'performer', html: 'alacritty-performer.html' }
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Memory Palace anchors for each room
|
|
171
|
+
// Claude Flow can customize these per user
|
|
172
|
+
const MEMORY_PALACES = {
|
|
173
|
+
builder: "Walk to the workshop. Blue light. Tools on the wall. The smell of sawdust.",
|
|
174
|
+
architect: "Walk up the stairs to the drafting room. Indigo light. Unroll the blueprints. See the whole war before you fight it.",
|
|
175
|
+
operator: "Enter the trading floor. Green light. The deals are live. Every conversation ends with a next step.",
|
|
176
|
+
vault: "Descend to the vault. Red light. The heavy door closes behind you. What enters here is protected forever.",
|
|
177
|
+
voice: "Step onto the stage. Purple spotlight. The audience is listening. Test the message.",
|
|
178
|
+
laboratory: "Enter the lab. Cyan glow. Safety goggles on. Break things safely here."
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Identity rules for each room
|
|
182
|
+
// Claude Flow can expand these
|
|
183
|
+
const IDENTITY_RULES = {
|
|
184
|
+
builder: [
|
|
185
|
+
"You are shipping, not theorizing",
|
|
186
|
+
"Done beats right when the demo is Sunday",
|
|
187
|
+
"Make it work, not perfect"
|
|
188
|
+
],
|
|
189
|
+
architect: [
|
|
190
|
+
"You are redrawing the entire territory",
|
|
191
|
+
"You see the whole war before you fight it",
|
|
192
|
+
"You do not execute battles. You sequence them."
|
|
193
|
+
],
|
|
194
|
+
operator: [
|
|
195
|
+
"You are closing, not exploring",
|
|
196
|
+
"Revenue is the only metric that matters here",
|
|
197
|
+
"Every conversation ends with a next step"
|
|
198
|
+
],
|
|
199
|
+
vault: [
|
|
200
|
+
"You are protecting the irreversible",
|
|
201
|
+
"You are validating before committing",
|
|
202
|
+
"You do not ship from here. You prove from here."
|
|
203
|
+
],
|
|
204
|
+
voice: [
|
|
205
|
+
"You are testing messaging",
|
|
206
|
+
"Experiment with variants",
|
|
207
|
+
"Find what resonates"
|
|
208
|
+
],
|
|
209
|
+
laboratory: [
|
|
210
|
+
"You are prototyping fast",
|
|
211
|
+
"Break things safely",
|
|
212
|
+
"Failure is data here"
|
|
213
|
+
]
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// ============================================================================
|
|
217
|
+
// TERMINAL DETECTION
|
|
218
|
+
// ============================================================================
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Detect which terminal Claude is running in
|
|
222
|
+
* Uses TERM_PROGRAM env var (set by most terminals)
|
|
223
|
+
*/
|
|
224
|
+
function detectTerminal() {
|
|
225
|
+
const termProgram = process.env.TERM_PROGRAM || '';
|
|
226
|
+
const termProgramVersion = process.env.TERM_PROGRAM_VERSION || '';
|
|
227
|
+
|
|
228
|
+
// Map env values to our terminal names
|
|
229
|
+
const termMap = {
|
|
230
|
+
'iTerm.app': 'iTerm',
|
|
231
|
+
'vscode': 'VS Code',
|
|
232
|
+
'Apple_Terminal': 'Terminal',
|
|
233
|
+
'kitty': 'Kitty',
|
|
234
|
+
'WezTerm': 'WezTerm',
|
|
235
|
+
'Cursor': 'Cursor',
|
|
236
|
+
'Alacritty': 'Alacritty'
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const detected = termMap[termProgram] || null;
|
|
240
|
+
const room = detected ? TERMINALS_MAC[detected]?.room : null;
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
termProgram,
|
|
244
|
+
termProgramVersion,
|
|
245
|
+
terminal: detected,
|
|
246
|
+
room: room,
|
|
247
|
+
html: detected ? TERMINALS_MAC[detected]?.html : null
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ============================================================================
|
|
252
|
+
// STATE MANAGEMENT (SQLite primary, JSON export layer)
|
|
253
|
+
// ============================================================================
|
|
254
|
+
|
|
255
|
+
let currentRoom = null;
|
|
256
|
+
let roomHistory = [];
|
|
257
|
+
let db = null;
|
|
258
|
+
let thetacogDir = null;
|
|
259
|
+
|
|
260
|
+
// Try to load SQLite
|
|
261
|
+
async function initDatabase() {
|
|
262
|
+
try {
|
|
263
|
+
const Database = (await import('better-sqlite3')).default;
|
|
264
|
+
thetacogDir = path.join(process.env.HOME, '.thetacog');
|
|
265
|
+
const dbPath = path.join(thetacogDir, 'thetacog.db');
|
|
266
|
+
|
|
267
|
+
// Ensure directory exists
|
|
268
|
+
if (!fs.existsSync(thetacogDir)) {
|
|
269
|
+
fs.mkdirSync(thetacogDir, { recursive: true });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
db = new Database(dbPath);
|
|
273
|
+
|
|
274
|
+
// Create tables if they don't exist
|
|
275
|
+
db.exec(`
|
|
276
|
+
CREATE TABLE IF NOT EXISTS room_state (
|
|
277
|
+
id INTEGER PRIMARY KEY,
|
|
278
|
+
current_room TEXT,
|
|
279
|
+
context_snapshot TEXT,
|
|
280
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
CREATE TABLE IF NOT EXISTS room_history (
|
|
284
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
285
|
+
from_room TEXT,
|
|
286
|
+
to_room TEXT,
|
|
287
|
+
signal TEXT,
|
|
288
|
+
confidence REAL,
|
|
289
|
+
switched_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
-- Todos per room (the main work items)
|
|
293
|
+
CREATE TABLE IF NOT EXISTS room_todos (
|
|
294
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
295
|
+
room TEXT NOT NULL,
|
|
296
|
+
text TEXT NOT NULL,
|
|
297
|
+
done INTEGER DEFAULT 0,
|
|
298
|
+
priority INTEGER DEFAULT 5,
|
|
299
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
300
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
-- Input streams between rooms (flywheel)
|
|
304
|
+
CREATE TABLE IF NOT EXISTS room_streams (
|
|
305
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
306
|
+
from_room TEXT NOT NULL,
|
|
307
|
+
to_room TEXT NOT NULL,
|
|
308
|
+
message TEXT NOT NULL,
|
|
309
|
+
read INTEGER DEFAULT 0,
|
|
310
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
CREATE TABLE IF NOT EXISTS room_configs (
|
|
314
|
+
room_name TEXT PRIMARY KEY,
|
|
315
|
+
config_json TEXT,
|
|
316
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
317
|
+
);
|
|
318
|
+
`);
|
|
319
|
+
|
|
320
|
+
// Load current room from state
|
|
321
|
+
const state = db.prepare('SELECT current_room FROM room_state WHERE id = 1').get();
|
|
322
|
+
if (state) {
|
|
323
|
+
currentRoom = state.current_room;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
console.error('[ThetaCog] SQLite initialized at', dbPath);
|
|
327
|
+
return true;
|
|
328
|
+
} catch (e) {
|
|
329
|
+
console.error('[ThetaCog] SQLite not available, running in memory-only mode');
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ============================================================================
|
|
335
|
+
// TODO CRUD (SQLite primary)
|
|
336
|
+
// ============================================================================
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Add a todo to a room
|
|
340
|
+
*/
|
|
341
|
+
function addTodo(room, text, priority = 5) {
|
|
342
|
+
if (!db) return { error: 'SQLite not available' };
|
|
343
|
+
|
|
344
|
+
const result = db.prepare(`
|
|
345
|
+
INSERT INTO room_todos (room, text, priority)
|
|
346
|
+
VALUES (?, ?, ?)
|
|
347
|
+
`).run(room, text, priority);
|
|
348
|
+
|
|
349
|
+
exportStateToJson(); // Sync to JSON
|
|
350
|
+
return { id: result.lastInsertRowid, room, text, priority, done: false };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* List todos for a room (or all rooms)
|
|
355
|
+
*/
|
|
356
|
+
function listTodos(room = null) {
|
|
357
|
+
if (!db) return [];
|
|
358
|
+
|
|
359
|
+
if (room) {
|
|
360
|
+
return db.prepare(`
|
|
361
|
+
SELECT * FROM room_todos WHERE room = ? ORDER BY priority ASC, created_at DESC
|
|
362
|
+
`).all(room);
|
|
363
|
+
} else {
|
|
364
|
+
return db.prepare(`
|
|
365
|
+
SELECT * FROM room_todos ORDER BY room, priority ASC, created_at DESC
|
|
366
|
+
`).all();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Update a todo (toggle done, change priority, edit text)
|
|
372
|
+
*/
|
|
373
|
+
function updateTodo(id, updates) {
|
|
374
|
+
if (!db) return { error: 'SQLite not available' };
|
|
375
|
+
|
|
376
|
+
const fields = [];
|
|
377
|
+
const values = [];
|
|
378
|
+
|
|
379
|
+
if (updates.done !== undefined) {
|
|
380
|
+
fields.push('done = ?');
|
|
381
|
+
values.push(updates.done ? 1 : 0);
|
|
382
|
+
}
|
|
383
|
+
if (updates.priority !== undefined) {
|
|
384
|
+
fields.push('priority = ?');
|
|
385
|
+
values.push(updates.priority);
|
|
386
|
+
}
|
|
387
|
+
if (updates.text !== undefined) {
|
|
388
|
+
fields.push('text = ?');
|
|
389
|
+
values.push(updates.text);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (fields.length === 0) return { error: 'No updates provided' };
|
|
393
|
+
|
|
394
|
+
fields.push('updated_at = CURRENT_TIMESTAMP');
|
|
395
|
+
values.push(id);
|
|
396
|
+
|
|
397
|
+
db.prepare(`UPDATE room_todos SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
|
398
|
+
exportStateToJson(); // Sync to JSON
|
|
399
|
+
return { success: true, id };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Delete a todo
|
|
404
|
+
*/
|
|
405
|
+
function deleteTodo(id) {
|
|
406
|
+
if (!db) return { error: 'SQLite not available' };
|
|
407
|
+
|
|
408
|
+
db.prepare('DELETE FROM room_todos WHERE id = ?').run(id);
|
|
409
|
+
exportStateToJson(); // Sync to JSON
|
|
410
|
+
return { success: true, id };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ============================================================================
|
|
414
|
+
// INPUT STREAMS (Flywheel coordination)
|
|
415
|
+
// ============================================================================
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Send a message from one room to another
|
|
419
|
+
*/
|
|
420
|
+
function sendStream(fromRoom, toRoom, message) {
|
|
421
|
+
if (!db) return { error: 'SQLite not available' };
|
|
422
|
+
|
|
423
|
+
const result = db.prepare(`
|
|
424
|
+
INSERT INTO room_streams (from_room, to_room, message)
|
|
425
|
+
VALUES (?, ?, ?)
|
|
426
|
+
`).run(fromRoom, toRoom, message);
|
|
427
|
+
|
|
428
|
+
exportStateToJson();
|
|
429
|
+
return { id: result.lastInsertRowid, fromRoom, toRoom, message };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get unread streams for a room
|
|
434
|
+
*/
|
|
435
|
+
function getStreams(room) {
|
|
436
|
+
if (!db) return [];
|
|
437
|
+
|
|
438
|
+
return db.prepare(`
|
|
439
|
+
SELECT * FROM room_streams WHERE to_room = ? AND read = 0
|
|
440
|
+
ORDER BY created_at DESC
|
|
441
|
+
`).all(room);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Mark streams as read
|
|
446
|
+
*/
|
|
447
|
+
function markStreamsRead(room) {
|
|
448
|
+
if (!db) return { error: 'SQLite not available' };
|
|
449
|
+
|
|
450
|
+
db.prepare('UPDATE room_streams SET read = 1 WHERE to_room = ?').run(room);
|
|
451
|
+
return { success: true };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// ============================================================================
|
|
455
|
+
// JSON EXPORT (for HTML to read on tab focus)
|
|
456
|
+
// ============================================================================
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Export full state to JSON file
|
|
460
|
+
* HTML files read this on visibilitychange event
|
|
461
|
+
*/
|
|
462
|
+
function exportStateToJson() {
|
|
463
|
+
if (!db || !thetacogDir) return;
|
|
464
|
+
|
|
465
|
+
const state = {
|
|
466
|
+
currentRoom: currentRoom || 'builder',
|
|
467
|
+
exportedAt: new Date().toISOString(),
|
|
468
|
+
rooms: {}
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// Get all rooms
|
|
472
|
+
const rooms = ['builder', 'architect', 'operator', 'vault', 'voice', 'laboratory'];
|
|
473
|
+
|
|
474
|
+
for (const room of rooms) {
|
|
475
|
+
const todos = db.prepare(`
|
|
476
|
+
SELECT id, text, done, priority FROM room_todos WHERE room = ?
|
|
477
|
+
ORDER BY priority ASC
|
|
478
|
+
`).all(room);
|
|
479
|
+
|
|
480
|
+
const streams = db.prepare(`
|
|
481
|
+
SELECT id, from_room, message, created_at FROM room_streams
|
|
482
|
+
WHERE to_room = ? AND read = 0
|
|
483
|
+
`).all(room);
|
|
484
|
+
|
|
485
|
+
state.rooms[room] = {
|
|
486
|
+
emoji: DETECTION_SIGNALS[room]?.emoji || '๐ง ',
|
|
487
|
+
color: DETECTION_SIGNALS[room]?.color || '#667eea',
|
|
488
|
+
tier: DETECTION_SIGNALS[room]?.tier || 'tactical',
|
|
489
|
+
memoryPalace: MEMORY_PALACES[room],
|
|
490
|
+
identityRules: IDENTITY_RULES[room],
|
|
491
|
+
todos: todos.map(t => ({ ...t, done: !!t.done })),
|
|
492
|
+
inputStreams: streams
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Write to JSON file
|
|
497
|
+
const jsonPath = path.join(thetacogDir, 'state.json');
|
|
498
|
+
fs.writeFileSync(jsonPath, JSON.stringify(state, null, 2));
|
|
499
|
+
|
|
500
|
+
return state;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Open the HTML for a room (or current room's HTML)
|
|
505
|
+
*/
|
|
506
|
+
function openRoomHtml(room = null) {
|
|
507
|
+
const targetRoom = room || currentRoom || 'builder';
|
|
508
|
+
const terminal = Object.values(TERMINALS_MAC).find(t => t.room === targetRoom);
|
|
509
|
+
|
|
510
|
+
if (!terminal) {
|
|
511
|
+
return { error: `No HTML mapping for room: ${targetRoom}` };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Find the HTML file in .workflow/rooms/ relative to cwd
|
|
515
|
+
const possiblePaths = [
|
|
516
|
+
path.join(process.cwd(), '.workflow', 'rooms', terminal.html),
|
|
517
|
+
path.join(process.env.HOME, '.thetacog', 'rooms', terminal.html)
|
|
518
|
+
];
|
|
519
|
+
|
|
520
|
+
for (const htmlPath of possiblePaths) {
|
|
521
|
+
if (fs.existsSync(htmlPath)) {
|
|
522
|
+
// Use execSync to open (works on macOS)
|
|
523
|
+
try {
|
|
524
|
+
execSync(`open "${htmlPath}"`, { stdio: 'pipe' });
|
|
525
|
+
return { success: true, opened: htmlPath, room: targetRoom };
|
|
526
|
+
} catch (e) {
|
|
527
|
+
return { error: `Failed to open: ${e.message}` };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return { error: `HTML not found for room: ${targetRoom}`, searched: possiblePaths };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// ============================================================================
|
|
536
|
+
// ROOM DETECTION LOGIC
|
|
537
|
+
// ============================================================================
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Detect which room the user should be in based on conversation text
|
|
541
|
+
* @param {string} text - The conversation text to analyze
|
|
542
|
+
* @returns {object} - { room, confidence, signals, switchRecommended }
|
|
543
|
+
*/
|
|
544
|
+
function detectRoom(text) {
|
|
545
|
+
const lowerText = text.toLowerCase();
|
|
546
|
+
const matches = [];
|
|
547
|
+
|
|
548
|
+
// Check each room's keywords
|
|
549
|
+
for (const [roomName, config] of Object.entries(DETECTION_SIGNALS)) {
|
|
550
|
+
const foundKeywords = config.keywords.filter(kw => lowerText.includes(kw.toLowerCase()));
|
|
551
|
+
if (foundKeywords.length > 0) {
|
|
552
|
+
// Confidence scales with number of matching keywords
|
|
553
|
+
const keywordRatio = foundKeywords.length / config.keywords.length;
|
|
554
|
+
const adjustedConfidence = config.confidence * (0.5 + 0.5 * keywordRatio);
|
|
555
|
+
|
|
556
|
+
matches.push({
|
|
557
|
+
room: roomName,
|
|
558
|
+
confidence: adjustedConfidence,
|
|
559
|
+
signals: foundKeywords,
|
|
560
|
+
emoji: config.emoji,
|
|
561
|
+
color: config.color,
|
|
562
|
+
tier: config.tier
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Sort by confidence
|
|
568
|
+
matches.sort((a, b) => b.confidence - a.confidence);
|
|
569
|
+
|
|
570
|
+
if (matches.length === 0) {
|
|
571
|
+
return {
|
|
572
|
+
room: currentRoom || 'builder',
|
|
573
|
+
confidence: 0.3,
|
|
574
|
+
signals: [],
|
|
575
|
+
switchRecommended: false,
|
|
576
|
+
message: "No clear room signal detected. Staying in current room."
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const best = matches[0];
|
|
581
|
+
const switchRecommended = best.room !== currentRoom && best.confidence > 0.7;
|
|
582
|
+
|
|
583
|
+
return {
|
|
584
|
+
room: best.room,
|
|
585
|
+
confidence: best.confidence,
|
|
586
|
+
signals: best.signals,
|
|
587
|
+
emoji: best.emoji,
|
|
588
|
+
color: best.color,
|
|
589
|
+
tier: best.tier,
|
|
590
|
+
currentRoom: currentRoom,
|
|
591
|
+
switchRecommended: switchRecommended,
|
|
592
|
+
memoryPalace: MEMORY_PALACES[best.room],
|
|
593
|
+
identityRules: IDENTITY_RULES[best.room],
|
|
594
|
+
alternatives: matches.slice(1, 3) // Top 2 alternatives
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Switch to a new room, preserving context
|
|
600
|
+
* @param {string} newRoom - The room to switch to
|
|
601
|
+
* @param {string} contextSnapshot - Optional context to save from previous room
|
|
602
|
+
* @returns {object} - The new room configuration
|
|
603
|
+
*/
|
|
604
|
+
function switchRoom(newRoom, contextSnapshot = null) {
|
|
605
|
+
const previousRoom = currentRoom;
|
|
606
|
+
|
|
607
|
+
// Save to history
|
|
608
|
+
if (db) {
|
|
609
|
+
db.prepare(`
|
|
610
|
+
INSERT INTO room_history (from_room, to_room, signal, confidence)
|
|
611
|
+
VALUES (?, ?, ?, ?)
|
|
612
|
+
`).run(previousRoom, newRoom, 'manual_switch', 1.0);
|
|
613
|
+
|
|
614
|
+
// Update current state
|
|
615
|
+
db.prepare(`
|
|
616
|
+
INSERT OR REPLACE INTO room_state (id, current_room, context_snapshot, updated_at)
|
|
617
|
+
VALUES (1, ?, ?, CURRENT_TIMESTAMP)
|
|
618
|
+
`).run(newRoom, contextSnapshot);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Update in-memory state
|
|
622
|
+
roomHistory.push({
|
|
623
|
+
from: previousRoom,
|
|
624
|
+
to: newRoom,
|
|
625
|
+
at: new Date().toISOString()
|
|
626
|
+
});
|
|
627
|
+
currentRoom = newRoom;
|
|
628
|
+
|
|
629
|
+
return {
|
|
630
|
+
previousRoom: previousRoom,
|
|
631
|
+
currentRoom: newRoom,
|
|
632
|
+
memoryPalace: MEMORY_PALACES[newRoom],
|
|
633
|
+
identityRules: IDENTITY_RULES[newRoom],
|
|
634
|
+
emoji: DETECTION_SIGNALS[newRoom]?.emoji || '๐ง ',
|
|
635
|
+
color: DETECTION_SIGNALS[newRoom]?.color || '#667eea',
|
|
636
|
+
tier: DETECTION_SIGNALS[newRoom]?.tier || 'tactical',
|
|
637
|
+
contextSaved: !!contextSnapshot,
|
|
638
|
+
terminal: Object.values(TERMINALS_MAC).find(t => t.room === newRoom)?.app || null,
|
|
639
|
+
html: Object.values(TERMINALS_MAC).find(t => t.room === newRoom)?.html || null
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Get current room status
|
|
645
|
+
* @returns {object} - Current room configuration and state
|
|
646
|
+
*/
|
|
647
|
+
function getStatus() {
|
|
648
|
+
const room = currentRoom || 'builder';
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
currentRoom: room,
|
|
652
|
+
emoji: DETECTION_SIGNALS[room]?.emoji || '๐ง ',
|
|
653
|
+
color: DETECTION_SIGNALS[room]?.color || '#667eea',
|
|
654
|
+
tier: DETECTION_SIGNALS[room]?.tier || 'tactical',
|
|
655
|
+
memoryPalace: MEMORY_PALACES[room],
|
|
656
|
+
identityRules: IDENTITY_RULES[room],
|
|
657
|
+
terminal: Object.values(TERMINALS_MAC).find(t => t.room === room)?.app || null,
|
|
658
|
+
html: Object.values(TERMINALS_MAC).find(t => t.room === room)?.html || null,
|
|
659
|
+
recentSwitches: roomHistory.slice(-5),
|
|
660
|
+
sqliteEnabled: !!db
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// ============================================================================
|
|
665
|
+
// MCP SERVER
|
|
666
|
+
// ============================================================================
|
|
667
|
+
|
|
668
|
+
class ThetaCogServer {
|
|
669
|
+
constructor() {
|
|
670
|
+
this.server = new Server(
|
|
671
|
+
{
|
|
672
|
+
name: 'thetacog-mcp',
|
|
673
|
+
version: '0.1.0',
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
capabilities: {
|
|
677
|
+
tools: {},
|
|
678
|
+
},
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
this.setupToolHandlers();
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
setupToolHandlers() {
|
|
686
|
+
// List available tools
|
|
687
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
688
|
+
return {
|
|
689
|
+
tools: [
|
|
690
|
+
{
|
|
691
|
+
name: 'thetacog-detect',
|
|
692
|
+
description: 'Analyze conversation to detect which cognitive room you should be in. Returns room suggestion, confidence score, and switching signals. Use when user says things like "let\'s think bigger picture" or "time to ship".',
|
|
693
|
+
inputSchema: {
|
|
694
|
+
type: 'object',
|
|
695
|
+
properties: {
|
|
696
|
+
text: {
|
|
697
|
+
type: 'string',
|
|
698
|
+
description: 'The conversation text to analyze for room detection signals',
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
required: ['text'],
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
name: 'thetacog-status',
|
|
706
|
+
description: 'Get current cognitive room status. Returns: current room, identity rules, memory palace anchor, recent switches, and terminal mapping.',
|
|
707
|
+
inputSchema: {
|
|
708
|
+
type: 'object',
|
|
709
|
+
properties: {},
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
name: 'thetacog-switch',
|
|
714
|
+
description: 'Switch to a different cognitive room. Saves context from previous room, loads new room configuration. Returns memory palace anchor and identity rules for new room.',
|
|
715
|
+
inputSchema: {
|
|
716
|
+
type: 'object',
|
|
717
|
+
properties: {
|
|
718
|
+
room: {
|
|
719
|
+
type: 'string',
|
|
720
|
+
description: 'Room to switch to: builder, architect, operator, vault, voice, or laboratory',
|
|
721
|
+
enum: ['builder', 'architect', 'operator', 'vault', 'voice', 'laboratory']
|
|
722
|
+
},
|
|
723
|
+
context: {
|
|
724
|
+
type: 'string',
|
|
725
|
+
description: 'Optional context snapshot to save from current room (e.g., "Working on Stripe integration, 80% complete")',
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
required: ['room'],
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
name: 'thetacog-open',
|
|
733
|
+
description: 'Open the HTML dashboard for a room in the browser. If no room specified, opens current room. The HTML auto-refreshes from state.json on tab focus.',
|
|
734
|
+
inputSchema: {
|
|
735
|
+
type: 'object',
|
|
736
|
+
properties: {
|
|
737
|
+
room: {
|
|
738
|
+
type: 'string',
|
|
739
|
+
description: 'Room to open (optional, defaults to current room)',
|
|
740
|
+
enum: ['builder', 'architect', 'operator', 'vault', 'voice', 'laboratory']
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
name: 'thetacog-todo',
|
|
747
|
+
description: 'Manage todos for a room. Actions: add, list, update, delete. Todos are stored in SQLite and synced to state.json for HTML display.',
|
|
748
|
+
inputSchema: {
|
|
749
|
+
type: 'object',
|
|
750
|
+
properties: {
|
|
751
|
+
action: {
|
|
752
|
+
type: 'string',
|
|
753
|
+
description: 'Action to perform',
|
|
754
|
+
enum: ['add', 'list', 'update', 'delete']
|
|
755
|
+
},
|
|
756
|
+
room: {
|
|
757
|
+
type: 'string',
|
|
758
|
+
description: 'Room for the todo (required for add, optional for list)',
|
|
759
|
+
},
|
|
760
|
+
text: {
|
|
761
|
+
type: 'string',
|
|
762
|
+
description: 'Todo text (required for add)',
|
|
763
|
+
},
|
|
764
|
+
id: {
|
|
765
|
+
type: 'number',
|
|
766
|
+
description: 'Todo ID (required for update/delete)',
|
|
767
|
+
},
|
|
768
|
+
done: {
|
|
769
|
+
type: 'boolean',
|
|
770
|
+
description: 'Mark as done (for update)',
|
|
771
|
+
},
|
|
772
|
+
priority: {
|
|
773
|
+
type: 'number',
|
|
774
|
+
description: 'Priority 1-10 (1=highest). For add or update.',
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
required: ['action'],
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
name: 'thetacog-stream',
|
|
782
|
+
description: 'Send messages between rooms (flywheel coordination). Get unread input streams for a room.',
|
|
783
|
+
inputSchema: {
|
|
784
|
+
type: 'object',
|
|
785
|
+
properties: {
|
|
786
|
+
action: {
|
|
787
|
+
type: 'string',
|
|
788
|
+
description: 'Action: send, get, or mark-read',
|
|
789
|
+
enum: ['send', 'get', 'mark-read']
|
|
790
|
+
},
|
|
791
|
+
from: {
|
|
792
|
+
type: 'string',
|
|
793
|
+
description: 'Source room (for send)',
|
|
794
|
+
},
|
|
795
|
+
to: {
|
|
796
|
+
type: 'string',
|
|
797
|
+
description: 'Target room (for send, get, mark-read)',
|
|
798
|
+
},
|
|
799
|
+
message: {
|
|
800
|
+
type: 'string',
|
|
801
|
+
description: 'Message to send (for send)',
|
|
802
|
+
},
|
|
803
|
+
},
|
|
804
|
+
required: ['action'],
|
|
805
|
+
},
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
name: 'thetacog-export',
|
|
809
|
+
description: 'Export current state to JSON file. HTML files read this on tab focus to refresh.',
|
|
810
|
+
inputSchema: {
|
|
811
|
+
type: 'object',
|
|
812
|
+
properties: {},
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
name: 'thetacog-terminal',
|
|
817
|
+
description: 'Detect which terminal Claude is running in, and which room it maps to.',
|
|
818
|
+
inputSchema: {
|
|
819
|
+
type: 'object',
|
|
820
|
+
properties: {},
|
|
821
|
+
},
|
|
822
|
+
},
|
|
823
|
+
],
|
|
824
|
+
};
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
// Handle tool calls
|
|
828
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
829
|
+
const { name, arguments: args } = request.params;
|
|
830
|
+
|
|
831
|
+
try {
|
|
832
|
+
switch (name) {
|
|
833
|
+
case 'thetacog-detect': {
|
|
834
|
+
const result = detectRoom(args.text || '');
|
|
835
|
+
return {
|
|
836
|
+
content: [
|
|
837
|
+
{
|
|
838
|
+
type: 'text',
|
|
839
|
+
text: JSON.stringify(result, null, 2),
|
|
840
|
+
},
|
|
841
|
+
],
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
case 'thetacog-status': {
|
|
846
|
+
const result = getStatus();
|
|
847
|
+
return {
|
|
848
|
+
content: [
|
|
849
|
+
{
|
|
850
|
+
type: 'text',
|
|
851
|
+
text: JSON.stringify(result, null, 2),
|
|
852
|
+
},
|
|
853
|
+
],
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
case 'thetacog-switch': {
|
|
858
|
+
const result = switchRoom(args.room, args.context || null);
|
|
859
|
+
return {
|
|
860
|
+
content: [
|
|
861
|
+
{
|
|
862
|
+
type: 'text',
|
|
863
|
+
text: JSON.stringify(result, null, 2),
|
|
864
|
+
},
|
|
865
|
+
],
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
case 'thetacog-open': {
|
|
870
|
+
const result = openRoomHtml(args.room || null);
|
|
871
|
+
return {
|
|
872
|
+
content: [
|
|
873
|
+
{
|
|
874
|
+
type: 'text',
|
|
875
|
+
text: JSON.stringify(result, null, 2),
|
|
876
|
+
},
|
|
877
|
+
],
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
case 'thetacog-todo': {
|
|
882
|
+
let result;
|
|
883
|
+
switch (args.action) {
|
|
884
|
+
case 'add':
|
|
885
|
+
result = addTodo(args.room, args.text, args.priority || 5);
|
|
886
|
+
break;
|
|
887
|
+
case 'list':
|
|
888
|
+
result = listTodos(args.room || null);
|
|
889
|
+
break;
|
|
890
|
+
case 'update':
|
|
891
|
+
result = updateTodo(args.id, {
|
|
892
|
+
done: args.done,
|
|
893
|
+
priority: args.priority,
|
|
894
|
+
text: args.text
|
|
895
|
+
});
|
|
896
|
+
break;
|
|
897
|
+
case 'delete':
|
|
898
|
+
result = deleteTodo(args.id);
|
|
899
|
+
break;
|
|
900
|
+
default:
|
|
901
|
+
result = { error: 'Unknown action. Use: add, list, update, delete' };
|
|
902
|
+
}
|
|
903
|
+
return {
|
|
904
|
+
content: [
|
|
905
|
+
{
|
|
906
|
+
type: 'text',
|
|
907
|
+
text: JSON.stringify(result, null, 2),
|
|
908
|
+
},
|
|
909
|
+
],
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
case 'thetacog-stream': {
|
|
914
|
+
let result;
|
|
915
|
+
switch (args.action) {
|
|
916
|
+
case 'send':
|
|
917
|
+
result = sendStream(args.from, args.to, args.message);
|
|
918
|
+
break;
|
|
919
|
+
case 'get':
|
|
920
|
+
result = getStreams(args.to);
|
|
921
|
+
break;
|
|
922
|
+
case 'mark-read':
|
|
923
|
+
result = markStreamsRead(args.to);
|
|
924
|
+
break;
|
|
925
|
+
default:
|
|
926
|
+
result = { error: 'Unknown action. Use: send, get, mark-read' };
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
content: [
|
|
930
|
+
{
|
|
931
|
+
type: 'text',
|
|
932
|
+
text: JSON.stringify(result, null, 2),
|
|
933
|
+
},
|
|
934
|
+
],
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
case 'thetacog-export': {
|
|
939
|
+
const result = exportStateToJson();
|
|
940
|
+
return {
|
|
941
|
+
content: [
|
|
942
|
+
{
|
|
943
|
+
type: 'text',
|
|
944
|
+
text: JSON.stringify({
|
|
945
|
+
success: true,
|
|
946
|
+
exportedAt: result?.exportedAt,
|
|
947
|
+
path: path.join(thetacogDir || '~/.thetacog', 'state.json')
|
|
948
|
+
}, null, 2),
|
|
949
|
+
},
|
|
950
|
+
],
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
case 'thetacog-terminal': {
|
|
955
|
+
const result = detectTerminal();
|
|
956
|
+
return {
|
|
957
|
+
content: [
|
|
958
|
+
{
|
|
959
|
+
type: 'text',
|
|
960
|
+
text: JSON.stringify(result, null, 2),
|
|
961
|
+
},
|
|
962
|
+
],
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
default:
|
|
967
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
968
|
+
}
|
|
969
|
+
} catch (error) {
|
|
970
|
+
return {
|
|
971
|
+
content: [
|
|
972
|
+
{
|
|
973
|
+
type: 'text',
|
|
974
|
+
text: JSON.stringify({ error: error.message }),
|
|
975
|
+
},
|
|
976
|
+
],
|
|
977
|
+
isError: true,
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
async shutdown() {
|
|
984
|
+
console.error('๐ Shutting down ThetaCog MCP server...');
|
|
985
|
+
|
|
986
|
+
// Close SQLite connection
|
|
987
|
+
if (db) {
|
|
988
|
+
try {
|
|
989
|
+
db.close();
|
|
990
|
+
console.error(' โ
Closed SQLite connection');
|
|
991
|
+
} catch (error) {
|
|
992
|
+
console.error(` โ ๏ธ Failed to close SQLite: ${error.message}`);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
console.error(' โ
Cleanup complete');
|
|
997
|
+
process.exit(0);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
async run() {
|
|
1001
|
+
// Initialize database (optional)
|
|
1002
|
+
await initDatabase();
|
|
1003
|
+
|
|
1004
|
+
// Start MCP server
|
|
1005
|
+
const transport = new StdioServerTransport();
|
|
1006
|
+
await this.server.connect(transport);
|
|
1007
|
+
|
|
1008
|
+
console.error('โ
ThetaCog MCP server v0.1.0 started');
|
|
1009
|
+
console.error(' ๐ง Mode management, not task management');
|
|
1010
|
+
console.error(' ๐ฆ SQLite: ' + (db ? 'enabled' : 'memory-only mode'));
|
|
1011
|
+
console.error(' ๐จ Current room: ' + (currentRoom || 'builder (default)'));
|
|
1012
|
+
|
|
1013
|
+
// Register cleanup handlers for graceful shutdown (copied from CRM pattern)
|
|
1014
|
+
process.on('SIGINT', () => this.shutdown());
|
|
1015
|
+
process.on('SIGTERM', () => this.shutdown());
|
|
1016
|
+
process.on('SIGHUP', () => this.shutdown());
|
|
1017
|
+
|
|
1018
|
+
// Handle uncaught errors gracefully
|
|
1019
|
+
process.on('uncaughtException', (error) => {
|
|
1020
|
+
console.error('โ Uncaught exception:', error.message);
|
|
1021
|
+
this.shutdown();
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// ============================================================================
|
|
1027
|
+
// STARTUP
|
|
1028
|
+
// ============================================================================
|
|
1029
|
+
|
|
1030
|
+
const server = new ThetaCogServer();
|
|
1031
|
+
server.run().catch(console.error);
|