@proagentstore/cli 0.2.2 → 0.2.3
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.
|
@@ -10,10 +10,24 @@ export class HeadlessSession {
|
|
|
10
10
|
run = "idle";
|
|
11
11
|
/** Claude Code's own session id (from the init event) — used to --resume. */
|
|
12
12
|
claudeSessionId = null;
|
|
13
|
+
/** "stream-json" for Claude (structured) · "raw" for any other CLI (stdout capture). */
|
|
14
|
+
mode;
|
|
15
|
+
cmdBin;
|
|
16
|
+
cmdArgs;
|
|
17
|
+
binName;
|
|
18
|
+
/** Wall-clock of the last stdout byte — drives the raw-mode idle heuristic. */
|
|
19
|
+
lastOutputAt = 0;
|
|
13
20
|
constructor(config) {
|
|
14
21
|
this.config = config;
|
|
15
22
|
this.sessionName = `pags-${config.clientType}-${config.id}`;
|
|
16
23
|
this.claudeSessionId = readState(config.statePath, config.id);
|
|
24
|
+
// Claude is the structured engine; everything else is a raw CLI.
|
|
25
|
+
this.mode = config.clientType === "claude" ? "stream-json" : "raw";
|
|
26
|
+
const tokens = config.command?.trim() ? config.command.trim().split(/\s+/) : [];
|
|
27
|
+
// A test/override bin wins; else the command's first word; else `claude`.
|
|
28
|
+
this.cmdBin = config.bin ?? tokens[0] ?? "claude";
|
|
29
|
+
this.cmdArgs = config.bin ? [] : tokens.slice(1);
|
|
30
|
+
this.binName = (this.cmdBin.split("/").pop() || this.cmdBin) || "cli";
|
|
17
31
|
}
|
|
18
32
|
/** True while the agent process is running. */
|
|
19
33
|
get alive() {
|
|
@@ -21,10 +35,17 @@ export class HeadlessSession {
|
|
|
21
35
|
}
|
|
22
36
|
/** Idle = ready for the next instruction. */
|
|
23
37
|
get ready() {
|
|
24
|
-
return this.alive && this.
|
|
38
|
+
return this.alive && this.runState() === "idle";
|
|
25
39
|
}
|
|
26
40
|
runState() {
|
|
27
|
-
|
|
41
|
+
if (!this.alive)
|
|
42
|
+
return "idle";
|
|
43
|
+
// Raw engines have no "turn over" event, so settle to idle once stdout has
|
|
44
|
+
// been quiet for a beat after a turn started.
|
|
45
|
+
if (this.mode === "raw" && this.run === "thinking" && Date.now() - this.lastOutputAt > 1500) {
|
|
46
|
+
this.run = "idle";
|
|
47
|
+
}
|
|
48
|
+
return this.run === "thinking" ? "thinking" : "idle";
|
|
28
49
|
}
|
|
29
50
|
/** The rendered conversation the brain/console reads (Claude's real output). */
|
|
30
51
|
snapshot() {
|
|
@@ -34,43 +55,51 @@ export class HeadlessSession {
|
|
|
34
55
|
start() {
|
|
35
56
|
if (this.alive)
|
|
36
57
|
return;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"--output-format",
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
let args;
|
|
59
|
+
if (this.mode === "stream-json") {
|
|
60
|
+
// Claude: the structured persistent-agent protocol. We own the stream-json
|
|
61
|
+
// flags; the user's command may add others (e.g. --model, a key via env).
|
|
62
|
+
args = ["-p", "--input-format", "stream-json", "--output-format", "stream-json", "--verbose"];
|
|
63
|
+
for (const a of this.cmdArgs)
|
|
64
|
+
if (!args.includes(a))
|
|
65
|
+
args.push(a);
|
|
66
|
+
if (!args.includes("--dangerously-skip-permissions"))
|
|
67
|
+
args.push("--dangerously-skip-permissions");
|
|
68
|
+
// Continue the prior conversation after a runner restart (state in ~/.claude).
|
|
69
|
+
if (this.claudeSessionId)
|
|
70
|
+
args.push("--resume", this.claudeSessionId);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Any other engine: run exactly what the user configured; capture stdout.
|
|
74
|
+
args = [...this.cmdArgs];
|
|
75
|
+
}
|
|
76
|
+
this.proc = spawn(this.cmdBin, args, {
|
|
50
77
|
cwd: this.config.workDir,
|
|
51
78
|
env: { ...process.env, ...this.config.env },
|
|
52
79
|
stdio: ["pipe", "pipe", "pipe"],
|
|
53
80
|
});
|
|
54
81
|
this.run = "idle";
|
|
55
|
-
|
|
82
|
+
this.lastOutputAt = Date.now();
|
|
83
|
+
// MUST handle 'error' — without a listener, a spawn failure (e.g. the binary
|
|
56
84
|
// not on PATH) is thrown as an uncaught exception and crashes the runner.
|
|
57
85
|
this.proc.on("error", (err) => {
|
|
58
86
|
this.run = "idle";
|
|
59
|
-
this.push(`[cannot run \`${this.
|
|
87
|
+
this.push(`[cannot run \`${this.cmdBin}\`: ${err.message} — is ${this.binName} installed and on your PATH?]`);
|
|
60
88
|
this.proc = null;
|
|
61
89
|
});
|
|
62
90
|
// Swallow EPIPE when writing to a process that just exited (one-shot turn).
|
|
63
91
|
this.proc.stdin?.on("error", () => { });
|
|
64
92
|
this.proc.stdout?.on("data", (d) => this.onStdout(d.toString("utf8")));
|
|
65
93
|
this.proc.stderr?.on("data", (d) => {
|
|
94
|
+
this.lastOutputAt = Date.now();
|
|
66
95
|
const text = d.toString("utf8").trim();
|
|
67
96
|
if (text)
|
|
68
|
-
this.push(`[
|
|
97
|
+
this.push(`[${this.binName}] ${stripAnsi(text)}`);
|
|
69
98
|
});
|
|
70
99
|
this.proc.on("exit", (code) => {
|
|
71
100
|
this.run = "idle";
|
|
72
101
|
if (code && code !== 0)
|
|
73
|
-
this.push(`[
|
|
102
|
+
this.push(`[${this.binName} exited with code ${code}]`);
|
|
74
103
|
// A bad --resume can kill the process instantly; drop it so the next
|
|
75
104
|
// start is a clean session rather than looping on a dead id.
|
|
76
105
|
if (code && code !== 0 && this.claudeSessionId) {
|
|
@@ -79,15 +108,22 @@ export class HeadlessSession {
|
|
|
79
108
|
}
|
|
80
109
|
});
|
|
81
110
|
}
|
|
82
|
-
/** Send a user turn to
|
|
111
|
+
/** Send a user turn to the agent (it acts on it). */
|
|
83
112
|
input(text) {
|
|
84
113
|
if (!this.alive)
|
|
85
114
|
this.start();
|
|
86
115
|
this.push(`\n❯ [${stamp()}] ${text}`); // ❯ — your turn, timestamped
|
|
87
116
|
this.run = "thinking";
|
|
88
|
-
|
|
117
|
+
this.lastOutputAt = Date.now();
|
|
89
118
|
try {
|
|
90
|
-
this.
|
|
119
|
+
if (this.mode === "stream-json") {
|
|
120
|
+
const msg = JSON.stringify({ type: "user", message: { role: "user", content: [{ type: "text", text }] } });
|
|
121
|
+
this.proc?.stdin?.write(`${msg}\n`);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// Raw CLI: write the line to stdin as if typed.
|
|
125
|
+
this.proc?.stdin?.write(`${text}\n`);
|
|
126
|
+
}
|
|
91
127
|
}
|
|
92
128
|
catch {
|
|
93
129
|
/* process may have died; the next snapshot reports not-alive */
|
|
@@ -120,16 +156,29 @@ export class HeadlessSession {
|
|
|
120
156
|
}
|
|
121
157
|
// ── internals ────────────────────────────────────────────────────────────
|
|
122
158
|
onStdout(chunk) {
|
|
159
|
+
this.lastOutputAt = Date.now();
|
|
123
160
|
this.buf += chunk;
|
|
124
161
|
let nl;
|
|
125
162
|
// biome-ignore lint/suspicious/noAssignInExpressions: standard line-buffer drain
|
|
126
163
|
while ((nl = this.buf.indexOf("\n")) >= 0) {
|
|
127
164
|
const line = this.buf.slice(0, nl).trim();
|
|
128
165
|
this.buf = this.buf.slice(nl + 1);
|
|
129
|
-
if (line)
|
|
166
|
+
if (!line)
|
|
167
|
+
continue;
|
|
168
|
+
if (this.mode === "stream-json")
|
|
130
169
|
this.handle(line);
|
|
170
|
+
else
|
|
171
|
+
this.pushRaw(line); // raw engine — the line IS the terminal output
|
|
131
172
|
}
|
|
132
173
|
}
|
|
174
|
+
/** Raw-engine stdout: strip ANSI control codes and append to the transcript. */
|
|
175
|
+
pushRaw(line) {
|
|
176
|
+
const clean = stripAnsi(line);
|
|
177
|
+
if (clean.trim())
|
|
178
|
+
this.push(clean);
|
|
179
|
+
if (this.transcript.length > 4000)
|
|
180
|
+
this.transcript = this.transcript.slice(-3000);
|
|
181
|
+
}
|
|
133
182
|
handle(line) {
|
|
134
183
|
let ev;
|
|
135
184
|
try {
|
|
@@ -181,6 +230,11 @@ export class HeadlessSession {
|
|
|
181
230
|
function stamp() {
|
|
182
231
|
return new Date().toTimeString().slice(0, 8);
|
|
183
232
|
}
|
|
233
|
+
/** Strip ANSI/VT escape sequences so a raw CLI's coloured output reads as plain text. */
|
|
234
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: stripping terminal escape codes
|
|
235
|
+
function stripAnsi(s) {
|
|
236
|
+
return s.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "").replace(/\x1b[()][AB0-2]/g, "").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
|
|
237
|
+
}
|
|
184
238
|
/** Compact a tool_use input object to a single readable line. */
|
|
185
239
|
function shortInput(input) {
|
|
186
240
|
if (input == null)
|
package/package.json
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
2
|
+
"name": "@proagentstore/cli",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "CLI for creating, publishing, and running ProAgentStore agents",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pags": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/ProAgentStore/platform#readme",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/ProAgentStore/platform.git",
|
|
14
|
+
"directory": "packages/cli"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"proagentstore",
|
|
18
|
+
"pags",
|
|
19
|
+
"cli",
|
|
20
|
+
"agents",
|
|
21
|
+
"mcp",
|
|
22
|
+
"browser-runner"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "pnpm --filter @proagentstore/browser-runner build && tsup src/index.ts --format esm --dts && node scripts/copy-browser-runner.mjs",
|
|
29
|
+
"dev": "tsx src/index.ts",
|
|
30
|
+
"typecheck": "tsc --noEmit"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"chalk": "^5.6.2",
|
|
34
|
+
"commander": "^13.0.0",
|
|
35
|
+
"playwright": "^1.60.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.0.0",
|
|
39
|
+
"tsup": "^8.4.0",
|
|
40
|
+
"tsx": "^4.21.0",
|
|
41
|
+
"typescript": "^5.7.0"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist"
|
|
45
|
+
]
|
|
46
46
|
}
|