@mmmbuto/nexuscli 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/bin/nexuscli.js +117 -0
  4. package/frontend/dist/apple-touch-icon.png +0 -0
  5. package/frontend/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  6. package/frontend/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  7. package/frontend/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  8. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  9. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  10. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  11. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  12. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  13. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  14. package/frontend/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  15. package/frontend/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  16. package/frontend/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  17. package/frontend/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  18. package/frontend/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  19. package/frontend/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  20. package/frontend/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  21. package/frontend/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  22. package/frontend/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  23. package/frontend/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  24. package/frontend/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  25. package/frontend/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  26. package/frontend/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  27. package/frontend/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  28. package/frontend/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  29. package/frontend/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  30. package/frontend/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  31. package/frontend/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  32. package/frontend/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  33. package/frontend/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  34. package/frontend/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  35. package/frontend/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  36. package/frontend/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  37. package/frontend/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  38. package/frontend/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  39. package/frontend/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  40. package/frontend/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  41. package/frontend/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  42. package/frontend/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  43. package/frontend/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  44. package/frontend/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  45. package/frontend/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  46. package/frontend/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  47. package/frontend/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  48. package/frontend/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  49. package/frontend/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  50. package/frontend/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  51. package/frontend/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  52. package/frontend/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  53. package/frontend/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  54. package/frontend/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  55. package/frontend/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  56. package/frontend/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  57. package/frontend/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  58. package/frontend/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  59. package/frontend/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  60. package/frontend/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  61. package/frontend/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  62. package/frontend/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  63. package/frontend/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  64. package/frontend/dist/assets/index-Bn_l1e6e.css +1 -0
  65. package/frontend/dist/assets/index-CikJbUR5.js +8617 -0
  66. package/frontend/dist/browserconfig.xml +12 -0
  67. package/frontend/dist/favicon-16x16.png +0 -0
  68. package/frontend/dist/favicon-32x32.png +0 -0
  69. package/frontend/dist/favicon-48x48.png +0 -0
  70. package/frontend/dist/favicon.ico +0 -0
  71. package/frontend/dist/icon-192.png +0 -0
  72. package/frontend/dist/icon-512.png +0 -0
  73. package/frontend/dist/icon-maskable-192.png +0 -0
  74. package/frontend/dist/icon-maskable-512.png +0 -0
  75. package/frontend/dist/index.html +79 -0
  76. package/frontend/dist/manifest.json +75 -0
  77. package/frontend/dist/sw.js +122 -0
  78. package/frontend/package.json +28 -0
  79. package/lib/cli/api.js +156 -0
  80. package/lib/cli/boot.js +172 -0
  81. package/lib/cli/config.js +185 -0
  82. package/lib/cli/engines.js +257 -0
  83. package/lib/cli/init.js +660 -0
  84. package/lib/cli/logs.js +72 -0
  85. package/lib/cli/start.js +220 -0
  86. package/lib/cli/status.js +187 -0
  87. package/lib/cli/stop.js +64 -0
  88. package/lib/cli/uninstall.js +194 -0
  89. package/lib/cli/users.js +295 -0
  90. package/lib/cli/workspaces.js +337 -0
  91. package/lib/config/manager.js +233 -0
  92. package/lib/server/.env.example +20 -0
  93. package/lib/server/db/adapter.js +314 -0
  94. package/lib/server/db/drivers/better-sqlite3.js +38 -0
  95. package/lib/server/db/drivers/sql-js.js +75 -0
  96. package/lib/server/db/migrate.js +174 -0
  97. package/lib/server/db/migrations/001_ultra_light_schema.sql +96 -0
  98. package/lib/server/db/migrations/002_session_conversation_mapping.sql +19 -0
  99. package/lib/server/db/migrations/003_message_engine_tracking.sql +18 -0
  100. package/lib/server/db/migrations/004_performance_indexes.sql +16 -0
  101. package/lib/server/db.js +2 -0
  102. package/lib/server/lib/cli-wrapper.js +164 -0
  103. package/lib/server/lib/output-parser.js +132 -0
  104. package/lib/server/lib/pty-adapter.js +57 -0
  105. package/lib/server/middleware/auth.js +103 -0
  106. package/lib/server/models/Conversation.js +259 -0
  107. package/lib/server/models/Message.js +228 -0
  108. package/lib/server/models/User.js +115 -0
  109. package/lib/server/package-lock.json +5895 -0
  110. package/lib/server/routes/auth.js +168 -0
  111. package/lib/server/routes/chat.js +206 -0
  112. package/lib/server/routes/codex.js +205 -0
  113. package/lib/server/routes/conversations.js +224 -0
  114. package/lib/server/routes/gemini.js +228 -0
  115. package/lib/server/routes/jobs.js +317 -0
  116. package/lib/server/routes/messages.js +60 -0
  117. package/lib/server/routes/models.js +198 -0
  118. package/lib/server/routes/sessions.js +285 -0
  119. package/lib/server/routes/upload.js +134 -0
  120. package/lib/server/routes/wake-lock.js +95 -0
  121. package/lib/server/routes/workspace.js +80 -0
  122. package/lib/server/routes/workspaces.js +142 -0
  123. package/lib/server/scripts/cleanup-ghost-sessions.js +71 -0
  124. package/lib/server/scripts/seed-users.js +37 -0
  125. package/lib/server/scripts/test-history-access.js +50 -0
  126. package/lib/server/server.js +227 -0
  127. package/lib/server/services/cache.js +85 -0
  128. package/lib/server/services/claude-wrapper.js +312 -0
  129. package/lib/server/services/cli-loader.js +384 -0
  130. package/lib/server/services/codex-output-parser.js +277 -0
  131. package/lib/server/services/codex-wrapper.js +224 -0
  132. package/lib/server/services/context-bridge.js +289 -0
  133. package/lib/server/services/gemini-output-parser.js +398 -0
  134. package/lib/server/services/gemini-wrapper.js +249 -0
  135. package/lib/server/services/history-sync.js +407 -0
  136. package/lib/server/services/output-parser.js +415 -0
  137. package/lib/server/services/session-manager.js +465 -0
  138. package/lib/server/services/summary-generator.js +259 -0
  139. package/lib/server/services/workspace-manager.js +516 -0
  140. package/lib/server/tests/history-sync.test.js +90 -0
  141. package/lib/server/tests/integration-session-sync.test.js +151 -0
  142. package/lib/server/tests/integration.test.js +76 -0
  143. package/lib/server/tests/performance.test.js +118 -0
  144. package/lib/server/tests/services.test.js +160 -0
  145. package/lib/setup/postinstall.js +216 -0
  146. package/lib/utils/paths.js +107 -0
  147. package/lib/utils/termux.js +145 -0
  148. package/package.json +82 -0
