agentp 0.3.0 → 0.4.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/README.md CHANGED
@@ -41,6 +41,7 @@ Notes:
41
41
  - `agentp` connects to the OpenCode event endpoint over HTTP.
42
42
  - In practice this means running `opencode serve` (or equivalent serve mode) so the port is open.
43
43
  - `opencode attach` is optional but useful to monitor the full conversation in another terminal/tmux pane.
44
+ - If the OpenCode server is password-protected (`OPENCODE_SERVER_PASSWORD`), both `agentp` and `ocmux` automatically send the required HTTP Basic Auth credentials.
44
45
 
45
46
  ## Usage
46
47
 
@@ -121,7 +122,7 @@ Without arguments, searches upward from `<directory>` (default: `$PWD`) for `.oc
121
122
 
122
123
  Subcommands:
123
124
 
124
- - **`new [--git|--GIT] [dir]`** — Create a new server in `dir` (default: `$PWD`). Errors if one already exists there. Warns if a parent directory already has a server.
125
+ - **`serve [--git|--GIT] [dir]`** — Create a server in `dir` (default: `$PWD`) and attach a TUI pane. Aliased as `new` for backwards compatibility. Errors if one already exists there. Warns if a parent directory already has a server.
125
126
  - `--git` resolves `dir` to the nearest parent with a `.git` entry (file or dir); errors if none is found.
126
127
  - `--GIT` resolves `dir` to the nearest parent with a `.git` directory only; errors if none is found.
127
128
  - **`kill [dir]`** — Kill the server found upward from `dir`. Removes its tmux window and state file.
@@ -129,7 +130,7 @@ Subcommands:
129
130
 
130
131
  Options:
131
132
 
132
- - `-l`: long output for default/new commands (append `→ <dir>` after the URL)
133
+ - `-l`: long output for default/serve commands (append `→ <dir>` after the URL)
133
134
  - `--version`: show version
134
135
  - `-h`: show help message
135
136
  - `--`: treat the next argument as a directory even if it matches a subcommand name
package/bin/agentp CHANGED
@@ -4,12 +4,26 @@ const http = require('http');
4
4
  const { URL } = require('url');
5
5
  const { version } = require('../package.json');
6
6
 
7
+ function getAuthHeaders() {
8
+ const password = process.env.OPENCODE_SERVER_PASSWORD;
9
+ if (!password) return {};
10
+ const username = process.env.OPENCODE_SERVER_USERNAME || 'opencode';
11
+ const encoded = Buffer.from(`${username}:${password}`).toString('base64');
12
+ return { 'Authorization': `Basic ${encoded}` };
13
+ }
14
+
7
15
  function makeRequest(options, data) {
8
16
  return new Promise((resolve, reject) => {
9
17
  const req = http.request(options, (res) => {
10
18
  let body = '';
11
19
  res.on('data', (chunk) => { body += chunk; });
12
- res.on('end', () => resolve({ status: res.statusCode, body }));
20
+ res.on('end', () => {
21
+ if (res.statusCode === 401) {
22
+ reject(new Error('OpenCode server authentication failed. Set OPENCODE_SERVER_PASSWORD to match the server password.'));
23
+ } else {
24
+ resolve({ status: res.statusCode, body });
25
+ }
26
+ });
13
27
  });
14
28
  req.on('error', reject);
15
29
  if (data) req.write(data);
@@ -26,7 +40,8 @@ function buildJsonRequest(url, method, body) {
26
40
  method: method,
27
41
  headers: {
28
42
  'Content-Type': 'application/json',
29
- 'Content-Length': Buffer.byteLength(body)
43
+ 'Content-Length': Buffer.byteLength(body),
44
+ ...getAuthHeaders()
30
45
  }
31
46
  };
32
47
  }
@@ -80,8 +95,13 @@ function listenForFinalAnswer(server, showFinalRow) {
80
95
  port: parsed.port,
81
96
  path: parsed.pathname,
82
97
  method: 'GET',
83
- headers: { 'Accept': 'text/event-stream' }
98
+ headers: { 'Accept': 'text/event-stream', ...getAuthHeaders() }
84
99
  }, (res) => {
100
+ if (res.statusCode === 401) {
101
+ res.resume();
102
+ reject(new Error('OpenCode server authentication failed. Set OPENCODE_SERVER_PASSWORD to match the server password.'));
103
+ return;
104
+ }
85
105
  let buffer = '';
86
106
  const userMessageIDs = new Set();
87
107
 
@@ -202,6 +222,11 @@ async function main() {
202
222
  }
203
223
 
204
224
  await listenForFinalAnswer(serverBase, qaMode);
225
+
226
+ if (!process.env.OPENCODE_SERVER_PASSWORD) {
227
+ process.stderr.write('\n');
228
+ process.stderr.write('🔓 ⚠️ Server is not password-protected! Set OPENCODE_SERVER_PASSWORD for better security.\n');
229
+ }
205
230
  }
206
231
 
207
232
  main().catch((err) => {
package/bin/ocmux CHANGED
@@ -98,7 +98,7 @@ function resolveGitDir(startDir, requireDir, flagLabel) {
98
98
  if (!result.dir) {
99
99
  const label = requireDir ? '.git directory' : '.git entry';
100
100
  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.`);
