@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.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/bin/nexuscli.js +117 -0
- package/frontend/dist/apple-touch-icon.png +0 -0
- package/frontend/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/frontend/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/frontend/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/frontend/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/frontend/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/frontend/dist/assets/index-Bn_l1e6e.css +1 -0
- package/frontend/dist/assets/index-CikJbUR5.js +8617 -0
- package/frontend/dist/browserconfig.xml +12 -0
- package/frontend/dist/favicon-16x16.png +0 -0
- package/frontend/dist/favicon-32x32.png +0 -0
- package/frontend/dist/favicon-48x48.png +0 -0
- package/frontend/dist/favicon.ico +0 -0
- package/frontend/dist/icon-192.png +0 -0
- package/frontend/dist/icon-512.png +0 -0
- package/frontend/dist/icon-maskable-192.png +0 -0
- package/frontend/dist/icon-maskable-512.png +0 -0
- package/frontend/dist/index.html +79 -0
- package/frontend/dist/manifest.json +75 -0
- package/frontend/dist/sw.js +122 -0
- package/frontend/package.json +28 -0
- package/lib/cli/api.js +156 -0
- package/lib/cli/boot.js +172 -0
- package/lib/cli/config.js +185 -0
- package/lib/cli/engines.js +257 -0
- package/lib/cli/init.js +660 -0
- package/lib/cli/logs.js +72 -0
- package/lib/cli/start.js +220 -0
- package/lib/cli/status.js +187 -0
- package/lib/cli/stop.js +64 -0
- package/lib/cli/uninstall.js +194 -0
- package/lib/cli/users.js +295 -0
- package/lib/cli/workspaces.js +337 -0
- package/lib/config/manager.js +233 -0
- package/lib/server/.env.example +20 -0
- package/lib/server/db/adapter.js +314 -0
- package/lib/server/db/drivers/better-sqlite3.js +38 -0
- package/lib/server/db/drivers/sql-js.js +75 -0
- package/lib/server/db/migrate.js +174 -0
- package/lib/server/db/migrations/001_ultra_light_schema.sql +96 -0
- package/lib/server/db/migrations/002_session_conversation_mapping.sql +19 -0
- package/lib/server/db/migrations/003_message_engine_tracking.sql +18 -0
- package/lib/server/db/migrations/004_performance_indexes.sql +16 -0
- package/lib/server/db.js +2 -0
- package/lib/server/lib/cli-wrapper.js +164 -0
- package/lib/server/lib/output-parser.js +132 -0
- package/lib/server/lib/pty-adapter.js +57 -0
- package/lib/server/middleware/auth.js +103 -0
- package/lib/server/models/Conversation.js +259 -0
- package/lib/server/models/Message.js +228 -0
- package/lib/server/models/User.js +115 -0
- package/lib/server/package-lock.json +5895 -0
- package/lib/server/routes/auth.js +168 -0
- package/lib/server/routes/chat.js +206 -0
- package/lib/server/routes/codex.js +205 -0
- package/lib/server/routes/conversations.js +224 -0
- package/lib/server/routes/gemini.js +228 -0
- package/lib/server/routes/jobs.js +317 -0
- package/lib/server/routes/messages.js +60 -0
- package/lib/server/routes/models.js +198 -0
- package/lib/server/routes/sessions.js +285 -0
- package/lib/server/routes/upload.js +134 -0
- package/lib/server/routes/wake-lock.js +95 -0
- package/lib/server/routes/workspace.js +80 -0
- package/lib/server/routes/workspaces.js +142 -0
- package/lib/server/scripts/cleanup-ghost-sessions.js +71 -0
- package/lib/server/scripts/seed-users.js +37 -0
- package/lib/server/scripts/test-history-access.js +50 -0
- package/lib/server/server.js +227 -0
- package/lib/server/services/cache.js +85 -0
- package/lib/server/services/claude-wrapper.js +312 -0
- package/lib/server/services/cli-loader.js +384 -0
- package/lib/server/services/codex-output-parser.js +277 -0
- package/lib/server/services/codex-wrapper.js +224 -0
- package/lib/server/services/context-bridge.js +289 -0
- package/lib/server/services/gemini-output-parser.js +398 -0
- package/lib/server/services/gemini-wrapper.js +249 -0
- package/lib/server/services/history-sync.js +407 -0
- package/lib/server/services/output-parser.js +415 -0
- package/lib/server/services/session-manager.js +465 -0
- package/lib/server/services/summary-generator.js +259 -0
- package/lib/server/services/workspace-manager.js +516 -0
- package/lib/server/tests/history-sync.test.js +90 -0
- package/lib/server/tests/integration-session-sync.test.js +151 -0
- package/lib/server/tests/integration.test.js +76 -0
- package/lib/server/tests/performance.test.js +118 -0
- package/lib/server/tests/services.test.js +160 -0
- package/lib/setup/postinstall.js +216 -0
- package/lib/utils/paths.js +107 -0
- package/lib/utils/termux.js +145 -0
- package/package.json +82 -0
package/lib/cli/init.js
ADDED
|
@@ -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;
|