agentp 0.2.0 → 0.3.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.
Files changed (4) hide show
  1. package/README.md +37 -11
  2. package/bin/agentp +18 -15
  3. package/bin/ocmux +79 -1
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # agentp
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/agentp.svg)](https://www.npmjs.com/package/agentp)
4
+ [![npm license](https://img.shields.io/npm/l/agentp.svg)](https://www.npmjs.com/package/agentp)
5
+ [![npm downloads](https://img.shields.io/npm/dm/agentp.svg)](https://www.npmjs.com/package/agentp)
6
+ [![node version](https://img.shields.io/node/v/agentp.svg)](https://www.npmjs.com/package/agentp)
7
+
3
8
  This package provides two CLI tools:
4
9
 
5
10
  - **`agentp`** — pipes prompt text into a running OpenCode TUI session and streams the assistant final answer back to stdout
@@ -34,23 +39,24 @@ npm link
34
39
  Notes:
35
40
 
36
41
  - `agentp` connects to the OpenCode event endpoint over HTTP.
37
- - In practice this means running `opencode --serve` (or equivalent serve mode) so the port is open.
38
- - `opencode --attach` is optional but useful to monitor the full conversation in another terminal/tmux pane.
42
+ - In practice this means running `opencode serve` (or equivalent serve mode) so the port is open.
43
+ - `opencode attach` is optional but useful to monitor the full conversation in another terminal/tmux pane.
39
44
 
40
45
  ## Usage
41
46
 
42
47
  ```bash
43
- agentp [options] [port]
48
+ agentp [options] [url]
44
49
  ```
45
50
 
46
51
  Options:
47
52
 
48
- - `--qa`: print question/answer separators around the streamed answer
53
+ - `--qa`: print the original prompt and answer with labels (useful when used as a filter)
54
+ - `--version`: show version
49
55
  - `--help`: show help message
50
56
 
51
57
  Arguments:
52
58
 
53
- - `port`: OpenCode TUI port (defaults to `4096`)
59
+ - `url`: OpenCode TUI server URL or port number (defaults to `4096`). Examples: `4096`, `http://localhost:4096`, `http://192.168.1.50:4096`
54
60
 
55
61
  ## Examples
56
62
 
@@ -73,10 +79,10 @@ Send a multi-line prompt from a file:
73
79
  cat prompt.txt | agentp
74
80
  ```
75
81
 
76
- Use QA separators and explicit port:
82
+ Use an explicit port:
77
83
 
78
84
  ```bash
79
- cat prompt.txt | agentp --qa 4096
85
+ cat prompt.txt | agentp 4096
80
86
  ```
81
87
 
82
88
  Capture answer to a file:
@@ -85,13 +91,19 @@ Capture answer to a file:
85
91
  cat prompt.txt | agentp > answer.txt
86
92
  ```
87
93
 
94
+ Connect to a remote OpenCode server:
95
+
96
+ ```bash
97
+ cat prompt.txt | agentp http://192.168.1.50:4096
98
+ ```
99
+
88
100
  From Vim/Neovim, send the current visual selection and replace it in place with the assistant answer:
89
101
 
90
102
  ```vim
91
103
  :'<,'>!agentp
92
104
  ```
93
105
 
94
- From Vim/Neovim, send the current visual selection and keep QA separators in the same file:
106
+ From Vim/Neovim, send the current visual selection and keep the prompt with the answer:
95
107
 
96
108
  ```vim
97
109
  :'<,'>!agentp --qa
@@ -109,9 +121,23 @@ Without arguments, searches upward from `<directory>` (default: `$PWD`) for `.oc
109
121
 
110
122
  Subcommands:
111
123
 
112
- - **`new [dir]`** — Create a new server in `dir` (default: `$PWD`). Errors if one already exists there. Warns if a parent directory already has a server.
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
+ - `--git` resolves `dir` to the nearest parent with a `.git` entry (file or dir); errors if none is found.
126
+ - `--GIT` resolves `dir` to the nearest parent with a `.git` directory only; errors if none is found.
113
127
  - **`kill [dir]`** — Kill the server found upward from `dir`. Removes its tmux window and state file.
114
- - **`list [-l]`** — List all running servers with their directories, URLs, and status.
128
+ - **`list`** — List all running servers with their directories, URLs, and status.
129
+
130
+ Options:
131
+
132
+ - `-l`: long output for default/new commands (append `→ <dir>` after the URL)
133
+ - `--version`: show version
134
+ - `-h`: show help message
135
+ - `--`: treat the next argument as a directory even if it matches a subcommand name
136
+
137
+ Notes:
138
+
139
+ - If `<directory>` is not a valid path, `ocmux` tries to match it against the basenames of existing sessions (exact unique match).
140
+ - When using `--git`/`--GIT`, `ocmux` refuses to create a new server above an existing `.ocmux.json` found while searching for the git root.
115
141
 
116
142
  ### Requirements
117
143
 
@@ -133,7 +159,7 @@ Subcommands:
133
159
 
134
160
  Operational hint:
135
161
 
136
- - You can keep a separate `opencode --attach` view open to see the full run context while `agentp` is used from shell scripts or editor buffers.
162
+ - You can keep a separate `opencode attach` view open to see the full run context while `agentp` is used from shell scripts or editor buffers.
137
163
 
138
164
  ## License
139
165
 
package/bin/agentp CHANGED
@@ -47,8 +47,7 @@ async function submitPrompt(server) {
47
47
  await makeRequest(buildJsonRequest(url, 'POST', '{}'), '{}');
48
48
  }
49
49
 
50
- async function sendPrompt(port) {
51
- const server = `http://localhost:${port}`;
50
+ async function sendPrompt(server) {
52
51
  let promptText = '';
53
52
 
54
53
  await clearPrompt(server);
@@ -71,9 +70,9 @@ const HUMAN_ROW = '👤:  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
71
70
  const AGENT_ROW = '🤖:  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\n'
72
71
  const FINAL_ROW = ' ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\n';
73
72
 
74
- function listenForFinalAnswer(port, showFinalRow) {
73
+ function listenForFinalAnswer(server, showFinalRow) {
75
74
  return new Promise((resolve, reject) => {
76
- const url = `http://localhost:${port}/event`;
75
+ const url = `${server}/event`;
77
76
  const parsed = new URL(url);
78
77
 
79
78
  const req = http.request({
@@ -155,7 +154,7 @@ function listenForFinalAnswer(port, showFinalRow) {
155
154
  }
156
155
 
157
156
  async function main() {
158
- let port = 4096;
157
+ let serverBase = 'http://localhost:4096';
159
158
  let qaMode = false;
160
159
 
161
160
  const args = process.argv.slice(2);
@@ -164,33 +163,37 @@ async function main() {
164
163
  console.log(version);
165
164
  process.exit(0);
166
165
  } else if (args[i] === '--help') {
167
- console.error('Usage: agentp [options] [port]');
166
+ console.error('Usage: agentp [options] [url]');
168
167
  console.error('');
169
- console.error('Pipe prompt text to an OpenCode TUI session running on localhost.');
170
- console.error('The assistant response is streamed to stdout.');
168
+ console.error('Pipe prompt text to an OpenCode TUI session and stream the assistant response to stdout.');
171
169
  console.error('');
172
170
  console.error('Options:');
173
171
  console.error(' --version Show version');
174
- console.error(' --qa Print question/answer separators before streaming output');
172
+ console.error(' --qa Print the original prompt and answer with labels (useful as a filter)');
175
173
  console.error(' --help Show this help message');
176
174
  console.error('');
177
175
  console.error('Arguments:');
178
- console.error(' port OpenCode TUI port (default: 4096)');
176
+ console.error(' url OpenCode TUI server URL or port number (default: 4096)');
177
+ console.error(' Examples: 4096, http://localhost:4096, http://192.168.1.50:4096');
179
178
  console.error('');
180
179
  console.error('See also: ocmux -- manage OpenCode tmux sessions (ocmux --help)');
181
180
  console.error('');
182
181
  console.error('Examples:');
183
182
  console.error(' printf "Summarize this file" | agentp');
184
- console.error(' cat prompt.txt | agentp --qa 4096');
183
+ console.error(' cat prompt.txt | agentp 4096');
184
+ console.error(' # Vim/Neovim filter, preserving prompt + answer:');
185
+ console.error(" :'<,'>!agentp --qa");
185
186
  process.exit(0);
186
187
  } else if (args[i] === '--qa') {
187
188
  qaMode = true;
188
- } else if (!isNaN(args[i]) && /^\d+$/.test(args[i])) {
189
- port = parseInt(args[i], 10);
189
+ } else if (args[i].includes('://')) {
190
+ serverBase = args[i].replace(/\/+$/, '');
191
+ } else if (/^\d+$/.test(args[i])) {
192
+ serverBase = 'http://localhost:' + args[i];
190
193
  }
191
194
  }
192
195
 
193
- const promptText = await sendPrompt(port);
196
+ const promptText = await sendPrompt(serverBase);
194
197
 
195
198
  if (qaMode) {
196
199
  process.stdout.write(HUMAN_ROW);
@@ -198,7 +201,7 @@ async function main() {
198
201
  process.stdout.write(AGENT_ROW);
199
202
  }
200
203
 
201
- await listenForFinalAnswer(port, qaMode);
204
+ await listenForFinalAnswer(serverBase, qaMode);
202
205
  }
203
206
 
204
207
  main().catch((err) => {
package/bin/ocmux CHANGED
@@ -66,6 +66,52 @@ function findParentStatefile(startDir) {
66
66
  }
67
67
  }
68
68
 
69
+ function findParentGitDir(startDir, requireDir) {
70
+ let dir = path.resolve(startDir);
71
+ let stateDir = null;
72
+ while (true) {
73
+ if (!stateDir && fs.existsSync(path.join(dir, '.ocmux.json'))) {
74
+ stateDir = dir;
75
+ }
76
+
77
+ const gitPath = path.join(dir, '.git');
78
+ if (fs.existsSync(gitPath)) {
79
+ if (!requireDir) {
80
+ return { dir, stateDir, gitPath };
81
+ }
82
+ try {
83
+ if (fs.statSync(gitPath).isDirectory()) {
84
+ return { dir, stateDir, gitPath };
85
+ }
86
+ } catch {
87
+ // ignore stat errors
88
+ }
89
+ }
90
+
91
+ if (dir === HOME || dir === path.dirname(dir)) return { dir: null, stateDir, gitPath: null };
92
+ dir = path.dirname(dir);
93
+ }
94
+ }
95
+
96
+ function resolveGitDir(startDir, requireDir, flagLabel) {
97
+ const result = findParentGitDir(startDir, requireDir);
98
+ if (!result.dir) {
99
+ const label = requireDir ? '.git directory' : '.git entry';
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.`);
102
+ process.exit(1);
103
+ }
104
+
105
+ if (result.stateDir && result.stateDir !== result.dir) {
106
+ console.error(`Error: found an existing ocmux server in ${result.stateDir}.`);
107
+ console.error(`Refusing to create a new server at ${result.dir} because it is above that session.`);
108
+ console.error(`Move outside ${result.stateDir} or pass an explicit directory.`);
109
+ process.exit(1);
110
+ }
111
+
112
+ return result.dir;
113
+ }
114
+
69
115
  function windowByDir(dir) {
70
116
  const r = tmux(['list-windows', '-t', SESSION, '-F', '#{window_index}\t#{window_name}']);
71
117
  if (r.status !== 0) return null;
@@ -190,6 +236,9 @@ function printHelp() {
190
236
  console.error(' new Create a new server in <directory> (default: $PWD).');
191
237
  console.error(' Errors if one already exists there.');
192
238
  console.error(' Warns if a parent directory already has a server.');
239
+ console.error(' --git Use the nearest parent with a .git entry (file or dir).');
240
+ console.error(' --GIT Use the nearest parent with a .git directory only.');
241
+ console.error(' Refuses to create above an existing .ocmux.json found while searching.');
193
242
  console.error(' kill Kill the server found upward from <directory>.');
194
243
  console.error(' Removes its tmux window and state file.');
195
244
  console.error(' list List all running servers with their directories and URLs.');
@@ -198,7 +247,7 @@ function printHelp() {
198
247
  console.error(' a subcommand name (e.g. "ocmux -- kill").');
199
248
  console.error('');
200
249
  console.error('Options:');
201
- console.error(' -l Long output: append " → <dir>" after the URL.');
250
+ console.error(' -l Long output: append " → <dir>" after the URL (new/default).');
202
251
  console.error(' --version Show version');
203
252
  console.error(' -h Show this help');
204
253
  console.error('');
@@ -439,12 +488,25 @@ function cmdKill(statefile) {
439
488
 
440
489
  function main() {
441
490
  let longMode = false;
491
+ let gitMode = null;
442
492
  const positional = [];
443
493
  let dirIsForced = false;
444
494
 
445
495
  for (const arg of process.argv.slice(2)) {
446
496
  if (arg === '-l') {
447
497
  longMode = true;
498
+ } else if (arg === '--git') {
499
+ if (gitMode && gitMode !== 'nearest') {
500
+ console.error("Error: cannot combine '--git' with '--GIT'.");
501
+ process.exit(1);
502
+ }
503
+ gitMode = 'nearest';
504
+ } else if (arg === '--GIT') {
505
+ if (gitMode && gitMode !== 'root') {
506
+ console.error("Error: cannot combine '--git' with '--GIT'.");
507
+ process.exit(1);
508
+ }
509
+ gitMode = 'root';
448
510
  } else if (arg === '--version') {
449
511
  console.log(version);
450
512
  process.exit(0);
@@ -495,12 +557,20 @@ function main() {
495
557
 
496
558
  // Dispatch
497
559
  if (subcommand === 'list') {
560
+ if (gitMode) {
561
+ console.error("Error: '--git' and '--GIT' are only valid with 'new'.");
562
+ process.exit(1);
563
+ }
498
564
  if (targetDir !== process.cwd()) {
499
565
  console.error("Error: 'list' does not accept a directory path.");
500
566
  process.exit(1);
501
567
  }
502
568
  cmdList();
503
569
  } else if (subcommand === 'kill') {
570
+ if (gitMode) {
571
+ console.error("Error: '--git' and '--GIT' are only valid with 'new'.");
572
+ process.exit(1);
573
+ }
504
574
  const sf = findParentStatefile(targetDir);
505
575
  if (!sf) {
506
576
  console.error(`Error: no opencode server found in ${targetDir} or any of its parents.`);
@@ -509,8 +579,16 @@ function main() {
509
579
  }
510
580
  cmdKill(sf);
511
581
  } else if (subcommand === 'new') {
582
+ if (gitMode) {
583
+ const flagLabel = gitMode === 'root' ? '--GIT' : '--git';
584
+ targetDir = resolveGitDir(targetDir, gitMode === 'root', flagLabel);
585
+ }
512
586
  cmdNew(targetDir, longMode);
513
587
  } else {
588
+ if (gitMode) {
589
+ console.error("Error: '--git' and '--GIT' are only valid with 'new'.");
590
+ process.exit(1);
591
+ }
514
592
  // Default mode
515
593
  const sf = findParentStatefile(targetDir);
516
594
  if (!sf) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentp",
3
- "version": "0.2.0",
3
+ "version": "0.3.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",