@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,660 @@
1
+ /**
2
+ * nexuscli init - Setup wizard
3
+ */
4
+
5
+ const inquirer = require('inquirer');
6
+ const chalk = require('chalk');
7
+ const ora = require('ora');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+ const { execSync } = require('child_process');
12
+
13
+ const { isInitialized, initializeConfig, getConfig, setConfigValue } = require('../config/manager');
14
+ const { PATHS, ensureDirectories, HOME } = require('../utils/paths');
15
+ const { isTermux, checkRequiredPackages, isTermuxApiWorking } = require('../utils/termux');
16
+ const pkg = require('../../package.json');
17
+
18
+ // AI CLI session directories
19
+ const CLAUDE_PROJECTS = path.join(os.homedir(), '.claude', 'projects');
20
+ const CODEX_SESSIONS = path.join(os.homedir(), '.codex', 'sessions');
21
+ const GEMINI_SESSIONS = path.join(os.homedir(), '.gemini', 'sessions');
22
+
23
+ // Default workspace path - always ~/nexuswork (user can change during setup)
24
+ const DEFAULT_WORKSPACE = path.join(os.homedir(), 'nexuswork');
25
+
26
+ /**
27
+ * Detect Claude CLI path
28
+ */
29
+ function detectClaudePath() {
30
+ const candidates = [
31
+ path.join(HOME, '.claude', 'local', 'claude'),
32
+ path.join(process.env.PREFIX || '/usr', 'bin', 'claude'),
33
+ '/usr/local/bin/claude',
34
+ '/usr/bin/claude'
35
+ ];
36
+
37
+ for (const p of candidates) {
38
+ if (fs.existsSync(p)) {
39
+ return p;
40
+ }
41
+ }
42
+
43
+ // Try which
44
+ try {
45
+ return execSync('which claude', { encoding: 'utf8' }).trim();
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Detect Codex CLI path
53
+ */
54
+ function detectCodexPath() {
55
+ const candidates = [
56
+ path.join(HOME, '.codex', 'bin', 'codex'),
57
+ path.join(process.env.PREFIX || '/usr', 'bin', 'codex'),
58
+ '/usr/local/bin/codex',
59
+ '/usr/bin/codex'
60
+ ];
61
+
62
+ for (const p of candidates) {
63
+ if (fs.existsSync(p)) {
64
+ return p;
65
+ }
66
+ }
67
+
68
+ try {
69
+ return execSync('which codex', { encoding: 'utf8' }).trim();
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Detect Gemini CLI path
77
+ */
78
+ function detectGeminiPath() {
79
+ const candidates = [
80
+ path.join(HOME, '.local', 'bin', 'gemini'),
81
+ path.join(process.env.PREFIX || '/usr', 'bin', 'gemini'),
82
+ '/usr/local/bin/gemini',
83
+ '/usr/bin/gemini'
84
+ ];
85
+
86
+ for (const p of candidates) {
87
+ if (fs.existsSync(p)) {
88
+ return p;
89
+ }
90
+ }
91
+
92
+ try {
93
+ return execSync('which gemini', { encoding: 'utf8' }).trim();
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Detect all available AI engines (TRI CLI v0.4.0)
101
+ */
102
+ function detectEngines() {
103
+ return {
104
+ claude: {
105
+ path: detectClaudePath(),
106
+ hasSessions: fs.existsSync(CLAUDE_PROJECTS)
107
+ },
108
+ codex: {
109
+ path: detectCodexPath(),
110
+ hasSessions: fs.existsSync(CODEX_SESSIONS)
111
+ },
112
+ gemini: {
113
+ path: detectGeminiPath(),
114
+ hasSessions: fs.existsSync(GEMINI_SESSIONS)
115
+ }
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Count sessions for each engine (TRI CLI v0.4.0)
121
+ */
122
+ function countSessions() {
123
+ const counts = { claude: 0, codex: 0, gemini: 0 };
124
+
125
+ // Count Claude sessions
126
+ if (fs.existsSync(CLAUDE_PROJECTS)) {
127
+ try {
128
+ const dirs = fs.readdirSync(CLAUDE_PROJECTS, { withFileTypes: true });
129
+ for (const dir of dirs) {
130
+ if (dir.isDirectory()) {
131
+ const projectDir = path.join(CLAUDE_PROJECTS, dir.name);
132
+ const sessions = fs.readdirSync(projectDir)
133
+ .filter(f => f.endsWith('.jsonl') && !f.startsWith('agent-'));
134
+ counts.claude += sessions.length;
135
+ }
136
+ }
137
+ } catch {}
138
+ }
139
+
140
+ // Count Codex sessions
141
+ if (fs.existsSync(CODEX_SESSIONS)) {
142
+ try {
143
+ const files = fs.readdirSync(CODEX_SESSIONS)
144
+ .filter(f => f.endsWith('.json'));
145
+ counts.codex = files.length;
146
+ } catch {}
147
+ }
148
+
149
+ // Count Gemini sessions
150
+ if (fs.existsSync(GEMINI_SESSIONS)) {
151
+ try {
152
+ const files = fs.readdirSync(GEMINI_SESSIONS)
153
+ .filter(f => f.endsWith('.json') || f.endsWith('.jsonl'));
154
+ counts.gemini = files.length;
155
+ } catch {}
156
+ }
157
+
158
+ return counts;
159
+ }
160
+
161
+ /**
162
+ * Find workspace directories (excluding DEFAULT_WORKSPACE to avoid duplication)
163
+ */
164
+ function findWorkspaces() {
165
+ // NOTE: Dev/ excluded - typically contains development repos, not user workspaces
166
+ const candidates = [
167
+ path.join(HOME, 'Projects'),
168
+ path.join(HOME, 'projects'),
169
+ path.join(HOME, 'src'),
170
+ path.join(HOME, 'workspace'),
171
+ path.join(HOME, 'work')
172
+ ];
173
+
174
+ // Return unique existing paths (DEFAULT_WORKSPACE handled separately)
175
+ const existing = candidates.filter(p => fs.existsSync(p) && p !== DEFAULT_WORKSPACE);
176
+ return [...new Set(existing)];
177
+ }
178
+
179
+ /**
180
+ * Scan existing sessions and find workspaces with AI activity
181
+ */
182
+ function scanExistingSessions() {
183
+ const foundWorkspaces = new Map();
184
+
185
+ // Scan Claude projects directory
186
+ if (fs.existsSync(CLAUDE_PROJECTS)) {
187
+ const dirs = fs.readdirSync(CLAUDE_PROJECTS, { withFileTypes: true });
188
+
189
+ for (const dir of dirs) {
190
+ if (!dir.isDirectory()) continue;
191
+
192
+ const projectDir = path.join(CLAUDE_PROJECTS, dir.name);
193
+ try {
194
+ const sessionFiles = fs.readdirSync(projectDir)
195
+ .filter(f => f.endsWith('.jsonl') && !f.startsWith('agent-'));
196
+
197
+ if (sessionFiles.length > 0) {
198
+ // Read workspace path from first session file
199
+ let workspacePath = null;
200
+ try {
201
+ const firstFile = path.join(projectDir, sessionFiles[0]);
202
+ const firstLine = fs.readFileSync(firstFile, 'utf8').split('\n')[0];
203
+ const entry = JSON.parse(firstLine);
204
+ workspacePath = entry.cwd;
205
+ } catch {
206
+ // Fallback: convert slug back to path
207
+ workspacePath = '/' + dir.name.replace(/^-/, '').replace(/-/g, '/');
208
+ }
209
+
210
+ if (workspacePath && fs.existsSync(workspacePath)) {
211
+ const existing = foundWorkspaces.get(workspacePath) || { claude: 0, codex: 0 };
212
+ existing.claude = sessionFiles.length;
213
+ foundWorkspaces.set(workspacePath, existing);
214
+ }
215
+ }
216
+ } catch {}
217
+ }
218
+ }
219
+
220
+ return foundWorkspaces;
221
+ }
222
+
223
+ /**
224
+ * Main init command
225
+ */
226
+ async function init(options) {
227
+ console.log('');
228
+ console.log(chalk.bold('╔═══════════════════════════════════════════╗'));
229
+ console.log(chalk.bold('║ 🚀 NexusCLI Setup Wizard ║'));
230
+ console.log(chalk.bold(`║ TRI CLI v${pkg.version} ║`));
231
+ console.log(chalk.bold('╚═══════════════════════════════════════════╝'));
232
+ console.log('');
233
+
234
+ // Check if already initialized
235
+ if (isInitialized() && !options.yes) {
236
+ const { overwrite } = await inquirer.prompt([{
237
+ type: 'confirm',
238
+ name: 'overwrite',
239
+ message: 'NexusCLI is already configured. Overwrite?',
240
+ default: false
241
+ }]);
242
+
243
+ if (!overwrite) {
244
+ console.log(chalk.yellow('\nSetup cancelled.'));
245
+ return;
246
+ }
247
+ }
248
+
249
+ // Show environment
250
+ console.log(chalk.cyan('Environment:'));
251
+ if (isTermux()) {
252
+ console.log(chalk.green(' ✓ Termux detected'));
253
+
254
+ // Check packages
255
+ const pkgStatus = checkRequiredPackages();
256
+ for (const [pkg, installed] of Object.entries(pkgStatus)) {
257
+ if (installed) {
258
+ console.log(chalk.green(` ✓ ${pkg} installed`));
259
+ } else {
260
+ console.log(chalk.yellow(` ⚠ ${pkg} not installed`));
261
+ }
262
+ }
263
+
264
+ // Check API app
265
+ if (isTermuxApiWorking()) {
266
+ console.log(chalk.green(' ✓ Termux:API app working'));
267
+ } else {
268
+ console.log(chalk.yellow(' ⚠ Termux:API app not detected (optional)'));
269
+ }
270
+ } else {
271
+ console.log(chalk.blue(' Desktop/VPS mode'));
272
+ }
273
+ console.log('');
274
+
275
+ // Detect ALL AI engines
276
+ const engines = detectEngines();
277
+ const sessionCounts = countSessions();
278
+
279
+ console.log(chalk.cyan('AI Engines Detected (TRI CLI):'));
280
+
281
+ // Claude
282
+ if (engines.claude.path) {
283
+ const sessInfo = sessionCounts.claude > 0 ? chalk.gray(` (${sessionCounts.claude} sessions)`) : '';
284
+ console.log(chalk.green(` ✓ Claude CLI: ${engines.claude.path}${sessInfo}`));
285
+ } else if (engines.claude.hasSessions) {
286
+ console.log(chalk.yellow(` ⚠ Claude: CLI not found but ${sessionCounts.claude} sessions exist`));
287
+ } else {
288
+ console.log(chalk.gray(' ○ Claude CLI: not installed'));
289
+ }
290
+
291
+ // Codex
292
+ if (engines.codex.path) {
293
+ const sessInfo = sessionCounts.codex > 0 ? chalk.gray(` (${sessionCounts.codex} sessions)`) : '';
294
+ console.log(chalk.green(` ✓ Codex CLI: ${engines.codex.path}${sessInfo}`));
295
+ } else if (engines.codex.hasSessions) {
296
+ console.log(chalk.yellow(` ⚠ Codex: CLI not found but ${sessionCounts.codex} sessions exist`));
297
+ } else {
298
+ console.log(chalk.gray(' ○ Codex CLI: not installed'));
299
+ }
300
+
301
+ // Gemini
302
+ if (engines.gemini.path) {
303
+ const sessInfo = sessionCounts.gemini > 0 ? chalk.gray(` (${sessionCounts.gemini} sessions)`) : '';
304
+ console.log(chalk.green(` ✓ Gemini CLI: ${engines.gemini.path}${sessInfo}`));
305
+ } else if (engines.gemini.hasSessions) {
306
+ console.log(chalk.yellow(` ⚠ Gemini: CLI not found but ${sessionCounts.gemini} sessions exist`));
307
+ } else {
308
+ console.log(chalk.gray(' ○ Gemini CLI: not installed'));
309
+ }
310
+
311
+ console.log('');
312
+
313
+ let answers = {};
314
+
315
+ // Build engine choices based on detection (TRI CLI v0.4.0)
316
+ const engineChoices = [];
317
+ if (engines.claude.path) {
318
+ engineChoices.push({ name: 'Claude CLI (Anthropic)', value: 'claude', checked: true });
319
+ }
320
+ if (engines.codex.path) {
321
+ engineChoices.push({ name: 'Codex CLI (OpenAI)', value: 'codex', checked: true });
322
+ }
323
+ if (engines.gemini.path) {
324
+ engineChoices.push({ name: 'Gemini CLI (Google)', value: 'gemini', checked: true });
325
+ }
326
+
327
+ // Determine default workspace
328
+ const existingWorkspaces = findWorkspaces();
329
+ const defaultWsExists = fs.existsSync(DEFAULT_WORKSPACE);
330
+ const defaultWorkspace = defaultWsExists ? DEFAULT_WORKSPACE : (existingWorkspaces[0] || HOME);
331
+
332
+ if (options.yes) {
333
+ // Use defaults
334
+ answers = {
335
+ username: 'tux',
336
+ password: 'tux',
337
+ port: 41800,
338
+ defaultWorkspace: defaultWorkspace,
339
+ workspaces: existingWorkspaces.slice(0, 3),
340
+ enabledEngines: engineChoices.map(e => e.value),
341
+ scanSessions: true,
342
+ wakeLock: true,
343
+ notifications: true,
344
+ bootStart: false,
345
+ checkEnginesOnBoot: false
346
+ };
347
+ } else {
348
+ // Interactive prompts - Part 1: Basic setup
349
+ const basicAnswers = await inquirer.prompt([
350
+ {
351
+ type: 'input',
352
+ name: 'username',
353
+ message: 'Admin username:',
354
+ default: 'tux'
355
+ },
356
+ {
357
+ type: 'password',
358
+ name: 'password',
359
+ message: 'Admin password:',
360
+ default: 'tux',
361
+ mask: '*'
362
+ },
363
+ {
364
+ type: 'number',
365
+ name: 'port',
366
+ message: 'Server port:',
367
+ default: 41800
368
+ }
369
+ ]);
370
+ answers = { ...answers, ...basicAnswers };
371
+
372
+ // Part 2: Workspace configuration
373
+ console.log('');
374
+ console.log(chalk.cyan('Workspace Configuration:'));
375
+
376
+ const workspaceAnswers = await inquirer.prompt([
377
+ {
378
+ type: 'input',
379
+ name: 'defaultWorkspace',
380
+ message: 'Default workspace path:',
381
+ default: DEFAULT_WORKSPACE,
382
+ validate: (input) => {
383
+ if (!input) return 'Path is required';
384
+ return true;
385
+ }
386
+ },
387
+ {
388
+ type: 'checkbox',
389
+ name: 'workspaces',
390
+ message: 'Additional workspace directories:',
391
+ choices: existingWorkspaces.map(w => ({
392
+ name: w.replace(HOME, '~'),
393
+ value: w,
394
+ checked: false
395
+ }))
396
+ }
397
+ ]);
398
+ answers = { ...answers, ...workspaceAnswers };
399
+
400
+ // Part 3: Engine selection (if any detected)
401
+ if (engineChoices.length > 0) {
402
+ console.log('');
403
+ console.log(chalk.cyan('AI Engine Configuration:'));
404
+
405
+ const engineAnswers = await inquirer.prompt([
406
+ {
407
+ type: 'checkbox',
408
+ name: 'enabledEngines',
409
+ message: 'Select AI engines to enable:',
410
+ choices: engineChoices
411
+ }
412
+ ]);
413
+ answers = { ...answers, ...engineAnswers };
414
+ } else {
415
+ answers.enabledEngines = [];
416
+ }
417
+
418
+ // Part 4: Session scan option
419
+ const totalSessions = sessionCounts.claude + sessionCounts.codex + sessionCounts.gemini;
420
+ if (totalSessions > 0) {
421
+ console.log('');
422
+ console.log(chalk.cyan(`Found ${totalSessions} existing AI sessions.`));
423
+
424
+ const scanAnswers = await inquirer.prompt([
425
+ {
426
+ type: 'confirm',
427
+ name: 'scanSessions',
428
+ message: 'Scan and import existing sessions?',
429
+ default: true
430
+ }
431
+ ]);
432
+ answers = { ...answers, ...scanAnswers };
433
+ } else {
434
+ answers.scanSessions = false;
435
+ }
436
+
437
+ // Part 5: Termux-specific options
438
+ if (isTermux()) {
439
+ console.log('');
440
+ console.log(chalk.cyan('Termux Options:'));
441
+
442
+ const termuxAnswers = await inquirer.prompt([
443
+ {
444
+ type: 'confirm',
445
+ name: 'wakeLock',
446
+ message: 'Enable wake-lock (keep CPU active)?',
447
+ default: true
448
+ },
449
+ {
450
+ type: 'confirm',
451
+ name: 'notifications',
452
+ message: 'Enable notifications?',
453
+ default: true
454
+ },
455
+ {
456
+ type: 'confirm',
457
+ name: 'bootStart',
458
+ message: 'Auto-start on device boot?',
459
+ default: false
460
+ }
461
+ ]);
462
+ answers = { ...answers, ...termuxAnswers };
463
+
464
+ // If boot start enabled, ask about engine re-check
465
+ if (answers.bootStart && engineChoices.length > 0) {
466
+ const bootCheckAnswers = await inquirer.prompt([
467
+ {
468
+ type: 'confirm',
469
+ name: 'checkEnginesOnBoot',
470
+ message: 'Re-check AI engines availability on each boot?',
471
+ default: true
472
+ }
473
+ ]);
474
+ answers = { ...answers, ...bootCheckAnswers };
475
+ } else {
476
+ answers.checkEnginesOnBoot = false;
477
+ }
478
+ }
479
+ }
480
+
481
+ // Ensure at least one workspace
482
+ if (!answers.workspaces || answers.workspaces.length === 0) {
483
+ answers.workspaces = [HOME];
484
+ }
485
+
486
+ // Add default workspace to list if not present
487
+ const allWorkspaces = [...new Set([answers.defaultWorkspace, ...answers.workspaces])];
488
+
489
+ // Create default workspace directory if it doesn't exist
490
+ if (answers.defaultWorkspace && !fs.existsSync(answers.defaultWorkspace)) {
491
+ try {
492
+ fs.mkdirSync(answers.defaultWorkspace, { recursive: true });
493
+ console.log(chalk.green(` ✓ Created workspace directory: ${answers.defaultWorkspace}`));
494
+ } catch (err) {
495
+ console.log(chalk.yellow(` ⚠ Could not create ${answers.defaultWorkspace}: ${err.message}`));
496
+ }
497
+ }
498
+
499
+ // Create config
500
+ console.log('');
501
+ const spinner = ora('Creating configuration...').start();
502
+
503
+ ensureDirectories();
504
+
505
+ const config = initializeConfig({
506
+ username: answers.username,
507
+ password: answers.password,
508
+ port: answers.port,
509
+ workspaces: allWorkspaces,
510
+ defaultWorkspace: answers.defaultWorkspace,
511
+ wakeLock: answers.wakeLock,
512
+ notifications: answers.notifications,
513
+ bootStart: answers.bootStart
514
+ });
515
+
516
+ if (!config) {
517
+ spinner.fail('Failed to create configuration');
518
+ process.exit(1);
519
+ }
520
+
521
+ spinner.succeed('Configuration created');
522
+
523
+ // Configure engines based on selection
524
+ if (answers.enabledEngines && answers.enabledEngines.length > 0) {
525
+ const engineSpinner = ora('Configuring AI engines...').start();
526
+
527
+ // Set enabled engines
528
+ setConfigValue('engines.active', answers.enabledEngines);
529
+
530
+ for (const engine of answers.enabledEngines) {
531
+ setConfigValue(`engines.${engine}.enabled`, true);
532
+ if (engines[engine]?.path) {
533
+ setConfigValue(`engines.${engine}.path`, engines[engine].path);
534
+ }
535
+ }
536
+
537
+ // Set check on boot option
538
+ if (answers.checkEnginesOnBoot) {
539
+ setConfigValue('engines.check_on_boot', true);
540
+ }
541
+
542
+ engineSpinner.succeed(`Configured ${answers.enabledEngines.length} AI engine(s)`);
543
+ }
544
+
545
+ // Scan existing sessions if requested
546
+ if (answers.scanSessions) {
547
+ const scanSpinner = ora('Scanning existing sessions...').start();
548
+
549
+ const foundWorkspaces = scanExistingSessions();
550
+
551
+ if (foundWorkspaces.size > 0) {
552
+ // Add discovered workspaces to config
553
+ const currentPaths = allWorkspaces.map(p => path.resolve(p));
554
+
555
+ for (const [ws] of foundWorkspaces) {
556
+ if (!currentPaths.includes(path.resolve(ws))) {
557
+ allWorkspaces.push(ws);
558
+ }
559
+ }
560
+
561
+ setConfigValue('workspaces.paths', allWorkspaces);
562
+ scanSpinner.succeed(`Found ${foundWorkspaces.size} workspaces with sessions`);
563
+ } else {
564
+ scanSpinner.info('No additional workspaces found');
565
+ }
566
+ }
567
+
568
+ // Setup boot if requested
569
+ if (answers.bootStart && isTermux()) {
570
+ const bootSpinner = ora('Setting up auto-start...').start();
571
+ try {
572
+ // Generate conditional script sections based on user preferences
573
+ const wakeLockSection = answers.wakeLock ? `
574
+ # Acquire wake lock to keep CPU active
575
+ termux-wake-lock
576
+ ` : '# Wake lock disabled by user';
577
+
578
+ const engineCheckScript = (answers.checkEnginesOnBoot && answers.notifications) ? `
579
+ # Check AI engines availability
580
+ echo "Checking AI engines..."
581
+ nexuscli engines list > /tmp/nexuscli_engines.log 2>&1
582
+
583
+ # Notify available engines
584
+ ENGINES_AVAILABLE=$(nexuscli engines list 2>&1 | grep -c "✓")
585
+ if [ "$ENGINES_AVAILABLE" -gt 0 ]; then
586
+ termux-notification --id nexuscli-engines --title "NexusCLI Engines" --content "$ENGINES_AVAILABLE AI engine(s) available"
587
+ else
588
+ termux-notification --id nexuscli-engines --title "NexusCLI Engines" --content "No AI engines detected" --priority high
589
+ fi
590
+ ` : (answers.checkEnginesOnBoot ? `
591
+ # Check AI engines availability (notifications disabled)
592
+ echo "Checking AI engines..."
593
+ nexuscli engines list > /tmp/nexuscli_engines.log 2>&1
594
+ ` : '');
595
+
596
+ const notifySection = answers.notifications ? `
597
+ # Notify user
598
+ termux-notification --id nexuscli --title "NexusCLI" --content "Server running on port ${answers.port}" --priority high
599
+ ` : '# Notifications disabled by user';
600
+
601
+ const bootScript = `#!/data/data/com.termux/files/usr/bin/bash
602
+ # NexusCLI Auto-Start - Generated by nexuscli init
603
+ # Config: wake_lock=${answers.wakeLock ? 'true' : 'false'}, notifications=${answers.notifications ? 'true' : 'false'}, check_engines=${answers.checkEnginesOnBoot ? 'true' : 'false'}
604
+ ${wakeLockSection}
605
+ # Wait for system initialization
606
+ sleep 3
607
+ ${engineCheckScript}
608
+ # Start NexusCLI daemon
609
+ nexuscli start --daemon
610
+ ${notifySection}
611
+ `;
612
+ fs.writeFileSync(PATHS.BOOT_SCRIPT, bootScript);
613
+ fs.chmodSync(PATHS.BOOT_SCRIPT, 0o755);
614
+
615
+ if (answers.checkEnginesOnBoot) {
616
+ bootSpinner.succeed('Boot script created (with engine check)');
617
+ } else {
618
+ bootSpinner.succeed('Boot script created');
619
+ }
620
+ } catch (err) {
621
+ bootSpinner.warn('Could not create boot script');
622
+ }
623
+ }
624
+
625
+ // Summary
626
+ console.log('');
627
+ console.log(chalk.bold('╔═══════════════════════════════════════════╗'));
628
+ console.log(chalk.bold('║ ✅ Setup Complete! ║'));
629
+ console.log(chalk.bold('╚═══════════════════════════════════════════╝'));
630
+ console.log('');
631
+ console.log(chalk.cyan('Configuration:'));
632
+ console.log(` User: ${chalk.white(answers.username)}`);
633
+ console.log(` Port: ${chalk.white(answers.port)}`);
634
+ console.log(` Default WS: ${chalk.white(answers.defaultWorkspace)}`);
635
+ console.log(` Workspaces: ${chalk.white(allWorkspaces.length)}`);
636
+
637
+ // Show engines
638
+ if (answers.enabledEngines && answers.enabledEngines.length > 0) {
639
+ console.log(` AI Engines: ${chalk.green(answers.enabledEngines.join(', '))}`);
640
+ } else {
641
+ console.log(` AI Engines: ${chalk.yellow('none configured')}`);
642
+ }
643
+
644
+ if (isTermux()) {
645
+ console.log(` Wake-lock: ${answers.wakeLock ? chalk.green('enabled') : chalk.gray('disabled')}`);
646
+ console.log(` Boot-start: ${answers.bootStart ? chalk.green('enabled') : chalk.gray('disabled')}`);
647
+ if (answers.bootStart && answers.checkEnginesOnBoot) {
648
+ console.log(` Engine check on boot: ${chalk.green('enabled')}`);
649
+ }
650
+ }
651
+ console.log('');
652
+ console.log(chalk.cyan('Next steps:'));
653
+ console.log(` ${chalk.white('nexuscli start')} Start the server`);
654
+ console.log(` ${chalk.white('nexuscli status')} Check status`);
655
+ console.log(` ${chalk.white('nexuscli engines')} Manage AI engines`);
656
+ console.log(` ${chalk.white('nexuscli workspaces')} Manage workspaces`);
657
+ console.log('');
658
+ }
659
+
660
+ module.exports = init;