@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
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nexuscli workspaces - Manage workspace directories
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* nexuscli workspaces - List configured workspaces
|
|
6
|
+
* nexuscli workspaces list - List configured workspaces
|
|
7
|
+
* nexuscli workspaces set-default <path> - Set default workspace
|
|
8
|
+
* nexuscli workspaces add <path> - Add workspace to list
|
|
9
|
+
* nexuscli workspaces remove <path> - Remove workspace from list
|
|
10
|
+
* nexuscli workspaces scan - Scan for Claude/Codex sessions
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const chalk = require('chalk');
|
|
17
|
+
const { getConfig, setConfigValue, getConfigValue } = require('../config/manager');
|
|
18
|
+
|
|
19
|
+
// Directories to scan for sessions
|
|
20
|
+
const SCAN_ROOTS = [
|
|
21
|
+
path.join(os.homedir(), 'Dev'),
|
|
22
|
+
path.join(os.homedir(), 'Projects'),
|
|
23
|
+
path.join(os.homedir(), 'projects'),
|
|
24
|
+
path.join(os.homedir(), 'src'),
|
|
25
|
+
path.join(os.homedir(), 'code'),
|
|
26
|
+
path.join(os.homedir(), 'work'),
|
|
27
|
+
os.homedir()
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// CLI session directories
|
|
31
|
+
const CLAUDE_PROJECTS = path.join(os.homedir(), '.claude', 'projects');
|
|
32
|
+
const CODEX_SESSIONS = path.join(os.homedir(), '.codex', 'sessions');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Expand ~ to home directory
|
|
36
|
+
*/
|
|
37
|
+
function expandPath(p) {
|
|
38
|
+
if (!p) return p;
|
|
39
|
+
if (p.startsWith('~/')) {
|
|
40
|
+
return path.join(os.homedir(), p.slice(2));
|
|
41
|
+
}
|
|
42
|
+
return path.resolve(p);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Collapse home directory to ~
|
|
47
|
+
*/
|
|
48
|
+
function collapsePath(p) {
|
|
49
|
+
const home = os.homedir();
|
|
50
|
+
if (p.startsWith(home)) {
|
|
51
|
+
return '~' + p.slice(home.length);
|
|
52
|
+
}
|
|
53
|
+
return p;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Convert workspace path to Claude projects slug
|
|
58
|
+
*/
|
|
59
|
+
function pathToSlug(workspacePath) {
|
|
60
|
+
return workspacePath.replace(/\//g, '-').replace(/\./g, '-');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* List configured workspaces
|
|
65
|
+
*/
|
|
66
|
+
function listWorkspaces() {
|
|
67
|
+
const config = getConfig();
|
|
68
|
+
const defaultWs = expandPath(config.workspaces?.default || '~/Dev');
|
|
69
|
+
const paths = config.workspaces?.paths || [];
|
|
70
|
+
|
|
71
|
+
console.log('\n' + chalk.cyan('📁 Configured Workspaces:\n'));
|
|
72
|
+
|
|
73
|
+
if (paths.length === 0) {
|
|
74
|
+
console.log(chalk.gray(' No workspaces configured.'));
|
|
75
|
+
console.log(chalk.gray(' Run: nexuscli workspaces scan'));
|
|
76
|
+
} else {
|
|
77
|
+
paths.forEach(ws => {
|
|
78
|
+
const expanded = expandPath(ws);
|
|
79
|
+
const isDefault = expanded === defaultWs;
|
|
80
|
+
const exists = fs.existsSync(expanded);
|
|
81
|
+
|
|
82
|
+
const marker = isDefault ? chalk.green('★ ') : ' ';
|
|
83
|
+
const pathDisplay = collapsePath(expanded);
|
|
84
|
+
const status = exists ? '' : chalk.red(' (not found)');
|
|
85
|
+
|
|
86
|
+
console.log(`${marker}${chalk.white(pathDisplay)}${status}`);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log('\n' + chalk.gray(`Default: ${collapsePath(defaultWs)}`));
|
|
91
|
+
console.log('');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Set default workspace
|
|
96
|
+
*/
|
|
97
|
+
function setDefault(workspacePath) {
|
|
98
|
+
if (!workspacePath) {
|
|
99
|
+
console.log(chalk.red('\n❌ Usage: nexuscli workspaces set-default <path>\n'));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const expanded = expandPath(workspacePath);
|
|
104
|
+
|
|
105
|
+
if (!fs.existsSync(expanded)) {
|
|
106
|
+
console.log(chalk.red(`\n❌ Path not found: ${expanded}\n`));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!fs.statSync(expanded).isDirectory()) {
|
|
111
|
+
console.log(chalk.red(`\n❌ Not a directory: ${expanded}\n`));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Set as default
|
|
116
|
+
setConfigValue('workspaces.default', collapsePath(expanded));
|
|
117
|
+
|
|
118
|
+
// Also add to paths if not present
|
|
119
|
+
const paths = getConfigValue('workspaces.paths') || [];
|
|
120
|
+
const collapsed = collapsePath(expanded);
|
|
121
|
+
if (!paths.includes(collapsed) && !paths.includes(expanded)) {
|
|
122
|
+
paths.push(collapsed);
|
|
123
|
+
setConfigValue('workspaces.paths', paths);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log(chalk.green(`\n✅ Default workspace set to: ${collapsed}\n`));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Add workspace to list
|
|
131
|
+
*/
|
|
132
|
+
function addWorkspace(workspacePath) {
|
|
133
|
+
if (!workspacePath) {
|
|
134
|
+
console.log(chalk.red('\n❌ Usage: nexuscli workspaces add <path>\n'));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const expanded = expandPath(workspacePath);
|
|
139
|
+
|
|
140
|
+
if (!fs.existsSync(expanded)) {
|
|
141
|
+
console.log(chalk.red(`\n❌ Path not found: ${expanded}\n`));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!fs.statSync(expanded).isDirectory()) {
|
|
146
|
+
console.log(chalk.red(`\n❌ Not a directory: ${expanded}\n`));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const paths = getConfigValue('workspaces.paths') || [];
|
|
151
|
+
const collapsed = collapsePath(expanded);
|
|
152
|
+
|
|
153
|
+
if (paths.includes(collapsed) || paths.includes(expanded)) {
|
|
154
|
+
console.log(chalk.yellow(`\n⚠️ Workspace already in list: ${collapsed}\n`));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
paths.push(collapsed);
|
|
159
|
+
setConfigValue('workspaces.paths', paths);
|
|
160
|
+
|
|
161
|
+
console.log(chalk.green(`\n✅ Added workspace: ${collapsed}\n`));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Remove workspace from list
|
|
166
|
+
*/
|
|
167
|
+
function removeWorkspace(workspacePath) {
|
|
168
|
+
if (!workspacePath) {
|
|
169
|
+
console.log(chalk.red('\n❌ Usage: nexuscli workspaces remove <path>\n'));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const expanded = expandPath(workspacePath);
|
|
174
|
+
const collapsed = collapsePath(expanded);
|
|
175
|
+
|
|
176
|
+
let paths = getConfigValue('workspaces.paths') || [];
|
|
177
|
+
const originalLen = paths.length;
|
|
178
|
+
|
|
179
|
+
paths = paths.filter(p => {
|
|
180
|
+
const pExpanded = expandPath(p);
|
|
181
|
+
return pExpanded !== expanded;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
if (paths.length === originalLen) {
|
|
185
|
+
console.log(chalk.yellow(`\n⚠️ Workspace not in list: ${collapsed}\n`));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
setConfigValue('workspaces.paths', paths);
|
|
190
|
+
|
|
191
|
+
// If removed default, clear it
|
|
192
|
+
const defaultWs = expandPath(getConfigValue('workspaces.default') || '');
|
|
193
|
+
if (defaultWs === expanded) {
|
|
194
|
+
setConfigValue('workspaces.default', paths[0] || '~/Dev');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(chalk.green(`\n✅ Removed workspace: ${collapsed}\n`));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Scan for workspaces with Claude/Codex sessions
|
|
202
|
+
*/
|
|
203
|
+
function scanWorkspaces() {
|
|
204
|
+
console.log('\n' + chalk.cyan('🔍 Scanning for workspaces with AI sessions...\n'));
|
|
205
|
+
|
|
206
|
+
const foundWorkspaces = new Map(); // path -> { claude: count, codex: count }
|
|
207
|
+
|
|
208
|
+
// 1. Scan Claude projects directory
|
|
209
|
+
if (fs.existsSync(CLAUDE_PROJECTS)) {
|
|
210
|
+
console.log(chalk.gray(` Scanning ${CLAUDE_PROJECTS}...`));
|
|
211
|
+
|
|
212
|
+
const dirs = fs.readdirSync(CLAUDE_PROJECTS, { withFileTypes: true });
|
|
213
|
+
|
|
214
|
+
for (const dir of dirs) {
|
|
215
|
+
if (!dir.isDirectory()) continue;
|
|
216
|
+
|
|
217
|
+
const projectDir = path.join(CLAUDE_PROJECTS, dir.name);
|
|
218
|
+
const sessionFiles = fs.readdirSync(projectDir)
|
|
219
|
+
.filter(f => f.endsWith('.jsonl') && !f.startsWith('agent-'));
|
|
220
|
+
|
|
221
|
+
if (sessionFiles.length > 0) {
|
|
222
|
+
// Read workspace path from first session file
|
|
223
|
+
let workspacePath = null;
|
|
224
|
+
try {
|
|
225
|
+
const firstFile = path.join(projectDir, sessionFiles[0]);
|
|
226
|
+
const firstLine = fs.readFileSync(firstFile, 'utf8').split('\n')[0];
|
|
227
|
+
const entry = JSON.parse(firstLine);
|
|
228
|
+
workspacePath = entry.cwd;
|
|
229
|
+
} catch (err) {
|
|
230
|
+
// Fallback: convert slug back to path
|
|
231
|
+
workspacePath = '/' + dir.name.replace(/^-/, '').replace(/-/g, '/');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (workspacePath && fs.existsSync(workspacePath)) {
|
|
235
|
+
const existing = foundWorkspaces.get(workspacePath) || { claude: 0, codex: 0 };
|
|
236
|
+
existing.claude = sessionFiles.length;
|
|
237
|
+
foundWorkspaces.set(workspacePath, existing);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 2. Scan Codex sessions directory
|
|
244
|
+
if (fs.existsSync(CODEX_SESSIONS)) {
|
|
245
|
+
console.log(chalk.gray(` Scanning ${CODEX_SESSIONS}...`));
|
|
246
|
+
|
|
247
|
+
const sessionFiles = fs.readdirSync(CODEX_SESSIONS)
|
|
248
|
+
.filter(f => f.endsWith('.json'));
|
|
249
|
+
|
|
250
|
+
// Codex sessions might have workspace in their metadata
|
|
251
|
+
// For now, count them without workspace association
|
|
252
|
+
if (sessionFiles.length > 0) {
|
|
253
|
+
console.log(chalk.gray(` Found ${sessionFiles.length} Codex sessions (global)`));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 3. Display results
|
|
258
|
+
console.log('');
|
|
259
|
+
|
|
260
|
+
if (foundWorkspaces.size === 0) {
|
|
261
|
+
console.log(chalk.yellow(' No workspaces with sessions found.\n'));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
console.log(chalk.green(`Found ${foundWorkspaces.size} workspaces with sessions:\n`));
|
|
266
|
+
|
|
267
|
+
// Sort by session count
|
|
268
|
+
const sorted = [...foundWorkspaces.entries()]
|
|
269
|
+
.sort((a, b) => (b[1].claude + b[1].codex) - (a[1].claude + a[1].codex));
|
|
270
|
+
|
|
271
|
+
// Get current configured paths
|
|
272
|
+
const currentPaths = (getConfigValue('workspaces.paths') || []).map(expandPath);
|
|
273
|
+
|
|
274
|
+
for (const [ws, counts] of sorted) {
|
|
275
|
+
const isConfigured = currentPaths.includes(ws);
|
|
276
|
+
const marker = isConfigured ? chalk.green('✓') : chalk.yellow('○');
|
|
277
|
+
const claudeCount = counts.claude ? chalk.cyan(`Claude: ${counts.claude}`) : '';
|
|
278
|
+
const codexCount = counts.codex ? chalk.magenta(`Codex: ${counts.codex}`) : '';
|
|
279
|
+
const sessionInfo = [claudeCount, codexCount].filter(Boolean).join(', ');
|
|
280
|
+
|
|
281
|
+
console.log(` ${marker} ${collapsePath(ws)}`);
|
|
282
|
+
console.log(chalk.gray(` ${sessionInfo}`));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Ask to add unconfigured workspaces
|
|
286
|
+
const unconfigured = sorted.filter(([ws]) => !currentPaths.includes(ws));
|
|
287
|
+
|
|
288
|
+
if (unconfigured.length > 0) {
|
|
289
|
+
console.log(chalk.cyan(`\nTo add these workspaces, run:`));
|
|
290
|
+
for (const [ws] of unconfigured) {
|
|
291
|
+
console.log(chalk.gray(` nexuscli workspaces add "${collapsePath(ws)}"`));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
console.log('');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Main workspaces command
|
|
300
|
+
*/
|
|
301
|
+
async function workspacesCommand(action, arg) {
|
|
302
|
+
if (!action || action === 'list') {
|
|
303
|
+
listWorkspaces();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
switch (action) {
|
|
308
|
+
case 'set-default':
|
|
309
|
+
setDefault(arg);
|
|
310
|
+
break;
|
|
311
|
+
|
|
312
|
+
case 'add':
|
|
313
|
+
addWorkspace(arg);
|
|
314
|
+
break;
|
|
315
|
+
|
|
316
|
+
case 'remove':
|
|
317
|
+
case 'rm':
|
|
318
|
+
removeWorkspace(arg);
|
|
319
|
+
break;
|
|
320
|
+
|
|
321
|
+
case 'scan':
|
|
322
|
+
scanWorkspaces();
|
|
323
|
+
break;
|
|
324
|
+
|
|
325
|
+
default:
|
|
326
|
+
console.log(chalk.red(`\n❌ Unknown action: ${action}`));
|
|
327
|
+
console.log('\nUsage:');
|
|
328
|
+
console.log(' nexuscli workspaces - List configured workspaces');
|
|
329
|
+
console.log(' nexuscli workspaces set-default <path> - Set default');
|
|
330
|
+
console.log(' nexuscli workspaces add <path> - Add workspace');
|
|
331
|
+
console.log(' nexuscli workspaces remove <path> - Remove workspace');
|
|
332
|
+
console.log(' nexuscli workspaces scan - Scan for sessions');
|
|
333
|
+
console.log('');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
module.exports = workspacesCommand;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Manager
|
|
3
|
+
* Handles reading/writing ~/.nexuscli/config.json
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { PATHS, ensureDirectories } = require('../utils/paths');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default configuration
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_CONFIG = {
|
|
14
|
+
version: 1,
|
|
15
|
+
server: {
|
|
16
|
+
port: 41800,
|
|
17
|
+
host: '0.0.0.0'
|
|
18
|
+
},
|
|
19
|
+
auth: {
|
|
20
|
+
jwt_secret: null, // Generated on init
|
|
21
|
+
user: 'tux',
|
|
22
|
+
pass_hash: null // Generated on init
|
|
23
|
+
},
|
|
24
|
+
engines: {
|
|
25
|
+
active: ['claude'],
|
|
26
|
+
check_on_boot: false, // Re-check engine availability on each boot
|
|
27
|
+
claude: {
|
|
28
|
+
enabled: true,
|
|
29
|
+
path: 'auto',
|
|
30
|
+
model: 'sonnet'
|
|
31
|
+
},
|
|
32
|
+
codex: {
|
|
33
|
+
enabled: false,
|
|
34
|
+
path: null
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
workspaces: {
|
|
38
|
+
default: null, // Set during init - will use HOME if not configured
|
|
39
|
+
paths: []
|
|
40
|
+
},
|
|
41
|
+
termux: {
|
|
42
|
+
wake_lock: true,
|
|
43
|
+
notifications: true,
|
|
44
|
+
boot_start: false
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Load configuration from file
|
|
50
|
+
*/
|
|
51
|
+
function loadConfig() {
|
|
52
|
+
ensureDirectories();
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync(PATHS.CONFIG_FILE)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const content = fs.readFileSync(PATHS.CONFIG_FILE, 'utf8');
|
|
60
|
+
return JSON.parse(content);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Error loading config:', error.message);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Save configuration to file
|
|
69
|
+
*/
|
|
70
|
+
function saveConfig(config) {
|
|
71
|
+
ensureDirectories();
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
fs.writeFileSync(
|
|
75
|
+
PATHS.CONFIG_FILE,
|
|
76
|
+
JSON.stringify(config, null, 2),
|
|
77
|
+
'utf8'
|
|
78
|
+
);
|
|
79
|
+
return true;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error('Error saving config:', error.message);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get configuration (load or create default)
|
|
88
|
+
*/
|
|
89
|
+
function getConfig() {
|
|
90
|
+
const config = loadConfig();
|
|
91
|
+
if (config) {
|
|
92
|
+
return config;
|
|
93
|
+
}
|
|
94
|
+
return { ...DEFAULT_CONFIG };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if NexusCLI is initialized
|
|
99
|
+
*/
|
|
100
|
+
function isInitialized() {
|
|
101
|
+
return fs.existsSync(PATHS.CONFIG_FILE);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get a config value by dot-notation path
|
|
106
|
+
* e.g., 'server.port' -> config.server.port
|
|
107
|
+
*/
|
|
108
|
+
function getConfigValue(key) {
|
|
109
|
+
const config = getConfig();
|
|
110
|
+
const parts = key.split('.');
|
|
111
|
+
let value = config;
|
|
112
|
+
|
|
113
|
+
for (const part of parts) {
|
|
114
|
+
if (value === undefined || value === null) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
value = value[part];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Set a config value by dot-notation path
|
|
125
|
+
*/
|
|
126
|
+
function setConfigValue(key, value) {
|
|
127
|
+
const config = getConfig();
|
|
128
|
+
const parts = key.split('.');
|
|
129
|
+
let current = config;
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
132
|
+
const part = parts[i];
|
|
133
|
+
if (current[part] === undefined) {
|
|
134
|
+
current[part] = {};
|
|
135
|
+
}
|
|
136
|
+
current = current[part];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Parse value type
|
|
140
|
+
let parsedValue = value;
|
|
141
|
+
if (value === 'true') parsedValue = true;
|
|
142
|
+
else if (value === 'false') parsedValue = false;
|
|
143
|
+
else if (!isNaN(value) && value !== '') parsedValue = Number(value);
|
|
144
|
+
|
|
145
|
+
current[parts[parts.length - 1]] = parsedValue;
|
|
146
|
+
|
|
147
|
+
return saveConfig(config);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get all config keys as flat list
|
|
152
|
+
*/
|
|
153
|
+
function getConfigKeys(obj = null, prefix = '') {
|
|
154
|
+
if (!obj) {
|
|
155
|
+
obj = getConfig();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const keys = [];
|
|
159
|
+
|
|
160
|
+
for (const key of Object.keys(obj)) {
|
|
161
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
162
|
+
|
|
163
|
+
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
164
|
+
keys.push(...getConfigKeys(obj[key], fullKey));
|
|
165
|
+
} else {
|
|
166
|
+
keys.push(fullKey);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return keys;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Initialize config with generated secrets
|
|
175
|
+
*/
|
|
176
|
+
function initializeConfig(options = {}) {
|
|
177
|
+
const { v4: uuidv4 } = require('uuid');
|
|
178
|
+
const bcrypt = require('bcryptjs');
|
|
179
|
+
|
|
180
|
+
const config = { ...DEFAULT_CONFIG };
|
|
181
|
+
|
|
182
|
+
// Generate JWT secret
|
|
183
|
+
config.auth.jwt_secret = uuidv4() + '-' + uuidv4();
|
|
184
|
+
|
|
185
|
+
// Set user credentials
|
|
186
|
+
const username = options.username || 'tux';
|
|
187
|
+
const password = options.password || 'tux';
|
|
188
|
+
|
|
189
|
+
config.auth.user = username;
|
|
190
|
+
config.auth.pass_hash = bcrypt.hashSync(password, 10);
|
|
191
|
+
|
|
192
|
+
// Set workspaces
|
|
193
|
+
if (options.workspaces) {
|
|
194
|
+
config.workspaces.paths = options.workspaces;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Set default workspace (explicit or first from list)
|
|
198
|
+
if (options.defaultWorkspace) {
|
|
199
|
+
config.workspaces.default = options.defaultWorkspace;
|
|
200
|
+
} else if (options.workspaces && options.workspaces.length > 0) {
|
|
201
|
+
config.workspaces.default = options.workspaces[0];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Set port
|
|
205
|
+
if (options.port) {
|
|
206
|
+
config.server.port = options.port;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Termux options
|
|
210
|
+
if (options.wakeLock !== undefined) {
|
|
211
|
+
config.termux.wake_lock = options.wakeLock;
|
|
212
|
+
}
|
|
213
|
+
if (options.notifications !== undefined) {
|
|
214
|
+
config.termux.notifications = options.notifications;
|
|
215
|
+
}
|
|
216
|
+
if (options.bootStart !== undefined) {
|
|
217
|
+
config.termux.boot_start = options.bootStart;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return saveConfig(config) ? config : null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = {
|
|
224
|
+
DEFAULT_CONFIG,
|
|
225
|
+
loadConfig,
|
|
226
|
+
saveConfig,
|
|
227
|
+
getConfig,
|
|
228
|
+
isInitialized,
|
|
229
|
+
getConfigValue,
|
|
230
|
+
setConfigValue,
|
|
231
|
+
getConfigKeys,
|
|
232
|
+
initializeConfig
|
|
233
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# NexusCLI Backend Configuration
|
|
2
|
+
|
|
3
|
+
# Port (NexusCLI Chaos Standard: 41800)
|
|
4
|
+
# 418 = ABRAHADABRA (Word of the Aeon, Crowley/Thelema)
|
|
5
|
+
# Represents the Great Work complete (Chaos Magic supreme formula)
|
|
6
|
+
# Range 41800-41899 (100 ports, UNIVERSAL compatibility, NO Elasticsearch conflict)
|
|
7
|
+
PORT=41800
|
|
8
|
+
|
|
9
|
+
# Database path (auto-detected for Linux/Termux)
|
|
10
|
+
# Linux: /var/lib/nexuscli/nexuscli.db
|
|
11
|
+
# Termux: $HOME/.nexuscli/nexuscli.db
|
|
12
|
+
|
|
13
|
+
# Node environment
|
|
14
|
+
NODE_ENV=production
|
|
15
|
+
|
|
16
|
+
# Workspace directory for CLI execution
|
|
17
|
+
WORKSPACE_DIR=/home/user/myproject
|
|
18
|
+
|
|
19
|
+
# Timeout for CLI commands (ms)
|
|
20
|
+
DEFAULT_TIMEOUT=30000
|