@with-logic/elwood 0.1.1 → 0.1.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.
Files changed (156) hide show
  1. package/CHANGELOG.md +30 -3
  2. package/CONTRIBUTING.md +104 -0
  3. package/README.md +43 -31
  4. package/SECURITY.md +42 -0
  5. package/dist/claude/model-picker.d.ts.map +1 -1
  6. package/dist/claude/model-picker.js +4 -3
  7. package/dist/claude/model-picker.js.map +1 -1
  8. package/dist/claude/model-switch-confirmation.js +9 -1
  9. package/dist/claude/model-switch-confirmation.js.map +1 -1
  10. package/dist/claude/screen-table.js +1 -1
  11. package/dist/claude/screen-table.js.map +1 -1
  12. package/dist/claude/session/build.d.ts.map +1 -1
  13. package/dist/claude/session/build.js +3 -0
  14. package/dist/claude/session/build.js.map +1 -1
  15. package/dist/claude/session/start-or-resume.js +1 -0
  16. package/dist/claude/session/start-or-resume.js.map +1 -1
  17. package/dist/cli/help.d.ts +1 -1
  18. package/dist/cli/help.d.ts.map +1 -1
  19. package/dist/cli/help.js +13 -10
  20. package/dist/cli/help.js.map +1 -1
  21. package/dist/cli/interactive/spawn.d.ts.map +1 -1
  22. package/dist/cli/interactive/spawn.js +30 -9
  23. package/dist/cli/interactive/spawn.js.map +1 -1
  24. package/dist/cli/main.d.ts.map +1 -1
  25. package/dist/cli/main.js +9 -0
  26. package/dist/cli/main.js.map +1 -1
  27. package/dist/cli/models/combined.d.ts +6 -0
  28. package/dist/cli/models/combined.d.ts.map +1 -0
  29. package/dist/cli/models/combined.js +92 -0
  30. package/dist/cli/models/combined.js.map +1 -0
  31. package/dist/cli/models/index.d.ts +2 -0
  32. package/dist/cli/models/index.d.ts.map +1 -1
  33. package/dist/cli/models/index.js +21 -7
  34. package/dist/cli/models/index.js.map +1 -1
  35. package/dist/cli/models/render.d.ts +4 -0
  36. package/dist/cli/models/render.d.ts.map +1 -1
  37. package/dist/cli/models/render.js +28 -1
  38. package/dist/cli/models/render.js.map +1 -1
  39. package/dist/cli/models/result.d.ts +13 -0
  40. package/dist/cli/models/result.d.ts.map +1 -0
  41. package/dist/cli/models/result.js +2 -0
  42. package/dist/cli/models/result.js.map +1 -0
  43. package/dist/cli/models/settings.d.ts +61 -0
  44. package/dist/cli/models/settings.d.ts.map +1 -0
  45. package/dist/cli/models/settings.js +24 -0
  46. package/dist/cli/models/settings.js.map +1 -0
  47. package/dist/cli/models/signals.d.ts +11 -0
  48. package/dist/cli/models/signals.d.ts.map +1 -0
  49. package/dist/cli/models/signals.js +21 -0
  50. package/dist/cli/models/signals.js.map +1 -0
  51. package/dist/cli/request/index.js +1 -1
  52. package/dist/cli/request/index.js.map +1 -1
  53. package/dist/cli/request/layers.d.ts +2 -0
  54. package/dist/cli/request/layers.d.ts.map +1 -1
  55. package/dist/cli/request/layers.js +9 -5
  56. package/dist/cli/request/layers.js.map +1 -1
  57. package/dist/cli/request/validation.d.ts.map +1 -1
  58. package/dist/cli/request/validation.js +0 -4
  59. package/dist/cli/request/validation.js.map +1 -1
  60. package/dist/cli/run/output.d.ts.map +1 -1
  61. package/dist/cli/run/output.js +3 -6
  62. package/dist/cli/run/output.js.map +1 -1
  63. package/dist/cli/sessions/index.d.ts +9 -2
  64. package/dist/cli/sessions/index.d.ts.map +1 -1
  65. package/dist/cli/sessions/index.js +2 -6
  66. package/dist/cli/sessions/index.js.map +1 -1
  67. package/dist/cli/sessions/settings.d.ts +11 -0
  68. package/dist/cli/sessions/settings.d.ts.map +1 -0
  69. package/dist/cli/sessions/settings.js +26 -0
  70. package/dist/cli/sessions/settings.js.map +1 -0
  71. package/dist/codex/model-picker.d.ts.map +1 -1
  72. package/dist/codex/model-picker.js +3 -2
  73. package/dist/codex/model-picker.js.map +1 -1
  74. package/dist/codex/session/build.d.ts.map +1 -1
  75. package/dist/codex/session/build.js +5 -7
  76. package/dist/codex/session/build.js.map +1 -1
  77. package/dist/codex/session/start-or-resume.js +1 -0
  78. package/dist/codex/session/start-or-resume.js.map +1 -1
  79. package/dist/core/input/index.d.ts.map +1 -1
  80. package/dist/core/input/index.js +15 -3
  81. package/dist/core/input/index.js.map +1 -1
  82. package/dist/core/models/input.d.ts +3 -0
  83. package/dist/core/models/input.d.ts.map +1 -0
  84. package/dist/core/models/input.js +9 -0
  85. package/dist/core/models/input.js.map +1 -0
  86. package/dist/core/models/picker.d.ts +2 -1
  87. package/dist/core/models/picker.d.ts.map +1 -1
  88. package/dist/core/models/picker.js +6 -5
  89. package/dist/core/models/picker.js.map +1 -1
  90. package/dist/core/models/tui-screen.d.ts +1 -1
  91. package/dist/core/models/tui-screen.d.ts.map +1 -1
  92. package/dist/core/models/tui-screen.js +26 -3
  93. package/dist/core/models/tui-screen.js.map +1 -1
  94. package/dist/runtime/session/base.d.ts.map +1 -1
  95. package/dist/runtime/session/base.js +4 -1
  96. package/dist/runtime/session/base.js.map +1 -1
  97. package/dist/runtime/session/commands.d.ts +2 -1
  98. package/dist/runtime/session/commands.d.ts.map +1 -1
  99. package/dist/runtime/session/commands.js +10 -4
  100. package/dist/runtime/session/commands.js.map +1 -1
  101. package/dist/runtime/session/lifecycle.d.ts +3 -4
  102. package/dist/runtime/session/lifecycle.d.ts.map +1 -1
  103. package/dist/runtime/session/lifecycle.js +6 -5
  104. package/dist/runtime/session/lifecycle.js.map +1 -1
  105. package/dist/state/directories.d.ts +4 -0
  106. package/dist/state/directories.d.ts.map +1 -0
  107. package/dist/state/directories.js +41 -0
  108. package/dist/state/directories.js.map +1 -0
  109. package/dist/state/files.d.ts.map +1 -1
  110. package/dist/state/files.js +25 -11
  111. package/dist/state/files.js.map +1 -1
  112. package/dist/state/private-session.d.ts.map +1 -1
  113. package/dist/state/private-session.js +2 -0
  114. package/dist/state/private-session.js.map +1 -1
  115. package/dist/state/store.d.ts.map +1 -1
  116. package/dist/state/store.js +7 -1
  117. package/dist/state/store.js.map +1 -1
  118. package/docs/cli-behavior.md +1 -1
  119. package/docs/cli-output.md +40 -10
  120. package/docs/cli.md +3 -2
  121. package/docs/codex/cli.md +57 -0
  122. package/docs/codex/config.md +47 -0
  123. package/docs/codex/hooks.md +70 -0
  124. package/examples/README.md +56 -0
  125. package/examples/full.ts +113 -0
  126. package/examples/minimal.ts +15 -0
  127. package/examples/stream.ts +39 -0
  128. package/examples/support.ts +71 -0
  129. package/package.json +10 -5
  130. package/prd/01-overview.md +21 -0
  131. package/prd/02-goals.md +47 -0
  132. package/prd/03-terminology.md +32 -0
  133. package/prd/04-architecture.md +121 -0
  134. package/prd/05-api/5.1-starting-claude.md +108 -0
  135. package/prd/05-api/5.2-resuming-claude.md +86 -0
  136. package/prd/05-api/5.3-claude-session-api.md +658 -0
  137. package/prd/05-api/5.4-events.md +145 -0
  138. package/prd/05-api/5.5-starting-codex.md +106 -0
  139. package/prd/05-api/5.6-resuming-codex.md +48 -0
  140. package/prd/05-api/5.7-codex-session-api.md +425 -0
  141. package/prd/05-api/5.8-session-api.md +122 -0
  142. package/prd/05-api/5.9-loops.md +170 -0
  143. package/prd/05-api/README.md +19 -0
  144. package/prd/06-claude-hook-bridge.md +164 -0
  145. package/prd/07-claude-policy.md +15 -0
  146. package/prd/07a-codex-hook-bridge.md +76 -0
  147. package/prd/08-state.md +127 -0
  148. package/prd/09-lifecycle.md +222 -0
  149. package/prd/10-errors.md +55 -0
  150. package/prd/11-test-app.md +53 -0
  151. package/prd/12-e2e.md +23 -0
  152. package/prd/12a-cli.md +402 -0
  153. package/prd/13-latitude.md +12 -0
  154. package/prd/14-conformance.md +333 -0
  155. package/prd/15-open-notes.md +8 -0
  156. package/prd/README.md +45 -0
