@bytespell/shella 0.1.3 → 0.1.4
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/README.md +1 -1
- package/bin/cli.js +153 -82
- package/dev/cli.tsx +26 -0
- package/dist/config/openai-codex-models.json +205 -0
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.js +66 -8
- package/dist/server/lib/opencode-client.d.ts +14 -0
- package/dist/server/lib/opencode-client.js +17 -0
- package/dist/server/lib/opencode-config.d.ts +14 -0
- package/dist/server/lib/opencode-config.js +25 -0
- package/dist/server/routes/config.d.ts +12 -0
- package/dist/server/routes/config.js +207 -0
- package/dist/server/routes/init.d.ts +4 -3
- package/dist/server/routes/init.js +23 -9
- package/dist/server/routes/local-llm.d.ts +8 -0
- package/dist/server/routes/local-llm.js +255 -0
- package/dist/server/routes/logs.js +35 -11
- package/dist/server/routes/prompt.d.ts +16 -0
- package/dist/server/routes/prompt.js +173 -0
- package/dist/server/routes/session.d.ts +8 -0
- package/dist/server/routes/session.js +63 -0
- package/dist/server/routes/status.d.ts +9 -0
- package/dist/server/routes/status.js +54 -0
- package/dist/server/routes/usage.d.ts +12 -0
- package/dist/server/routes/usage.js +60 -0
- package/dist/server/routes/windows.js +4 -4
- package/dist/server/schema.d.ts +47 -16
- package/dist/server/schema.js +8 -1
- package/dist/server/services/database.d.ts +10 -1
- package/dist/server/services/database.js +19 -6
- package/dist/web/assets/{_baseUniq-BXqY9Mam.js → _baseUniq-6T01QAux.js} +1 -1
- package/dist/web/assets/{arc-Bn6tUpO_.js → arc-BkH3TPJb.js} +1 -1
- package/dist/web/assets/{architectureDiagram-VXUJARFQ-C7FAApUY.js → architectureDiagram-VXUJARFQ-BSi6BLCC.js} +1 -1
- package/dist/web/assets/{blockDiagram-VD42YOAC-C2fdaEWa.js → blockDiagram-VD42YOAC-QSPUbinO.js} +1 -1
- package/dist/web/assets/{c4Diagram-YG6GDRKO-FEVzhARQ.js → c4Diagram-YG6GDRKO-Cya_BihR.js} +1 -1
- package/dist/web/assets/channel-DGAtS-pa.js +1 -0
- package/dist/web/assets/{chunk-4BX2VUAB-DLekcSAU.js → chunk-4BX2VUAB-DIL6eizv.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-8hFRjyTP.js → chunk-55IACEB6-CgwejoZz.js} +1 -1
- package/dist/web/assets/{chunk-B4BG7PRW-DULC9-MQ.js → chunk-B4BG7PRW-9mIPqoGe.js} +1 -1
- package/dist/web/assets/{chunk-DI55MBZ5-DuOE5RH1.js → chunk-DI55MBZ5-BRbyRfgT.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-DaDNiCk7.js → chunk-FMBD7UC4-CVBT25Fj.js} +1 -1
- package/dist/web/assets/{chunk-QN33PNHL-CKshfIHj.js → chunk-QN33PNHL-rTj-WT2G.js} +1 -1
- package/dist/web/assets/{chunk-QZHKN3VN-D2Qy0tdi.js → chunk-QZHKN3VN-BaUBiHya.js} +1 -1
- package/dist/web/assets/{chunk-TZMSLE5B-SPxkj-lp.js → chunk-TZMSLE5B-C4_O5TI-.js} +1 -1
- package/dist/web/assets/classDiagram-2ON5EDUG-DLvlUUJq.js +1 -0
- package/dist/web/assets/classDiagram-v2-WZHVMYZB-DLvlUUJq.js +1 -0
- package/dist/web/assets/clone-BZW2JABw.js +1 -0
- package/dist/web/assets/{code-block-QI2IAROF-BZdAQmZ2.js → code-block-QI2IAROF-Bj_2OIYt.js} +1 -1
- package/dist/web/assets/{cose-bilkent-S5V4N54A-DbasixUk.js → cose-bilkent-S5V4N54A-T7a1luWi.js} +1 -1
- package/dist/web/assets/{dagre-6UL2VRFP-CStyjTc9.js → dagre-6UL2VRFP-CeH5ZsdW.js} +1 -1
- package/dist/web/assets/{diagram-PSM6KHXK-Crk93U8d.js → diagram-PSM6KHXK-Cdod2Lna.js} +1 -1
- package/dist/web/assets/{diagram-QEK2KX5R-DiW6RNbg.js → diagram-QEK2KX5R-CYks2r54.js} +1 -1
- package/dist/web/assets/{diagram-S2PKOQOG-CKksz_qL.js → diagram-S2PKOQOG-DCmy0g7p.js} +1 -1
- package/dist/web/assets/{erDiagram-Q2GNP2WA-CisACqqq.js → erDiagram-Q2GNP2WA-Dlz1bNvI.js} +1 -1
- package/dist/web/assets/{flowDiagram-NV44I4VS-BBp_5zAe.js → flowDiagram-NV44I4VS-Di5Iit1B.js} +1 -1
- package/dist/web/assets/{ganttDiagram-JELNMOA3-BKZ30gLA.js → ganttDiagram-JELNMOA3-9i1dugg-.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-NY62KEGX-ClizxUXq.js → gitGraphDiagram-NY62KEGX-BORbMVri.js} +1 -1
- package/dist/web/assets/{graph-DqhaNOTU.js → graph-C0SCKxbQ.js} +1 -1
- package/dist/web/assets/index-CYVJT8rN.js +1 -0
- package/dist/web/assets/index-CcAJUkQw.css +1 -0
- package/dist/web/assets/index-CcDdxbB-.js +1719 -0
- package/dist/web/assets/{infoDiagram-WHAUD3N6-BQwNR0md.js → infoDiagram-WHAUD3N6-7ohMQFLY.js} +1 -1
- package/dist/web/assets/{journeyDiagram-XKPGCS4Q-YOqPPID4.js → journeyDiagram-XKPGCS4Q-DZp7Z7wE.js} +1 -1
- package/dist/web/assets/{kanban-definition-3W4ZIXB7-Dtu8bvBx.js → kanban-definition-3W4ZIXB7-BCNLCm54.js} +1 -1
- package/dist/web/assets/{layout-Cc1ESzTe.js → layout-AUnZuY21.js} +1 -1
- package/dist/web/assets/{linear-BwI2ANFG.js → linear-B0bfAqGt.js} +1 -1
- package/dist/web/assets/{mermaid.core-npIGP8NS.js → mermaid.core-D5fXNCxA.js} +5 -5
- package/dist/web/assets/{min--MKscDc6.js → min-BZUFOEEw.js} +1 -1
- package/dist/web/assets/{mindmap-definition-VGOIOE7T-Cr39Vhym.js → mindmap-definition-VGOIOE7T-hEGJLJ8N.js} +1 -1
- package/dist/web/assets/{pieDiagram-ADFJNKIX-Cv8ke00t.js → pieDiagram-ADFJNKIX-BRpCTJIO.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-AYHSOK5B-BPhHaTg8.js → quadrantDiagram-AYHSOK5B-m7jaiHQb.js} +1 -1
- package/dist/web/assets/{requirementDiagram-UZGBJVZJ-Cc42SoK0.js → requirementDiagram-UZGBJVZJ-Coh9g9Sp.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-TZEHDZUN-CtgBuq8T.js → sankeyDiagram-TZEHDZUN-CrD_kUGR.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-WL72ISMW-B9lNGN6V.js → sequenceDiagram-WL72ISMW-C04yD1EI.js} +1 -1
- package/dist/web/assets/{stateDiagram-FKZM4ZOC-C3dRTOMb.js → stateDiagram-FKZM4ZOC-DhP-DMZW.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-4FDKWEC3-DWi5vrD6.js +1 -0
- package/dist/web/assets/{timeline-definition-IT6M3QCI-CXhSuTlt.js → timeline-definition-IT6M3QCI-40iW2p_5.js} +1 -1
- package/dist/web/assets/{treemap-KMMF4GRG-Csy25Uov.js → treemap-KMMF4GRG-BnxWQbzt.js} +1 -1
- package/dist/web/assets/welcome-screen-test-CLeWuIqq.js +1 -0
- package/dist/web/assets/{xychartDiagram-PRI3JC2R-CxEERqse.js → xychartDiagram-PRI3JC2R-D6lcJDCc.js} +1 -1
- package/dist/web/index.html +3 -3
- package/package.json +14 -5
- package/dist/web/assets/channel-CxjnQtV7.js +0 -1
- package/dist/web/assets/classDiagram-2ON5EDUG-CVG91-fs.js +0 -1
- package/dist/web/assets/classDiagram-v2-WZHVMYZB-CVG91-fs.js +0 -1
- package/dist/web/assets/clone-C7jxvixc.js +0 -1
- package/dist/web/assets/index-B0jWvqrS.css +0 -1
- package/dist/web/assets/index-Dnmavb3d.js +0 -1716
- package/dist/web/assets/stateDiagram-v2-4FDKWEC3-oHTO1yj_.js +0 -1
package/README.md
CHANGED
package/bin/cli.js
CHANGED
|
@@ -4,11 +4,26 @@ import { createOpencodeServer } from '@opencode-ai/sdk';
|
|
|
4
4
|
import { networkInterfaces } from 'os';
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
6
|
import { createConnection } from 'net';
|
|
7
|
-
import { existsSync, readdirSync } from 'fs';
|
|
7
|
+
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
|
+
import * as p from '@clack/prompts';
|
|
10
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
const VERSION = '0.1.
|
|
12
|
+
const VERSION = '0.1.4';
|
|
13
|
+
// Brand color (indigo) for terminal output
|
|
14
|
+
const BRAND = '\x1b[38;2;99;102;241m'; // rgb(99, 102, 241) - matches --primary
|
|
15
|
+
const RESET = '\x1b[0m';
|
|
16
|
+
const DIM = '\x1b[2m';
|
|
17
|
+
const brand = (text) => `${BRAND}${text}${RESET}`;
|
|
18
|
+
const dim = (text) => `${DIM}${text}${RESET}`;
|
|
19
|
+
/**
|
|
20
|
+
* Load OpenCode config with plugin and model definitions.
|
|
21
|
+
*/
|
|
22
|
+
function getOpencodeConfig() {
|
|
23
|
+
const configPath = path.join(__dirname, '..', 'dist', 'config', 'openai-codex-models.json');
|
|
24
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
25
|
+
return JSON.parse(raw);
|
|
26
|
+
}
|
|
12
27
|
/**
|
|
13
28
|
* Check if running inside Docker container
|
|
14
29
|
*/
|
|
@@ -52,31 +67,6 @@ function getLanIp() {
|
|
|
52
67
|
}
|
|
53
68
|
return 'localhost';
|
|
54
69
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Print a nice boxed message with the access URL
|
|
57
|
-
*/
|
|
58
|
-
function printAccessBox(lanIp, port, projectName) {
|
|
59
|
-
const url = `http://${lanIp}:${port}`;
|
|
60
|
-
const urlLine = ` ${url}`;
|
|
61
|
-
const width = Math.max(44, urlLine.length + 4);
|
|
62
|
-
const border = '-'.repeat(width);
|
|
63
|
-
const pad = (s) => s + ' '.repeat(width - s.length);
|
|
64
|
-
console.log('');
|
|
65
|
-
console.log(`+${border}+`);
|
|
66
|
-
console.log(`|${pad('')}|`);
|
|
67
|
-
if (projectName) {
|
|
68
|
-
console.log(`|${pad(` Shella v${VERSION} - ${projectName}`)}|`);
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
console.log(`|${pad(` Shella v${VERSION}`)}|`);
|
|
72
|
-
}
|
|
73
|
-
console.log(`|${pad('')}|`);
|
|
74
|
-
console.log(`|${pad(' Access from any device:')}|`);
|
|
75
|
-
console.log(`|${pad(urlLine)}|`);
|
|
76
|
-
console.log(`|${pad('')}|`);
|
|
77
|
-
console.log(`+${border}+`);
|
|
78
|
-
console.log('');
|
|
79
|
-
}
|
|
80
70
|
/**
|
|
81
71
|
* Register projects with OpenCode by creating a session in each directory.
|
|
82
72
|
* This triggers OpenCode to discover and register the project.
|
|
@@ -88,14 +78,14 @@ async function registerProjects(opencodePort, projectsDir) {
|
|
|
88
78
|
// Resolve to absolute path
|
|
89
79
|
const absDir = path.resolve(projectsDir);
|
|
90
80
|
if (!existsSync(absDir)) {
|
|
91
|
-
|
|
81
|
+
p.log.error(`projects directory not found: ${absDir}`);
|
|
92
82
|
process.exit(1);
|
|
93
83
|
}
|
|
94
84
|
// Register each subdirectory (OpenCode will determine if it's a git repo)
|
|
95
85
|
const entries = readdirSync(absDir, { withFileTypes: true });
|
|
96
86
|
const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith('.'));
|
|
97
87
|
if (dirs.length === 0) {
|
|
98
|
-
|
|
88
|
+
p.log.error(`no subdirectories found in ${absDir}`);
|
|
99
89
|
process.exit(1);
|
|
100
90
|
}
|
|
101
91
|
for (const entry of dirs) {
|
|
@@ -143,28 +133,29 @@ async function startCommand(options) {
|
|
|
143
133
|
const port = parseInt(options.port);
|
|
144
134
|
const opencodePort = parseInt(options.opencodePort);
|
|
145
135
|
const cwd = process.cwd();
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
136
|
+
const verbose = options.verbose;
|
|
137
|
+
// Determine mode based on --projects-dir presence
|
|
138
|
+
const mode = options.projectsDir ? 'server' : 'cwd';
|
|
139
|
+
// Docker requires --projects-dir (server mode)
|
|
140
|
+
if (isDocker() && mode === 'cwd') {
|
|
141
|
+
p.log.error('--projects-dir is required when running in Docker');
|
|
142
|
+
p.log.message('example: docker run -v ~/code:/project shella --projects-dir /project');
|
|
143
|
+
p.log.message('or in docker-compose.yml: command: ["--projects-dir", "/project"]');
|
|
153
144
|
process.exit(1);
|
|
154
145
|
}
|
|
155
|
-
// Determine the
|
|
146
|
+
// Determine the directory to pass to the server
|
|
156
147
|
const projectsDir = options.projectsDir ? path.resolve(options.projectsDir) : cwd;
|
|
157
148
|
// Check if ports are available
|
|
158
149
|
const webPortAvailable = await isPortAvailable(port);
|
|
159
150
|
const ocPortAvailable = await isPortAvailable(opencodePort);
|
|
160
151
|
if (!webPortAvailable) {
|
|
161
|
-
|
|
162
|
-
|
|
152
|
+
p.log.error(`port ${port} is already in use`);
|
|
153
|
+
p.log.message('try: shella --port 3070');
|
|
163
154
|
process.exit(1);
|
|
164
155
|
}
|
|
165
156
|
if (!ocPortAvailable) {
|
|
166
|
-
|
|
167
|
-
|
|
157
|
+
p.log.error(`port ${opencodePort} is already in use (opencode)`);
|
|
158
|
+
p.log.message('try: pkill -f "opencode serve" or shella --opencode-port 4097');
|
|
168
159
|
process.exit(1);
|
|
169
160
|
}
|
|
170
161
|
// Ensure opencode binary from node_modules/.bin is in PATH
|
|
@@ -174,26 +165,61 @@ async function startCommand(options) {
|
|
|
174
165
|
const binPath = path.join(__dirname, '..', 'node_modules', '.bin');
|
|
175
166
|
const pathSep = process.platform === 'win32' ? ';' : ':';
|
|
176
167
|
process.env.PATH = process.env.PATH ? `${binPath}${pathSep}${process.env.PATH}` : binPath;
|
|
177
|
-
//
|
|
178
|
-
|
|
168
|
+
// In verbose mode, use simple status messages instead of clack UI
|
|
169
|
+
// This prevents spinner interference with log streaming
|
|
170
|
+
const s = verbose ? null : p.spinner();
|
|
171
|
+
const log = (msg) => {
|
|
172
|
+
if (verbose) {
|
|
173
|
+
console.log(msg);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
if (!verbose) {
|
|
177
|
+
p.intro(`${brand('shella')} ${dim(`v${VERSION}`)}`);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
log(`${brand('shella')} ${dim(`v${VERSION}`)}`);
|
|
181
|
+
}
|
|
179
182
|
// Start OpenCode server using the bundled binary
|
|
183
|
+
if (s)
|
|
184
|
+
s.start('Starting OpenCode...');
|
|
185
|
+
else
|
|
186
|
+
log('starting opencode...');
|
|
180
187
|
try {
|
|
188
|
+
const config = getOpencodeConfig();
|
|
181
189
|
opencodeServer = await createOpencodeServer({
|
|
182
190
|
port: opencodePort,
|
|
183
191
|
timeout: 30000,
|
|
192
|
+
config,
|
|
184
193
|
});
|
|
185
194
|
}
|
|
186
195
|
catch (err) {
|
|
187
196
|
const message = err instanceof Error ? err.message : String(err);
|
|
188
|
-
|
|
197
|
+
if (s)
|
|
198
|
+
s.stop('Failed to start OpenCode', 1);
|
|
199
|
+
p.log.error(message);
|
|
189
200
|
process.exit(1);
|
|
190
201
|
}
|
|
191
202
|
// Register projects with OpenCode
|
|
203
|
+
if (s)
|
|
204
|
+
s.message('Registering directories...');
|
|
205
|
+
else
|
|
206
|
+
log('registering directories...');
|
|
192
207
|
const { names } = await registerProjects(opencodePort, options.projectsDir);
|
|
193
208
|
const projectName = names.length === 1 ? names[0] : names.length > 1 ? `${names.length} projects` : undefined;
|
|
194
|
-
// Start Express server in production mode, passing
|
|
209
|
+
// Start Express server in production mode, passing mode and directory
|
|
210
|
+
if (s)
|
|
211
|
+
s.message('Starting server...');
|
|
212
|
+
else
|
|
213
|
+
log('starting server...');
|
|
195
214
|
const serverPath = path.join(__dirname, '..', 'dist', 'server', 'index.js');
|
|
196
|
-
|
|
215
|
+
if (verbose) {
|
|
216
|
+
console.log(`[shella] mode: ${mode}, directory: ${projectsDir}`);
|
|
217
|
+
}
|
|
218
|
+
// Pass mode and directory to server
|
|
219
|
+
const serverArgs = mode === 'server'
|
|
220
|
+
? ['--mode', 'server', '--projects-dir', projectsDir]
|
|
221
|
+
: ['--mode', 'cwd', '--directory', projectsDir];
|
|
222
|
+
expressProcess = spawn('node', [serverPath, ...serverArgs], {
|
|
197
223
|
env: {
|
|
198
224
|
...process.env,
|
|
199
225
|
NODE_ENV: 'production',
|
|
@@ -201,49 +227,82 @@ async function startCommand(options) {
|
|
|
201
227
|
},
|
|
202
228
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
203
229
|
});
|
|
230
|
+
// Track if server is ready (for verbose mode log streaming)
|
|
231
|
+
let serverReady = false;
|
|
204
232
|
// Wait for server to be ready
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
233
|
+
try {
|
|
234
|
+
await new Promise((resolve, reject) => {
|
|
235
|
+
const timeout = setTimeout(() => {
|
|
236
|
+
reject(new Error('Server startup timeout'));
|
|
237
|
+
}, 10000);
|
|
238
|
+
expressProcess.stdout?.on('data', (data) => {
|
|
239
|
+
const output = data.toString();
|
|
240
|
+
if (output.includes('Running on')) {
|
|
241
|
+
clearTimeout(timeout);
|
|
242
|
+
serverReady = true;
|
|
243
|
+
resolve();
|
|
244
|
+
}
|
|
245
|
+
// In verbose mode, stream logs after server is ready
|
|
246
|
+
if (verbose && serverReady) {
|
|
247
|
+
process.stdout.write(output);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
expressProcess.stderr?.on('data', (data) => {
|
|
251
|
+
// In verbose mode, stream stderr after server is ready
|
|
252
|
+
if (verbose && serverReady) {
|
|
253
|
+
process.stderr.write(data);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
expressProcess.on('error', (err) => {
|
|
226
257
|
clearTimeout(timeout);
|
|
227
|
-
reject(new Error(`
|
|
228
|
-
}
|
|
258
|
+
reject(new Error(`Failed to spawn server: ${err.message}`));
|
|
259
|
+
});
|
|
260
|
+
expressProcess.on('exit', (code) => {
|
|
261
|
+
if (code !== 0 && code !== null) {
|
|
262
|
+
clearTimeout(timeout);
|
|
263
|
+
reject(new Error(`Server exited with code ${code}`));
|
|
264
|
+
}
|
|
265
|
+
});
|
|
229
266
|
});
|
|
230
|
-
}
|
|
231
|
-
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
270
|
+
if (s)
|
|
271
|
+
s.stop('Failed', 1);
|
|
272
|
+
p.log.error(message);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
// Stop spinner and show success
|
|
276
|
+
if (s) {
|
|
277
|
+
s.stop('Ready');
|
|
278
|
+
}
|
|
279
|
+
// Show access URL
|
|
232
280
|
const lanIp = getLanIp();
|
|
233
|
-
|
|
234
|
-
|
|
281
|
+
const url = `http://${lanIp}:${port}`;
|
|
282
|
+
const title = projectName ? `${projectName}` : 'access from any device';
|
|
283
|
+
if (verbose) {
|
|
284
|
+
log(`\n${brand(url)}`);
|
|
285
|
+
log(dim(title));
|
|
286
|
+
log(`\n${dim('press ctrl+c to stop')}\n`);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
p.log.success(`${brand(url)}`);
|
|
290
|
+
p.log.message(dim(title));
|
|
291
|
+
p.outro(dim('press ctrl+c to stop'));
|
|
292
|
+
}
|
|
235
293
|
// Open browser if requested
|
|
236
294
|
if (options.open) {
|
|
237
|
-
const
|
|
295
|
+
const localUrl = `http://localhost:${port}`;
|
|
238
296
|
const openCommand = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
239
|
-
spawn(openCommand, [
|
|
297
|
+
spawn(openCommand, [localUrl], { stdio: 'ignore', detached: true }).unref();
|
|
240
298
|
}
|
|
241
299
|
}
|
|
242
300
|
/**
|
|
243
301
|
* Graceful shutdown handler
|
|
244
302
|
*/
|
|
245
303
|
function shutdown() {
|
|
246
|
-
console.log('
|
|
304
|
+
console.log(''); // newline after ^C
|
|
305
|
+
console.log(dim('stopping...'));
|
|
247
306
|
if (expressProcess) {
|
|
248
307
|
expressProcess.kill('SIGTERM');
|
|
249
308
|
expressProcess = null;
|
|
@@ -252,22 +311,34 @@ function shutdown() {
|
|
|
252
311
|
opencodeServer.close();
|
|
253
312
|
opencodeServer = null;
|
|
254
313
|
}
|
|
314
|
+
console.log(`${brand('shella')} ${dim('stopped')}`);
|
|
255
315
|
process.exit(0);
|
|
256
316
|
}
|
|
257
317
|
// Handle shutdown signals
|
|
258
318
|
process.on('SIGINT', shutdown);
|
|
259
319
|
process.on('SIGTERM', shutdown);
|
|
260
320
|
// CLI definition
|
|
321
|
+
// Default verbose to true in Docker for easier debugging
|
|
322
|
+
const defaultVerbose = isDocker();
|
|
261
323
|
program
|
|
262
324
|
.name('shella')
|
|
263
|
-
.description('Self-hosted AI coding agents. Access from your phone.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
325
|
+
.description(`${brand('shella')} - Self-hosted AI coding agents. Access from your phone.
|
|
326
|
+
|
|
327
|
+
Modes:
|
|
328
|
+
${brand('shella')} Run in current directory (cwd mode)
|
|
329
|
+
${brand('shella')} --projects-dir . Register all subdirectories as projects (server mode)`)
|
|
330
|
+
.version(VERSION)
|
|
268
331
|
.option('-p, --port <port>', 'Web server port', '3067')
|
|
269
332
|
.option('--opencode-port <port>', 'OpenCode server port', '4096')
|
|
270
333
|
.option('--projects-dir <path>', 'Directory containing projects (each subdirectory becomes a project)')
|
|
271
334
|
.option('--no-open', "Don't open browser automatically")
|
|
272
|
-
.
|
|
335
|
+
.option('-v, --verbose', 'Show detailed logs during startup', defaultVerbose)
|
|
336
|
+
.option('--quiet', 'Suppress logs (opposite of --verbose)')
|
|
337
|
+
.action((options) => {
|
|
338
|
+
// --quiet overrides --verbose
|
|
339
|
+
if (options.quiet) {
|
|
340
|
+
options.verbose = false;
|
|
341
|
+
}
|
|
342
|
+
return startCommand(options);
|
|
343
|
+
});
|
|
273
344
|
program.parse();
|
package/dev/cli.tsx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* shella dev CLI - Ink-based development server orchestrator
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx devi Start interactive TUI (default)
|
|
7
|
+
* npx devi [command] Run command and exit
|
|
8
|
+
*
|
|
9
|
+
* Commands:
|
|
10
|
+
* status, start, stop, logs, errors, clear
|
|
11
|
+
* docker:up, docker:down, docker:status, etc.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export {}; // Make this a module for top-level await
|
|
15
|
+
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
|
|
18
|
+
if (args.length === 0) {
|
|
19
|
+
// Interactive mode - launch Ink TUI
|
|
20
|
+
const { runInteractive } = await import('./interactive.js');
|
|
21
|
+
await runInteractive();
|
|
22
|
+
} else {
|
|
23
|
+
// Non-interactive - run command and exit
|
|
24
|
+
const { runCommand } = await import('./core.js');
|
|
25
|
+
await runCommand(args);
|
|
26
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"plugin": ["opencode-openai-codex-auth"],
|
|
4
|
+
"provider": {
|
|
5
|
+
"openai": {
|
|
6
|
+
"options": {
|
|
7
|
+
"reasoningEffort": "medium",
|
|
8
|
+
"reasoningSummary": "auto",
|
|
9
|
+
"textVerbosity": "medium",
|
|
10
|
+
"include": ["reasoning.encrypted_content"],
|
|
11
|
+
"store": false
|
|
12
|
+
},
|
|
13
|
+
"models": {
|
|
14
|
+
"gpt-5.2": {
|
|
15
|
+
"name": "GPT 5.2 (OAuth)",
|
|
16
|
+
"limit": {
|
|
17
|
+
"context": 272000,
|
|
18
|
+
"output": 128000
|
|
19
|
+
},
|
|
20
|
+
"modalities": {
|
|
21
|
+
"input": ["text", "image"],
|
|
22
|
+
"output": ["text"]
|
|
23
|
+
},
|
|
24
|
+
"variants": {
|
|
25
|
+
"none": {
|
|
26
|
+
"reasoningEffort": "none",
|
|
27
|
+
"reasoningSummary": "auto",
|
|
28
|
+
"textVerbosity": "medium"
|
|
29
|
+
},
|
|
30
|
+
"low": {
|
|
31
|
+
"reasoningEffort": "low",
|
|
32
|
+
"reasoningSummary": "auto",
|
|
33
|
+
"textVerbosity": "medium"
|
|
34
|
+
},
|
|
35
|
+
"medium": {
|
|
36
|
+
"reasoningEffort": "medium",
|
|
37
|
+
"reasoningSummary": "auto",
|
|
38
|
+
"textVerbosity": "medium"
|
|
39
|
+
},
|
|
40
|
+
"high": {
|
|
41
|
+
"reasoningEffort": "high",
|
|
42
|
+
"reasoningSummary": "detailed",
|
|
43
|
+
"textVerbosity": "medium"
|
|
44
|
+
},
|
|
45
|
+
"xhigh": {
|
|
46
|
+
"reasoningEffort": "xhigh",
|
|
47
|
+
"reasoningSummary": "detailed",
|
|
48
|
+
"textVerbosity": "medium"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"gpt-5.2-codex": {
|
|
53
|
+
"name": "GPT 5.2 Codex (OAuth)",
|
|
54
|
+
"limit": {
|
|
55
|
+
"context": 272000,
|
|
56
|
+
"output": 128000
|
|
57
|
+
},
|
|
58
|
+
"modalities": {
|
|
59
|
+
"input": ["text", "image"],
|
|
60
|
+
"output": ["text"]
|
|
61
|
+
},
|
|
62
|
+
"variants": {
|
|
63
|
+
"low": {
|
|
64
|
+
"reasoningEffort": "low",
|
|
65
|
+
"reasoningSummary": "auto",
|
|
66
|
+
"textVerbosity": "medium"
|
|
67
|
+
},
|
|
68
|
+
"medium": {
|
|
69
|
+
"reasoningEffort": "medium",
|
|
70
|
+
"reasoningSummary": "auto",
|
|
71
|
+
"textVerbosity": "medium"
|
|
72
|
+
},
|
|
73
|
+
"high": {
|
|
74
|
+
"reasoningEffort": "high",
|
|
75
|
+
"reasoningSummary": "detailed",
|
|
76
|
+
"textVerbosity": "medium"
|
|
77
|
+
},
|
|
78
|
+
"xhigh": {
|
|
79
|
+
"reasoningEffort": "xhigh",
|
|
80
|
+
"reasoningSummary": "detailed",
|
|
81
|
+
"textVerbosity": "medium"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"gpt-5.1-codex-max": {
|
|
86
|
+
"name": "GPT 5.1 Codex Max (OAuth)",
|
|
87
|
+
"limit": {
|
|
88
|
+
"context": 272000,
|
|
89
|
+
"output": 128000
|
|
90
|
+
},
|
|
91
|
+
"modalities": {
|
|
92
|
+
"input": ["text", "image"],
|
|
93
|
+
"output": ["text"]
|
|
94
|
+
},
|
|
95
|
+
"variants": {
|
|
96
|
+
"low": {
|
|
97
|
+
"reasoningEffort": "low",
|
|
98
|
+
"reasoningSummary": "detailed",
|
|
99
|
+
"textVerbosity": "medium"
|
|
100
|
+
},
|
|
101
|
+
"medium": {
|
|
102
|
+
"reasoningEffort": "medium",
|
|
103
|
+
"reasoningSummary": "detailed",
|
|
104
|
+
"textVerbosity": "medium"
|
|
105
|
+
},
|
|
106
|
+
"high": {
|
|
107
|
+
"reasoningEffort": "high",
|
|
108
|
+
"reasoningSummary": "detailed",
|
|
109
|
+
"textVerbosity": "medium"
|
|
110
|
+
},
|
|
111
|
+
"xhigh": {
|
|
112
|
+
"reasoningEffort": "xhigh",
|
|
113
|
+
"reasoningSummary": "detailed",
|
|
114
|
+
"textVerbosity": "medium"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"gpt-5.1-codex": {
|
|
119
|
+
"name": "GPT 5.1 Codex (OAuth)",
|
|
120
|
+
"limit": {
|
|
121
|
+
"context": 272000,
|
|
122
|
+
"output": 128000
|
|
123
|
+
},
|
|
124
|
+
"modalities": {
|
|
125
|
+
"input": ["text", "image"],
|
|
126
|
+
"output": ["text"]
|
|
127
|
+
},
|
|
128
|
+
"variants": {
|
|
129
|
+
"low": {
|
|
130
|
+
"reasoningEffort": "low",
|
|
131
|
+
"reasoningSummary": "auto",
|
|
132
|
+
"textVerbosity": "medium"
|
|
133
|
+
},
|
|
134
|
+
"medium": {
|
|
135
|
+
"reasoningEffort": "medium",
|
|
136
|
+
"reasoningSummary": "auto",
|
|
137
|
+
"textVerbosity": "medium"
|
|
138
|
+
},
|
|
139
|
+
"high": {
|
|
140
|
+
"reasoningEffort": "high",
|
|
141
|
+
"reasoningSummary": "detailed",
|
|
142
|
+
"textVerbosity": "medium"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"gpt-5.1-codex-mini": {
|
|
147
|
+
"name": "GPT 5.1 Codex Mini (OAuth)",
|
|
148
|
+
"limit": {
|
|
149
|
+
"context": 272000,
|
|
150
|
+
"output": 128000
|
|
151
|
+
},
|
|
152
|
+
"modalities": {
|
|
153
|
+
"input": ["text", "image"],
|
|
154
|
+
"output": ["text"]
|
|
155
|
+
},
|
|
156
|
+
"variants": {
|
|
157
|
+
"medium": {
|
|
158
|
+
"reasoningEffort": "medium",
|
|
159
|
+
"reasoningSummary": "auto",
|
|
160
|
+
"textVerbosity": "medium"
|
|
161
|
+
},
|
|
162
|
+
"high": {
|
|
163
|
+
"reasoningEffort": "high",
|
|
164
|
+
"reasoningSummary": "detailed",
|
|
165
|
+
"textVerbosity": "medium"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
"gpt-5.1": {
|
|
170
|
+
"name": "GPT 5.1 (OAuth)",
|
|
171
|
+
"limit": {
|
|
172
|
+
"context": 272000,
|
|
173
|
+
"output": 128000
|
|
174
|
+
},
|
|
175
|
+
"modalities": {
|
|
176
|
+
"input": ["text", "image"],
|
|
177
|
+
"output": ["text"]
|
|
178
|
+
},
|
|
179
|
+
"variants": {
|
|
180
|
+
"none": {
|
|
181
|
+
"reasoningEffort": "none",
|
|
182
|
+
"reasoningSummary": "auto",
|
|
183
|
+
"textVerbosity": "medium"
|
|
184
|
+
},
|
|
185
|
+
"low": {
|
|
186
|
+
"reasoningEffort": "low",
|
|
187
|
+
"reasoningSummary": "auto",
|
|
188
|
+
"textVerbosity": "low"
|
|
189
|
+
},
|
|
190
|
+
"medium": {
|
|
191
|
+
"reasoningEffort": "medium",
|
|
192
|
+
"reasoningSummary": "auto",
|
|
193
|
+
"textVerbosity": "medium"
|
|
194
|
+
},
|
|
195
|
+
"high": {
|
|
196
|
+
"reasoningEffort": "high",
|
|
197
|
+
"reasoningSummary": "detailed",
|
|
198
|
+
"textVerbosity": "high"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
declare const CONFIG_DIR: string;
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
2
|
+
export declare const MODE: "cwd" | "server";
|
|
3
|
+
export declare const DIRECTORY: string | null;
|
|
4
|
+
export declare const PROJECTS_DIR: string | null;
|
|
5
|
+
export declare const VERSION = "0.1.4";
|
|
4
6
|
export { CONFIG_DIR };
|