@@ -0,0 +1,259 @@
1
+ const ClaudeWrapper = require('./claude-wrapper');
2
+ const { v4: uuidv4 } = require('uuid');
3
+ const { prepare, saveDb } = require('../db');
4
+
5
+ /**
6
+ * SummaryGenerator - produce contextual summaries for sessions.
7
+ *
8
+ * Features:
9
+ * - Generate AI titles for new conversations (uses Claude Haiku - fast & cheap)
10
+ * - Generate contextual summaries for sessions
11
+ * - Auto-save summaries to session_summaries table
12
+ * - Support for summary-based context bridging
13
+ */
14
+ class SummaryGenerator {
15
+ constructor(options = {}) {
16
+ this.claude = new ClaudeWrapper(options);
17
+ }
18
+
19
+ /**
20
+ * Generate a short title for a new conversation (3-8 words)
21
+ * Called after first AI response, runs in background
22
+ * @param {string} userMessage - First user message
23
+ * @param {string} assistantResponse - First AI response (optional, for context)
24
+ * @returns {Promise<string>} Generated title
25
+ */
26
+ async generateTitle(userMessage, assistantResponse = '') {
27
+ const startedAt = Date.now();
28
+
29
+ // Keep context brief for fast generation
30
+ const context = assistantResponse
31
+ ? `User: ${userMessage.slice(0, 500)}\nAssistant: ${assistantResponse.slice(0, 500)}`
32
+ : `User: ${userMessage.slice(0, 800)}`;
33
+
34
+ const prompt = `Generate a brief title (3-8 words, no quotes) for this conversation:
35
+
36
+ ${context}
37
+
38
+ Reply with ONLY the title, nothing else.`;
39
+
40
+ try {
41
+ const result = await this.claude.sendMessage({
42
+ prompt,
43
+ conversationId: uuidv4(), // Ephemeral session
44
+ model: 'haiku',
45
+ onStatus: () => {} // Silence status
46
+ });
47
+
48
+ // Clean up the response
49
+ let title = result.text.trim()
50
+ .replace(/^["']|["']$/g, '') // Remove surrounding quotes
51
+ .replace(/^Title:\s*/i, '') // Remove "Title:" prefix
52
+ .slice(0, 60); // Max 60 chars
53
+
54
+ console.log(`[SummaryGenerator] Title generated in ${Date.now() - startedAt}ms: ${title}`);
55
+ return title || 'New Chat';
56
+ } catch (error) {
57
+ console.error('[SummaryGenerator] Title generation failed:', error.message);
58
+ // Fallback to truncated first message
59
+ return userMessage.slice(0, 50).trim() + (userMessage.length > 50 ? '...' : '');
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Generate or refresh summary for a session.
65
+ * @param {Object} params
66
+ * @param {string} params.sessionId
67
+ * @param {Array} params.messages - Array of {role, content, created_at}
68
+ * @param {Object|null} params.existingSummary
69
+ * @returns {Promise<Object>} summary payload
70
+ */
71
+ async generateSummary({ sessionId, messages = [], existingSummary = null }) {
72
+ if (!sessionId) throw new Error('sessionId is required');
73
+
74
+ const startedAt = Date.now();
75
+
76
+ // Build transcript snippet (limit to 40 latest messages for brevity)
77
+ const transcript = this.buildTranscript(messages.slice(-40));
78
+
79
+ const prompt = this.buildPrompt({ sessionId, transcript, existingSummary });
80
+
81
+ const result = await this.claude.sendMessage({
82
+ prompt,
83
+ conversationId: uuidv4(), // Ephemeral session (Claude CLI requires valid UUID)
84
+ model: 'haiku',
85
+ onStatus: () => {} // silence status for summaries
86
+ });
87
+
88
+ const parsed = this.safeParseJson(result.text);
89
+
90
+ console.log(`[SummaryGenerator] Summary for ${sessionId} took ${Date.now() - startedAt}ms`);
91
+
92
+ return parsed;
93
+ }
94
+
95
+ /**
96
+ * Save summary to database
97
+ * @param {string} sessionId
98
+ * @param {Object} summary - {summary_short, summary_long, key_decisions, tools_used, files_modified}
99
+ * @returns {boolean} Success
100
+ */
101
+ saveSummary(sessionId, summary) {
102
+ try {
103
+ const stmt = prepare(`
104
+ INSERT OR REPLACE INTO session_summaries
105
+ (session_id, summary_short, summary_long, key_decisions, tools_used, files_modified, updated_at, version)
106
+ VALUES (?, ?, ?, ?, ?, ?, ?, COALESCE((SELECT version FROM session_summaries WHERE session_id = ?), 0) + 1)
107
+ `);
108
+
109
+ stmt.run(
110
+ sessionId,
111
+ summary.summary_short || '',
112
+ summary.summary_long || null,
113
+ JSON.stringify(summary.key_decisions || []),
114
+ JSON.stringify(summary.tools_used || []),
115
+ JSON.stringify(summary.files_modified || []),
116
+ Date.now(),
117
+ sessionId
118
+ );
119
+
120
+ saveDb();
121
+ console.log(`[SummaryGenerator] Saved summary for ${sessionId}`);
122
+ return true;
123
+ } catch (error) {
124
+ console.error(`[SummaryGenerator] Failed to save summary:`, error.message);
125
+ return false;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Get existing summary from database
131
+ * @param {string} sessionId
132
+ * @returns {Object|null}
133
+ */
134
+ getSummary(sessionId) {
135
+ try {
136
+ const stmt = prepare('SELECT * FROM session_summaries WHERE session_id = ?');
137
+ const row = stmt.get(sessionId);
138
+
139
+ if (!row) return null;
140
+
141
+ return {
142
+ summary_short: row.summary_short,
143
+ summary_long: row.summary_long,
144
+ key_decisions: JSON.parse(row.key_decisions || '[]'),
145
+ tools_used: JSON.parse(row.tools_used || '[]'),
146
+ files_modified: JSON.parse(row.files_modified || '[]'),
147
+ updated_at: row.updated_at,
148
+ version: row.version
149
+ };
150
+ } catch (error) {
151
+ console.warn(`[SummaryGenerator] Failed to get summary:`, error.message);
152
+ return null;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Generate summary and save to DB (convenience method)
158
+ * @param {string} sessionId
159
+ * @param {Array} messages
160
+ * @returns {Promise<Object|null>}
161
+ */
162
+ async generateAndSave(sessionId, messages) {
163
+ try {
164
+ const existingSummary = this.getSummary(sessionId);
165
+ const summary = await this.generateSummary({ sessionId, messages, existingSummary });
166
+ this.saveSummary(sessionId, summary);
167
+ return summary;
168
+ } catch (error) {
169
+ console.error(`[SummaryGenerator] generateAndSave failed:`, error.message);
170
+ return null;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Get summary text for context bridging
176
+ * Returns a formatted string suitable for prefixing prompts
177
+ * @param {string} sessionId
178
+ * @returns {string|null}
179
+ */
180
+ getBridgeContext(sessionId) {
181
+ const summary = this.getSummary(sessionId);
182
+ if (!summary || !summary.summary_short) return null;
183
+
184
+ let context = `[Session Summary]\n${summary.summary_short}`;
185
+
186
+ if (summary.key_decisions && summary.key_decisions.length > 0) {
187
+ context += `\n\nKey decisions:\n- ${summary.key_decisions.slice(0, 5).join('\n- ')}`;
188
+ }
189
+
190
+ if (summary.files_modified && summary.files_modified.length > 0) {
191
+ context += `\n\nFiles worked on:\n- ${summary.files_modified.slice(0, 10).join('\n- ')}`;
192
+ }
193
+
194
+ return context;
195
+ }
196
+
197
+ buildTranscript(messages) {
198
+ return messages
199
+ .map(m => {
200
+ const role = m.role || 'assistant';
201
+ const content = (m.content || '').trim();
202
+ const ts = m.created_at ? new Date(m.created_at).toISOString() : '';
203
+ return `[${ts}] ${role.toUpperCase()}: ${content}`;
204
+ })
205
+ .join('\n')
206
+ .slice(-6000); // keep prompt size reasonable
207
+ }
208
+
209
+ buildPrompt({ sessionId, transcript, existingSummary }) {
210
+ const existing = existingSummary
211
+ ? `Existing summary (for refresh): ${JSON.stringify(existingSummary)}`
212
+ : 'No existing summary.';
213
+
214
+ return `
215
+ You are a concise assistant. Summarize the coding/chat session into JSON.
216
+ Session ID: ${sessionId}
217
+
218
+ ${existing}
219
+
220
+ Provide JSON with keys:
221
+ - summary_short (<=80 words)
222
+ - summary_long (<=200 words)
223
+ - key_decisions (array of short bullet strings)
224
+ - tools_used (array of tool names or commands)
225
+ - files_modified (array of file paths)
226
+
227
+ Do not include any extra text outside valid JSON.
228
+
229
+ Transcript:
230
+ ${transcript}
231
+ `;
232
+ }
233
+
234
+ safeParseJson(text) {
235
+ // Try to extract JSON block
236
+ const start = text.indexOf('{');
237
+ const end = text.lastIndexOf('}');
238
+ if (start === -1 || end === -1) {
239
+ throw new Error('Failed to parse summary JSON');
240
+ }
241
+
242
+ const jsonString = text.slice(start, end + 1);
243
+
244
+ try {
245
+ const parsed = JSON.parse(jsonString);
246
+ // Normalize array fields
247
+ ['key_decisions', 'tools_used', 'files_modified'].forEach(key => {
248
+ if (parsed[key] && !Array.isArray(parsed[key])) {
249
+ parsed[key] = [parsed[key]].filter(Boolean);
250
+ }
251
+ });
252
+ return parsed;
253
+ } catch (e) {
254
+ throw new Error('Invalid JSON returned by model');
255
+ }
256
+ }
257
+ }
258
+
259
+ module.exports = SummaryGenerator;