@lucascouts/claude-agent-tui 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.
Files changed (112) hide show
  1. package/LICENSE +191 -0
  2. package/NOTICE +14 -0
  3. package/README.md +50 -0
  4. package/dist/acp-agent.d.ts +594 -0
  5. package/dist/acp-agent.d.ts.map +1 -0
  6. package/dist/acp-agent.js +2139 -0
  7. package/dist/ansi-mirror.d.ts +42 -0
  8. package/dist/ansi-mirror.d.ts.map +1 -0
  9. package/dist/ansi-mirror.js +61 -0
  10. package/dist/besteffort.d.ts +44 -0
  11. package/dist/besteffort.d.ts.map +1 -0
  12. package/dist/besteffort.js +100 -0
  13. package/dist/billing/entrypoint-guard.d.ts +97 -0
  14. package/dist/billing/entrypoint-guard.d.ts.map +1 -0
  15. package/dist/billing/entrypoint-guard.js +166 -0
  16. package/dist/claude-path.d.ts +12 -0
  17. package/dist/claude-path.d.ts.map +1 -0
  18. package/dist/claude-path.js +61 -0
  19. package/dist/diff-enriched-reader.d.ts +41 -0
  20. package/dist/diff-enriched-reader.d.ts.map +1 -0
  21. package/dist/diff-enriched-reader.js +106 -0
  22. package/dist/diff-source.d.ts +104 -0
  23. package/dist/diff-source.d.ts.map +1 -0
  24. package/dist/diff-source.js +164 -0
  25. package/dist/end-of-turn.d.ts +172 -0
  26. package/dist/end-of-turn.d.ts.map +1 -0
  27. package/dist/end-of-turn.js +415 -0
  28. package/dist/engine-lifecycle.d.ts +222 -0
  29. package/dist/engine-lifecycle.d.ts.map +1 -0
  30. package/dist/engine-lifecycle.js +236 -0
  31. package/dist/engine-pty.d.ts +143 -0
  32. package/dist/engine-pty.d.ts.map +1 -0
  33. package/dist/engine-pty.js +222 -0
  34. package/dist/engine-watcher.d.ts +83 -0
  35. package/dist/engine-watcher.d.ts.map +1 -0
  36. package/dist/engine-watcher.js +173 -0
  37. package/dist/engine.d.ts +30 -0
  38. package/dist/engine.d.ts.map +1 -0
  39. package/dist/engine.js +34 -0
  40. package/dist/event-switch.d.ts +164 -0
  41. package/dist/event-switch.d.ts.map +1 -0
  42. package/dist/event-switch.js +206 -0
  43. package/dist/gate/port.d.ts +38 -0
  44. package/dist/gate/port.d.ts.map +1 -0
  45. package/dist/gate/port.js +126 -0
  46. package/dist/gate/settings-writer.d.ts +130 -0
  47. package/dist/gate/settings-writer.d.ts.map +1 -0
  48. package/dist/gate/settings-writer.js +349 -0
  49. package/dist/index.d.ts +3 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +106 -0
  52. package/dist/jsonl.d.ts +267 -0
  53. package/dist/jsonl.d.ts.map +1 -0
  54. package/dist/jsonl.js +527 -0
  55. package/dist/lib.d.ts +6 -0
  56. package/dist/lib.d.ts.map +1 -0
  57. package/dist/lib.js +5 -0
  58. package/dist/linearize.d.ts +219 -0
  59. package/dist/linearize.d.ts.map +1 -0
  60. package/dist/linearize.js +444 -0
  61. package/dist/live-diff-env.d.ts +7 -0
  62. package/dist/live-diff-env.d.ts.map +1 -0
  63. package/dist/live-diff-env.js +18 -0
  64. package/dist/live-subagent-env.d.ts +7 -0
  65. package/dist/live-subagent-env.d.ts.map +1 -0
  66. package/dist/live-subagent-env.js +19 -0
  67. package/dist/permissions/allow-inject.d.ts +67 -0
  68. package/dist/permissions/allow-inject.d.ts.map +1 -0
  69. package/dist/permissions/allow-inject.js +85 -0
  70. package/dist/permissions/deny.d.ts +60 -0
  71. package/dist/permissions/deny.d.ts.map +1 -0
  72. package/dist/permissions/deny.js +81 -0
  73. package/dist/permissions/gate-wiring.d.ts +112 -0
  74. package/dist/permissions/gate-wiring.d.ts.map +1 -0
  75. package/dist/permissions/gate-wiring.js +350 -0
  76. package/dist/permissions/hook-server.d.ts +72 -0
  77. package/dist/permissions/hook-server.d.ts.map +1 -0
  78. package/dist/permissions/hook-server.js +179 -0
  79. package/dist/permissions/permission-mode.d.ts +67 -0
  80. package/dist/permissions/permission-mode.d.ts.map +1 -0
  81. package/dist/permissions/permission-mode.js +100 -0
  82. package/dist/permissions/request-permission.d.ts +102 -0
  83. package/dist/permissions/request-permission.d.ts.map +1 -0
  84. package/dist/permissions/request-permission.js +124 -0
  85. package/dist/settings.d.ts +68 -0
  86. package/dist/settings.d.ts.map +1 -0
  87. package/dist/settings.js +182 -0
  88. package/dist/stop-reason-map.d.ts +17 -0
  89. package/dist/stop-reason-map.d.ts.map +1 -0
  90. package/dist/stop-reason-map.js +33 -0
  91. package/dist/subagent-source.d.ts +63 -0
  92. package/dist/subagent-source.d.ts.map +1 -0
  93. package/dist/subagent-source.js +132 -0
  94. package/dist/subagent-watcher.d.ts +40 -0
  95. package/dist/subagent-watcher.d.ts.map +1 -0
  96. package/dist/subagent-watcher.js +108 -0
  97. package/dist/tools.d.ts +119 -0
  98. package/dist/tools.d.ts.map +1 -0
  99. package/dist/tools.js +729 -0
  100. package/dist/usage-env.d.ts +7 -0
  101. package/dist/usage-env.d.ts.map +1 -0
  102. package/dist/usage-env.js +16 -0
  103. package/dist/usage.d.ts +54 -0
  104. package/dist/usage.d.ts.map +1 -0
  105. package/dist/usage.js +53 -0
  106. package/dist/utils.d.ts +16 -0
  107. package/dist/utils.d.ts.map +1 -0
  108. package/dist/utils.js +83 -0
  109. package/dist/zed-register.d.ts +26 -0
  110. package/dist/zed-register.d.ts.map +1 -0
  111. package/dist/zed-register.js +106 -0
  112. package/package.json +79 -0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Story 042 (R1.1) — the USAGE_UPDATE entrypoint flag parse. Defaults ON: only the explicit
