@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.
Files changed (89) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +153 -82
  3. package/dev/cli.tsx +26 -0
  4. package/dist/config/openai-codex-models.json +205 -0
  5. package/dist/server/index.d.ts +4 -2
  6. package/dist/server/index.js +66 -8
  7. package/dist/server/lib/opencode-client.d.ts +14 -0
  8. package/dist/server/lib/opencode-client.js +17 -0
  9. package/dist/server/lib/opencode-config.d.ts +14 -0
  10. package/dist/server/lib/opencode-config.js +25 -0
  11. package/dist/server/routes/config.d.ts +12 -0
  12. package/dist/server/routes/config.js +207 -0
  13. package/dist/server/routes/init.d.ts +4 -3
  14. package/dist/server/routes/init.js +23 -9
  15. package/dist/server/routes/local-llm.d.ts +8 -0
  16. package/dist/server/routes/local-llm.js +255 -0
  17. package/dist/server/routes/logs.js +35 -11
  18. package/dist/server/routes/prompt.d.ts +16 -0
  19. package/dist/server/routes/prompt.js +173 -0
  20. package/dist/server/routes/session.d.ts +8 -0
  21. package/dist/server/routes/session.js +63 -0
  22. package/dist/server/routes/status.d.ts +9 -0
  23. package/dist/server/routes/status.js +54 -0
  24. package/dist/server/routes/usage.d.ts +12 -0
  25. package/dist/server/routes/usage.js +60 -0
  26. package/dist/server/routes/windows.js +4 -4
  27. package/dist/server/schema.d.ts +47 -16
  28. package/dist/server/schema.js +8 -1
  29. package/dist/server/services/database.d.ts +10 -1
  30. package/dist/server/services/database.js +19 -6
  31. package/dist/web/assets/{_baseUniq-BXqY9Mam.js → _baseUniq-6T01QAux.js} +1 -1
  32. package/dist/web/assets/{arc-Bn6tUpO_.js → arc-BkH3TPJb.js} +1 -1
  33. package/dist/web/assets/{architectureDiagram-VXUJARFQ-C7FAApUY.js → architectureDiagram-VXUJARFQ-BSi6BLCC.js} +1 -1
  34. package/dist/web/assets/{blockDiagram-VD42YOAC-C2fdaEWa.js → blockDiagram-VD42YOAC-QSPUbinO.js} +1 -1
  35. package/dist/web/assets/{c4Diagram-YG6GDRKO-FEVzhARQ.js → c4Diagram-YG6GDRKO-Cya_BihR.js} +1 -1
  36. package/dist/web/assets/channel-DGAtS-pa.js +1 -0
  37. package/dist/web/assets/{chunk-4BX2VUAB-DLekcSAU.js → chunk-4BX2VUAB-DIL6eizv.js} +1 -1
  38. package/dist/web/assets/{chunk-55IACEB6-8hFRjyTP.js → chunk-55IACEB6-CgwejoZz.js} +1 -1
  39. package/dist/web/assets/{chunk-B4BG7PRW-DULC9-MQ.js → chunk-B4BG7PRW-9mIPqoGe.js} +1 -1
  40. package/dist/web/assets/{chunk-DI55MBZ5-DuOE5RH1.js → chunk-DI55MBZ5-BRbyRfgT.js} +1 -1
  41. package/dist/web/assets/{chunk-FMBD7UC4-DaDNiCk7.js → chunk-FMBD7UC4-CVBT25Fj.js} +1 -1
  42. package/dist/web/assets/{chunk-QN33PNHL-CKshfIHj.js → chunk-QN33PNHL-rTj-WT2G.js} +1 -1
  43. package/dist/web/assets/{chunk-QZHKN3VN-D2Qy0tdi.js → chunk-QZHKN3VN-BaUBiHya.js} +1 -1
  44. package/dist/web/assets/{chunk-TZMSLE5B-SPxkj-lp.js → chunk-TZMSLE5B-C4_O5TI-.js} +1 -1
  45. package/dist/web/assets/classDiagram-2ON5EDUG-DLvlUUJq.js +1 -0
  46. package/dist/web/assets/classDiagram-v2-WZHVMYZB-DLvlUUJq.js +1 -0
  47. package/dist/web/assets/clone-BZW2JABw.js +1 -0
  48. package/dist/web/assets/{code-block-QI2IAROF-BZdAQmZ2.js → code-block-QI2IAROF-Bj_2OIYt.js} +1 -1
  49. package/dist/web/assets/{cose-bilkent-S5V4N54A-DbasixUk.js → cose-bilkent-S5V4N54A-T7a1luWi.js} +1 -1
  50. package/dist/web/assets/{dagre-6UL2VRFP-CStyjTc9.js → dagre-6UL2VRFP-CeH5ZsdW.js} +1 -1
  51. package/dist/web/assets/{diagram-PSM6KHXK-Crk93U8d.js → diagram-PSM6KHXK-Cdod2Lna.js} +1 -1
  52. package/dist/web/assets/{diagram-QEK2KX5R-DiW6RNbg.js → diagram-QEK2KX5R-CYks2r54.js} +1 -1
  53. package/dist/web/assets/{diagram-S2PKOQOG-CKksz_qL.js → diagram-S2PKOQOG-DCmy0g7p.js} +1 -1
  54. package/dist/web/assets/{erDiagram-Q2GNP2WA-CisACqqq.js → erDiagram-Q2GNP2WA-Dlz1bNvI.js} +1 -1
  55. package/dist/web/assets/{flowDiagram-NV44I4VS-BBp_5zAe.js → flowDiagram-NV44I4VS-Di5Iit1B.js} +1 -1
  56. package/dist/web/assets/{ganttDiagram-JELNMOA3-BKZ30gLA.js → ganttDiagram-JELNMOA3-9i1dugg-.js} +1 -1
  57. package/dist/web/assets/{gitGraphDiagram-NY62KEGX-ClizxUXq.js → gitGraphDiagram-NY62KEGX-BORbMVri.js} +1 -1
  58. package/dist/web/assets/{graph-DqhaNOTU.js → graph-C0SCKxbQ.js} +1 -1
  59. package/dist/web/assets/index-CYVJT8rN.js +1 -0
  60. package/dist/web/assets/index-CcAJUkQw.css +1 -0
  61. package/dist/web/assets/index-CcDdxbB-.js +1719 -0
  62. package/dist/web/assets/{infoDiagram-WHAUD3N6-BQwNR0md.js → infoDiagram-WHAUD3N6-7ohMQFLY.js} +1 -1
  63. package/dist/web/assets/{journeyDiagram-XKPGCS4Q-YOqPPID4.js → journeyDiagram-XKPGCS4Q-DZp7Z7wE.js} +1 -1
  64. package/dist/web/assets/{kanban-definition-3W4ZIXB7-Dtu8bvBx.js → kanban-definition-3W4ZIXB7-BCNLCm54.js} +1 -1
  65. package/dist/web/assets/{layout-Cc1ESzTe.js → layout-AUnZuY21.js} +1 -1
  66. package/dist/web/assets/{linear-BwI2ANFG.js → linear-B0bfAqGt.js} +1 -1
  67. package/dist/web/assets/{mermaid.core-npIGP8NS.js → mermaid.core-D5fXNCxA.js} +5 -5
  68. package/dist/web/assets/{min--MKscDc6.js → min-BZUFOEEw.js} +1 -1
  69. package/dist/web/assets/{mindmap-definition-VGOIOE7T-Cr39Vhym.js → mindmap-definition-VGOIOE7T-hEGJLJ8N.js} +1 -1
  70. package/dist/web/assets/{pieDiagram-ADFJNKIX-Cv8ke00t.js → pieDiagram-ADFJNKIX-BRpCTJIO.js} +1 -1
  71. package/dist/web/assets/{quadrantDiagram-AYHSOK5B-BPhHaTg8.js → quadrantDiagram-AYHSOK5B-m7jaiHQb.js} +1 -1
  72. package/dist/web/assets/{requirementDiagram-UZGBJVZJ-Cc42SoK0.js → requirementDiagram-UZGBJVZJ-Coh9g9Sp.js} +1 -1
  73. package/dist/web/assets/{sankeyDiagram-TZEHDZUN-CtgBuq8T.js → sankeyDiagram-TZEHDZUN-CrD_kUGR.js} +1 -1
  74. package/dist/web/assets/{sequenceDiagram-WL72ISMW-B9lNGN6V.js → sequenceDiagram-WL72ISMW-C04yD1EI.js} +1 -1
  75. package/dist/web/assets/{stateDiagram-FKZM4ZOC-C3dRTOMb.js → stateDiagram-FKZM4ZOC-DhP-DMZW.js} +1 -1
  76. package/dist/web/assets/stateDiagram-v2-4FDKWEC3-DWi5vrD6.js +1 -0
  77. package/dist/web/assets/{timeline-definition-IT6M3QCI-CXhSuTlt.js → timeline-definition-IT6M3QCI-40iW2p_5.js} +1 -1
  78. package/dist/web/assets/{treemap-KMMF4GRG-Csy25Uov.js → treemap-KMMF4GRG-BnxWQbzt.js} +1 -1
  79. package/dist/web/assets/welcome-screen-test-CLeWuIqq.js +1 -0
  80. package/dist/web/assets/{xychartDiagram-PRI3JC2R-CxEERqse.js → xychartDiagram-PRI3JC2R-D6lcJDCc.js} +1 -1
  81. package/dist/web/index.html +3 -3
  82. package/package.json +14 -5
  83. package/dist/web/assets/channel-CxjnQtV7.js +0 -1
  84. package/dist/web/assets/classDiagram-2ON5EDUG-CVG91-fs.js +0 -1
  85. package/dist/web/assets/classDiagram-v2-WZHVMYZB-CVG91-fs.js +0 -1
  86. package/dist/web/assets/clone-C7jxvixc.js +0 -1
  87. package/dist/web/assets/index-B0jWvqrS.css +0 -1
  88. package/dist/web/assets/index-Dnmavb3d.js +0 -1716
  89. package/dist/web/assets/stateDiagram-v2-4FDKWEC3-oHTO1yj_.js +0 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Shella
