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 +12 -0
- package/README.md +111 -0
- package/bin/agentp +200 -0
- package/package.json +23 -0
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
|
+
}
|