agentp 0.3.0 → 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/bin/ocmux CHANGED
@@ -6,6 +6,16 @@ const crypto = require('crypto');
6
6
  const readline = require('readline');
7
7
  const { spawnSync, execSync } = require('child_process');
8
8
  const os = require('os');
9
+ const {
10
+ readState,
11
+ statefileFor,
12
+ tuiPaneId,
13
+ windowByDir,
14
+ windowNameByIndex,
15
+ activeWindowIndex,
16
+ paneCount,
17
+ listServers,
18
+ } = require('../lib/ocmux');
9
19
 
10
20
  // ---------------------------------------------------------------------------
11
21
  // Constants
@@ -43,18 +53,6 @@ function logfileFor(dir) {
43
53
  return `/tmp/opencode-serve-${hashDir(dir)}.log`;
44
54
  }
45
55
 
46
- function statefileFor(dir) {
47
- return path.join(dir, '.ocmux.json');
48
- }
49
-
50
- function readState(file) {
51
- try {
52
- return JSON.parse(fs.readFileSync(file, 'utf8'));
53
- } catch {
54
- return null;
55
- }
56
- }
57
-
58
56
  function findParentStatefile(startDir) {
59
57
  let dir = path.resolve(startDir);
60
58
  while (true) {
@@ -98,7 +96,7 @@ function resolveGitDir(startDir, requireDir, flagLabel) {
98
96
  if (!result.dir) {
99
97
  const label = requireDir ? '.git directory' : '.git entry';
100
98
  console.error(`Error: no ${label} found in ${startDir} or any of its parents.`);
101
- console.error(`Run '${SCRIPT} new' without ${flagLabel} to create a server in the current directory.`);
99
+ console.error(`Run '${SCRIPT} serve' without ${flagLabel} to create a server in the current directory.`);
102
100
  process.exit(1);
103
101
  }
104
102
 
@@ -112,52 +110,6 @@ function resolveGitDir(startDir, requireDir, flagLabel) {
112
110
  return result.dir;
113
111
  }
114
112
 
115
- function windowByDir(dir) {
116
- const r = tmux(['list-windows', '-t', SESSION, '-F', '#{window_index}\t#{window_name}']);
117
- if (r.status !== 0) return null;
118
- for (const line of r.stdout.trim().split('\n').filter(Boolean)) {
119
- const [idx, name] = line.split('\t');
120
- if (name === dir) return parseInt(idx, 10);
121
- }
122
- return null;
123
- }
124
-
125
- function windowNameByIndex(idx) {
126
- const r = tmux(['list-windows', '-t', SESSION, '-F', '#{window_index}\t#{window_name}']);
127
- if (r.status !== 0) return null;
128
- for (const line of r.stdout.trim().split('\n').filter(Boolean)) {
129
- const [wi, name] = line.split('\t');
130
- if (parseInt(wi, 10) === idx) return name;
131
- }
132
- return null;
133
- }
134
-
135
- function activeWindowIndex() {
136
- const r = tmux(['list-windows', '-t', SESSION, '-F', '#{window_index} #{window_active}']);
137
- if (r.status !== 0) return null;
138
- for (const line of r.stdout.trim().split('\n').filter(Boolean)) {
139
- const [idx, active] = line.split(' ');
140
- if (active === '1') return parseInt(idx, 10);
141
- }
142
- return null;
143
- }
144
-
145
- function tuiPaneId(windowIndex) {
146
- const r = tmux(['list-panes', '-t', `${SESSION}:${windowIndex}`, '-F', '#{pane_id}\t#{pane_current_command}']);
147
- if (r.status !== 0) return null;
148
- for (const line of r.stdout.trim().split('\n').filter(Boolean)) {
149
- const [paneId, cmd] = line.split('\t');
150
- if (cmd === 'node') return paneId;
151
- }
152
- return null;
153
- }
154
-
155
- function paneCount(windowIndex) {
156
- const r = tmux(['list-panes', '-t', `${SESSION}:${windowIndex}`, '-F', '#{pane_id}']);
157
- if (r.status !== 0) return 0;
158
- return r.stdout.trim().split('\n').filter(Boolean).length;
159
- }
160
-
161
113
  function findWindowsByBasename(name) {
162
114
  const r = tmux(['list-windows', '-t', SESSION, '-F', '#{window_name}']);
163
115
  if (r.status !== 0) return [];
@@ -233,7 +185,8 @@ function printHelp() {
233
185
  console.error('that session is used.');
234
186
  console.error('');
235
187
  console.error('Subcommands:');
236
- console.error(' new Create a new server in <directory> (default: $PWD).');
188
+ console.error(' serve Create a server in <directory> (default: $PWD) and attach a TUI.');
189
+ console.error(' Aliased as "new" for backwards compatibility.');
237
190
  console.error(' Errors if one already exists there.');
238
191
  console.error(' Warns if a parent directory already has a server.');
239
192
  console.error(' --git Use the nearest parent with a .git entry (file or dir).');
@@ -247,7 +200,7 @@ function printHelp() {
247
200
  console.error(' a subcommand name (e.g. "ocmux -- kill").');
248
201
  console.error('');
249
202
  console.error('Options:');
250
- console.error(' -l Long output: append " → <dir>" after the URL (new/default).');
203
+ console.error(' -l Long output: append " → <dir>" after the URL (serve/default).');
251
204
  console.error(' --version Show version');
252
205
  console.error(' -h Show this help');
253
206
  console.error('');
@@ -275,7 +228,7 @@ function cmdSwitch(statefile, longMode) {
275
228
  const found = windowByDir(serverDir);
276
229
  if (found == null) {
277
230
  console.error(`Error: tmux window for ${serverDir} not found`);
278
- console.error("Run 'ocmux new' to create a new server.");
231
+ console.error("Run 'ocmux serve' to create a new server.");
279
232
  process.exit(1);
280
233
  }
281
234
  winIdx = found;
@@ -315,29 +268,7 @@ function cmdSwitch(statefile, longMode) {
315
268
  // ---------------------------------------------------------------------------
316
269
 
317
270
  function cmdList() {
318
- const sessionR = tmux(['has-session', '-t', SESSION]);
319
- if (sessionR.status !== 0) {
320
- console.log('No opencode servers running.');
321
- return;
322
- }
323
-
324
- const listR = tmux(['list-windows', '-t', SESSION, '-F', '#{window_index}\t#{window_name}']);
325
- if (listR.status !== 0 || !listR.stdout.trim()) {
326
- console.log('No opencode servers running.');
327
- return;
328
- }
329
-
330
- const rows = [];
331
- for (const line of listR.stdout.trim().split('\n').filter(Boolean)) {
332
- const [idx, dir] = line.split('\t');
333
- const sf = path.join(dir, '.ocmux.json');
334
- if (!fs.existsSync(sf)) continue;
335
- const state = readState(sf);
336
- if (!state || !state.url) continue;
337
- const tui = tuiPaneId(parseInt(idx, 10));
338
- rows.push({ url: state.url, dir, status: tui ? 'alive' : 'dead' });
339
- }
340
-
271
+ const rows = listServers();
341
272
  if (rows.length === 0) {
342
273
  console.log('No opencode servers running.');
343
274
  return;
@@ -393,13 +324,28 @@ function doNew(targetDir, longMode) {
393
324
 
394
325
  console.error(`Starting opencode server for ${targetDir}...`);
395
326
 
396
- const rc = tmux(['new-window', '-d', '-t', SESSION, '-n', targetDir, '-c', targetDir,
397
- `opencode serve --port 0 2>&1 | tee '${lf}'`]);
327
+ // Create window with interactive shell so .bashrc/.zshrc is sourced
328
+ const rc = tmux(['new-window', '-d', '-t', SESSION, '-n', targetDir, '-c', targetDir]);
398
329
  if (rc.status !== 0) {
399
330
  console.error(`Error: failed to create tmux window: ${rc.stderr}`);
400
331
  process.exit(1);
401
332
  }
402
333
 
334
+ const winIdx = windowByDir(targetDir);
335
+ if (winIdx == null) {
336
+ console.error('Error: window was not created properly');
337
+ process.exit(1);
338
+ }
339
+
340
+ pinWindowName(winIdx);
341
+
342
+ // Truncate log file so stale URL from previous run can't be matched by polling
343
+ try { fs.truncateSync(lf); } catch { /* may not exist yet */ }
344
+
345
+ // Send the command to the interactive shell (like typing it manually)
346
+ tmux(['send-keys', '-t', `${SESSION}:${winIdx}.0`,
347
+ `opencode serve --port 0 2>&1 | tee '${lf}'`, 'Enter']);
348
+
403
349
  // Wait up to 10s for the URL in the logfile
404
350
  let url = null;
405
351
  for (let i = 0; i < 50; i++) {
@@ -416,21 +362,16 @@ function doNew(targetDir, longMode) {
416
362
  process.exit(1);
417
363
  }
418
364
 
419
- const winIdx = windowByDir(targetDir);
420
- if (winIdx == null) {
421
- console.error('Error: window was not created properly');
422
- process.exit(1);
423
- }
424
-
425
- pinWindowName(winIdx);
426
-
427
365
  const state = { url, logfile: lf, window_index: winIdx };
428
366
  fs.writeFileSync(path.join(targetDir, '.ocmux.json'), JSON.stringify(state) + '\n');
429
367
 
430
- tmux(['split-window', '-v', '-t', `${SESSION}:${winIdx}`, '-c', targetDir,
431
- `opencode attach --continue '${url}'`]);
368
+ const splitResult = tmux(['split-window', '-v', '-P', '-F', '#{pane_id}', '-t', `${SESSION}:${winIdx}`, '-c', targetDir]);
369
+ const tuiPane = splitResult.stdout.trim();
370
+ sleep(0.2);
371
+ tmux(['send-keys', '-t', tuiPane,
372
+ `opencode attach --continue '${url}'`, 'Enter']);
432
373
  sleep(0.5);
433
- ensureTuiZoomed(winIdx);
374
+ if (tuiPane) tmux(['resize-pane', '-Z', '-t', tuiPane]);
434
375
 
435
376
  const activeWin = activeWindowIndex();
436
377
  if (activeWin !== winIdx) {
@@ -515,8 +456,10 @@ function main() {
515
456
  } else if (arg === '--') {
516
457
  dirIsForced = true;
517
458
  } else if (arg.startsWith('-')) {
518
- if (arg === '--new' || arg === '--kill') {
519
- console.error(`Error: '${arg}' is no longer a flag. Use 'ocmux ${arg.slice(2)}' instead.`);
459
+ if (arg === '--new') {
460
+ console.error("Error: '--new' is no longer a flag. Use 'ocmux serve' instead.");
461
+ } else if (arg === '--kill') {
462
+ console.error("Error: '--kill' is no longer a flag. Use 'ocmux kill' instead.");
520
463
  } else {
521
464
  console.error(`Error: unknown option '${arg}'`);
522
465
  console.error(`Run '${SCRIPT} -h' for help.`);
@@ -538,7 +481,7 @@ function main() {
538
481
  process.exit(1);
539
482
  }
540
483
  targetDir = resolveDir(arg);
541
- } else if (arg === 'new' || arg === 'kill' || arg === 'list') {
484
+ } else if (arg === 'serve' || arg === 'new' || arg === 'kill' || arg === 'list') {
542
485
  if (subcommand) {
543
486
  console.error(`Error: multiple subcommands specified: '${subcommand}' and '${arg}'`);
544
487
  process.exit(1);
@@ -558,7 +501,7 @@ function main() {
558
501
  // Dispatch
559
502
  if (subcommand === 'list') {
560
503
  if (gitMode) {
561
- console.error("Error: '--git' and '--GIT' are only valid with 'new'.");
504
+ console.error("Error: '--git' and '--GIT' are only valid with 'serve'.");
562
505
  process.exit(1);
563
506
  }
564
507
  if (targetDir !== process.cwd()) {
@@ -568,17 +511,17 @@ function main() {
568
511
  cmdList();
569
512
  } else if (subcommand === 'kill') {
570
513
  if (gitMode) {
571
- console.error("Error: '--git' and '--GIT' are only valid with 'new'.");
514
+ console.error("Error: '--git' and '--GIT' are only valid with 'serve'.");
572
515
  process.exit(1);
573
516
  }
574
517
  const sf = findParentStatefile(targetDir);
575
518
  if (!sf) {
576
519
  console.error(`Error: no opencode server found in ${targetDir} or any of its parents.`);
577
- console.error("Run 'ocmux new' to create a new server.");
520
+ console.error("Run 'ocmux serve' to create a new server.");
578
521
  process.exit(1);
579
522
  }
580
523
  cmdKill(sf);
581
- } else if (subcommand === 'new') {
524
+ } else if (subcommand === 'serve' || subcommand === 'new') {
582
525
  if (gitMode) {
583
526
  const flagLabel = gitMode === 'root' ? '--GIT' : '--git';
584
527
  targetDir = resolveGitDir(targetDir, gitMode === 'root', flagLabel);
@@ -586,14 +529,14 @@ function main() {
586
529
  cmdNew(targetDir, longMode);
587
530
  } else {
588
531
  if (gitMode) {
589
- console.error("Error: '--git' and '--GIT' are only valid with 'new'.");
532
+ console.error("Error: '--git' and '--GIT' are only valid with 'serve'.");
590
533
  process.exit(1);
591
534
  }
592
535
  // Default mode
593
536
  const sf = findParentStatefile(targetDir);
594
537
  if (!sf) {
595
538
  console.error(`Error: no opencode server found in ${targetDir} or any of its parents.`);
596
- console.error("Run 'ocmux new' to create a new server.");
539
+ console.error("Run 'ocmux serve' to create a new server.");
597
540
  process.exit(1);
598
541
  }
599
542
  ensureSession();