1
+ # shella
2
2
 
3
3
  Self-hosted AI coding agents. Access from your phone.
4
4
 
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.3';
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
- console.error(`Error: Projects directory not found: ${absDir}`);
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
- console.error(`Error: No subdirectories found in ${absDir}`);
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
- // Docker requires --projects-dir
147
- if (isDocker() && !options.projectsDir) {
148
- console.error('Error: --projects-dir is required when running in Docker.\n');
149
- console.error('Example:');
150
- console.error(' docker run -v ~/code:/project shella --projects-dir /project\n');
151
- console.error('Or in docker-compose.yml:');
152
- console.error(' command: ["--projects-dir", "/project"]');
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 projects directory to pass to the server
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
- console.error(`Error: Port ${port} is already in use.`);
162
- console.error(`\nTry: shella --port 3070`);
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
- console.error(`Error: Port ${opencodePort} is already in use (OpenCode).`);
167
- console.error(`\nTry: pkill -f "opencode serve" or shella --opencode-port 4097`);
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
- // Show minimal startup message
178
- console.log('Starting Shella...');
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
- console.error(`Failed to start OpenCode: ${message}`);
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 projectsDir
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
- expressProcess = spawn('node', [serverPath, '--projects-dir', projectsDir], {
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
- await new Promise((resolve, reject) => {
206
- const timeout = setTimeout(() => {
207
- reject(new Error('Server startup timeout'));
208
- }, 10000);
209
- expressProcess.stdout?.on('data', (data) => {
210
- const output = data.toString();
211
- if (output.includes('Running on')) {
212
- clearTimeout(timeout);
213
- resolve();
214
- }
215
- // Suppress all server output
216
- });
217
- expressProcess.stderr?.on('data', () => {
218
- // Suppress stderr too
219
- });
220
- expressProcess.on('error', (err) => {
221
- clearTimeout(timeout);
222
- reject(new Error(`Failed to spawn server: ${err.message}`));
223
- });
224
- expressProcess.on('exit', (code) => {
225
- if (code !== 0 && code !== null) {
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(`Server exited with code ${code}`));
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
- // Print access box
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
- printAccessBox(lanIp, port, projectName);
234
- console.log('Press Ctrl+C to stop');
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 url = `http://localhost:${port}`;
295
+ const localUrl = `http://localhost:${port}`;
238
296
  const openCommand = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
239
- spawn(openCommand, [url], { stdio: 'ignore', detached: true }).unref();
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('\nStopping...');
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
- .version(VERSION);
265
- program
266
- .command('start', { isDefault: true })
267
- .description('Start Shella')
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
- .action(startCommand);
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
+ }
@@ -1,4 +1,6 @@
1
1
  declare const CONFIG_DIR: string;
2
- export declare const PROJECTS_DIR: string;
3
- export declare const VERSION = "0.1.3";
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 };