@plurnk/plurnk 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PossumTech Laboratories, LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # plurnk
2
+
3
+ Client app for [plurnk-service](https://github.com/plurnk/plurnk-service). Type a prompt at the terminal, drive a real model loop through the plurnk DSL.
4
+
5
+ ## install
6
+
7
+ ```
8
+ npm install -g @plurnk/plurnk
9
+ ```
10
+
11
+ Requires Node ≥ 25.
12
+
13
+ ## use
14
+
15
+ Set up env (copy `.env.example` to `.env`, or export the vars in your shell):
16
+
17
+ ```
18
+ PLURNK_DB_PATH=./plurnk.db
19
+ PLURNK_MAX_TURNS=50
20
+ OPENAI_BASE_URL=http://localhost:11435 # or your provider endpoint
21
+ OPENAI_API_KEY=
22
+ OPENAI_MODEL=macher.gguf
23
+ OPENAI_CONTEXT_SIZE=262144
24
+ OPENAI_FETCH_TIMEOUT_MS=600000
25
+ OPENAI_THINK=0
26
+ ```
27
+
28
+ Run:
29
+
30
+ ```
31
+ plurnk "What is the capital of France? Store at known://france/capital and reply SEND[200]."
32
+ ```
33
+
34
+ You get a per-turn trace of the model's plurnk DSL emissions, the final loop status, and a list of entries written.
35
+
36
+ ## what plurnk does
37
+
38
+ Plurnk is the agent's command grammar. The model emits operations like:
39
+
40
+ ```
41
+ <<EDIT[france,europe](known://countries/france/capital):Paris:EDIT
42
+ <<SEND[200]:Paris:SEND
43
+ ```
44
+
45
+ The CLI parses these, dispatches each to its scheme handler, persists state, and surfaces what happened. Multi-turn loops emerge naturally — the model emits `SEND[102]` to continue, `SEND[200]` to terminate.
46
+
47
+ The protocol's pitch: fancy multi-turn agent behavior on weak models, via the structure rather than the model's raw capability. See [plurnk-service](https://github.com/plurnk/plurnk-service)'s AGENTS.md for the full architecture.
48
+
49
+ ## exit codes
50
+
51
+ - `0` — loop terminated successfully (`SEND[200]`)
52
+ - `1` — runtime error
53
+ - `2` — loop hit maxTurns safety cap
54
+ - `3` — loop terminated with cancellation (`SEND[499]`)
55
+ - `64` — usage error (missing prompt or env)
56
+
57
+ ## license
58
+
59
+ MIT.
package/bin/plurnk.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "../dist/cli.js";
3
+ main(process.argv);
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare const main: (argv: string[]) => Promise<void>;
2
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAqEA,eAAO,MAAM,IAAI,GAAU,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAyDvD,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,122 @@
1
+ // plurnk client entrypoint. Dispatches to one-shot CLI mode (positional args)
2
+ // or TUI REPL mode (no positionals). Per TUI.md §2 and §3.
3
+ import { parseArgs } from "node:util";
4
+ import Rpc from "./rpc.js";
5
+ import { runOneShot } from "./oneshot.js";
6
+ import { runTui } from "./tui.js";
7
+ const USAGE = `usage: plurnk [--json] [--session <name>] [--run <name>] [--model <alias>] [prompt...]
8
+
9
+ Connects to the plurnk-service daemon. Run a single prompt one-shot
10
+ (positional args) or enter the interactive REPL (no args).
11
+
12
+ env:
13
+ PLURNK_URL daemon WebSocket URL (default ws://127.0.0.1:3044)
14
+ PLURNK_SESSION resume an existing session by name
15
+ PLURNK_RUN resume (or create) a named run within that session
16
+ PLURNK_MODEL model alias to use for every loop.run on this invocation.
17
+ Shared with the daemon (user-level preference). --model
18
+ overrides for this invocation only.
19
+ PLURNK_YOLO when truthy, auto-accept every proposal without prompting.
20
+ Client-side only — proposals still go through the wire.
21
+
22
+ options:
23
+ -h, --help print this message and exit
24
+ --json emit the terminal answer as a JSON value on stdout
25
+ (compact, validated; non-JSON replies are wrapped as
26
+ JSON string literals). CLI mode only.
27
+ --session <name> resume the named session; without it, a fresh session
28
+ is created. Overrides PLURNK_SESSION.
29
+ --run <name> resume (or create) the named run within the session.
30
+ Requires --session. Overrides PLURNK_RUN.
31
+ --model <alias> model alias to pass on every loop.run. Resolved
32
+ server-side against PLURNK_MODEL_<alias>. Without
33
+ this (and PLURNK_MODEL unset), the daemon uses its
34
+ own boot-time PLURNK_MODEL.
35
+ --yolo auto-accept every proposal locally without prompting.
36
+ Overrides PLURNK_YOLO.
37
+ `;
38
+ const die = (code, message) => {
39
+ process.stderr.write(`${message}\n`);
40
+ process.exit(code);
41
+ };
42
+ // Resolve the session by name (via session.list filter) or create a fresh one.
43
+ // Names are the user-facing handle — ids are internals, not exposed via flags.
44
+ const attachOrCreateSession = async (rpc, opts) => {
45
+ if (opts.sessionName === undefined) {
46
+ return await rpc.call("session.create");
47
+ }
48
+ const { sessions } = await rpc.call("session.list");
49
+ const matches = sessions.filter((s) => s.name === opts.sessionName);
50
+ if (matches.length === 0) {
51
+ throw new Error(`no session named ${JSON.stringify(opts.sessionName)}; run without --session to create one`);
52
+ }
53
+ if (matches.length > 1) {
54
+ throw new Error(`${matches.length} sessions named ${JSON.stringify(opts.sessionName)}; pick a unique name`);
55
+ }
56
+ const attachParams = { id: matches[0].id };
57
+ if (opts.runName !== undefined)
58
+ attachParams.runName = opts.runName;
59
+ return await rpc.call("session.attach", attachParams);
60
+ };
61
+ export const main = async (argv) => {
62
+ try {
63
+ process.loadEnvFile(".env");
64
+ }
65
+ catch { /* .env is optional */ }
66
+ const { positionals, values } = parseArgs({
67
+ args: argv.slice(2),
68
+ allowPositionals: true,
69
+ options: {
70
+ help: { type: "boolean", short: "h" },
71
+ json: { type: "boolean" },
72
+ session: { type: "string" },
73
+ run: { type: "string" },
74
+ model: { type: "string" },
75
+ yolo: { type: "boolean" },
76
+ },
77
+ });
78
+ if (values.help) {
79
+ process.stdout.write(USAGE);
80
+ process.exit(0);
81
+ }
82
+ if (values.json === true && positionals.length === 0) {
83
+ die(64, "plurnk: --json requires a prompt (CLI mode only)");
84
+ }
85
+ // CLI flag overrides env; env overrides nothing.
86
+ const sessionName = values.session ?? process.env.PLURNK_SESSION;
87
+ const runName = values.run ?? process.env.PLURNK_RUN;
88
+ const modelAlias = values.model ?? process.env.PLURNK_MODEL;
89
+ const yolo = values.yolo === true || ["1", "true", "yes", "on"].includes((process.env.PLURNK_YOLO ?? "").toLowerCase());
90
+ if (runName !== undefined && sessionName === undefined) {
91
+ die(64, "plurnk: --run / PLURNK_RUN requires --session / PLURNK_SESSION");
92
+ }
93
+ const url = process.env.PLURNK_URL ?? "ws://127.0.0.1:3044";
94
+ const rpc = new Rpc({ url });
95
+ try {
96
+ await rpc.connect();
97
+ }
98
+ catch (cause) {
99
+ die(1, `plurnk: cannot connect to daemon at ${url}\n ${cause instanceof Error ? cause.message : String(cause)}\n\nIs the daemon running? Start it from plurnk-service with:\n node bin/plurnk-service.js start`);
100
+ }
101
+ try {
102
+ const session = await attachOrCreateSession(rpc, { sessionName, runName });
103
+ if (positionals.length === 0) {
104
+ await runTui(rpc, session, { modelAlias, yolo });
105
+ process.exit(0);
106
+ }
107
+ const prompt = positionals.join(" ");
108
+ const exitCode = await runOneShot(rpc, prompt, session, { json: values.json === true, modelAlias, yolo });
109
+ process.exit(exitCode);
110
+ }
111
+ catch (cause) {
112
+ process.stderr.write(`plurnk: ${cause instanceof Error ? cause.message : String(cause)}\n`);
113
+ if (cause instanceof Error && cause.cause !== undefined) {
114
+ process.stderr.write(` cause: ${cause.cause instanceof Error ? cause.cause.message : String(cause.cause)}\n`);
115
+ }
116
+ process.exit(1);
117
+ }
118
+ finally {
119
+ await rpc.close();
120
+ }
121
+ };
122
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,2DAA2D;AAE3D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8Bb,CAAC;AAEF,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,OAAe,EAAS,EAAE;IACjD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvB,CAAC,CAAC;AAIF,+EAA+E;AAC/E,+EAA+E;AAC/E,MAAM,qBAAqB,GAAG,KAAK,EAC/B,GAAQ,EACR,IAAgD,EAC1B,EAAE;IACxB,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,MAAM,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAkB,CAAC;IAC7D,CAAC;IACD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,cAAc,CAAkC,CAAC;IACrF,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,WAAW,CAAC,CAAC;IACpE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,uCAAuC,CAAC,CAAC;IACjH,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,mBAAmB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC;IAChH,CAAC;IACD,MAAM,YAAY,GAAqC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAC7E,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IACpE,OAAO,MAAM,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,YAAY,CAAkB,CAAC;AAC3E,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,IAAI,GAAG,KAAK,EAAE,IAAc,EAAiB,EAAE;IACxD,IAAI,CAAC;QAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC;IAErE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;QACtC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACnB,gBAAgB,EAAE,IAAI;QACtB,OAAO,EAAE;YACL,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;YACrC,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;YACzB,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC3B,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACvB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACzB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;SAC5B;KACJ,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAAC,CAAC;IAClE,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnD,GAAG,CAAC,EAAE,EAAE,kDAAkD,CAAC,CAAC;IAChE,CAAC;IAED,iDAAiD;IACjD,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IACjE,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;IACrD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IACxH,IAAI,OAAO,KAAK,SAAS,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QACrD,GAAG,CAAC,EAAE,EAAE,gEAAgE,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,qBAAqB,CAAC;IAC5D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IAE7B,IAAI,CAAC;QACD,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,GAAG,CAAC,CAAC,EAAE,uCAAuC,GAAG,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,mGAAmG,CAAC,CAAC;IACvN,CAAC;IAED,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,qBAAqB,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;QAC3E,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QACD,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1G,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5F,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnH,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;YAAS,CAAC;QACP,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { main } from "./cli.ts";
2
+ export { default as Rpc } from "./rpc.ts";
3
+ export type { RpcOptions } from "./rpc.ts";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,UAAU,CAAC;AAC1C,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { main } from "./cli.js";
2
+ export { default as Rpc } from "./rpc.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type Rpc from "./rpc.ts";
2
+ interface SessionResult {
3
+ id: number;
4
+ name: string;
5
+ }
6
+ export declare const runOneShot: (rpc: Rpc, prompt: string, session: SessionResult, opts: {
7
+ json: boolean;
8
+ modelAlias?: string;
9
+ yolo: boolean;
10
+ }) => Promise<number>;
11
+ export {};
12
+ //# sourceMappingURL=oneshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oneshot.d.ts","sourceRoot":"","sources":["../src/oneshot.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAahC,UAAU,aAAa;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AA2BpD,eAAO,MAAM,UAAU,GAAU,KAAK,GAAG,EAAE,QAAQ,MAAM,EAAE,SAAS,aAAa,EAAE,MAAM;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,KAAG,OAAO,CAAC,MAAM,CAmD9J,CAAC"}
@@ -0,0 +1,82 @@
1
+ // One-shot CLI mode. Plain text, no glyphs (per TUI.md §2 — Unix tool posture).
2
+ // Subscribes to log/entry notifications and prints each op as a plain line.
3
+ // Suitable for piping to grep / awk / head.
4
+ import { extractSendBody } from "./render.js";
5
+ import { reviewProposal } from "./proposal.js";
6
+ const formatPlain = (entry) => {
7
+ const path = entry.target_scheme !== null
8
+ ? `${entry.target_scheme}://${entry.target_hostname ?? ""}${entry.target_pathname ?? ""}${entry.target_fragment !== null ? `#${entry.target_fragment}` : ""}`
9
+ : "";
10
+ const sub = entry.op === "SEND" && typeof entry.signal === "number" ? `[${entry.signal}]` : "";
11
+ return `[${entry.status_rx}] ${entry.origin} ${entry.op}${sub} ${path}`.trim();
12
+ };
13
+ // Per plurnk-service Engine.ts, only SEND[200] and SEND[499] terminate a loop.
14
+ // Intermediate broadcasts (SEND[102] etc.) are protocol mechanics, not the answer.
15
+ const isTerminalBroadcast = (entry) => entry.op === "SEND"
16
+ && entry.target_scheme === null
17
+ && typeof entry.signal === "number"
18
+ && (entry.signal === 200 || entry.signal === 499);
19
+ const formatJsonReply = (txUnknown) => {
20
+ const tx = txUnknown;
21
+ if (tx === null || tx === undefined || tx.body === null || tx.body === undefined)
22
+ return "";
23
+ const { raw, json } = tx.body;
24
+ if (json !== null && json !== undefined)
25
+ return JSON.stringify(json);
26
+ if (typeof raw !== "string")
27
+ return "";
28
+ return JSON.stringify(raw);
29
+ };
30
+ export const runOneShot = async (rpc, prompt, session, opts) => {
31
+ // stdout is the program's product (the terminal answer); stderr is its narration.
32
+ // Per SPEC.md §2: `plurnk "X" > answer.txt` captures just the terminal broadcast body.
33
+ process.stderr.write(`session: ${session.name}\n`);
34
+ process.stderr.write(`prompt: ${prompt}\n\n`);
35
+ rpc.onNotification("log/entry", (params) => {
36
+ const p = params;
37
+ process.stderr.write(`${formatPlain(p.entry)}\n`);
38
+ if (!isTerminalBroadcast(p.entry))
39
+ return;
40
+ const out = opts.json ? formatJsonReply(p.entry.tx) : extractSendBody(p.entry.tx, /* prettify */ false);
41
+ if (out.length > 0)
42
+ process.stdout.write(`${out}\n`);
43
+ });
44
+ // Proposal lifecycle (plurnk-service #42): pause-and-review for side-effecting
45
+ // ops. Three paths:
46
+ // - --yolo: auto-accept locally without prompting (server still sends the notification).
47
+ // - TTY: interactive review.
48
+ // - no TTY, no yolo: fail closed (reject) so the daemon doesn't hang for 5 minutes.
49
+ rpc.onNotification("loop/proposal", (params) => {
50
+ const p = params;
51
+ void (async () => {
52
+ if (opts.yolo) {
53
+ await rpc.call("loop.resolve", { logEntryId: p.logEntryId, decision: "accept", outcome: "client_yolo" });
54
+ return;
55
+ }
56
+ if (process.stdin.isTTY !== true) {
57
+ await rpc.call("loop.resolve", {
58
+ logEntryId: p.logEntryId,
59
+ decision: "reject",
60
+ outcome: "no_tty_review",
61
+ });
62
+ return;
63
+ }
64
+ const resolution = await reviewProposal(p);
65
+ await rpc.call("loop.resolve", { logEntryId: p.logEntryId, ...resolution });
66
+ })();
67
+ });
68
+ const start = Date.now();
69
+ const loopParams = { prompt };
70
+ if (opts.modelAlias !== undefined)
71
+ loopParams.alias = opts.modelAlias;
72
+ const result = await rpc.call("loop.run", loopParams);
73
+ const wallMs = Date.now() - start;
74
+ process.stderr.write(`\nfinal status: ${result.finalStatus}${result.hitMaxTurns ? " (maxTurns reached)" : ""}\n`);
75
+ process.stderr.write(`turns: ${result.turnIds.length}, wall: ${(wallMs / 1000).toFixed(2)}s\n`);
76
+ if (result.finalStatus === 200)
77
+ return 0;
78
+ if (result.hitMaxTurns)
79
+ return 2;
80
+ return 3;
81
+ };
82
+ //# sourceMappingURL=oneshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oneshot.js","sourceRoot":"","sources":["../src/oneshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface ProposalParams {
2
+ logEntryId: number;
3
+ loopId: number;
4
+ turnId: number;
5
+ op: string;
6
+ target: {
7
+ scheme: string | null;
8
+ pathname: string | null;
9
+ };
10
+ body: string;
11
+ attrs: unknown;
12
+ }
13
+ export interface Resolution {
14
+ decision: "accept" | "reject" | "cancel";
15
+ body?: string;
16
+ outcome?: string;
17
+ }
18
+ export declare const reviewProposal: (params: ProposalParams) => Promise<Resolution>;
19
+ //# sourceMappingURL=proposal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposal.d.ts","sourceRoot":"","sources":["../src/proposal.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,cAAc;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACvB,QAAQ,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAqED,eAAO,MAAM,cAAc,GAAU,QAAQ,cAAc,KAAG,OAAO,CAAC,UAAU,CAyB/E,CAAC"}
@@ -0,0 +1,109 @@
1
+ // Proposal review — receives loop/proposal notifications, presents the user
2
+ // with an accept/edit/reject/cancel choice, and returns the resolution to send
3
+ // back via loop.resolve. Shared between CLI (one-shot) and TUI modes; mode-
4
+ // specific wiring (TTY check, readline pause) lives in the caller.
5
+ //
6
+ // Wire shape per plurnk-service Daemon.ts: loop/proposal carries an op kind,
7
+ // a target {scheme, pathname}, a body string (udiff for EDIT, command summary
8
+ // for EXEC), and an opaque attrs object. loop.resolve takes {logEntryId,
9
+ // decision, body?, outcome?}.
10
+ import { spawn } from "node:child_process";
11
+ import { writeFile, readFile, mkdtemp, rm } from "node:fs/promises";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ const useColor = process.env.NO_COLOR !== "1" && process.env.NO_COLOR !== "true";
15
+ const ansi = (code) => useColor ? `\x1b[${code}m` : "";
16
+ const RESET = ansi("0");
17
+ const BOLD = ansi("1");
18
+ const DIM = ansi("2");
19
+ const GREEN = ansi("32");
20
+ const RED = ansi("31");
21
+ const CYAN = ansi("36");
22
+ // Color udiff lines for EDIT proposals. Anything else renders plain.
23
+ const renderBody = (op, body) => {
24
+ if (op !== "EDIT")
25
+ return body;
26
+ return body.split("\n").map((line) => {
27
+ if (line.startsWith("+++") || line.startsWith("---"))
28
+ return `${BOLD}${line}${RESET}`;
29
+ if (line.startsWith("+"))
30
+ return `${GREEN}${line}${RESET}`;
31
+ if (line.startsWith("-"))
32
+ return `${RED}${line}${RESET}`;
33
+ if (line.startsWith("@@"))
34
+ return `${CYAN}${line}${RESET}`;
35
+ return line;
36
+ }).join("\n");
37
+ };
38
+ const formatTarget = ({ scheme, pathname }) => {
39
+ if (scheme === null)
40
+ return "(no target)";
41
+ return `${scheme}://${pathname ?? ""}`;
42
+ };
43
+ // Read one raw byte from stdin and return its char form. Caller is responsible
44
+ // for pausing any reader (readline) that competes for stdin first.
45
+ const readSingleKey = () => new Promise((resolve) => {
46
+ const stdin = process.stdin;
47
+ const wasRaw = stdin.isRaw;
48
+ stdin.setRawMode(true);
49
+ stdin.resume();
50
+ const onData = (chunk) => {
51
+ stdin.removeListener("data", onData);
52
+ stdin.setRawMode(wasRaw);
53
+ stdin.pause();
54
+ resolve(chunk.toString("utf8")[0] ?? "");
55
+ };
56
+ stdin.on("data", onData);
57
+ });
58
+ // Drop the proposal body into a tmpfile, spawn $EDITOR (or VISUAL, or vi) on
59
+ // it, wait for the editor to exit, read the result back. Empty file ⇒ cancel
60
+ // (git-commit convention). Non-empty ⇒ the new body to accept with.
61
+ const editInEditor = async (body, op) => {
62
+ const editor = process.env.VISUAL ?? process.env.EDITOR ?? "vi";
63
+ const dir = await mkdtemp(join(tmpdir(), "plurnk-proposal-"));
64
+ const suffix = op === "EDIT" ? ".diff" : op === "EXEC" ? ".sh" : ".txt";
65
+ const path = join(dir, `proposal${suffix}`);
66
+ try {
67
+ await writeFile(path, body, "utf8");
68
+ await new Promise((resolve, reject) => {
69
+ const proc = spawn(editor, [path], { stdio: "inherit" });
70
+ proc.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`${editor} exited with code ${code}`)));
71
+ proc.on("error", reject);
72
+ });
73
+ const edited = await readFile(path, "utf8");
74
+ return edited.trim().length === 0 ? null : edited;
75
+ }
76
+ finally {
77
+ await rm(dir, { recursive: true, force: true });
78
+ }
79
+ };
80
+ // Interactively review a proposal. Writes the rendered diff + menu to stderr
81
+ // (telemetry, not product); reads the user's single keypress; returns the
82
+ // resolution. Caller sends loop.resolve.
83
+ export const reviewProposal = async (params) => {
84
+ process.stderr.write(`\n${BOLD}── proposal ${params.op} ${formatTarget(params.target)} ──${RESET}\n`);
85
+ process.stderr.write(renderBody(params.op, params.body));
86
+ if (!params.body.endsWith("\n"))
87
+ process.stderr.write("\n");
88
+ process.stderr.write(`${DIM}[a]ccept · [e]dit · [r]eject · [c]ancel${RESET} `);
89
+ const key = (await readSingleKey()).toLowerCase();
90
+ process.stderr.write(`${key}\n`);
91
+ switch (key) {
92
+ case "a":
93
+ return { decision: "accept" };
94
+ case "e": {
95
+ const edited = await editInEditor(params.body, params.op);
96
+ if (edited === null)
97
+ return { decision: "cancel", outcome: "empty_editor_buffer" };
98
+ return { decision: "accept", body: edited };
99
+ }
100
+ case "r":
101
+ return { decision: "reject" };
102
+ case "c":
103
+ return { decision: "cancel" };
104
+ default:
105
+ // Anything else (including ctrl-c = \x03) → cancel for safety.
106
+ return { decision: "cancel", outcome: "unknown_key" };
107
+ }
108
+ };
109
+ //# sourceMappingURL=proposal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposal.js","sourceRoot":"","sources":["../src/proposal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export declare const OP_GLYPHS: Record<string, string>;
2
+ export declare const ORIGIN_GLYPHS: Record<string, string>;
3
+ export declare const sendSubGlyph: (status: number) => string;
4
+ export interface LogEntryWire {
5
+ id: number;
6
+ op: string;
7
+ suffix: string;
8
+ origin: string;
9
+ signal: unknown;
10
+ target_scheme: string | null;
11
+ target_pathname: string | null;
12
+ target_hostname: string | null;
13
+ target_fragment: string | null;
14
+ status_rx: number;
15
+ tx: unknown;
16
+ rx: unknown;
17
+ }
18
+ export declare const extractSendBody: (txUnknown: unknown, prettify: boolean) => string;
19
+ export declare const renderLogEntry: (entry: LogEntryWire) => string;
20
+ export declare const renderSummary: (turns: number, wallMs: number, tokens: number, finalStatus: number, hitMaxTurns: boolean) => string;
21
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAU5C,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKhD,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,KAAG,MAQ7C,CAAC;AAkEF,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,OAAO,CAAC;IACZ,EAAE,EAAE,OAAO,CAAC;CACf;AAgCD,eAAO,MAAM,eAAe,GAAI,WAAW,OAAO,EAAE,UAAU,OAAO,KAAG,MAWvE,CAAC;AA4BF,eAAO,MAAM,cAAc,GAAI,OAAO,YAAY,KAAG,MAyBpD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,EAAE,QAAQ,MAAM,EAAE,QAAQ,MAAM,EAAE,aAAa,MAAM,EAAE,aAAa,OAAO,KAAG,MAIxH,CAAC"}
package/dist/render.js ADDED
@@ -0,0 +1,196 @@
1
+ // Glyph palette + line formatting for the TUI log waterfall.
2
+ // Glyphs per TUI.md §4 (canonical for the constellation).
3
+ export const OP_GLYPHS = {
4
+ FIND: "🔍",
5
+ READ: "📖",
6
+ EDIT: "✏️ ",
7
+ COPY: "📋",
8
+ MOVE: "📦",
9
+ SHOW: "➕",
10
+ HIDE: "➖",
11
+ SEND: "✉️ ",
12
+ EXEC: "⚙️ ",
13
+ };
14
+ export const ORIGIN_GLYPHS = {
15
+ model: "🤖",
16
+ client: "👤",
17
+ system: "⚙️ ",
18
+ plugin: "🔌",
19
+ };
20
+ export const sendSubGlyph = (status) => {
21
+ if (status === 410)
22
+ return "🗑";
23
+ if (status === 499)
24
+ return "✋";
25
+ if (status === 102)
26
+ return "⏳";
27
+ if (status >= 200 && status < 300)
28
+ return "✅";
29
+ if (status >= 400 && status < 500)
30
+ return "⚠️ ";
31
+ if (status >= 500 && status < 600)
32
+ return "🔥";
33
+ return "";
34
+ };
35
+ // ANSI escape codes. NO_COLOR support per Unix convention.
36
+ const useColor = process.env.NO_COLOR !== "1" && process.env.NO_COLOR !== "true";
37
+ const code = (n) => useColor ? `\x1b[${n}m` : "";
38
+ const RESET = code("0");
39
+ const DIM = code("2");
40
+ const CYAN = code("36");
41
+ const GREEN = code("32");
42
+ const YELLOW = code("33");
43
+ const RED = code("31");
44
+ const BRIGHT_RED = code("1;31");
45
+ const colorForStatus = (status) => {
46
+ if (status === 102 || (status >= 300 && status < 400))
47
+ return YELLOW;
48
+ if (status >= 200 && status < 300)
49
+ return GREEN;
50
+ if (status >= 400 && status < 500)
51
+ return RED;
52
+ if (status >= 500 && status < 600)
53
+ return BRIGHT_RED;
54
+ return "";
55
+ };
56
+ const ellipsize = (s, max) => {
57
+ if (s.length <= max)
58
+ return s;
59
+ return s.slice(0, max - 1) + "…";
60
+ };
61
+ // Build the EXTRA segment based on the op + entry shape.
62
+ const buildExtra = (entry) => {
63
+ const tx = entry.tx;
64
+ const rx = entry.rx;
65
+ if (tx === null)
66
+ return "";
67
+ switch (entry.op) {
68
+ case "EDIT": {
69
+ const body = typeof tx.body === "string" ? tx.body : "";
70
+ return body.length > 0 ? `${DIM}"${ellipsize(body.replace(/\n/g, " "), 40)}"${RESET}` : "";
71
+ }
72
+ case "COPY":
73
+ case "MOVE": {
74
+ const body = tx.body;
75
+ if (body === null)
76
+ return `${DIM}(deleted)${RESET}`;
77
+ return `${DIM}→ ${body.raw ?? ""}${RESET}`;
78
+ }
79
+ case "FIND": {
80
+ const results = rx !== null && typeof rx.results === "string" ? rx.results : "";
81
+ const count = results.length === 0 ? 0 : results.split("\n").filter((l) => l.length > 0).length;
82
+ return `${DIM}→ ${count} result${count === 1 ? "" : "s"}${RESET}`;
83
+ }
84
+ case "READ": {
85
+ const content = rx !== null && typeof rx.content === "string" ? rx.content : "";
86
+ return content.length > 0 ? `${DIM}"${ellipsize(content.replace(/\n/g, " "), 40)}"${RESET}` : "";
87
+ }
88
+ case "SEND": {
89
+ // Broadcast (target_scheme === null) is handled by renderBroadcast — not reached here.
90
+ return `${DIM}→ ${entry.target_scheme}://${entry.target_pathname ?? ""}${RESET}`;
91
+ }
92
+ case "EXEC": {
93
+ const body = typeof tx.body === "string" ? tx.body : "";
94
+ return body.length > 0 ? `${DIM}"${ellipsize(body.replace(/\n/g, " "), 40)}"${RESET}` : "";
95
+ }
96
+ default:
97
+ return "";
98
+ }
99
+ };
100
+ const BOLD = code("1");
101
+ const ITALIC = code("3");
102
+ // Heuristic: body looks like markdown if it carries any of the structural markers.
103
+ // False positives on plain text containing isolated `*` or `_` are avoided by requiring
104
+ // paired markers or line-anchored constructs.
105
+ const looksLikeMarkdown = (s) => /(^|\n)#{1,6}\s/.test(s) ||
106
+ /\*\*[^*\n]+\*\*/.test(s) ||
107
+ /(^|\n)[-*+]\s/.test(s) ||
108
+ /```/.test(s) ||
109
+ /\[[^\]]+\]\([^)]+\)/.test(s);
110
+ // Minimal vanilla-ANSI markdown: enough to make a reply readable, not a full parser.
111
+ const renderMarkdown = (s) => {
112
+ let out = s;
113
+ out = out.replace(/^(#{1,6})\s+(.*)$/gm, (_m, _h, text) => `${BOLD}${text}${RESET}`);
114
+ out = out.replace(/\*\*([^*\n]+)\*\*/g, (_m, t) => `${BOLD}${t}${RESET}`);
115
+ out = out.replace(/(^|[^*_])[*_]([^*_\n]+)[*_](?!\*)/g, (_m, pre, t) => `${pre}${ITALIC}${t}${RESET}`);
116
+ out = out.replace(/`([^`\n]+)`/g, (_m, t) => `${DIM}${t}${RESET}`);
117
+ out = out.replace(/^[-*+]\s/gm, "• ");
118
+ return out;
119
+ };
120
+ // Read a SEND body off a log_entry.tx, dispatching by content type.
121
+ // Per plurnk-grammar/schema/SendBody.json: tx.body is { raw, json } | null.
122
+ //
123
+ // prettify=true (TUI): json → pretty-print, markdown → ANSI, else raw.
124
+ // prettify=false (CLI): always raw verbatim — pretty-printing is a TUI convenience,
125
+ // not something a downstream pipe consumer should have to undo.
126
+ export const extractSendBody = (txUnknown, prettify) => {
127
+ const tx = txUnknown;
128
+ if (tx === null || tx === undefined)
129
+ return "";
130
+ const sendBody = tx.body;
131
+ if (sendBody === null || sendBody === undefined)
132
+ return "";
133
+ const { raw, json } = sendBody;
134
+ if (!prettify)
135
+ return typeof raw === "string" ? raw : "";
136
+ if (json !== null && json !== undefined)
137
+ return JSON.stringify(json, null, 2);
138
+ if (typeof raw !== "string")
139
+ return "";
140
+ if (looksLikeMarkdown(raw))
141
+ return renderMarkdown(raw);
142
+ return raw;
143
+ };
144
+ // Broadcast SEND (model → user) — multi-line block, content not telemetry.
145
+ // Per TUI.md §3.4.1 / SPEC.md §5.4. Header column-aligns with trace lines (2-space indent);
146
+ // body indents further (5 spaces) so it visually nests under the speaker.
147
+ const renderBroadcast = (entry) => {
148
+ const origin = ORIGIN_GLYPHS[entry.origin] ?? "?";
149
+ const opGlyph = OP_GLYPHS.SEND;
150
+ const subGlyph = typeof entry.signal === "number" ? sendSubGlyph(entry.signal) : "";
151
+ const statusColor = colorForStatus(entry.status_rx);
152
+ const statusText = `${statusColor}${entry.status_rx}${RESET}`;
153
+ const headerParts = [origin, opGlyph];
154
+ if (subGlyph.length > 0)
155
+ headerParts.push(subGlyph);
156
+ headerParts.push(statusText);
157
+ const header = headerParts.join(" ");
158
+ const body = extractSendBody(entry.tx, /* prettify */ true);
159
+ const bodyBlock = body.length > 0
160
+ ? "\n" + body.split("\n").map((line) => ` ${line}`).join("\n")
161
+ : "";
162
+ return `\n ${header}${bodyBlock}\n`;
163
+ };
164
+ // Render a log entry as one waterfall line.
165
+ // Returns the full ANSI-formatted line WITHOUT trailing newline,
166
+ // EXCEPT for broadcast SEND which returns a multi-line block with its own surrounding blank lines.
167
+ export const renderLogEntry = (entry) => {
168
+ if (entry.op === "SEND" && entry.target_scheme === null)
169
+ return renderBroadcast(entry);
170
+ const origin = ORIGIN_GLYPHS[entry.origin] ?? "?";
171
+ const opGlyph = OP_GLYPHS[entry.op] ?? "?";
172
+ const subGlyph = entry.op === "SEND" && typeof entry.signal === "number" ? sendSubGlyph(entry.signal) : "";
173
+ const statusColor = colorForStatus(entry.status_rx);
174
+ const statusText = `${statusColor}${entry.status_rx}${RESET}`;
175
+ let pathText = "";
176
+ if (entry.target_scheme !== null) {
177
+ const path = `${entry.target_scheme}://${entry.target_hostname ?? ""}${entry.target_pathname ?? ""}${entry.target_fragment !== null ? `#${entry.target_fragment}` : ""}`;
178
+ pathText = `${CYAN}${path}${RESET}`;
179
+ }
180
+ const extra = buildExtra(entry);
181
+ const parts = [origin, opGlyph];
182
+ if (subGlyph.length > 0)
183
+ parts.push(subGlyph);
184
+ parts.push(statusText);
185
+ if (pathText.length > 0)
186
+ parts.push(pathText);
187
+ if (extra.length > 0)
188
+ parts.push(extra);
189
+ return ` ${parts.join(" ")}`;
190
+ };
191
+ export const renderSummary = (turns, wallMs, tokens, finalStatus, hitMaxTurns) => {
192
+ const tag = hitMaxTurns ? "maxTurns" : finalStatus === 200 ? "done" : `final ${finalStatus}`;
193
+ const ms = wallMs >= 1000 ? `${(wallMs / 1000).toFixed(2)}s` : `${wallMs}ms`;
194
+ return `${DIM} ${tag} · ${turns} turn${turns === 1 ? "" : "s"} · ${ms} · ${tokens} tokens${RESET}`;
195
+ };
196
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
package/dist/rpc.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export interface RpcOptions {
2
+ url: string;
3
+ }
4
+ export default class Rpc {
5
+ #private;
6
+ constructor({ url }: RpcOptions);
7
+ connect(): Promise<void>;
8
+ call(method: string, params?: object): Promise<unknown>;
9
+ onNotification(method: string, handler: (params: unknown) => void): void;
10
+ close(): Promise<void>;
11
+ }
12
+ //# sourceMappingURL=rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,UAAU;IACvB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,MAAM,CAAC,OAAO,OAAO,GAAG;;gBAOR,EAAE,GAAG,EAAE,EAAE,UAAU;IAIzB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBxB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAW7D,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI;IAMlE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CA8B/B"}
package/dist/rpc.js ADDED
@@ -0,0 +1,84 @@
1
+ // WebSocket JSON-RPC client. Shared by CLI and TUI. Per TUI.md §1.
2
+ import { WebSocket } from "ws";
3
+ export default class Rpc {
4
+ #url;
5
+ #ws = null;
6
+ #nextId = 1;
7
+ #pending = new Map();
8
+ #notificationHandlers = new Map();
9
+ constructor({ url }) {
10
+ this.#url = url;
11
+ }
12
+ async connect() {
13
+ if (this.#ws !== null)
14
+ throw new Error("rpc: already connected");
15
+ const ws = new WebSocket(this.#url);
16
+ await new Promise((resolve, reject) => {
17
+ ws.once("open", () => resolve());
18
+ ws.once("error", (err) => reject(err));
19
+ });
20
+ this.#ws = ws;
21
+ ws.on("message", (data) => this.#onMessage(data));
22
+ ws.on("close", () => {
23
+ for (const [, pending] of this.#pending) {
24
+ pending.reject(new Error("rpc: connection closed before response"));
25
+ }
26
+ this.#pending.clear();
27
+ this.#ws = null;
28
+ });
29
+ }
30
+ async call(method, params) {
31
+ if (this.#ws === null)
32
+ throw new Error("rpc: not connected");
33
+ const id = this.#nextId++;
34
+ const payload = { jsonrpc: "2.0", id, method };
35
+ if (params !== undefined)
36
+ payload.params = params;
37
+ return new Promise((resolve, reject) => {
38
+ this.#pending.set(id, { resolve, reject });
39
+ this.#ws?.send(JSON.stringify(payload));
40
+ });
41
+ }
42
+ onNotification(method, handler) {
43
+ const existing = this.#notificationHandlers.get(method);
44
+ if (existing === undefined)
45
+ this.#notificationHandlers.set(method, [handler]);
46
+ else
47
+ existing.push(handler);
48
+ }
49
+ async close() {
50
+ if (this.#ws === null)
51
+ return;
52
+ const ws = this.#ws;
53
+ await new Promise((resolve) => {
54
+ ws.once("close", () => resolve());
55
+ ws.close();
56
+ });
57
+ this.#ws = null;
58
+ }
59
+ #onMessage(data) {
60
+ const text = typeof data === "string" ? data : data.toString("utf8");
61
+ const parsed = JSON.parse(text);
62
+ if ("id" in parsed && parsed.id !== undefined) {
63
+ const pending = this.#pending.get(parsed.id);
64
+ if (pending === undefined)
65
+ return;
66
+ this.#pending.delete(parsed.id);
67
+ if (parsed.error !== undefined) {
68
+ pending.reject(new Error(`rpc error ${parsed.error.code}: ${parsed.error.message}`));
69
+ }
70
+ else {
71
+ pending.resolve(parsed.result);
72
+ }
73
+ return;
74
+ }
75
+ if ("method" in parsed && parsed.method !== undefined) {
76
+ const handlers = this.#notificationHandlers.get(parsed.method);
77
+ if (handlers === undefined)
78
+ return;
79
+ for (const h of handlers)
80
+ h(parsed.params);
81
+ }
82
+ }
83
+ }
84
+ //# sourceMappingURL=rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"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"}
package/dist/tui.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type Rpc from "./rpc.ts";
2
+ interface SessionResult {
3
+ id: number;
4
+ name: string;
5
+ }
6
+ export declare const runTui: (rpc: Rpc, session: SessionResult, opts: {
7
+ modelAlias?: string;
8
+ yolo: boolean;
9
+ }) => Promise<void>;
10
+ export {};
11
+ //# sourceMappingURL=tui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAahC,UAAU,aAAa;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAEpD,eAAO,MAAM,MAAM,GAAU,KAAK,GAAG,EAAE,SAAS,aAAa,EAAE,MAAM;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,KAAG,OAAO,CAAC,IAAI,CAkGzH,CAAC"}
package/dist/tui.js ADDED
@@ -0,0 +1,99 @@
1
+ // TUI mode — interactive REPL with glyph waterfall. Vanilla ANSI + readline.
2
+ // Per TUI.md §3.
3
+ import readline from "node:readline";
4
+ import { renderLogEntry, renderSummary } from "./render.js";
5
+ import { reviewProposal } from "./proposal.js";
6
+ export const runTui = async (rpc, session, opts) => {
7
+ // Subscribe to log/entry notifications — render each as a waterfall line.
8
+ // `\r\x1b[2K` wipes any readline-redrawn prompt sitting on the current line
9
+ // before our output, otherwise the first trace line lands beside `> `.
10
+ rpc.onNotification("log/entry", (params) => {
11
+ const p = params;
12
+ process.stdout.write(`\r\x1b[2K${renderLogEntry(p.entry)}\n`);
13
+ });
14
+ process.stdout.write(`\x1b[2mplurnk v0.1.0 · ctrl-c to quit · session: ${session.name}\x1b[0m\n\n`);
15
+ const rl = readline.createInterface({
16
+ input: process.stdin,
17
+ output: process.stdout,
18
+ prompt: "\x1b[1m> \x1b[0m",
19
+ });
20
+ // Proposal lifecycle: --yolo auto-accepts locally; otherwise pause readline,
21
+ // review, resolve, resume.
22
+ rpc.onNotification("loop/proposal", (params) => {
23
+ const p = params;
24
+ void (async () => {
25
+ if (opts.yolo) {
26
+ await rpc.call("loop.resolve", { logEntryId: p.logEntryId, decision: "accept", outcome: "client_yolo" });
27
+ return;
28
+ }
29
+ rl.pause();
30
+ try {
31
+ const resolution = await reviewProposal(p);
32
+ await rpc.call("loop.resolve", { logEntryId: p.logEntryId, ...resolution });
33
+ }
34
+ finally {
35
+ rl.resume();
36
+ rl.prompt(true);
37
+ }
38
+ })();
39
+ });
40
+ rl.prompt();
41
+ return new Promise((resolve) => {
42
+ let inFlight = false;
43
+ rl.on("line", async (line) => {
44
+ const trimmed = line.trim();
45
+ if (trimmed.length === 0) {
46
+ rl.prompt();
47
+ return;
48
+ }
49
+ if (inFlight) {
50
+ process.stdout.write(" \x1b[2m(busy; wait for current dispatch to finish)\x1b[0m\n");
51
+ rl.prompt();
52
+ return;
53
+ }
54
+ inFlight = true;
55
+ const start = Date.now();
56
+ let tokenCount = 0;
57
+ let turnCount = 0;
58
+ let finalStatus = 0;
59
+ let hitMaxTurns = false;
60
+ try {
61
+ if (trimmed.startsWith("<<")) {
62
+ // Raw DSL: send to op.parse
63
+ const result = await rpc.call("op.parse", { text: trimmed });
64
+ finalStatus = result.results[result.results.length - 1]?.status ?? 0;
65
+ }
66
+ else {
67
+ // Prompt: send to loop.run
68
+ const loopParams = { prompt: trimmed };
69
+ if (opts.modelAlias !== undefined)
70
+ loopParams.alias = opts.modelAlias;
71
+ const result = await rpc.call("loop.run", loopParams);
72
+ finalStatus = result.finalStatus;
73
+ hitMaxTurns = result.hitMaxTurns;
74
+ turnCount = result.turnIds.length;
75
+ // Token count would come from log.read; for now we just don't have it
76
+ }
77
+ const wallMs = Date.now() - start;
78
+ process.stdout.write(`${renderSummary(turnCount, wallMs, tokenCount, finalStatus, hitMaxTurns)}\n`);
79
+ }
80
+ catch (cause) {
81
+ const msg = cause instanceof Error ? cause.message : String(cause);
82
+ process.stdout.write(` \x1b[31merror: ${msg}\x1b[0m\n`);
83
+ }
84
+ finally {
85
+ inFlight = false;
86
+ rl.prompt();
87
+ }
88
+ });
89
+ rl.on("close", () => {
90
+ process.stdout.write("\n");
91
+ resolve();
92
+ });
93
+ rl.on("SIGINT", () => {
94
+ // Ctrl-C: exit cleanly. Future: cancel in-flight run via SEND[499].
95
+ rl.close();
96
+ });
97
+ });
98
+ };
99
+ //# sourceMappingURL=tui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.js","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@plurnk/plurnk",
3
+ "version": "0.1.0",
4
+ "description": "Plurnk client app — CLI/TUI consuming plurnk-service. Type prompts at a terminal, drive real model loops through the protocol.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "engines": {
11
+ "node": ">=25"
12
+ },
13
+ "bin": {
14
+ "plurnk": "./bin/plurnk.js"
15
+ },
16
+ "files": [
17
+ "bin/plurnk.js",
18
+ "dist/**/*",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "test:lint": "tsc --noEmit",
23
+ "build:dist": "tsc -p tsconfig.build.json",
24
+ "build": "npm run build:dist",
25
+ "prepare": "npm run build"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^25.8.0",
29
+ "@types/ws": "^8.18.1",
30
+ "typescript": "^6.0.3"
31
+ },
32
+ "dependencies": {
33
+ "ws": "^8.20.1"
34
+ }
35
+ }