@aerode/pish 0.8.0 → 0.9.1
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 +1 -1
- package/dist/agent.js +40 -27
- package/dist/app.js +54 -101
- package/dist/config.js +23 -0
- package/dist/log.js +3 -4
- package/dist/main.js +6 -13
- package/dist/recorder.js +23 -9
- package/dist/render.js +23 -15
- package/dist/session.js +61 -0
- package/dist/strip.js +3 -8
- package/dist/vterm.js +1 -1
- package/package.json +10 -2
package/README.md
CHANGED
package/dist/agent.js
CHANGED
|
@@ -34,19 +34,19 @@ function toRpcResponse(obj) {
|
|
|
34
34
|
export class AgentManager {
|
|
35
35
|
proc = null;
|
|
36
36
|
buf = '';
|
|
37
|
-
|
|
37
|
+
_onEvent = null;
|
|
38
38
|
_running = false;
|
|
39
39
|
_submitted = false;
|
|
40
40
|
startTime = 0;
|
|
41
41
|
lastUsage = null;
|
|
42
|
-
|
|
42
|
+
config;
|
|
43
43
|
pendingRpc = new Map();
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
/** Crash info (in-memory), displayed on next enterAgentMode */
|
|
47
|
-
|
|
48
|
-
constructor(
|
|
49
|
-
this.
|
|
44
|
+
/** Session file path. Maintained by pish across agent process restarts. */
|
|
45
|
+
_sessionFile;
|
|
46
|
+
/** Crash info (in-memory), displayed on next enterAgentMode. */
|
|
47
|
+
_crashInfo;
|
|
48
|
+
constructor(config) {
|
|
49
|
+
this.config = config;
|
|
50
50
|
}
|
|
51
51
|
get running() {
|
|
52
52
|
return this._running;
|
|
@@ -56,18 +56,18 @@ export class AgentManager {
|
|
|
56
56
|
return this.proc !== null && !this.proc.killed;
|
|
57
57
|
}
|
|
58
58
|
onEvent(cb) {
|
|
59
|
-
this.
|
|
59
|
+
this._onEvent = cb;
|
|
60
60
|
}
|
|
61
61
|
/** Ensure the pi process is alive (lazy spawn). */
|
|
62
62
|
ensureRunning() {
|
|
63
63
|
if (this.proc && !this.proc.killed)
|
|
64
64
|
return;
|
|
65
65
|
const args = ['--mode', 'rpc'];
|
|
66
|
-
if (this.
|
|
67
|
-
args.push('--session', this.
|
|
66
|
+
if (this._sessionFile) {
|
|
67
|
+
args.push('--session', this._sessionFile);
|
|
68
68
|
}
|
|
69
69
|
log('agent_spawn', { args });
|
|
70
|
-
this.proc = spawn(this.piPath, args, {
|
|
70
|
+
this.proc = spawn(this.config.piPath, args, {
|
|
71
71
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
72
72
|
env: { ...process.env },
|
|
73
73
|
});
|
|
@@ -125,7 +125,7 @@ export class AgentManager {
|
|
|
125
125
|
code !== 0 &&
|
|
126
126
|
signal !== 'SIGTERM' &&
|
|
127
127
|
signal !== 'SIGKILL') {
|
|
128
|
-
this.
|
|
128
|
+
this._crashInfo = `agent process exited unexpectedly (code ${code})`;
|
|
129
129
|
}
|
|
130
130
|
});
|
|
131
131
|
}
|
|
@@ -167,6 +167,7 @@ export class AgentManager {
|
|
|
167
167
|
this.emitEvent({ type: 'turn_end' });
|
|
168
168
|
break;
|
|
169
169
|
case 'message_end': {
|
|
170
|
+
// Stash per-message usage as fallback — agent_end may lack messages[]
|
|
170
171
|
const msg = obj.message;
|
|
171
172
|
if (msg?.role === 'assistant' && msg.usage) {
|
|
172
173
|
const u = msg.usage;
|
|
@@ -262,7 +263,10 @@ export class AgentManager {
|
|
|
262
263
|
// toolcall_start/delta/end handled by tool_execution_* events
|
|
263
264
|
}
|
|
264
265
|
}
|
|
265
|
-
/**
|
|
266
|
+
/**
|
|
267
|
+
* Aggregate usage from agent_end.messages[]. Falls back to lastUsage
|
|
268
|
+
* (stashed from message_end) if messages array is absent or empty.
|
|
269
|
+
*/
|
|
266
270
|
aggregateUsage(agentEnd) {
|
|
267
271
|
const messages = agentEnd.messages;
|
|
268
272
|
if (!Array.isArray(messages)) {
|
|
@@ -309,7 +313,7 @@ export class AgentManager {
|
|
|
309
313
|
};
|
|
310
314
|
}
|
|
311
315
|
emitEvent(event) {
|
|
312
|
-
this.
|
|
316
|
+
this._onEvent?.(event);
|
|
313
317
|
}
|
|
314
318
|
/** Submit a prompt to the agent. */
|
|
315
319
|
submit(message) {
|
|
@@ -332,15 +336,9 @@ export class AgentManager {
|
|
|
332
336
|
const cmd = `${JSON.stringify({ type: 'abort' })}\n`;
|
|
333
337
|
this.proc.stdin.write(cmd);
|
|
334
338
|
}
|
|
335
|
-
/** Send an RPC command (fire-and-forget). */
|
|
336
|
-
rpc(command) {
|
|
337
|
-
this.ensureRunning();
|
|
338
|
-
if (!this.proc?.stdin?.writable)
|
|
339
|
-
return;
|
|
340
|
-
this.proc.stdin.write(`${JSON.stringify(command)}\n`);
|
|
341
|
-
}
|
|
342
339
|
/** Send an RPC command and wait for the matching response. */
|
|
343
|
-
async rpcWait(command, timeoutMs
|
|
340
|
+
async rpcWait(command, timeoutMs) {
|
|
341
|
+
const timeout = timeoutMs ?? this.config.rpcTimeout;
|
|
344
342
|
this.ensureRunning();
|
|
345
343
|
if (!this.proc?.stdin?.writable) {
|
|
346
344
|
return {
|
|
@@ -355,7 +353,7 @@ export class AgentManager {
|
|
|
355
353
|
if (this.pendingRpc.delete(id)) {
|
|
356
354
|
resolve({ type: 'response', success: false, error: 'RPC timeout' });
|
|
357
355
|
}
|
|
358
|
-
},
|
|
356
|
+
}, timeout);
|
|
359
357
|
this.pendingRpc.set(id, { resolve, timer });
|
|
360
358
|
this.proc.stdin.write(`${JSON.stringify({ ...command, id })}\n`);
|
|
361
359
|
});
|
|
@@ -375,7 +373,7 @@ export class AgentManager {
|
|
|
375
373
|
if (this.proc && !this.proc.killed) {
|
|
376
374
|
const p = this.proc;
|
|
377
375
|
p.kill('SIGTERM');
|
|
378
|
-
// Escalate to SIGKILL if process doesn't exit
|
|
376
|
+
// Escalate to SIGKILL if process doesn't exit in time
|
|
379
377
|
const forceKill = setTimeout(() => {
|
|
380
378
|
try {
|
|
381
379
|
p.kill('SIGKILL');
|
|
@@ -383,16 +381,31 @@ export class AgentManager {
|
|
|
383
381
|
catch {
|
|
384
382
|
/* already exited */
|
|
385
383
|
}
|
|
386
|
-
},
|
|
384
|
+
}, this.config.killTimeout);
|
|
387
385
|
forceKill.unref(); // Don't prevent Node from exiting
|
|
388
386
|
this.proc = null;
|
|
389
387
|
}
|
|
390
388
|
this._running = false;
|
|
391
389
|
this._submitted = false;
|
|
392
390
|
}
|
|
391
|
+
get sessionFile() {
|
|
392
|
+
return this._sessionFile;
|
|
393
|
+
}
|
|
394
|
+
set sessionFile(path) {
|
|
395
|
+
this._sessionFile = path;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Consume crash info — returns the stored message and clears it.
|
|
399
|
+
* Designed for one-time display on next enterAgentMode.
|
|
400
|
+
*/
|
|
401
|
+
consumeCrashInfo() {
|
|
402
|
+
const info = this._crashInfo;
|
|
403
|
+
this._crashInfo = undefined;
|
|
404
|
+
return info;
|
|
405
|
+
}
|
|
393
406
|
/** Kill process + clear session (full reset via Ctrl+L). */
|
|
394
407
|
reset() {
|
|
395
408
|
this.kill();
|
|
396
|
-
this.
|
|
409
|
+
this._sessionFile = undefined;
|
|
397
410
|
}
|
|
398
411
|
}
|
package/dist/app.js
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* and FIFO responses. Created by main.ts after all resources are ready.
|
|
7
7
|
*/
|
|
8
8
|
import * as fs from 'node:fs';
|
|
9
|
-
import * as os from 'node:os';
|
|
10
9
|
import * as path from 'node:path';
|
|
11
10
|
import { closeLog, log } from './log.js';
|
|
12
11
|
import { printBanner, printControl, printControlResult, printExit, printNotice, StreamRenderer, startSpinner, } from './render.js';
|
|
12
|
+
import { findLatestSession } from './session.js';
|
|
13
13
|
// ═══════════════════════════════════════
|
|
14
14
|
// Pure helpers (module-level, no `this`)
|
|
15
15
|
// ═══════════════════════════════════════
|
|
@@ -32,55 +32,6 @@ function formatContext(entries) {
|
|
|
32
32
|
})
|
|
33
33
|
.join('\n\n');
|
|
34
34
|
}
|
|
35
|
-
/** pi agent directory, respects PI_CODING_AGENT_DIR env var. */
|
|
36
|
-
function getAgentDir() {
|
|
37
|
-
const envDir = process.env.PI_CODING_AGENT_DIR;
|
|
38
|
-
if (envDir) {
|
|
39
|
-
if (envDir === '~')
|
|
40
|
-
return os.homedir();
|
|
41
|
-
if (envDir.startsWith('~/'))
|
|
42
|
-
return os.homedir() + envDir.slice(1);
|
|
43
|
-
return envDir;
|
|
44
|
-
}
|
|
45
|
-
return path.join(os.homedir(), '.pi', 'agent');
|
|
46
|
-
}
|
|
47
|
-
/** CWD encoding rule, matching pi's getDefaultSessionDir. */
|
|
48
|
-
function cwdToSessionSubdir(cwd) {
|
|
49
|
-
return `--${cwd.replace(/^[\/\\]/, '').replace(/[\/\\:]/g, '-')}--`;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Find the latest session file in the CWD session directory with mtime > since.
|
|
53
|
-
*/
|
|
54
|
-
async function findLatestSession(since, debug) {
|
|
55
|
-
const cwdSessionDir = path.join(getAgentDir(), 'sessions', cwdToSessionSubdir(process.cwd()));
|
|
56
|
-
let files;
|
|
57
|
-
try {
|
|
58
|
-
files = await fs.promises.readdir(cwdSessionDir);
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return null; // directory doesn't exist
|
|
62
|
-
}
|
|
63
|
-
let latest = null;
|
|
64
|
-
for (const file of files) {
|
|
65
|
-
if (!file.endsWith('.jsonl'))
|
|
66
|
-
continue;
|
|
67
|
-
try {
|
|
68
|
-
const filePath = path.join(cwdSessionDir, file);
|
|
69
|
-
const fstat = await fs.promises.stat(filePath);
|
|
70
|
-
const mtime = fstat.mtimeMs;
|
|
71
|
-
if (mtime > since && (!latest || mtime > latest.mtime)) {
|
|
72
|
-
latest = { path: filePath, mtime };
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
debug('findLatestSession: stat error for', file);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return latest?.path ?? null;
|
|
80
|
-
}
|
|
81
|
-
// ═══════════════════════════════════════
|
|
82
|
-
// App
|
|
83
|
-
// ═══════════════════════════════════════
|
|
84
35
|
export class App {
|
|
85
36
|
// ── Injected dependencies ──
|
|
86
37
|
cfg;
|
|
@@ -95,12 +46,8 @@ export class App {
|
|
|
95
46
|
// ── FIFO ──
|
|
96
47
|
fifoFd = null;
|
|
97
48
|
cleaned = false;
|
|
98
|
-
// ── Agent mode
|
|
99
|
-
|
|
100
|
-
agentCmd = '';
|
|
101
|
-
agentStartTime = 0;
|
|
102
|
-
stdinBuffer = [];
|
|
103
|
-
renderer = null;
|
|
49
|
+
// ── Agent mode (null = normal) ──
|
|
50
|
+
agentSession = null;
|
|
104
51
|
// ── Reverse session recovery ──
|
|
105
52
|
sessionEpoch = Date.now();
|
|
106
53
|
reverseStartTime = 0;
|
|
@@ -114,8 +61,9 @@ export class App {
|
|
|
114
61
|
this.tmpDir = infra.tmpDir;
|
|
115
62
|
this.rcPath = infra.rcPath;
|
|
116
63
|
// Open debug log file (same file shell hooks append to)
|
|
117
|
-
|
|
118
|
-
|
|
64
|
+
this.debugFd = deps.cfg.debugPath
|
|
65
|
+
? fs.openSync(deps.cfg.debugPath, 'a')
|
|
66
|
+
: null;
|
|
119
67
|
// Wire internal event handlers
|
|
120
68
|
this.agent.onEvent((event) => this.onAgentEvent(event));
|
|
121
69
|
this.recorder.onEvent((evt) => this.onRecorderEvent(evt));
|
|
@@ -132,19 +80,19 @@ export class App {
|
|
|
132
80
|
onPtyExit(code) {
|
|
133
81
|
this.debugLog('PTY exited, code:', code);
|
|
134
82
|
printExit();
|
|
135
|
-
log('exit', { context_count: this.recorder.
|
|
83
|
+
log('exit', { context_count: this.recorder.contextCount, code });
|
|
136
84
|
closeLog();
|
|
137
85
|
this.cleanup();
|
|
138
86
|
process.exit(code);
|
|
139
87
|
}
|
|
140
88
|
/** Terminal stdin data → mode routing. */
|
|
141
89
|
onStdin(data) {
|
|
142
|
-
if (this.
|
|
90
|
+
if (this.agentSession) {
|
|
143
91
|
if (data.length === 1 && data[0] === 0x03) {
|
|
144
92
|
this.abortAgent();
|
|
145
93
|
}
|
|
146
94
|
else {
|
|
147
|
-
this.stdinBuffer.push(Buffer.from(data));
|
|
95
|
+
this.agentSession.stdinBuffer.push(Buffer.from(data));
|
|
148
96
|
}
|
|
149
97
|
return;
|
|
150
98
|
}
|
|
@@ -163,6 +111,7 @@ export class App {
|
|
|
163
111
|
/** Terminal resized. */
|
|
164
112
|
onResize(cols, rows) {
|
|
165
113
|
this.pty.resize(cols, rows);
|
|
114
|
+
this.recorder.updateSize(cols, rows);
|
|
166
115
|
}
|
|
167
116
|
/** Cleanup all resources. Public — called by signal handlers in main.ts. */
|
|
168
117
|
cleanup() {
|
|
@@ -212,16 +161,16 @@ export class App {
|
|
|
212
161
|
// Agent event handler
|
|
213
162
|
// ═══════════════════════════════════════
|
|
214
163
|
onAgentEvent(event) {
|
|
215
|
-
this.renderer
|
|
216
|
-
if (event.type === 'agent_done' && this.
|
|
164
|
+
this.agentSession?.renderer.handleEvent(event);
|
|
165
|
+
if (event.type === 'agent_done' && this.agentSession) {
|
|
217
166
|
log('agent_done', {
|
|
218
|
-
cmd: this.
|
|
219
|
-
duration_ms: Date.now() - this.
|
|
167
|
+
cmd: this.agentSession.cmd,
|
|
168
|
+
duration_ms: Date.now() - this.agentSession.startTime,
|
|
220
169
|
});
|
|
221
170
|
this.exitAgentMode();
|
|
222
171
|
}
|
|
223
|
-
if (event.type === 'agent_error' && this.
|
|
224
|
-
log('agent_error', { cmd: this.
|
|
172
|
+
if (event.type === 'agent_error' && this.agentSession) {
|
|
173
|
+
log('agent_error', { cmd: this.agentSession.cmd, error: event.error });
|
|
225
174
|
this.exitAgentMode();
|
|
226
175
|
}
|
|
227
176
|
}
|
|
@@ -235,14 +184,18 @@ export class App {
|
|
|
235
184
|
this.fifoFd = fs.openSync(this.fifoPath, 'w');
|
|
236
185
|
this.debugLog('FIFO write fd opened');
|
|
237
186
|
log('shell_ready', { pid: this.pty.pid });
|
|
238
|
-
|
|
187
|
+
if (!this.cfg.noBanner) {
|
|
188
|
+
printBanner(this.cfg.version, this.cfg.shell, {
|
|
189
|
+
noAgent: this.cfg.noAgent,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
239
192
|
break;
|
|
240
193
|
case 'context':
|
|
241
194
|
log('context', {
|
|
242
195
|
prompt: evt.entry.prompt,
|
|
243
196
|
output: evt.entry.output,
|
|
244
197
|
rc: evt.entry.rc,
|
|
245
|
-
kept: this.recorder.
|
|
198
|
+
kept: this.recorder.contextCount,
|
|
246
199
|
});
|
|
247
200
|
break;
|
|
248
201
|
case 'context_skip':
|
|
@@ -271,19 +224,16 @@ export class App {
|
|
|
271
224
|
// Agent mode transitions
|
|
272
225
|
// ═══════════════════════════════════════
|
|
273
226
|
enterAgentMode(cmd) {
|
|
274
|
-
this.mode = 'agent';
|
|
275
|
-
this.agentCmd = cmd;
|
|
276
|
-
this.agentStartTime = Date.now();
|
|
277
|
-
this.stdinBuffer = [];
|
|
278
227
|
this.debugLog('enterAgentMode:', cmd);
|
|
279
228
|
const entries = this.recorder.drain();
|
|
280
229
|
log('agent', { cmd, context_count: entries.length });
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
230
|
+
const renderer = new StreamRenderer(this.cfg.toolResultLines, this.cfg.spinnerInterval);
|
|
231
|
+
this.agentSession = { cmd, startTime: Date.now(), stdinBuffer: [], renderer };
|
|
232
|
+
const crashInfo = this.agent.consumeCrashInfo();
|
|
233
|
+
if (crashInfo) {
|
|
234
|
+
printNotice(crashInfo);
|
|
285
235
|
}
|
|
286
|
-
|
|
236
|
+
renderer.showSpinner();
|
|
287
237
|
let message = cmd;
|
|
288
238
|
const ctx = formatContext(entries);
|
|
289
239
|
if (ctx) {
|
|
@@ -292,15 +242,15 @@ export class App {
|
|
|
292
242
|
this.agent.submit(message);
|
|
293
243
|
}
|
|
294
244
|
exitAgentMode() {
|
|
295
|
-
|
|
296
|
-
this.
|
|
297
|
-
this.
|
|
245
|
+
const session = this.agentSession;
|
|
246
|
+
this.debugLog('exitAgentMode, stdinBuffer:', session.stdinBuffer.length, 'chunks');
|
|
247
|
+
this.agentSession = null;
|
|
298
248
|
this.fifoWrite('PROCEED');
|
|
299
249
|
// Request state to get session file (for subsequent reverse).
|
|
300
250
|
// Only if agent process is alive — don't respawn after crash.
|
|
301
251
|
if (this.agent.alive) {
|
|
302
252
|
this.agent
|
|
303
|
-
.rpcWait({ type: 'get_state' }
|
|
253
|
+
.rpcWait({ type: 'get_state' })
|
|
304
254
|
.then((response) => {
|
|
305
255
|
if (response.success && response.data?.sessionFile) {
|
|
306
256
|
this.agent.sessionFile = response.data.sessionFile;
|
|
@@ -310,28 +260,25 @@ export class App {
|
|
|
310
260
|
log('get_state_error', { error: String(err) });
|
|
311
261
|
});
|
|
312
262
|
}
|
|
313
|
-
|
|
314
|
-
this.stdinBuffer = [];
|
|
315
|
-
if (buffered.length > 0) {
|
|
263
|
+
if (session.stdinBuffer.length > 0) {
|
|
316
264
|
setTimeout(() => {
|
|
317
|
-
for (const chunk of
|
|
265
|
+
for (const chunk of session.stdinBuffer) {
|
|
318
266
|
this.pty.write(chunk.toString());
|
|
319
267
|
}
|
|
320
|
-
this.debugLog('replayed',
|
|
321
|
-
},
|
|
268
|
+
this.debugLog('replayed', session.stdinBuffer.length, 'stdin chunks');
|
|
269
|
+
}, this.cfg.stdinReplayDelay);
|
|
322
270
|
}
|
|
323
271
|
}
|
|
324
272
|
abortAgent() {
|
|
325
273
|
this.debugLog('abortAgent');
|
|
274
|
+
const session = this.agentSession;
|
|
326
275
|
this.agent.abort();
|
|
327
|
-
|
|
276
|
+
session.renderer.printInterrupted();
|
|
328
277
|
log('agent_abort', {
|
|
329
|
-
cmd:
|
|
330
|
-
duration_ms: Date.now() -
|
|
278
|
+
cmd: session.cmd,
|
|
279
|
+
duration_ms: Date.now() - session.startTime,
|
|
331
280
|
});
|
|
332
|
-
this.
|
|
333
|
-
this.renderer = null;
|
|
334
|
-
this.stdinBuffer = [];
|
|
281
|
+
this.agentSession = null;
|
|
335
282
|
this.fifoWrite('PROCEED');
|
|
336
283
|
}
|
|
337
284
|
// ═══════════════════════════════════════
|
|
@@ -373,9 +320,9 @@ export class App {
|
|
|
373
320
|
const arg = parts.slice(1).join(' ');
|
|
374
321
|
switch (name) {
|
|
375
322
|
case '/compact': {
|
|
376
|
-
const stopSpinner = startSpinner('Compacting...');
|
|
323
|
+
const stopSpinner = startSpinner('Compacting...', this.cfg.spinnerInterval);
|
|
377
324
|
try {
|
|
378
|
-
return await this.agent.rpcWait({ type: 'compact', ...(arg ? { customInstructions: arg } : {}) },
|
|
325
|
+
return await this.agent.rpcWait({ type: 'compact', ...(arg ? { customInstructions: arg } : {}) }, this.cfg.compactTimeout);
|
|
379
326
|
}
|
|
380
327
|
finally {
|
|
381
328
|
stopSpinner();
|
|
@@ -383,7 +330,9 @@ export class App {
|
|
|
383
330
|
}
|
|
384
331
|
case '/model': {
|
|
385
332
|
if (!arg) {
|
|
386
|
-
|
|
333
|
+
// Query current model via get_state, then wrap as set_model response
|
|
334
|
+
// to reuse printControlResult's existing set_model rendering.
|
|
335
|
+
const state = await this.agent.rpcWait({ type: 'get_state' });
|
|
387
336
|
if (state.success && state.data?.model) {
|
|
388
337
|
const m = state.data.model;
|
|
389
338
|
const prov = m.provider;
|
|
@@ -411,15 +360,19 @@ export class App {
|
|
|
411
360
|
type: 'set_model',
|
|
412
361
|
provider: arg.slice(0, slashIdx),
|
|
413
362
|
modelId: arg.slice(slashIdx + 1),
|
|
414
|
-
}
|
|
363
|
+
});
|
|
415
364
|
}
|
|
416
365
|
else {
|
|
417
|
-
return await this.agent.rpcWait({
|
|
366
|
+
return await this.agent.rpcWait({
|
|
367
|
+
type: 'set_model',
|
|
368
|
+
provider: '',
|
|
369
|
+
modelId: arg,
|
|
370
|
+
});
|
|
418
371
|
}
|
|
419
372
|
}
|
|
420
373
|
case '/think': {
|
|
421
374
|
const level = arg || 'medium';
|
|
422
|
-
return await this.agent.rpcWait({ type: 'set_thinking_level', level }
|
|
375
|
+
return await this.agent.rpcWait({ type: 'set_thinking_level', level });
|
|
423
376
|
}
|
|
424
377
|
default:
|
|
425
378
|
return null;
|
|
@@ -435,7 +388,7 @@ export class App {
|
|
|
435
388
|
this.reverseStartTime = Date.now();
|
|
436
389
|
this.preReverseSessionFile = sessionFile;
|
|
437
390
|
log('reverse', {
|
|
438
|
-
context_count: this.recorder.
|
|
391
|
+
context_count: this.recorder.contextCount,
|
|
439
392
|
session: sessionFile || null,
|
|
440
393
|
});
|
|
441
394
|
if (sessionFile) {
|
package/dist/config.js
CHANGED
|
@@ -11,11 +11,24 @@ const require = createRequire(import.meta.url);
|
|
|
11
11
|
// ── Defaults ──
|
|
12
12
|
export const DEFAULTS = {
|
|
13
13
|
shell: 'bash',
|
|
14
|
+
// Context truncation
|
|
14
15
|
maxContext: 20,
|
|
15
16
|
headLines: 50,
|
|
16
17
|
tailLines: 30,
|
|
17
18
|
lineWidth: 512,
|
|
19
|
+
// Rendering
|
|
18
20
|
toolResultLines: 10,
|
|
21
|
+
// Timeouts (ms)
|
|
22
|
+
rpcTimeout: 30_000, // default RPC response timeout
|
|
23
|
+
compactTimeout: 60_000, // /compact needs LLM generation — much longer
|
|
24
|
+
killTimeout: 2_000, // SIGTERM → SIGKILL escalation wait
|
|
25
|
+
stdinReplayDelay: 50, // wait for shell readline ready after agent exit
|
|
26
|
+
// Terminal defaults (PTY spawn + vterm replay fallback)
|
|
27
|
+
defaultCols: 120,
|
|
28
|
+
defaultRows: 30,
|
|
29
|
+
// Internal limits
|
|
30
|
+
compactBufferThreshold: 100_000, // recorder fullBuffer trim threshold (bytes)
|
|
31
|
+
spinnerInterval: 80, // spinner animation frame interval (ms)
|
|
19
32
|
};
|
|
20
33
|
// ── Version ──
|
|
21
34
|
function readVersion() {
|
|
@@ -182,6 +195,16 @@ export function loadConfig() {
|
|
|
182
195
|
tailLines: envInt('PISH_TAIL_LINES', DEFAULTS.tailLines),
|
|
183
196
|
lineWidth: envInt('PISH_LINE_WIDTH', DEFAULTS.lineWidth),
|
|
184
197
|
toolResultLines: envInt('PISH_TOOL_LINES', DEFAULTS.toolResultLines),
|
|
198
|
+
rpcTimeout: DEFAULTS.rpcTimeout,
|
|
199
|
+
compactTimeout: DEFAULTS.compactTimeout,
|
|
200
|
+
killTimeout: DEFAULTS.killTimeout,
|
|
201
|
+
stdinReplayDelay: DEFAULTS.stdinReplayDelay,
|
|
202
|
+
defaultCols: DEFAULTS.defaultCols,
|
|
203
|
+
defaultRows: DEFAULTS.defaultRows,
|
|
204
|
+
compactBufferThreshold: DEFAULTS.compactBufferThreshold,
|
|
205
|
+
spinnerInterval: DEFAULTS.spinnerInterval,
|
|
206
|
+
debugPath: process.env.PISH_DEBUG || null,
|
|
207
|
+
logTarget: process.env.PISH_LOG || null,
|
|
185
208
|
noBanner: process.env.PISH_NO_BANNER === '1',
|
|
186
209
|
};
|
|
187
210
|
}
|
package/dist/log.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Structured JSON event log.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Target values (from Config.logTarget):
|
|
5
|
+
* null → no output
|
|
6
6
|
* "1" | "stderr" → stderr
|
|
7
7
|
* file path → append to file
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from 'node:fs';
|
|
10
10
|
let logFd = null;
|
|
11
11
|
let logToStderr = false;
|
|
12
|
-
export function initLog() {
|
|
13
|
-
const target = process.env.PISH_LOG;
|
|
12
|
+
export function initLog(target) {
|
|
14
13
|
if (!target)
|
|
15
14
|
return;
|
|
16
15
|
if (target === '1' || target === 'stderr') {
|
package/dist/main.js
CHANGED
|
@@ -26,22 +26,15 @@ if (process.env.PISH_PID) {
|
|
|
26
26
|
// Bootstrap
|
|
27
27
|
// ═══════════════════════════════════════
|
|
28
28
|
const cfg = loadConfig();
|
|
29
|
-
initLog();
|
|
29
|
+
initLog(cfg.logTarget);
|
|
30
30
|
// ── Infrastructure ──
|
|
31
31
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pish-'));
|
|
32
32
|
const fifoPath = path.join(tmpDir, 'fifo');
|
|
33
33
|
execFileSync('mkfifo', [fifoPath]);
|
|
34
34
|
const rcPath = generateRcfile({ shell: cfg.shell, fifoPath, tmpDir });
|
|
35
35
|
// ── Objects ──
|
|
36
|
-
const recorder = new Recorder(
|
|
37
|
-
|
|
38
|
-
truncate: {
|
|
39
|
-
headLines: cfg.headLines,
|
|
40
|
-
tailLines: cfg.tailLines,
|
|
41
|
-
maxLineWidth: cfg.lineWidth,
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
const agent = new AgentManager(cfg.piPath);
|
|
36
|
+
const recorder = new Recorder(cfg);
|
|
37
|
+
const agent = new AgentManager(cfg);
|
|
45
38
|
// ── PTY ──
|
|
46
39
|
const shellArgs = cfg.shell === 'bash' ? ['--rcfile', rcPath, '-i'] : ['-i'];
|
|
47
40
|
const env = {
|
|
@@ -56,8 +49,8 @@ if (cfg.shell === 'zsh') {
|
|
|
56
49
|
}
|
|
57
50
|
const ptyProcess = pty.spawn(cfg.shellPath, shellArgs, {
|
|
58
51
|
name: 'xterm-256color',
|
|
59
|
-
cols: process.stdout.columns ||
|
|
60
|
-
rows: process.stdout.rows ||
|
|
52
|
+
cols: process.stdout.columns || cfg.defaultCols,
|
|
53
|
+
rows: process.stdout.rows || cfg.defaultRows,
|
|
61
54
|
cwd: process.cwd(),
|
|
62
55
|
env,
|
|
63
56
|
});
|
|
@@ -72,7 +65,7 @@ process.stdin.setRawMode?.(true);
|
|
|
72
65
|
process.stdin.resume();
|
|
73
66
|
process.stdin.on('data', (data) => app.onStdin(data));
|
|
74
67
|
process.stdout.on('resize', () => {
|
|
75
|
-
app.onResize(process.stdout.columns ||
|
|
68
|
+
app.onResize(process.stdout.columns || cfg.defaultCols, process.stdout.rows || cfg.defaultRows);
|
|
76
69
|
});
|
|
77
70
|
// ═══════════════════════════════════════
|
|
78
71
|
// Signals
|
package/dist/recorder.js
CHANGED
|
@@ -9,12 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { log } from './log.js';
|
|
11
11
|
import { OscParser } from './osc.js';
|
|
12
|
-
import {
|
|
12
|
+
import { isAltScreen, stripAnsi, truncateLines } from './strip.js';
|
|
13
13
|
import { vtermReplay } from './vterm.js';
|
|
14
|
-
const DEFAULT_OPTIONS = {
|
|
15
|
-
maxContext: 20,
|
|
16
|
-
truncate: DEFAULT_TRUNCATE,
|
|
17
|
-
};
|
|
18
14
|
export class Recorder {
|
|
19
15
|
/**
|
|
20
16
|
* Complete clean PTY data (OSC 9154 stripped).
|
|
@@ -29,13 +25,27 @@ export class Recorder {
|
|
|
29
25
|
reverseInProgress = false;
|
|
30
26
|
gotFirstD = false;
|
|
31
27
|
pending = Promise.resolve();
|
|
28
|
+
/** Current terminal dimensions (updated via updateSize). */
|
|
29
|
+
cols;
|
|
30
|
+
rows;
|
|
32
31
|
opts;
|
|
33
32
|
oscParser = new OscParser();
|
|
34
33
|
/** Committed context entries. */
|
|
35
34
|
context = [];
|
|
36
35
|
_onEvent = null;
|
|
37
36
|
constructor(opts) {
|
|
38
|
-
this.opts =
|
|
37
|
+
this.opts = opts;
|
|
38
|
+
this.cols = opts.defaultCols;
|
|
39
|
+
this.rows = opts.defaultRows;
|
|
40
|
+
}
|
|
41
|
+
/** Number of committed context entries. */
|
|
42
|
+
get contextCount() {
|
|
43
|
+
return this.context.length;
|
|
44
|
+
}
|
|
45
|
+
/** Update terminal dimensions (called on resize). */
|
|
46
|
+
updateSize(cols, rows) {
|
|
47
|
+
this.cols = cols;
|
|
48
|
+
this.rows = rows;
|
|
39
49
|
}
|
|
40
50
|
onEvent(cb) {
|
|
41
51
|
this._onEvent = cb;
|
|
@@ -121,12 +131,16 @@ export class Recorder {
|
|
|
121
131
|
const cRel = snap.cAbs - snap.segStart;
|
|
122
132
|
const promptRaw = segData.slice(0, cRel);
|
|
123
133
|
const outputRaw = segData.slice(cRel);
|
|
124
|
-
promptText = await vtermReplay(promptRaw);
|
|
134
|
+
promptText = await vtermReplay(promptRaw, this.cols, this.rows);
|
|
125
135
|
if (isAltScreen(outputRaw)) {
|
|
126
136
|
outputText = '[full-screen app]';
|
|
127
137
|
}
|
|
128
138
|
else {
|
|
129
|
-
outputText = truncateLines(stripAnsi(outputRaw).trim(),
|
|
139
|
+
outputText = truncateLines(stripAnsi(outputRaw).trim(), {
|
|
140
|
+
headLines: this.opts.headLines,
|
|
141
|
+
tailLines: this.opts.tailLines,
|
|
142
|
+
lineWidth: this.opts.lineWidth,
|
|
143
|
+
});
|
|
130
144
|
}
|
|
131
145
|
}
|
|
132
146
|
else {
|
|
@@ -156,7 +170,7 @@ export class Recorder {
|
|
|
156
170
|
}
|
|
157
171
|
/** Release memory periodically (fullBuffer grows indefinitely). */
|
|
158
172
|
maybeCompact() {
|
|
159
|
-
if (this.segStart >
|
|
173
|
+
if (this.segStart > this.opts.compactBufferThreshold) {
|
|
160
174
|
this.fullBuffer = this.fullBuffer.slice(this.segStart);
|
|
161
175
|
if (this.cAbs !== null)
|
|
162
176
|
this.cAbs -= this.segStart;
|
package/dist/render.js
CHANGED
|
@@ -7,11 +7,9 @@
|
|
|
7
7
|
import { Box, Markdown, Text, visibleWidth, } from '@mariozechner/pi-tui';
|
|
8
8
|
import { bold, createMarkdownTheme, dim, TAG, theme, toolBg } from './theme.js';
|
|
9
9
|
// ─── Banner / Exit / Control ───
|
|
10
|
-
export function printBanner(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const parts = [`v${cfg.version}`, dim(cfg.shell)];
|
|
14
|
-
if (cfg.noAgent)
|
|
10
|
+
export function printBanner(version, shell, flags) {
|
|
11
|
+
const parts = [`v${version}`, dim(shell)];
|
|
12
|
+
if (flags?.noAgent)
|
|
15
13
|
parts.push(theme.warning('no-agent'));
|
|
16
14
|
process.stderr.write(`${TAG} ${parts.join(dim(' │ '))}\n`);
|
|
17
15
|
}
|
|
@@ -63,7 +61,7 @@ export function printNotice(msg) {
|
|
|
63
61
|
}
|
|
64
62
|
// ─── Spinner ───
|
|
65
63
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
66
|
-
export function startSpinner(label) {
|
|
64
|
+
export function startSpinner(label, intervalMs) {
|
|
67
65
|
let frame = 0;
|
|
68
66
|
let stopped = false;
|
|
69
67
|
const maxLabelCols = termWidth() - 3;
|
|
@@ -81,7 +79,7 @@ export function startSpinner(label) {
|
|
|
81
79
|
frame++;
|
|
82
80
|
};
|
|
83
81
|
render();
|
|
84
|
-
const timer = setInterval(render,
|
|
82
|
+
const timer = setInterval(render, intervalMs);
|
|
85
83
|
return () => {
|
|
86
84
|
if (stopped)
|
|
87
85
|
return;
|
|
@@ -92,7 +90,7 @@ export function startSpinner(label) {
|
|
|
92
90
|
}
|
|
93
91
|
// ─── Helpers ───
|
|
94
92
|
function termWidth() {
|
|
95
|
-
return process.stderr.columns ||
|
|
93
|
+
return process.stderr.columns || FALLBACK_TERM_WIDTH;
|
|
96
94
|
}
|
|
97
95
|
function flush(comp) {
|
|
98
96
|
for (const line of comp.render(termWidth())) {
|
|
@@ -105,6 +103,12 @@ function shortenPath(p) {
|
|
|
105
103
|
return `~${p.slice(home.length)}`;
|
|
106
104
|
return p;
|
|
107
105
|
}
|
|
106
|
+
/** Max visible chars for generic tool-title arg truncation. */
|
|
107
|
+
const TOOL_TITLE_MAX_CHARS = 60;
|
|
108
|
+
/** Max visible chars for compaction summary. */
|
|
109
|
+
const COMPACTION_SUMMARY_MAX_CHARS = 80;
|
|
110
|
+
/** Default terminal width when stderr columns is unknown. */
|
|
111
|
+
const FALLBACK_TERM_WIDTH = 80;
|
|
108
112
|
function truncate(s, max) {
|
|
109
113
|
const flat = s.replace(/\n/g, ' ');
|
|
110
114
|
if (flat.length <= max)
|
|
@@ -136,7 +140,7 @@ function formatToolTitle(toolName, args) {
|
|
|
136
140
|
}
|
|
137
141
|
const keys = Object.keys(args);
|
|
138
142
|
if (keys.length > 0)
|
|
139
|
-
return `${toolName} ${truncate(String(args[keys[0]]),
|
|
143
|
+
return `${toolName} ${truncate(String(args[keys[0]]), TOOL_TITLE_MAX_CHARS)}`;
|
|
140
144
|
return toolName;
|
|
141
145
|
}
|
|
142
146
|
function extractResultText(result) {
|
|
@@ -240,9 +244,11 @@ export class StreamRenderer {
|
|
|
240
244
|
mdTheme;
|
|
241
245
|
pendingTools = new Map();
|
|
242
246
|
toolResultLines;
|
|
243
|
-
|
|
247
|
+
spinnerInterval;
|
|
248
|
+
constructor(toolResultLines, spinnerInterval) {
|
|
244
249
|
this.mdTheme = createMarkdownTheme();
|
|
245
250
|
this.toolResultLines = toolResultLines;
|
|
251
|
+
this.spinnerInterval = spinnerInterval;
|
|
246
252
|
}
|
|
247
253
|
handleEvent(event) {
|
|
248
254
|
switch (event.type) {
|
|
@@ -290,7 +296,7 @@ export class StreamRenderer {
|
|
|
290
296
|
}
|
|
291
297
|
showSpinner() {
|
|
292
298
|
process.stderr.write('\x1b[?25l'); // hide cursor
|
|
293
|
-
this.stopSpinner = startSpinner('Working...');
|
|
299
|
+
this.stopSpinner = startSpinner('Working...', this.spinnerInterval);
|
|
294
300
|
}
|
|
295
301
|
// ─── Thinking ───
|
|
296
302
|
onThinkingStart() {
|
|
@@ -352,7 +358,7 @@ export class StreamRenderer {
|
|
|
352
358
|
this.clearSpinner();
|
|
353
359
|
const title = formatToolTitle(toolName, args);
|
|
354
360
|
this.pendingTools.set(toolCallId, title);
|
|
355
|
-
this.stopSpinner = startSpinner(title);
|
|
361
|
+
this.stopSpinner = startSpinner(title, this.spinnerInterval);
|
|
356
362
|
}
|
|
357
363
|
onToolEnd(toolCallId, _toolName, result, isError) {
|
|
358
364
|
this.clearSpinner();
|
|
@@ -385,7 +391,7 @@ export class StreamRenderer {
|
|
|
385
391
|
const reasonText = reason === 'overflow'
|
|
386
392
|
? 'Context overflow — auto-compacting...'
|
|
387
393
|
: 'Auto-compacting...';
|
|
388
|
-
this.stopSpinner = startSpinner(reasonText);
|
|
394
|
+
this.stopSpinner = startSpinner(reasonText, this.spinnerInterval);
|
|
389
395
|
}
|
|
390
396
|
onCompactionEnd(summary, aborted, error) {
|
|
391
397
|
this.clearSpinner();
|
|
@@ -396,7 +402,9 @@ export class StreamRenderer {
|
|
|
396
402
|
process.stderr.write(`${theme.error('✗')} compaction failed: ${error}\n`);
|
|
397
403
|
}
|
|
398
404
|
else if (summary) {
|
|
399
|
-
const short = summary.length >
|
|
405
|
+
const short = summary.length > COMPACTION_SUMMARY_MAX_CHARS
|
|
406
|
+
? `${summary.slice(0, COMPACTION_SUMMARY_MAX_CHARS - 3)}...`
|
|
407
|
+
: summary;
|
|
400
408
|
process.stderr.write(`${theme.accent('●')} compacted: ${theme.muted(short)}\n`);
|
|
401
409
|
}
|
|
402
410
|
}
|
|
@@ -437,7 +445,7 @@ export class StreamRenderer {
|
|
|
437
445
|
}
|
|
438
446
|
}
|
|
439
447
|
restartSpinner() {
|
|
440
|
-
this.stopSpinner = startSpinner('Working...');
|
|
448
|
+
this.stopSpinner = startSpinner('Working...', this.spinnerInterval);
|
|
441
449
|
}
|
|
442
450
|
countCursorLinesFromStart(text) {
|
|
443
451
|
const w = termWidth();
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session file discovery.
|
|
3
|
+
*
|
|
4
|
+
* Locates pi session files on disk. Encapsulates the path encoding
|
|
5
|
+
* convention shared with pi (getDefaultSessionDir) so that changes
|
|
6
|
+
* to session layout only affect this module.
|
|
7
|
+
*
|
|
8
|
+
* All functions are pure / side-effect-free (aside from filesystem reads).
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import * as os from 'node:os';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
/** pi agent directory, respects PI_CODING_AGENT_DIR env var. */
|
|
14
|
+
export function getAgentDir() {
|
|
15
|
+
const envDir = process.env.PI_CODING_AGENT_DIR;
|
|
16
|
+
if (envDir) {
|
|
17
|
+
if (envDir === '~')
|
|
18
|
+
return os.homedir();
|
|
19
|
+
if (envDir.startsWith('~/'))
|
|
20
|
+
return os.homedir() + envDir.slice(1);
|
|
21
|
+
return envDir;
|
|
22
|
+
}
|
|
23
|
+
return path.join(os.homedir(), '.pi', 'agent');
|
|
24
|
+
}
|
|
25
|
+
/** CWD encoding rule, matching pi's getDefaultSessionDir. */
|
|
26
|
+
export function cwdToSessionSubdir(cwd) {
|
|
27
|
+
return `--${cwd.replace(/^[\/\\]/, '').replace(/[\/\\:]/g, '-')}--`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Find the latest session file (.jsonl) in the CWD session directory
|
|
31
|
+
* with mtime strictly greater than `since` (epoch ms).
|
|
32
|
+
*
|
|
33
|
+
* Returns the absolute path, or null if none found.
|
|
34
|
+
*/
|
|
35
|
+
export async function findLatestSession(since, debug = () => { }) {
|
|
36
|
+
const cwdSessionDir = path.join(getAgentDir(), 'sessions', cwdToSessionSubdir(process.cwd()));
|
|
37
|
+
let files;
|
|
38
|
+
try {
|
|
39
|
+
files = await fs.promises.readdir(cwdSessionDir);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null; // directory doesn't exist
|
|
43
|
+
}
|
|
44
|
+
let latest = null;
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
if (!file.endsWith('.jsonl'))
|
|
47
|
+
continue;
|
|
48
|
+
try {
|
|
49
|
+
const filePath = path.join(cwdSessionDir, file);
|
|
50
|
+
const fstat = await fs.promises.stat(filePath);
|
|
51
|
+
const mtime = fstat.mtimeMs;
|
|
52
|
+
if (mtime > since && (!latest || mtime > latest.mtime)) {
|
|
53
|
+
latest = { path: filePath, mtime };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
debug('findLatestSession: stat error for', file);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return latest?.path ?? null;
|
|
61
|
+
}
|
package/dist/strip.js
CHANGED
|
@@ -14,11 +14,6 @@ export function isAltScreen(data) {
|
|
|
14
14
|
export function stripAnsi(data) {
|
|
15
15
|
return data.replace(ANSI_RE, '');
|
|
16
16
|
}
|
|
17
|
-
export const DEFAULT_TRUNCATE = {
|
|
18
|
-
headLines: 50,
|
|
19
|
-
tailLines: 30,
|
|
20
|
-
maxLineWidth: 512,
|
|
21
|
-
};
|
|
22
17
|
/**
|
|
23
18
|
* Line-level truncation: keep head + tail lines, truncate the middle.
|
|
24
19
|
* Also truncates individual long lines.
|
|
@@ -26,12 +21,12 @@ export const DEFAULT_TRUNCATE = {
|
|
|
26
21
|
* Head > tail ratio: command output typically starts with structured info
|
|
27
22
|
* (headers, column names, first results); tail preserves errors and final results.
|
|
28
23
|
*/
|
|
29
|
-
export function truncateLines(text, opts
|
|
24
|
+
export function truncateLines(text, opts) {
|
|
30
25
|
let lines = text.split('\n');
|
|
31
26
|
// Per-line truncation
|
|
32
27
|
lines = lines.map((line) => {
|
|
33
|
-
if (line.length > opts.
|
|
34
|
-
return `${line.slice(0, opts.
|
|
28
|
+
if (line.length > opts.lineWidth) {
|
|
29
|
+
return `${line.slice(0, opts.lineWidth)} ...`;
|
|
35
30
|
}
|
|
36
31
|
return line;
|
|
37
32
|
});
|
package/dist/vterm.js
CHANGED
|
@@ -37,7 +37,7 @@ function getTerm(cols, rows) {
|
|
|
37
37
|
* Replay raw PTY data and return the final displayed text.
|
|
38
38
|
* Digests readline editing, ANSI color, cursor operations, alt screen, etc.
|
|
39
39
|
*/
|
|
40
|
-
export function vtermReplay(data, cols
|
|
40
|
+
export function vtermReplay(data, cols, rows) {
|
|
41
41
|
return new Promise((resolve) => {
|
|
42
42
|
const term = getTerm(cols, rows);
|
|
43
43
|
term.write(data, () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aerode/pish",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "A shell-first pi coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dacapoday",
|
|
@@ -12,7 +12,15 @@
|
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/dacapoday/pish/issues"
|
|
14
14
|
},
|
|
15
|
-
"keywords": [
|
|
15
|
+
"keywords": [
|
|
16
|
+
"shell",
|
|
17
|
+
"ai",
|
|
18
|
+
"coding-agent",
|
|
19
|
+
"bash",
|
|
20
|
+
"zsh",
|
|
21
|
+
"pi",
|
|
22
|
+
"cli"
|
|
23
|
+
],
|
|
16
24
|
"engines": {
|
|
17
25
|
"node": ">=18"
|
|
18
26
|
},
|