agentp 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ - Rename package and CLI command from `aprompt` to `agentp`.
8
+
9
+ ## [0.1.0] - 2026-05-08
10
+
11
+ - Initial release.
12
+ - Add project documentation and usage examples.
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # agentp
2
+
3
+ `agentp` is a tiny CLI that pipes prompt text into a running OpenCode TUI session and streams the assistant final answer back to stdout.
4
+
5
+ It is designed for prompt-driven workflows where you want to do things like:
6
+
7
+ - compose prompts with `cat`, `printf`, or heredocs
8
+ - submit them to OpenCode from scripts
9
+ - capture output in files or pipe to other tools
10
+ - **drive prompts directly from editors** like Vim/Neovim
11
+
12
+ ## Install
13
+
14
+ From npm:
15
+
16
+ ```bash
17
+ npm install -g agentp
18
+ ```
19
+
20
+ For local development in this repo:
21
+
22
+ ```bash
23
+ npm link
24
+ ```
25
+
26
+ ## Requirements
27
+
28
+ - Node.js 18+
29
+ - An OpenCode server session listening locally (default port: `4096`)
30
+
31
+ Notes:
32
+
33
+ - `agentp` connects to the OpenCode event endpoint over HTTP.
34
+ - In practice this means running `opencode --serve` (or equivalent serve mode) so the port is open.
35
+ - `opencode --attach` is optional but useful to monitor the full conversation in another terminal/tmux pane.
36
+
37
+ ## Usage
38
+
39
+ ```bash
40
+ agentp [options] [port]
41
+ ```
42
+
43
+ Options:
44
+
45
+ - `--qa`: print question/answer separators around the streamed answer
46
+ - `--help`: show help message
47
+
48
+ Arguments:
49
+
50
+ - `port`: OpenCode TUI port (defaults to `4096`)
51
+
52
+ ## Examples
53
+
54
+ Send a one-line prompt:
55
+
56
+ ```bash
57
+ printf "Summarize the latest logs" | agentp
58
+ ```
59
+
60
+ Type and send a multi-line prompt:
61
+
62
+ ```bash
63
+ cat | agentp
64
+ # (press Ctrl+D to end input)
65
+ ```
66
+
67
+ Send a multi-line prompt from a file:
68
+
69
+ ```bash
70
+ cat prompt.txt | agentp
71
+ ```
72
+
73
+ Use QA separators and explicit port:
74
+
75
+ ```bash
76
+ cat prompt.txt | agentp --qa 4096
77
+ ```
78
+
79
+ Capture answer to a file:
80
+
81
+ ```bash
82
+ cat prompt.txt | agentp > answer.txt
83
+ ```
84
+
85
+ From Vim/Neovim, send the current visual selection and replace it in place with the assistant answer:
86
+
87
+ ```vim
88
+ :'<,'>!agentp
89
+ ```
90
+
91
+ From Vim/Neovim, send the current visual selection and keep QA separators in the same file:
92
+
93
+ ```vim
94
+ :'<,'>!agentp --qa
95
+ ```
96
+
97
+ ## How It Works
98
+
99
+ 1. Clears the current TUI prompt input.
100
+ 2. Appends each line from stdin to the TUI prompt.
101
+ 3. Submits the prompt.
102
+ 4. Listens to the event stream and prints assistant text parts.
103
+ 5. Stops when the session reaches idle state.
104
+
105
+ Operational hint:
106
+
107
+ - 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.
108
+
109
+ ## License
110
+
111
+ MIT
package/bin/agentp ADDED
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env node
2
+
3
+ const http = require('http');
4
+ const { URL } = require('url');
5
+
6
+ function makeRequest(options, data) {
7
+ return new Promise((resolve, reject) => {
8
+ const req = http.request(options, (res) => {
9
+ let body = '';
10
+ res.on('data', (chunk) => { body += chunk; });
11
+ res.on('end', () => resolve({ status: res.statusCode, body }));
12
+ });
13
+ req.on('error', reject);
14
+ if (data) req.write(data);
15
+ req.end();
16
+ });
17
+ }
18
+
19
+ function buildJsonRequest(url, method, body) {
20
+ const parsed = new URL(url);
21
+ return {
22
+ hostname: parsed.hostname,
23
+ port: parsed.port,
24
+ path: parsed.pathname,
25
+ method: method,
26
+ headers: {
27
+ 'Content-Type': 'application/json',
28
+ 'Content-Length': Buffer.byteLength(body)
29
+ }
30
+ };
31
+ }
32
+
33
+ async function clearPrompt(server) {
34
+ const url = `${server}/tui/clear-prompt`;
35
+ await makeRequest(buildJsonRequest(url, 'POST', '{}'), '{}');
36
+ }
37
+
38
+ async function appendPrompt(server, text) {
39
+ const url = `${server}/tui/append-prompt`;
40
+ const body = JSON.stringify({ text });
41
+ await makeRequest(buildJsonRequest(url, 'POST', body), body);
42
+ }
43
+
44
+ async function submitPrompt(server) {
45
+ const url = `${server}/tui/submit-prompt`;
46
+ await makeRequest(buildJsonRequest(url, 'POST', '{}'), '{}');
47
+ }
48
+
49
+ async function sendPrompt(port) {
50
+ const server = `http://localhost:${port}`;
51
+ let promptText = '';
52
+
53
+ await clearPrompt(server);
54
+
55
+ const readline = require('readline');
56
+ const rl = readline.createInterface({ input: process.stdin });
57
+
58
+ for await (const line of rl) {
59
+ const promptLine = line + '\n';
60
+ promptText += promptLine;
61
+ await appendPrompt(server, promptLine);
62
+ }
63
+
64
+ rl.close();
65
+ await submitPrompt(server);
66
+ return promptText;
67
+ }
68
+
69
+ const HUMAN_ROW = '👤:  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\n'
70
+ const AGENT_ROW = '🤖:  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\n'
71
+ const FINAL_ROW = ' ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\n';
72
+
73
+ function listenForFinalAnswer(port, showFinalRow) {
74
+ return new Promise((resolve, reject) => {
75
+ const url = `http://localhost:${port}/event`;
76
+ const parsed = new URL(url);
77
+
78
+ const req = http.request({
79
+ hostname: parsed.hostname,
80
+ port: parsed.port,
81
+ path: parsed.pathname,
82
+ method: 'GET',
83
+ headers: { 'Accept': 'text/event-stream' }
84
+ }, (res) => {
85
+ let buffer = '';
86
+ const userMessageIDs = new Set();
87
+
88
+ res.on('data', (chunk) => {
89
+ buffer += chunk.toString();
90
+
91
+ // Split by double newline (SSE event separator)
92
+ while (buffer.includes('\n\n')) {
93
+ const eventEnd = buffer.indexOf('\n\n');
94
+ const eventData = buffer.slice(0, eventEnd);
95
+ buffer = buffer.slice(eventEnd + 2);
96
+
97
+ // Extract data lines
98
+ const lines = eventData.split('\n');
99
+
100
+ let jsonStr = '';
101
+
102
+ for (const line of lines) {
103
+ if (line.startsWith('data: ')) {
104
+ jsonStr += line.slice(6);
105
+ }
106
+ }
107
+
108
+ if (!jsonStr.trim()) continue;
109
+
110
+ try {
111
+ const event = JSON.parse(jsonStr);
112
+
113
+ // Track user message IDs
114
+ if (event.type === 'message.updated' && event.properties?.info?.role === 'user') {
115
+ userMessageIDs.add(event.properties.info.id);
116
+ }
117
+
118
+ // Check for session.idle to detect completion
119
+ if (event.type === 'session.idle') {
120
+ process.stdout.write('\n');
121
+ if (showFinalRow) {
122
+ process.stdout.write(`${FINAL_ROW}`);
123
+ }
124
+ req.destroy();
125
+ res.destroy();
126
+ resolve();
127
+ return;
128
+ }
129
+
130
+ const part = event.properties?.part;
131
+ if (!part) continue;
132
+
133
+ // Filter out user input by checking messageID
134
+ if (part.messageID && userMessageIDs.has(part.messageID)) {
135
+ continue;
136
+ }
137
+
138
+ if (part.type === 'text' && part.text != null && part.text !== '') {
139
+ process.stdout.write(part.text);
140
+ }
141
+ } catch (e) {
142
+ }
143
+ }
144
+ });
145
+
146
+ res.on('end', () => {
147
+ resolve();
148
+ });
149
+ });
150
+
151
+ req.on('error', reject);
152
+ req.end();
153
+ });
154
+ }
155
+
156
+ async function main() {
157
+ let port = 4096;
158
+ let qaMode = false;
159
+
160
+ const args = process.argv.slice(2);
161
+ for (let i = 0; i < args.length; i++) {
162
+ if (args[i] === '--help') {
163
+ console.error('Usage: agentp [options] [port]');
164
+ console.error('');
165
+ console.error('Pipe prompt text to an OpenCode TUI session running on localhost.');
166
+ console.error('The assistant response is streamed to stdout.');
167
+ console.error('');
168
+ console.error('Options:');
169
+ console.error(' --qa Print question/answer separators before streaming output');
170
+ console.error(' --help Show this help message');
171
+ console.error('');
172
+ console.error('Arguments:');
173
+ console.error(' port OpenCode TUI port (default: 4096)');
174
+ console.error('');
175
+ console.error('Examples:');
176
+ console.error(' printf "Summarize this file" | agentp');
177
+ console.error(' cat prompt.txt | agentp --qa 4096');
178
+ process.exit(0);
179
+ } else if (args[i] === '--qa') {
180
+ qaMode = true;
181
+ } else if (!isNaN(args[i]) && /^\d+$/.test(args[i])) {
182
+ port = parseInt(args[i], 10);
183
+ }
184
+ }
185
+
186
+ const promptText = await sendPrompt(port);
187
+
188
+ if (qaMode) {
189
+ process.stdout.write(HUMAN_ROW);
190
+ process.stdout.write(promptText);
191
+ process.stdout.write(AGENT_ROW);
192
+ }
193
+
194
+ await listenForFinalAnswer(port, qaMode);
195
+ }
196
+
197
+ main().catch((err) => {
198
+ console.error('Error:', err.message);
199
+ process.exit(1);
200
+ });
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "agentp",
3
+ "version": "0.1.0",
4
+ "description": "Pipe prompts into a running OpenCode TUI session and stream the final answer",
5
+ "bin": {
6
+ "agentp": "bin/agentp"
7
+ },
8
+ "keywords": [
9
+ "ai",
10
+ "cli",
11
+ "prompt",
12
+ "opencode"
13
+ ],
14
+ "author": "",
15
+ "license": "MIT",
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "scripts": {
20
+ "test": "echo \"Error: no test specified\" && exit 1"
21
+ },
22
+ "type": "commonjs"
23
+ }