101
+ console.error(`Run '${SCRIPT} serve' without ${flagLabel} to create a server in the current directory.`);
102
102
  process.exit(1);
103
103
  }
104
104
 
@@ -233,7 +233,8 @@ function printHelp() {
233
233
  console.error('that session is used.');
234
234
  console.error('');
235
235
  console.error('Subcommands:');
236
- console.error(' new Create a new server in <directory> (default: $PWD).');
236
+ console.error(' serve Create a server in <directory> (default: $PWD) and attach a TUI.');
237
+ console.error(' Aliased as "new" for backwards compatibility.');
237
238
  console.error(' Errors if one already exists there.');
238
239
  console.error(' Warns if a parent directory already has a server.');
239
240
  console.error(' --git Use the nearest parent with a .git entry (file or dir).');
@@ -247,7 +248,7 @@ function printHelp() {
247
248
  console.error(' a subcommand name (e.g. "ocmux -- kill").');
248
249
  console.error('');
249
250
  console.error('Options:');
250
- console.error(' -l Long output: append " → <dir>" after the URL (new/default).');
251
+ console.error(' -l Long output: append " → <dir>" after the URL (serve/default).');
251
252
  console.error(' --version Show version');
252
253
  console.error(' -h Show this help');
253
254
  console.error('');
@@ -275,7 +276,7 @@ function cmdSwitch(statefile, longMode) {
275
276
  const found = windowByDir(serverDir);
276
277
  if (found == null) {
277
278
  console.error(`Error: tmux window for ${serverDir} not found`);
278
- console.error("Run 'ocmux new' to create a new server.");
279
+ console.error("Run 'ocmux serve' to create a new server.");
279
280
  process.exit(1);
280
281
  }
281
282
  winIdx = found;
@@ -393,13 +394,25 @@ function doNew(targetDir, longMode) {
393
394
 
394
395
  console.error(`Starting opencode server for ${targetDir}...`);
395
396
 
396
- const rc = tmux(['new-window', '-d', '-t', SESSION, '-n', targetDir, '-c', targetDir,
397
- `opencode serve --port 0 2>&1 | tee '${lf}'`]);
397
+ // Create window with interactive shell so .bashrc/.zshrc is sourced
398
+ const rc = tmux(['new-window', '-d', '-t', SESSION, '-n', targetDir, '-c', targetDir]);
398
399
  if (rc.status !== 0) {
399
400
  console.error(`Error: failed to create tmux window: ${rc.stderr}`);
400
401
  process.exit(1);
401
402
  }
402
403
 
404
+ const winIdx = windowByDir(targetDir);
405
+ if (winIdx == null) {
406
+ console.error('Error: window was not created properly');
407
+ process.exit(1);
408
+ }
409
+
410
+ pinWindowName(winIdx);
411
+
412
+ // Send the command to the interactive shell (like typing it manually)
413
+ tmux(['send-keys', '-t', `${SESSION}:${winIdx}.0`,
414
+ `opencode serve --port 0 2>&1 | tee '${lf}'`, 'Enter']);
415
+
403
416
  // Wait up to 10s for the URL in the logfile
404
417
  let url = null;
405
418
  for (let i = 0; i < 50; i++) {
@@ -416,21 +429,16 @@ function doNew(targetDir, longMode) {
416
429
  process.exit(1);
417
430
  }
418
431
 
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
432
  const state = { url, logfile: lf, window_index: winIdx };
428
433
  fs.writeFileSync(path.join(targetDir, '.ocmux.json'), JSON.stringify(state) + '\n');
429
434
 
430
- tmux(['split-window', '-v', '-t', `${SESSION}:${winIdx}`, '-c', targetDir,
431
- `opencode attach --continue '${url}'`]);
435
+ const splitResult = tmux(['split-window', '-v', '-P', '-F', '#{pane_id}', '-t', `${SESSION}:${winIdx}`, '-c', targetDir]);
436
+ const tuiPane = splitResult.stdout.trim();
437
+ sleep(0.2);
438
+ tmux(['send-keys', '-t', tuiPane,
439
+ `opencode attach --continue '${url}'`, 'Enter']);
432
440
  sleep(0.5);
433
- ensureTuiZoomed(winIdx);
441
+ if (tuiPane) tmux(['resize-pane', '-Z', '-t', tuiPane]);
434
442
 
435
443
  const activeWin = activeWindowIndex();
436
444
  if (activeWin !== winIdx) {
@@ -515,8 +523,10 @@ function main() {
515
523
  } else if (arg === '--') {
516
524
  dirIsForced = true;
517
525
  } 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.`);
526
+ if (arg === '--new') {
527
+ console.error("Error: '--new' is no longer a flag. Use 'ocmux serve' instead.");
528
+ } else if (arg === '--kill') {
529
+ console.error("Error: '--kill' is no longer a flag. Use 'ocmux kill' instead.");
520
530
  } else {
521
531
  console.error(`Error: unknown option '${arg}'`);
522
532
  console.error(`Run '${SCRIPT} -h' for help.`);
@@ -538,7 +548,7 @@ function main() {
538
548
  process.exit(1);
539
549
  }
540
550
  targetDir = resolveDir(arg);
541
- } else if (arg === 'new' || arg === 'kill' || arg === 'list') {
551
+ } else if (arg === 'serve' || arg === 'new' || arg === 'kill' || arg === 'list') {
542
552
  if (subcommand) {
543
553
  console.error(`Error: multiple subcommands specified: '${subcommand}' and '${arg}'`);
544
554
  process.exit(1);
@@ -558,7 +568,7 @@ function main() {
558
568
  // Dispatch
559
569
  if (subcommand === 'list') {
560
570
  if (gitMode) {
561
- console.error("Error: '--git' and '--GIT' are only valid with 'new'.");
571
+ console.error("Error: '--git' and '--GIT' are only valid with 'serve'.");
562
572
  process.exit(1);
563
573
  }
564
574
  if (targetDir !== process.cwd()) {
@@ -568,17 +578,17 @@ function main() {
568
578
  cmdList();
569
579
  } else if (subcommand === 'kill') {
570
580
  if (gitMode) {
571
- console.error("Error: '--git' and '--GIT' are only valid with 'new'.");
581
+ console.error("Error: '--git' and '--GIT' are only valid with 'serve'.");
572
582
  process.exit(1);
573
583
  }
574
584
  const sf = findParentStatefile(targetDir);
575
585
  if (!sf) {
576
586
  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.");
587
+ console.error("Run 'ocmux serve' to create a new server.");
578
588
  process.exit(1);
579
589
  }
580
590
  cmdKill(sf);
581
- } else if (subcommand === 'new') {
591
+ } else if (subcommand === 'serve' || subcommand === 'new') {
582
592
  if (gitMode) {
583
593
  const flagLabel = gitMode === 'root' ? '--GIT' : '--git';
584
594
  targetDir = resolveGitDir(targetDir, gitMode === 'root', flagLabel);
@@ -586,14 +596,14 @@ function main() {
586
596
  cmdNew(targetDir, longMode);
587
597
  } else {
588
598
  if (gitMode) {
589
- console.error("Error: '--git' and '--GIT' are only valid with 'new'.");
599
+ console.error("Error: '--git' and '--GIT' are only valid with 'serve'.");
590
600
  process.exit(1);
591
601
  }
592
602
  // Default mode
593
603
  const sf = findParentStatefile(targetDir);
594
604
  if (!sf) {
595
605
  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.");
606
+ console.error("Run 'ocmux serve' to create a new server.");
597
607
  process.exit(1);
598
608
  }
599
609
  ensureSession();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentp",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Pipe prompts into a running OpenCode TUI session and stream the final answer",
5
5
  "bin": {
6
6
  "agentp": "bin/agentp",