3
+ * opt-out values "0"/"false" disable it (unset/empty/any other value → ON). Pure + testable;
4
+ * index.ts calls this so the truth table is unit-checkable without running the entrypoint.
5
+ */
6
+ export declare function usageUpdateEnabled(env?: Record<string, string | undefined>): boolean;
7
+ //# sourceMappingURL=usage-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-env.d.ts","sourceRoot":"","sources":["../src/usage-env.ts"],"names":[],"mappings":"AASA;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,OAAO,CAEjG"}
@@ -0,0 +1,16 @@
1
+ // Story 042 / Task 1.1 (R1.1) — the USAGE_UPDATE entrypoint flag parse. Defaults ON: the optional
2
+ // UNSTABLE `usage_update` notification (src/usage.ts) is emitted by default, and only the explicit
3
+ // opt-out values "0"/"false" disable it (unset / empty / any other value → ON). This FLIPS the
4
+ // story-038 default-OFF posture now that story 039 confirmed the user's Zed ACCEPTS+RENDERS
5
+ // usage_update by code (the ZED-CLIENT-STUDY verdict); the per-session R8 reject latch still guards a
6
+ // client that rejects it. Kept a self-contained, PURE module (cf. usage.ts) so the truth table is
7
+ // unit-checkable without booting the entrypoint; index.ts calls it and threads the result through
8
+ // runAcp → AgentDeps → the agent. It does NOT go through lib.ts (the frozen upstream export surface).
9
+ /**
10
+ * Story 042 (R1.1) — the USAGE_UPDATE entrypoint flag parse. Defaults ON: only the explicit
11
+ * opt-out values "0"/"false" disable it (unset/empty/any other value → ON). Pure + testable;
12
+ * index.ts calls this so the truth table is unit-checkable without running the entrypoint.
13
+ */
14
+ export function usageUpdateEnabled(env = process.env) {
15
+ return env.USAGE_UPDATE !== "0" && env.USAGE_UPDATE !== "false";
16
+ }
@@ -0,0 +1,54 @@
1
+ import type { SessionUpdate } from "@agentclientprotocol/sdk";
2
+ /** The UNSTABLE `usage_update` variant of the SDK 0.22.1 SessionUpdate union. */
3
+ export type UsageUpdateNotification = Extract<SessionUpdate, {
4
+ sessionUpdate: "usage_update";
5
+ }>;
6
+ /** Defensive view of a JSONL message-event's `usage` block (§7). All fields optional. */
7
+ export interface UsageCarrier {
8
+ usage?: {
9
+ input_tokens?: number | null;
10
+ output_tokens?: number | null;
11
+ } | null;
12
+ }
13
+ export interface UsageOptions {
14
+ /**
15
+ * Total context-window size for the notification's `size` field. The pump supplies
16
+ * SessionState.contextWindowSize; absent it, `size` best-effort falls back to `used`
17
+ * (never an invented number — §1 "never fabricate counts").
18
+ */
19
+ contextWindowSize?: number;
20
+ }
21
+ /**
22
+ * Task 3.2 (R3.2) — map a message-event `usage.{input,output}_tokens` into the UNSTABLE
23
+ * `usage_update` notification. PURE and TOTAL: returns `undefined` (best-effort skip, §1)
24
+ * when neither token count is present; never throws on a partial/absent usage block.
25
+ *
26
+ * Story 042 — the EMITTED SHAPE is pinned to the Zed v1 `UsageUpdate` struct (story 039):
27
+ * EXACTLY `{ sessionUpdate: "usage_update", size, used }`, camelCase, and nothing more.
28
+ * • R2.2 — `used = input_tokens + output_tokens`; `size = contextWindowSize` (the inferred
29
+ * window). The `?? used` here is ONLY a library fallback for a caller that omits the window;
30
+ * the live pump (acp-agent.ts) ALWAYS supplies `contextWindowSize`, so the live path never
31
+ * takes it (the degenerate `size === used` cannot occur over the wire).
32
+ * • R3.1 — the optional `cost` field is INTENTIONALLY OMITTED and must NEVER be fabricated:
33
+ * the JSONL usage block carries only token counts, and `cost` is optional in the Zed v1 struct,
34
+ * so omitting it is contract-correct (an invented cost would violate §1 "never fabricate").
35
+ */
36
+ export declare function toUsageUpdate(message: UsageCarrier, options?: UsageOptions): UsageUpdateNotification | undefined;
37
+ export interface UsageFlagOptions extends UsageOptions {
38
+ /** Feature flag (Task 3.1, R3.1) — defaults OFF. When false, NOTHING is constructed. */
39
+ usageUpdate?: boolean;
40
+ }
41
+ /**
42
+ * Task 3.1 (R3.1) — the feature-flag gate. Returns the `usage_update` notifications to append
43
+ * to a turn's session/update stream: an EMPTY array when the flag is OFF, so the stream is
44
+ * byte-for-byte unaffected by the flag's presence; a single notification when the flag is ON and
45
+ * the message carries usage tokens. A no-op when OFF — `toUsageUpdate` is not even called — not a
46
+ * suppressed-after-build.
47
+ *
48
+ * Story 042 — the flag DEFAULT is decided by the caller: the entrypoint (src/usage-env.ts,
49
+ * `usageUpdateEnabled`) now defaults it ON, while a directly-constructed/test agent stays opt-in
50
+ * (acp-agent.ts `?? false`). Each emitted object is EXACTLY the pinned `{ sessionUpdate, size, used }`
51
+ * shape (see {@link toUsageUpdate}): no `cost`, and `size` is the inferred window on the live path.
52
+ */
53
+ export declare function usageUpdatesFor(message: UsageCarrier, options?: UsageFlagOptions): UsageUpdateNotification[];
54
+ //# sourceMappingURL=usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,iFAAiF;AACjF,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAAC,aAAa,EAAE;IAAE,aAAa,EAAE,cAAc,CAAA;CAAE,CAAC,CAAC;AAEhG,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CAChF;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,YAAiB,GACzB,uBAAuB,GAAG,SAAS,CAUrC;AAED,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,wFAAwF;IACxF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,gBAAqB,GAC7B,uBAAuB,EAAE,CAI3B"}
package/dist/usage.js ADDED
@@ -0,0 +1,53 @@
1
+ // Story 025 / Group 3 (R3.1, R3.2) — the optional, default-OFF UNSTABLE `usage_update`
2
+ // mapping. `usage_update` exists ONLY in the @agentclientprotocol/sdk 0.22.1 schema and is
3
+ // best-effort per §1; it maps the JSONL message-event `usage.{input,output}_tokens` into the
4
+ // SDK's { sessionUpdate:"usage_update", size, used } shape. The feature defaults OFF and stays
5
+ // OFF in production until the live-Zed acceptance probe (Task 3.3, R8) confirms the user's Zed
6
+ // tolerates it. A rejected emission is suppressed by the pump's per-session latch (Task 5.1).
7
+ //
8
+ // Kept a self-contained module (cf. diff-source.ts) imported by the acp-agent pump; it does
9
+ // NOT go through lib.ts (the frozen upstream export surface).
10
+ /**
11
+ * Task 3.2 (R3.2) — map a message-event `usage.{input,output}_tokens` into the UNSTABLE
12
+ * `usage_update` notification. PURE and TOTAL: returns `undefined` (best-effort skip, §1)
13
+ * when neither token count is present; never throws on a partial/absent usage block.
14
+ *
15
+ * Story 042 — the EMITTED SHAPE is pinned to the Zed v1 `UsageUpdate` struct (story 039):
16
+ * EXACTLY `{ sessionUpdate: "usage_update", size, used }`, camelCase, and nothing more.
17
+ * • R2.2 — `used = input_tokens + output_tokens`; `size = contextWindowSize` (the inferred
18
+ * window). The `?? used` here is ONLY a library fallback for a caller that omits the window;
19
+ * the live pump (acp-agent.ts) ALWAYS supplies `contextWindowSize`, so the live path never
20
+ * takes it (the degenerate `size === used` cannot occur over the wire).
21
+ * • R3.1 — the optional `cost` field is INTENTIONALLY OMITTED and must NEVER be fabricated:
22
+ * the JSONL usage block carries only token counts, and `cost` is optional in the Zed v1 struct,
23
+ * so omitting it is contract-correct (an invented cost would violate §1 "never fabricate").
24
+ */
25
+ export function toUsageUpdate(message, options = {}) {
26
+ const input = message.usage?.input_tokens;
27
+ const output = message.usage?.output_tokens;
28
+ // best-effort: no usage tokens at all -> emit nothing, never fabricate.
29
+ if ((input === undefined || input === null) && (output === undefined || output === null)) {
30
+ return undefined;
31
+ }
32
+ const used = (input ?? 0) + (output ?? 0);
33
+ const size = options.contextWindowSize ?? used;
34
+ return { sessionUpdate: "usage_update", size, used };
35
+ }
36
+ /**
37
+ * Task 3.1 (R3.1) — the feature-flag gate. Returns the `usage_update` notifications to append
38
+ * to a turn's session/update stream: an EMPTY array when the flag is OFF, so the stream is
39
+ * byte-for-byte unaffected by the flag's presence; a single notification when the flag is ON and
40
+ * the message carries usage tokens. A no-op when OFF — `toUsageUpdate` is not even called — not a
41
+ * suppressed-after-build.
42
+ *
43
+ * Story 042 — the flag DEFAULT is decided by the caller: the entrypoint (src/usage-env.ts,
44
+ * `usageUpdateEnabled`) now defaults it ON, while a directly-constructed/test agent stays opt-in
45
+ * (acp-agent.ts `?? false`). Each emitted object is EXACTLY the pinned `{ sessionUpdate, size, used }`
46
+ * shape (see {@link toUsageUpdate}): no `cost`, and `size` is the inferred window on the live path.
47
+ */
48
+ export function usageUpdatesFor(message, options = {}) {
49
+ if (!options.usageUpdate)
50
+ return []; // flag OFF (default) -> construct nothing
51
+ const update = toUsageUpdate(message, options);
52
+ return update ? [update] : [];
53
+ }
@@ -0,0 +1,16 @@
1
+ import { Readable, Writable } from "node:stream";
2
+ import { WritableStream, ReadableStream } from "node:stream/web";
3
+ import { Logger } from "./acp-agent.js";
4
+ export declare class Pushable<T> implements AsyncIterable<T> {
5
+ private queue;
6
+ private resolvers;
7
+ private done;
8
+ push(item: T): void;
9
+ end(): void;
10
+ [Symbol.asyncIterator](): AsyncIterator<T>;
11
+ }
12
+ export declare function nodeToWebWritable(nodeStream: Writable): WritableStream<Uint8Array>;
13
+ export declare function nodeToWebReadable(nodeStream: Readable): ReadableStream<Uint8Array>;
14
+ export declare function unreachable(value: never, logger?: Logger): void;
15
+ export declare function sleep(time: number): Promise<void>;
16
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGxC,qBAAa,QAAQ,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAClD,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,IAAI,CAAS;IAErB,IAAI,CAAC,IAAI,EAAE,CAAC;IASZ,GAAG;IAQH,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC;CAgB3C;AAGD,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAclF;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAUlF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,GAAE,MAAgB,QAQjE;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjD"}
package/dist/utils.js ADDED
@@ -0,0 +1,83 @@
1
+ // A pushable async iterable: allows you to push items and consume them with for-await.
2
+ import { WritableStream, ReadableStream } from "node:stream/web";
3
+ // Useful for bridging push-based and async-iterator-based code.
4
+ export class Pushable {
5
+ constructor() {
6
+ this.queue = [];
7
+ this.resolvers = [];
8
+ this.done = false;
9
+ }
10
+ push(item) {
11
+ if (this.resolvers.length > 0) {
12
+ const resolve = this.resolvers.shift();
13
+ resolve({ value: item, done: false });
14
+ }
15
+ else {
16
+ this.queue.push(item);
17
+ }
18
+ }
19
+ end() {
20
+ this.done = true;
21
+ while (this.resolvers.length > 0) {
22
+ const resolve = this.resolvers.shift();
23
+ resolve({ value: undefined, done: true });
24
+ }
25
+ }
26
+ [Symbol.asyncIterator]() {
27
+ return {
28
+ next: () => {
29
+ if (this.queue.length > 0) {
30
+ const value = this.queue.shift();
31
+ return Promise.resolve({ value, done: false });
32
+ }
33
+ if (this.done) {
34
+ return Promise.resolve({ value: undefined, done: true });
35
+ }
36
+ return new Promise((resolve) => {
37
+ this.resolvers.push(resolve);
38
+ });
39
+ },
40
+ };
41
+ }
42
+ }
43
+ // Helper to convert Node.js streams to Web Streams
44
+ export function nodeToWebWritable(nodeStream) {
45
+ return new WritableStream({
46
+ write(chunk) {
47
+ return new Promise((resolve, reject) => {
48
+ nodeStream.write(Buffer.from(chunk), (err) => {
49
+ if (err) {
50
+ reject(err);
51
+ }
52
+ else {
53
+ resolve();
54
+ }
55
+ });
56
+ });
57
+ },
58
+ });
59
+ }
60
+ export function nodeToWebReadable(nodeStream) {
61
+ return new ReadableStream({
62
+ start(controller) {
63
+ nodeStream.on("data", (chunk) => {
64
+ controller.enqueue(new Uint8Array(chunk));
65
+ });
66
+ nodeStream.on("end", () => controller.close());
67
+ nodeStream.on("error", (err) => controller.error(err));
68
+ },
69
+ });
70
+ }
71
+ export function unreachable(value, logger = console) {
72
+ let valueAsString;
73
+ try {
74
+ valueAsString = JSON.stringify(value);
75
+ }
76
+ catch {
77
+ valueAsString = value;
78
+ }
79
+ logger.error(`Unexpected case: ${valueAsString}`);
80
+ }
81
+ export function sleep(time) {
82
+ return new Promise((resolve) => setTimeout(resolve, time));
83
+ }
@@ -0,0 +1,26 @@
1
+ export interface ZedAgentEntry {
2
+ type: "custom";
3
+ command: "node";
4
+ args: [string];
5
+ env: Record<string, string>;
6
+ }
7
+ /**
8
+ * Build the single §15 `agent_servers` entry: a custom node spawn of the built
9
+ * dist/index.js with an EMPTY env (R6.2). Rejects a non-absolute path or a target that
10
+ * is not the built dist/index.js — a wrong `args[0]` makes the agent un-launchable.
11
+ */
12
+ export declare function buildZedAgentEntry(absDistPath: string): ZedAgentEntry;
13
+ /**
14
+ * Tolerant JSONC parse: strip // line comments and block comments that are NOT inside a
15
+ * string, drop trailing commas, then JSON.parse. String-aware so a `//` or `,` inside a
16
+ * value is never mangled.
17
+ */
18
+ export declare function parseJsonc(text: string): any;
19
+ /**
20
+ * Merge a single `agent_servers[name] = entry` into JSONC settings text, preserving
21
+ * every other key. Returns canonical JSON text. Idempotent: re-merging the same entry
22
+ * is a fixed point. (The user's real comment-bearing file is edited surgically instead;
23
+ * this is the programmatic merge used in tests and tooling.)
24
+ */
25
+ export declare function mergeAgentServer(settingsText: string, name: string, entry: ZedAgentEntry): string;
26
+ //# sourceMappingURL=zed-register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zed-register.d.ts","sourceRoot":"","sources":["../src/zed-register.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,CAQrE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAmD5C;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CAWjG"}
@@ -0,0 +1,106 @@
1
+ // Story 027 / Task 1.2 — Zed `agent_servers` registration helpers (§15, R1.2, R6.2).
2
+ //
3
+ // The fork is registered in Zed by merging ONE `agent_servers` entry into the user's
4
+ // settings.json. Two invariants:
5
+ // - NON-DESTRUCTIVE: every pre-existing key is preserved (read-modify-write a parsed
6
+ // copy, never overwrite wholesale).
7
+ // - env LEFT EMPTY (R6.2): the §15 entry's `env` is `{}` because billing sanitization
8
+ // is internal to the PTY spawn (story 013) — a single authoritative scrub point.
9
+ //
10
+ // NOTE on JSONC: Zed settings.json is JSONC (// comments + trailing commas). parseJsonc
11
+ // tolerates both for programmatic reads/merges. For the USER'S real file (which also
12
+ // carries header comments worth keeping), registration is applied as a surgical edit
13
+ // that preserves comments; this module is the tested programmatic merge over parsed
14
+ // JSON. No new dependency is added (the frozen base forbids one) — parseJsonc is a
15
+ // minimal, string-aware scrubber, not a full JSONC editor.
16
+ import { isAbsolute } from "node:path";
17
+ /**
18
+ * Build the single §15 `agent_servers` entry: a custom node spawn of the built
19
+ * dist/index.js with an EMPTY env (R6.2). Rejects a non-absolute path or a target that
20
+ * is not the built dist/index.js — a wrong `args[0]` makes the agent un-launchable.
21
+ */
22
+ export function buildZedAgentEntry(absDistPath) {
23
+ if (!isAbsolute(absDistPath)) {
24
+ throw new Error(`Zed agent_servers args[0] must be an ABSOLUTE path, got: ${absDistPath}`);
25
+ }
26
+ if (!absDistPath.endsWith("dist/index.js")) {
27
+ throw new Error(`Zed agent_servers args[0] must point at the built dist/index.js, got: ${absDistPath}`);
28
+ }
29
+ return { type: "custom", command: "node", args: [absDistPath], env: {} };
30
+ }
31
+ /**
32
+ * Tolerant JSONC parse: strip // line comments and block comments that are NOT inside a
33
+ * string, drop trailing commas, then JSON.parse. String-aware so a `//` or `,` inside a
34
+ * value is never mangled.
35
+ */
36
+ export function parseJsonc(text) {
37
+ let out = "";
38
+ let inString = false;
39
+ let inLineComment = false;
40
+ let inBlockComment = false;
41
+ for (let i = 0; i < text.length; i++) {
42
+ const c = text[i];
43
+ const next = text[i + 1];
44
+ if (inLineComment) {
45
+ if (c === "\n") {
46
+ inLineComment = false;
47
+ out += c;
48
+ }
49
+ continue;
50
+ }
51
+ if (inBlockComment) {
52
+ if (c === "*" && next === "/") {
53
+ inBlockComment = false;
54
+ i++;
55
+ }
56
+ continue;
57
+ }
58
+ if (inString) {
59
+ out += c;
60
+ if (c === "\\") {
61
+ out += next ?? "";
62
+ i++;
63
+ }
64
+ else if (c === '"') {
65
+ inString = false;
66
+ }
67
+ continue;
68
+ }
69
+ if (c === '"') {
70
+ inString = true;
71
+ out += c;
72
+ continue;
73
+ }
74
+ if (c === "/" && next === "/") {
75
+ inLineComment = true;
76
+ i++;
77
+ continue;
78
+ }
79
+ if (c === "/" && next === "*") {
80
+ inBlockComment = true;
81
+ i++;
82
+ continue;
83
+ }
84
+ out += c;
85
+ }
86
+ out = out.replace(/,(\s*[}\]])/g, "$1"); // drop trailing commas
87
+ return JSON.parse(out);
88
+ }
89
+ /**
90
+ * Merge a single `agent_servers[name] = entry` into JSONC settings text, preserving
91
+ * every other key. Returns canonical JSON text. Idempotent: re-merging the same entry
92
+ * is a fixed point. (The user's real comment-bearing file is edited surgically instead;
93
+ * this is the programmatic merge used in tests and tooling.)
94
+ */
95
+ export function mergeAgentServer(settingsText, name, entry) {
96
+ const settings = parseJsonc(settingsText);
97
+ if (typeof settings !== "object" || settings === null || Array.isArray(settings)) {
98
+ throw new Error("Zed settings root must be a JSON object");
99
+ }
100
+ const merged = structuredClone(settings);
101
+ if (typeof merged.agent_servers !== "object" || merged.agent_servers === null) {
102
+ merged.agent_servers = {};
103
+ }
104
+ merged.agent_servers[name] = entry;
105
+ return JSON.stringify(merged, null, 2);
106
+ }
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@lucascouts/claude-agent-tui",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.1.0",
7
+ "description": "ACP agent that drives the Claude Code subscription TUI over a PTY, rendering Claude Code threads in Zed and other ACP-compatible clients.",
8
+ "main": "dist/lib.js",
9
+ "types": "dist/lib.d.ts",
10
+ "bin": {
11
+ "claude-agent-tui": "dist/index.js"
12
+ },
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/lib.d.ts",
17
+ "import": "./dist/lib.js"
18
+ },
19
+ "./*": "./*"
20
+ },
21
+ "files": [
22
+ "dist/",
23
+ "!dist/tests/",
24
+ "README.md",
25
+ "LICENSE",
26
+ "NOTICE",
27
+ "package.json"
28
+ ],
29
+ "keywords": [
30
+ "claude",
31
+ "claude-code",
32
+ "acp",
33
+ "agent",
34
+ "anthropic",
35
+ "zed",
36
+ "tui",
37
+ "typescript"
38
+ ],
39
+ "homepage": "https://github.com/lucascouts/claude-agent-tui#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/lucascouts/claude-agent-tui/issues"
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/lucascouts/claude-agent-tui.git"
46
+ },
47
+ "author": "lucascouts",
48
+ "license": "Apache-2.0",
49
+ "dependencies": {
50
+ "@agentclientprotocol/sdk": "0.22.1",
51
+ "@anthropic-ai/claude-agent-sdk": "0.3.156",
52
+ "node-pty": "1.0.0",
53
+ "zod": "^3.25.0 || ^4.0.0"
54
+ },
55
+ "devDependencies": {
56
+ "@anthropic-ai/sdk": "0.100.0",
57
+ "@eslint/js": "10.0.1",
58
+ "@types/node": "25.9.1",
59
+ "@typescript-eslint/eslint-plugin": "8.60.0",
60
+ "@typescript-eslint/parser": "8.60.0",
61
+ "eslint": "10.4.0",
62
+ "eslint-config-prettier": "10.1.8",
63
+ "globals": "17.6.0",
64
+ "prettier": "3.8.3",
65
+ "ts-node": "10.9.2",
66
+ "typescript": "6.0.3"
67
+ },
68
+ "scripts": {
69
+ "build": "tsc",
70
+ "start": "node dist/index.js",
71
+ "dev": "npm run build && npm run start",
72
+ "lint": "eslint src --ext .ts",
73
+ "lint:fix": "eslint src --ext .ts --fix",
74
+ "format": "prettier --write .",
75
+ "format:check": "prettier --check .",
76
+ "check": "npm run lint && npm run format:check",
77
+ "test": "npm run build && node --experimental-strip-types --test test/*.test.ts"
78
+ }
79
+ }