@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.run === "idle";
38
+ return this.alive && this.runState() === "idle";
25
39
  }
26
40
  runState() {
27
- return this.alive ? (this.run === "thinking" ? "thinking" : "idle") : "idle";
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
- const args = [
38
- "-p",
39
- "--input-format",
40
- "stream-json",
41
- "--output-format",
42
- "stream-json",
43
- "--verbose",
44
- "--dangerously-skip-permissions",
45
- ];
46
- // Continue the prior conversation after a runner restart (state in ~/.claude).
47
- if (this.claudeSessionId)
48
- args.push("--resume", this.claudeSessionId);
49
- this.proc = spawn(this.config.bin ?? "claude", args, {
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
- // MUST handle 'error' — without a listener, a spawn failure (e.g. `claude`
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.config.bin ?? "claude"}\`: ${err.message} — is Claude Code installed and on your PATH?]`);
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(`[claude] ${text}`);
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(`[claude exited with code ${code}]`);
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 Claude (the agent acts on it). */
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
- const msg = JSON.stringify({ type: "user", message: { role: "user", content: [{ type: "text", text }] } });
117
+ this.lastOutputAt = Date.now();
89
118
  try {
90
- this.proc?.stdin?.write(`${msg}\n`);
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)
@@ -40,6 +40,7 @@ export class CodingRuntime {
40
40
  id: input.sessionId,
41
41
  workDir,
42
42
  clientType: input.clientType,
43
+ command: input.command,
43
44
  env: input.env,
44
45
  statePath: defaultStatePath(this.reposBaseDir),
45
46
  bin: input.bin,
package/package.json CHANGED
@@ -1,46 +1,46 @@
1
1
  {
2
- "name": "@proagentstore/cli",
3
- "version": "0.2.2",
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
- ]
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
  }