package/prd/12a-cli.md ADDED
@@ -0,0 +1,402 @@
1
+ ## 12A. Headless Command-Line Interface
2
+
3
+ Elwood ships a compiled JavaScript executable named `elwood` for macOS and
4
+ Node.js 24 or newer. Local, git, and packed-tarball installs MUST run emitted
5
+ JavaScript from `dist`; an installed package MUST NOT depend on Node's native
6
+ TypeScript loading. The package's root import MUST likewise resolve to emitted
7
+ JavaScript and declarations.
8
+
9
+ ### 12A.1 Commands and input
10
+
11
+ The primary form is `elwood [options] [prompt...]`. `elwood run [options]
12
+ [prompt...]` is an exact explicit equivalent. `help`, `run`, and `config` are
13
+ commands only when they are the first argument before `--`; prompt words are
14
+ otherwise joined with spaces. `elwood help`, `elwood --help`, and `elwood
15
+ --version` MUST complete without reading user configuration or launching an
16
+ agent. `elwood config --help` and `elwood config help` MUST likewise print
17
+ config-specific help without reading configuration. An invocation with no
18
+ arguments is another exact top-level help form and MUST NOT read piped or terminal
19
+ stdin. `elwood run` remains an explicit run and therefore requires prompt input,
20
+ including when that input comes only from piped stdin.
21
+
22
+ `resume`, `interactive`, `sessions`, and `models` are further first-argument
23
+ commands with the same reservation rule; they are specified in §12A.7 through
24
+ §12A.10. Each accepts `-h`/`--help` and prints the top-level help without
25
+ reading configuration or launching an agent.
26
+
27
+ A new session defaults to the invocation directory; `-C`/`--cwd` overrides it.
28
+ When no flag, environment variable, or configuration key selects an agent, a
29
+ new session auto-detects one: Elwood tries `claude` first, then `codex`, and
30
+ uses the first whose command resolves in the user's login shell — the same
31
+ resolution the agent launch itself uses (§4.2) — without running either agent.
32
+ Both probes run concurrently; the order only decides the winner. The resolved
33
+ agent reports the source `auto-detected`. When neither command resolves, the
34
+ invocation fails before any launch with the stable code `no_agent_found` and
35
+ status 2; the message names both agents, how to install one, and that
36
+ `--agent`, `ELWOOD_AGENT`, or the config `agent` key selects one explicitly.
37
+ When the probe itself cannot run — the login shell is missing or broken, or the
38
+ probe times out — the invocation fails with the same code and status but the
39
+ message names the shell and the underlying error and says the probe failed,
40
+ because nothing is then known about which agents are installed. Adapter-specific
41
+ options that conflict with an auto-detected agent are reported as conflicting
42
+ with the auto-detected agent, and the recovery additionally offers selecting the
43
+ other adapter with `--agent`. Resume never auto-detects; it uses the stored
44
+ adapter. Elwood MUST fail when the selected adapter is unavailable and MUST NOT
45
+ silently fall back to another adapter.
46
+
47
+ Non-empty piped stdin is appended to positional prompt text after one blank
48
+ line; terminal stdin is never read. Whitespace-only input is a usage error.
49
+ Combined input has an 8 MiB UTF-8 limit that is enforced incrementally before
50
+ session creation. Repeatable `--image <path>` flags attach readable files, in
51
+ flag order, to the same user turn; paths resolve from the effective workspace.
52
+
53
+ ### 12A.2 Execution and lifecycle
54
+
55
+ CLI turns use the public normalized turn boundary and preserve normal login
56
+ shell, workspace, instruction, settings, skill, MCP, and hook behavior. By
57
+ default Elwood answers only the folder/directory and extension trust classes
58
+ documented in §5.1. `--no-trust` disables that authorization but does not
59
+ disable Codex's session-wide hook-trust bypass, which also covers non-Elwood hooks. Any other recognized blocking
60
+ prompt fails as `blocked_prompt` and reports only its stable rule label.
61
+
62
+ The built-in non-interactive launch posture is Claude `dontAsk`, and Codex
63
+ `workspace-write` with approval policy `never`. Flags and global configuration
64
+ may select the adapter, model, reasoning effort, persona, state directory,
65
+ Claude permission mode, Codex sandbox, and Codex approval policy. An
66
+ adapter-specific option is a usage error for an incompatible adapter.
67
+
68
+ `--high-trust` is the agent-neutral "never ask for permissions" switch (§5.1,
69
+ §5.5): for whichever adapter runs, it expands to Claude `bypassPermissions` or
70
+ Codex `danger-full-access` with approval policy `never`. It layers like every
71
+ other setting (`--high-trust` / `--no-high-trust`, `ELWOOD_HIGH_TRUST`, the
72
+ `highTrust` config key, built-in `false`), and `--no-high-trust` reverses an
73
+ inherited `true`. An effective `true` from a flag or environment variable
74
+ combined with an explicit per-agent posture flag or variable
75
+ (`--claude-permission-mode`, `--codex-sandbox`, `--codex-approval-policy`, or
76
+ their `ELWOOD_*` forms) in the same invocation is a usage error naming both
77
+ sources, whether the run is new or resumed. Saved `claude.permissionMode`,
78
+ `codex.sandbox`, and `codex.approvalPolicy` config keys are simply overridden by
79
+ high trust; a saved `highTrust: true` is itself overridden field by field by an
80
+ explicit per-agent flag or variable. On resume, `--high-trust` behaves like the
81
+ other posture flags: it overrides the stored posture for the resumed adapter.
82
+
83
+ Turns have no whole-invocation timeout by default. `--timeout` accepts a
84
+ positive safe integer followed by `ms`, `s`, `m`, or `h`; its deadline spans
85
+ launch, persona setup, and the user turn. Signal handlers are active before
86
+ launch. The first `SIGINT` requests interruption and cleanup, a repeated
87
+ `SIGINT` force-kills the process tree, and an interrupted invocation exits 130.
88
+
89
+ New and resumed headless runs preserve their Elwood-owned state after every
90
+ outcome by default, after stop-or-kill cleanup. `--keep` explicitly selects this
91
+ default. `--ephemeral` instead requests teardown for either a new or resumed
92
+ run; it cannot be combined with `--keep`. Teardown does not undo workspace
93
+ changes or remove history owned by the underlying agent. A preserved run reports
94
+ its resumable session ID on stderr in text mode. A resume uses `--resume <id>` to load exactly the
95
+ stored adapter and cwd; it never falls back to a new conversation, and `--cwd`
96
+ is therefore rejected with `--resume` rather than overriding the stored
97
+ workspace. Explicit adapter conflicts are usage errors. Before launch, resumed state and its cwd MUST satisfy the private,
98
+ owner-matched, regular-file and directory constraints in §12A.5. Resume
99
+ preserves state after every outcome unless `--ephemeral` requests teardown.
100
+
101
+ A persona applies only to a new session. Elwood runs it as a separate completed
102
+ setup turn and discards that turn's response before observing the user turn. It
103
+ is a real agent turn: tools and other side effects performed during persona setup
104
+ remain even though its assistant response is omitted from CLI output. Explicit
105
+ persona plus resume is a usage error. An agent process exit before
106
+ CLI-requested cleanup is `agent_exited`, including a zero-status exit. Response
107
+ data already collected remains available on later failure.
108
+
109
+ Cleanup runs exactly once. A cleanup failure never masks a primary usage,
110
+ agent, timeout, or interruption outcome, and it changes an otherwise successful
111
+ invocation to failure.
112
+
113
+ ### 12A.3 Output protocols
114
+
115
+ Stdout is a protocol channel. Human diagnostics, warnings, progress, cleanup
116
+ failures, and text-mode preserved-session notices go only to stderr. Production
117
+ output MUST exclude raw PTY frames, raw hook payloads, terminal screen contents,
118
+ stacks, bridge credentials, and terminal escape sequences.
119
+
120
+ Text is the default output. It writes the combined assistant response — every
121
+ observed assistant text message in order, separated by one blank line — with one
122
+ trailing newline when non-empty and zero bytes for an empty response. Live session
123
+ warnings are quiet by default for text and JSON runs, including model probes.
124
+ `--verbose` enables concise sanitized warnings and elapsed-time phase, tool-name,
125
+ and cleanup progress on stderr without replaying assistant or thinking text.
126
+ `--debug` also enables warnings. JSONL always represents warnings as ordered
127
+ records without duplicating them on stderr. Fatal errors and `sessions` warnings
128
+ about omitted unreadable records remain visible without verbosity. `--debug` emits
129
+ full sanitized normalized event details to stderr and is intended for diagnosis
130
+ rather than routine progress. `--stream` is text-only, emits assistant chunks once with one
131
+ blank line between distinct messages, preserves partial output on failure, and
132
+ adds one trailing newline when non-empty.
133
+
134
+ `--output json` emits one version-1 terminal result or error document containing
135
+ the record type, adapter, combined response, nullable session ID, integer
136
+ duration in milliseconds, and cleanup outcome. A result record's `agent` is
137
+ always the adapter that ran. An error record's `agent` is the adapter that ran
138
+ or was selected; it is `null` when the failure happened before anything selected
139
+ an adapter — for example `no_agent_found`, or an argument or configuration
140
+ failure in an invocation with neither `--agent` nor an honored `ELWOOD_AGENT`. `--output jsonl` emits
141
+ monotonically sequenced version-1 normalized text, thinking, tool, status, and
142
+ warning records, followed by exactly one terminal result or error record with
143
+ the combined response. Every JSONL record includes a non-negative integer
144
+ `elapsedMs`; tool records include a sanitized `toolCallId` when the adapter
145
+ provides one so calls and results can be paired. A parseable, explicit structured-output selection
146
+ also represents argument, configuration, and runtime failures in that protocol.
147
+
148
+ All writes MUST honor stream backpressure. Downstream `EPIPE` stops output and
149
+ cleans up without an uncaught diagnostic. Consumer closure exits 0 unless an
150
+ earlier primary outcome already established a nonzero status.
151
+
152
+ ### 12A.4 Configuration
153
+
154
+ The user config path is a non-empty `$ELWOOD_CONFIG`, otherwise
155
+ `$XDG_CONFIG_HOME/elwood/config.json` when the XDG base is absolute and
156
+ non-empty, otherwise `~/.config/elwood/config.json`. V1 does not read
157
+ project-local configuration.
158
+
159
+ Configuration is a strict version-1 JSON object. Documented keys are
160
+ `schemaVersion`, `agent`, `output`, `timeout`, `trust`, `highTrust`, `stateDir`,
161
+ `verbose`, `stream`, `persona`, `claude.model`, `claude.reasoningEffort`,
162
+ `claude.permissionMode`, `codex.model`, `codex.reasoningEffort`,
163
+ `codex.sandbox`, and `codex.approvalPolicy`. Config MUST NOT set prompt, cwd,
164
+ keep, ephemeral, resume ID, or images.
165
+
166
+ Supported environment variables are `ELWOOD_AGENT`, `ELWOOD_OUTPUT`,
167
+ `ELWOOD_TIMEOUT`, `ELWOOD_TRUST`, `ELWOOD_HIGH_TRUST`, `ELWOOD_STATE_DIR`,
168
+ `ELWOOD_VERBOSE`, `ELWOOD_STREAM`, `ELWOOD_PERSONA`, `ELWOOD_MODEL`,
169
+ `ELWOOD_REASONING_EFFORT`, `ELWOOD_CLAUDE_PERMISSION_MODE`,
170
+ `ELWOOD_CODEX_SANDBOX`, and `ELWOOD_CODEX_APPROVAL_POLICY`. Boolean variables
171
+ accept only `true` or `false`. Precedence is command-line flags, environment,
172
+ user configuration, then built-in defaults. The `agent` setting has no fixed
173
+ built-in default: when nothing selects it, a new session auto-detects the first
174
+ available of `claude` then `codex` as described in §12A.1.
175
+
176
+ Boolean settings inherited from environment or configuration remain reversible
177
+ per invocation: `--no-stream`, `--no-verbose`, and `--no-high-trust` explicitly
178
+ select false with normal flag precedence. `--no-defaults` ignores the user configuration document
179
+ and every `ELWOOD_*` run-setting variable for that invocation, while retaining
180
+ command-line flags, built-in defaults, the normal home/XDG state-location rules,
181
+ and the selected agent's inherited process environment.
182
+
183
+ `elwood config path`, `show`, `get`, `set`, and `unset` manage documented dotted
184
+ keys without launching an agent; `show` prints only the saved document.
185
+ `elwood config effective [run options]` resolves and validates launch/output
186
+ settings without reading prompt input or starting an agent, then prints one
187
+ version-1 JSON document containing the config path and whether it was loaded,
188
+ plus the effective agent, model, workspace, timeout, output controls (including
189
+ `head`), state directory, `highTrust`, and adapter permission posture. When high
190
+ trust decides a posture value, that value's source is the high-trust source
191
+ (`--high-trust`, `ELWOOD_HIGH_TRUST`, or the config path and `highTrust` key),
192
+ so the document explains WHY the posture is what it is. Claude posture includes
193
+ `permissionMode`, `allowedTools`, `disallowedTools`, and `tools`; exact resume
194
+ inspection merges persisted posture by the same field-by-field rules as launch so
195
+ the document reports the tool policy the resumed process will receive. Every
196
+ reported setting includes its source (`--flag`, `ELWOOD_*`, the saved config path
197
+ and key, stored session, invocation context, auto-detected, built-in, unset, or
198
+ not applicable). When nothing selects the agent, `config effective` reports the
199
+ auto-detected agent with source `auto-detected`, resolved by the same
200
+ login-shell command probe a run would use (§12A.1), so the document shows the
201
+ agent that invocation would actually launch; the probe only asks the shell
202
+ whether the command resolves and never starts an agent, and a missing agent is
203
+ the same `no_agent_found` failure a run would report.
204
+ Mutations are atomic and silent on success;
205
+ config parsing and writes reject unknown keys, unknown schema versions,
206
+ symlinks, non-regular files, wrong ownership, and non-private permissions.
207
+
208
+ ### 12A.5 State, failures, and compatibility
209
+
210
+ CLI-owned state defaults to `$XDG_STATE_HOME/elwood` when that base is absolute,
211
+ otherwise `~/.local/state/elwood`. It never defaults to the invocation
212
+ workspace. CLI state directories and records MUST be private, owner-matched,
213
+ regular where applicable, symlink-safe, and atomically written. One session
214
+ identity supports at most one live owner; concurrent exact resumes may fail on
215
+ the adapter's underlying startup conflict in v1.
216
+
217
+ Exit status is 0 for success or consumer closure, 1 for agent or cleanup
218
+ failure, 2 for usage or configuration failure, 124 for timeout, and 130 for
219
+ interruption. A primary nonzero status survives cleanup failure. Text errors are
220
+ concise and go to stderr while structured protocols retain stable error codes.
221
+ Unknown long options identify the token and suggest the nearest documented
222
+ option when the match is unambiguous. Validation messages identify relevant
223
+ paths and name the inherited environment/config source when it explains a
224
+ conflict, together with a concrete recovery flag when one exists. Debug mode may
225
+ include sanitized event detail but never weakens §12A.3. Static arguments, environment, config, cwd, image, and option
226
+ compatibility MUST be validated before session creation whenever validation
227
+ does not require a stored resume record.
228
+
229
+ ### 12A.6 Same-terminal head mode
230
+
231
+ `--head` is an opt-in, view-only terminal mirror for a run. It requires both a
232
+ terminal stdin and terminal stderr; the latter is the dedicated display channel,
233
+ so stdout retains the selected text or JSON protocol and may still be redirected.
234
+ The flag is invocation-only: it has no environment or configuration equivalent.
235
+ An unavailable controlling terminal is a usage failure detected before session
236
+ creation. Because the headed display exclusively owns the terminal while the
237
+ turn runs, `--head` is incompatible with `--stream`, `--verbose`, `--debug`, and JSONL's
238
+ live record protocol. It remains compatible with final text and JSON output.
239
+
240
+ Head mode MUST render the raw `terminal:data` stream verbatim to terminal stderr
241
+ after Elwood's required headless xterm render. It is a VT/ANSI terminal mirror,
242
+ not a line-oriented log: cursor-addressed repainting, alternate-screen buffers,
243
+ colors, cursor visibility, and other full-screen TUI control sequences MUST
244
+ remain intact. Raw frames remain excluded from stdout and from redirected
245
+ stderr because head mode requires that display channel to be a terminal.
246
+
247
+ Before launch, Elwood MUST size the agent PTY and headless terminal from the
248
+ headed terminal's positive integer columns and rows, falling back to the normal
249
+ Elwood terminal size for missing or invalid dimensions. During the run,
250
+ terminal resize events MUST resize both models through the public session
251
+ operation. Display writes MUST preserve order and honor backpressure. Because the
252
+ PTY producer cannot be paused at this boundary, Elwood MUST cap outstanding display
253
+ work at 4 MiB of UTF-8 data or 1,024 frames, whichever comes first. Crossing either
254
+ limit fails the run, stops accepting display frames, drains every already-accepted
255
+ byte in order, and performs the normal terminal restoration and cleanup before the
256
+ final nonzero result.
257
+
258
+ While attached, Elwood temporarily puts terminal stdin into raw mode and
259
+ discards ordinary keyboard, mouse, paste, and terminal-response bytes: v1 head
260
+ mode is observational, not interactive. Every Ctrl-C byte follows the same
261
+ first-interrupt/repeated-force-kill lifecycle as SIGINT. On every normal,
262
+ failure, timeout, or interruption outcome, Elwood MUST remove its input and
263
+ resize listeners, restore terminal stdin's prior raw/cooked mode, flush pending
264
+ display writes, reset attributes, disable mouse and bracketed-paste modes, show
265
+ the cursor, and leave the alternate screen before emitting the final stdout
266
+ protocol. `SIGKILL` cannot provide process-level terminal restoration. If the
267
+ host has already closed the terminal input handle, Elwood contains that handle's
268
+ terminal-gone error; the parent shell or terminal owner is then the only remaining
269
+ authority able to restore its modes, and that lost handle does not replace a
270
+ completed agent result with a runtime failure.
271
+
272
+ ### 12A.7 Resume subcommand
273
+
274
+ `elwood resume <id> [options] [prompt...]` is an exact parse-time rewrite of
275
+ `elwood run --resume <id> [options] [prompt...]`. Both forms share one
276
+ resolution and execution path, so stored adapter and workspace loading, the
277
+ `--cwd` rejection, explicit-adapter conflicts, `--keep`, persona, `--ephemeral`,
278
+ and prompt composition from positional words plus piped stdin behave identically.
279
+ The first positional word is the session id and the remaining words are the
280
+ prompt. A missing id is a usage error, and supplying `--resume` together with
281
+ the subcommand is a usage error rather than a silent override.
282
+
283
+ ### 12A.8 Session listing
284
+
285
+ `elwood sessions [--state-dir <path>] [--output <text|json>] [--no-defaults]`
286
+ lists the Elwood-owned session records under the effective CLI state directory,
287
+ resolved with the same flag, environment, config, and built-in precedence as a
288
+ run. It reads no prompt input and never launches an agent. Each listed record
289
+ reports the session id, adapter, stored workspace, `createdAt` (the session
290
+ directory's creation time) and `lastUsedAt` (the record's last write time) as
291
+ ISO-8601 UTC timestamps, `resumable` (whether the adapter's own conversation id
292
+ is stored), and `live` — whether a launch's bridge socket file is currently
293
+ present in the session's socket home. `live` is a cheap file-presence signal
294
+ with no side effects on a running owner: a socket left behind by a force-killed
295
+ process reads as live until that identity is next started or torn down. Records
296
+ are ordered by most recent use, then id. A record that is missing, unreadable,
297
+ or invalid under §12A.5 is skipped with one concise stderr warning and never
298
+ fails the listing; a state directory that does not exist yet is an empty listing.
299
+
300
+ Text output is an aligned table with a header row. An empty listing writes
301
+ nothing to stdout, one concise notice naming the state directory to stderr, and
302
+ exits 0. JSON output is exactly one version-1 document
303
+ `{ "schemaVersion": 1, "type": "sessions", "stateDir": "...", "sessions": [...] }`
304
+ whose array may be empty. JSONL is not a listing protocol; selecting it from any
305
+ source is a usage error that names the source. Any other run option or a
306
+ positional word is a usage error.
307
+
308
+ ### 12A.9 Interactive mode
309
+
310
+ `elwood interactive [id] [options]` starts the selected agent's own interactive
311
+ CLI in the foreground of the caller's terminal with Elwood's resolved settings
312
+ passed as that CLI's native flags, then exits with the agent's exit status (128
313
+ plus the signal number when the agent is terminated by a signal). There is no
314
+ hidden PTY, headless terminal, hook bridge, readiness or turn detection, trust
315
+ automation, or stdout protocol: the agent inherits stdin, stdout, and stderr,
316
+ Elwood neither observes nor records the conversation, and no Elwood session
317
+ record is created, updated, or removed. Both stdin and stdout MUST be terminals;
318
+ otherwise the command fails before spawning with status 2. While the agent runs,
319
+ Elwood ignores `SIGINT` so Ctrl-C reaches the agent exactly as in a direct launch.
320
+
321
+ Agent selection, model, reasoning effort, workspace (`-C`), Claude permission
322
+ mode, and Codex sandbox and approval policy resolve with normal precedence,
323
+ validate against the effective adapter before spawning, and map through the same
324
+ launch-argument builder the adapters use (`claude --model`, `--effort`,
325
+ `--permission-mode`, `--allowedTools`, `--disallowedTools`, `--tools`; `codex
326
+ --model`, `--sandbox`, `--ask-for-approval`, `--cd`, and
327
+ `-c model_reasoning_effort=...`). The built-in non-interactive posture defaults
328
+ of §12A.2 are NOT applied: a posture flag reaches the agent only when a flag,
329
+ environment variable, config key, or stored record selected it, so an
330
+ unconfigured `elwood interactive` is the agent's own default experience. Trust
331
+ flags have no effect because the user answers the agent's dialogs directly.
332
+
333
+ With `[id]`, Elwood loads the stored record exactly as `--resume` does — the
334
+ stored adapter and workspace are used, `--cwd` is rejected, a conflicting
335
+ explicit adapter is a usage error, and the private-state constraints of §12A.5
336
+ apply — merges the stored launch posture with explicit posture options field by
337
+ field (the same rule as library resume), and hands the agent its own conversation
338
+ id (`claude --resume <conversation id>` or `codex resume <conversation id>`). A
339
+ record without a stored conversation id fails as `resume_unavailable`. Because
340
+ the agent owns the resumed conversation, later `elwood resume <id>` runs see
341
+ whatever the user said interactively.
342
+
343
+ `--output json`, `--output jsonl`, `--stream`, `--verbose`, `--debug`, `--head`,
344
+ `--timeout`, `--persona`, `--image`, `--keep`, `--ephemeral`, and `--resume`
345
+ are usage errors with `interactive`, and more than one positional word is a usage
346
+ error. A launch that cannot spawn fails with the adapter's not-found error code
347
+ and status 1.
348
+
349
+ ### 12A.10 Model listing
350
+
351
+ `elwood models [options] [--output <text|json>]` briefly starts a headless
352
+ session for each requested agent — honoring workspace, model, reasoning
353
+ effort, timeout, trust, state directory, and adapter posture with normal
354
+ precedence — waits for readiness, drives the agent's own model picker through
355
+ the public `listModels` operation (which opens and cancels the picker and leaves
356
+ the model unchanged), tears the session down, and exits 0. It sends no prompt,
357
+ runs no persona turn, uses a fresh session identity under the effective state
358
+ directory, and always tears that identity down, so no Elwood state remains
359
+ afterward. Starting the agent is unavoidable and the help text says so.
360
+
361
+ Without an explicit `--agent`, the command MUST probe Claude and then Codex
362
+ sequentially, regardless of `ELWOOD_AGENT` or a saved agent default. Each probe
363
+ uses its own adapter settings; adapter-specific flags and environment posture
364
+ settings apply only to their matching adapter. Shared settings apply to both.
365
+ `--timeout` is one budget for the whole command, including settings resolution
366
+ and preparation. Each probe receives only the remaining budget. Once exhausted,
367
+ the next adapter MUST NOT start; available catalogs remain in the partial result. A failed or unavailable adapter MUST NOT
368
+ hide the other adapter’s available models. SIGINT stops the command and MUST
369
+ NOT launch the next adapter. Every started probe is cleaned up before the next.
370
+
371
+ For combined listings, text includes an `AGENT` column and per-agent errors go
372
+ to stderr. JSON is exactly one document
373
+ `{ "schemaVersion": 1, "type": "models", "agents": [{ "agent": "...", "models": [...] }], "errors": [...] }`.
374
+ `agents` contains successful catalogs (including empty catalogs), and `errors`
375
+ contains the canonical version-1 error records with agent, error, duration, and
376
+ cleanup fields. Both arrays retain probe order. Status is 0 only if both probes
377
+ succeeded; otherwise interruption (130) takes precedence over timeout (124),
378
+ usage errors (2), and other failures (1). Output-protocol errors and global
379
+ config-file read, parse, or schema errors fail before any probe. Adapter-specific
380
+ setting validation failures are recorded for that adapter and permit the other
381
+ adapter to proceed. An explicit `--agent` preserves the single-agent output
382
+ and error contract below. All model probes remain ephemeral.
383
+
384
+ Single-agent text output is an aligned table with a header row: `*` in the first column marks
385
+ the current model, followed by the id, label, `(default)` when the row is the
386
+ adapter's default, and the description. JSON output is exactly one version-1
387
+ document `{ "schemaVersion": 1, "type": "models", "agent": "...", "models": [...] }`
388
+ whose rows are the `AgentModelOption` objects. Every string is sanitized under
389
+ §12A.3. Failures — timeout (124), interruption (130), blocked prompts, agent
390
+ exit, and picker automation failures — use the existing stderr, error-document,
391
+ and status conventions, and cleanup still runs exactly once. `--stream`,
392
+ `--head`, `--persona`, `--image`, `--keep`, `--resume`, `--ephemeral`,
393
+ positional words, and JSONL output are usage errors.
394
+
395
+ Listing sessions resolves only state-directory and output settings. Adapter,
396
+ model, permission, trust, and turn-lifecycle environment defaults are irrelevant
397
+ and MUST NOT prevent listing. Config files still undergo strict schema decoding.
398
+ Interactive foreground launches MUST resolve the executable in the same
399
+ interactive login-shell environment used by auto-detection. If the shell exits
400
+ successfully without returning a complete PATH probe frame, the foreground
401
+ launch MUST preserve the inherited PATH. A complete frame containing an empty
402
+ PATH remains authoritative. C-CLI-24/25.
@@ -0,0 +1,12 @@
1
+ ## 13. Implementation Latitude
2
+
3
+ The following are implementation choices unless they affect the public behavior
4
+ above:
5
+
6
+ - PTY library or native binding choice.
7
+ - IPC transport details, provided it is local-only and session-scoped.
8
+ - Exact generated settings file layout.
9
+ - Exact event-emitter implementation.
10
+ - Whether the package runs on Node.js or another compatible JavaScript runtime, as long as the TypeScript API
11
+ contract is met by supported hosts.
12
+ - Internal storage filenames under `.elwood/`.