@giovannijecha/jecode 0.1.5-rc.2

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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +264 -0
  3. package/bin/jecode.js +11 -0
  4. package/dist/atomic.js +78 -0
  5. package/dist/batch-view.js +17 -0
  6. package/dist/batch.js +100 -0
  7. package/dist/cli-info.js +40 -0
  8. package/dist/commands.js +171 -0
  9. package/dist/config.js +97 -0
  10. package/dist/controller.js +90 -0
  11. package/dist/credential-commands.js +134 -0
  12. package/dist/credential-safety.js +86 -0
  13. package/dist/credentials.js +124 -0
  14. package/dist/main.js +7 -0
  15. package/dist/ollama-settings-command.js +75 -0
  16. package/dist/prompt.js +29 -0
  17. package/dist/provider-commands.js +236 -0
  18. package/dist/provider-errors.js +10 -0
  19. package/dist/providers/anthropic-stream.js +111 -0
  20. package/dist/providers/anthropic-wire.js +79 -0
  21. package/dist/providers/anthropic.js +77 -0
  22. package/dist/providers/catalog.js +37 -0
  23. package/dist/providers/http.js +219 -0
  24. package/dist/providers/index.js +18 -0
  25. package/dist/providers/ollama-endpoint.js +40 -0
  26. package/dist/providers/ollama-stream.js +60 -0
  27. package/dist/providers/ollama-wire.js +95 -0
  28. package/dist/providers/ollama.js +87 -0
  29. package/dist/providers/openai-stream.js +48 -0
  30. package/dist/providers/openai-wire.js +112 -0
  31. package/dist/providers/openai.js +70 -0
  32. package/dist/providers/sse.js +81 -0
  33. package/dist/providers/stream-limits.js +11 -0
  34. package/dist/session.js +3 -0
  35. package/dist/settings-command.js +202 -0
  36. package/dist/settings.js +98 -0
  37. package/dist/start.js +47 -0
  38. package/dist/tools/args.js +38 -0
  39. package/dist/tools/fs.js +277 -0
  40. package/dist/tools/index.js +39 -0
  41. package/dist/tools/paths.js +122 -0
  42. package/dist/tools/search.js +213 -0
  43. package/dist/tools/shell.js +139 -0
  44. package/dist/tools/text-boundary.js +102 -0
  45. package/dist/tools/types.js +1 -0
  46. package/dist/transcript-export.js +11 -0
  47. package/dist/transcript.js +49 -0
  48. package/dist/tui/activity.js +11 -0
  49. package/dist/tui/app-input.js +155 -0
  50. package/dist/tui/app-state.js +17 -0
  51. package/dist/tui/app-workflows.js +105 -0
  52. package/dist/tui/app.js +215 -0
  53. package/dist/tui/approve.js +67 -0
  54. package/dist/tui/blocks.js +23 -0
  55. package/dist/tui/complete.js +54 -0
  56. package/dist/tui/components/command-menu.js +17 -0
  57. package/dist/tui/components/composer.js +47 -0
  58. package/dist/tui/components/dock.js +10 -0
  59. package/dist/tui/components/footer.js +21 -0
  60. package/dist/tui/components/menu.js +38 -0
  61. package/dist/tui/components/messages.js +43 -0
  62. package/dist/tui/components/misc.js +22 -0
  63. package/dist/tui/components/status.js +45 -0
  64. package/dist/tui/components/tool.js +85 -0
  65. package/dist/tui/components/types.js +1 -0
  66. package/dist/tui/editor.js +105 -0
  67. package/dist/tui/feedback.js +74 -0
  68. package/dist/tui/field.js +60 -0
  69. package/dist/tui/frame.js +33 -0
  70. package/dist/tui/input.js +52 -0
  71. package/dist/tui/keys.js +177 -0
  72. package/dist/tui/modal.js +24 -0
  73. package/dist/tui/overlay.js +93 -0
  74. package/dist/tui/picker.js +99 -0
  75. package/dist/tui/screen.js +94 -0
  76. package/dist/tui/scroll.js +7 -0
  77. package/dist/tui/session-view.js +49 -0
  78. package/dist/tui/transcript-view.js +134 -0
  79. package/dist/tui/turn.js +255 -0
  80. package/dist/tui/view.js +88 -0
  81. package/dist/tui/workspace.js +59 -0
  82. package/dist/types.js +9 -0
  83. package/dist/ui/diff.js +109 -0
  84. package/dist/ui/highlight.js +158 -0
  85. package/dist/ui/inline.js +39 -0
  86. package/dist/ui/markdown.js +147 -0
  87. package/dist/ui/render.js +232 -0
  88. package/dist/ui/table.js +127 -0
  89. package/dist/ui/terminal-text.js +42 -0
  90. package/dist/ui/theme.js +25 -0
  91. package/dist/ui/width.js +196 -0
  92. package/dist/usage.js +30 -0
  93. package/dist/user-data.js +29 -0
  94. package/package.json +56 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Giovanni Jecha
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,264 @@
1
+ <p align="center">
2
+ <img src="docs/assets/brand/jeco-256.png" width="128" alt="Jeco, the steel-blue Jecode gecko">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <picture>
7
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/brand/wordmark-light.svg">
8
+ <source media="(prefers-color-scheme: light)" srcset="docs/assets/brand/wordmark-dark.svg">
9
+ <img src="docs/assets/brand/wordmark-dark.svg" width="280" alt="Jecode">
10
+ </picture>
11
+ </p>
12
+
13
+ <p align="center"><strong>Your code. Your loop.</strong></p>
14
+
15
+ <p align="center">
16
+ A focused coding agent that lives in your terminal, keeps tool use visible,
17
+ and stays under your control.
18
+ </p>
19
+
20
+ <p align="center">
21
+ <a href="https://github.com/giovannijecha/jecode/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/giovannijecha/jecode/actions/workflows/ci.yml/badge.svg"></a>
22
+ <a href="LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-669BD2"></a>
23
+ <img alt="Node.js 24+" src="https://img.shields.io/badge/node-%3E%3D24-86CB92">
24
+ <img alt="Runtime dependencies: zero" src="https://img.shields.io/badge/runtime_dependencies-0-8DB4DD">
25
+ </p>
26
+
27
+ > Jecode is an early 0.1.x release. The core loop is usable today; commands and
28
+ > terminal interactions may still evolve before 1.0.
29
+
30
+ ## Why Jecode
31
+
32
+ - **One controller.** One visible loop talks to the model, runs tools, and returns
33
+ control to you. There are no hidden workers or delegated agents.
34
+ - **Terminal-native.** The transcript, composer, searchable menus, tool output,
35
+ diffs, approvals, reasoning, and status all share one full-screen TUI.
36
+ - **Permission-aware.** Reads stay transparent; dangerous actions ask first.
37
+ Session approvals can be reviewed and revoked.
38
+ - **Provider-neutral.** Use Anthropic, OpenAI, or a local/remote Ollama server
39
+ without changing the workflow.
40
+ - **Lean by construction.** Jecode installs as plain JavaScript, runs on
41
+ Node.js 24, executes no installation scripts, and has zero third-party
42
+ runtime dependencies.
43
+
44
+ ## Install
45
+
46
+ Jecode requires **Node.js 24 or newer** and npm. Release candidates are
47
+ published on the **next** channel:
48
+
49
+ ~~~console
50
+ npm install --global @giovannijecha/jecode@next
51
+ jecode --version
52
+ ~~~
53
+
54
+ Then open the project you want to work on and run Jecode:
55
+
56
+ ~~~console
57
+ cd path/to/your/project
58
+ jecode
59
+ ~~~
60
+
61
+ You can point at another workspace explicitly:
62
+
63
+ ~~~console
64
+ jecode --root path/to/your/project
65
+ ~~~
66
+
67
+ Run **jecode --help** for all startup options. Tested platforms are Windows,
68
+ Ubuntu, and macOS.
69
+
70
+ ### Update
71
+
72
+ Install the current release candidate over the existing global command:
73
+
74
+ ~~~console
75
+ npm install --global @giovannijecha/jecode@next
76
+ jecode --version
77
+ ~~~
78
+
79
+ ### Uninstall
80
+
81
+ ~~~console
82
+ npm uninstall --global @giovannijecha/jecode
83
+ ~~~
84
+
85
+ Uninstalling the command preserves **~/.jecode** so saved settings and
86
+ credentials remain available after a reinstall. Remove that directory only
87
+ when you intentionally want to erase Jecode's local data.
88
+
89
+ ### Linux and WSL
90
+
91
+ WSL has its own Node.js installation and `PATH`; the Node.js version installed
92
+ on Windows does not apply inside it. If installation succeeds but `jecode` is
93
+ not found, run **npm config get prefix** and ensure its **bin/** directory is on
94
+ your Linux `PATH`. Do not ignore an `EBADENGINE` warning: **node --version** must
95
+ report 24 or newer.
96
+
97
+ ### Build from source
98
+
99
+ ~~~console
100
+ git clone https://github.com/giovannijecha/jecode.git
101
+ cd jecode
102
+ npm ci
103
+ npm run build:release
104
+ npm link
105
+ jecode
106
+ ~~~
107
+
108
+ Development runs TypeScript directly with **npm run start**. **dist/** is an
109
+ ignored, generated tree used only by linked commands and release tarballs.
110
+ **npm pack** and **npm publish** rebuild it from a clean target; installing the
111
+ published package runs no compilation or installation scripts.
112
+
113
+ ## First session
114
+
115
+ Jecode opens on an empty composer instead of forcing a setup wizard. Type
116
+ **/settings** when you are ready to choose a provider, select a model, and add a
117
+ credential. A credential can remain in memory for the current session or be
118
+ saved explicitly under **~/.jecode**; it is never stored in the workspace.
119
+
120
+ | Provider | Credential | Notes |
121
+ |---|---|---|
122
+ | Anthropic | ANTHROPIC_API_KEY | Cloud |
123
+ | OpenAI | OPENAI_API_KEY | Cloud |
124
+ | Ollama | OLLAMA_API_KEY for Cloud/remote | Cloud with a key, local without one |
125
+
126
+ Choose **cloud**, **local**, or a custom endpoint from the Ollama connection row
127
+ in **/settings**. Existing users with an Ollama API key automatically use
128
+ **https://ollama.com**; without a key, Jecode uses the local daemon at
129
+ **http://127.0.0.1:11434**. Remote custom endpoints must use HTTPS.
130
+
131
+ ## Use the TUI
132
+
133
+ Type **/** to open searchable command completion inside the composer.
134
+
135
+ | Command | What it does |
136
+ |---|---|
137
+ | /settings | Manage provider, connection, model, limits, motion, and credentials |
138
+ | /effort | Change and save reasoning effort directly |
139
+ | /providers | Switch the provider for the next turn |
140
+ | /models | Search the live model catalogue |
141
+ | /credentials | Add, replace, inspect, or forget saved credentials |
142
+ | /permissions | Review or revoke remembered session approvals |
143
+ | /usage | Show normalized token usage |
144
+ | /new | Start a clean in-memory conversation |
145
+ | /export | Save a timestamped Markdown transcript in the launch directory |
146
+ | /help | Show commands and controls |
147
+ | /exit | Restore the terminal and exit |
148
+
149
+ Useful controls:
150
+
151
+ - **Up/Down** moves through command suggestions, menus, and input history.
152
+ - **Tab** completes a slash command without running it; **Enter** sends.
153
+ - **Alt+Enter** inserts a newline.
154
+ - **Esc** closes a menu or interrupts the foreground operation.
155
+ - **Ctrl+C** interrupts, or exits while idle. **Ctrl+D** requests a clean exit.
156
+ - **PageUp/PageDown** and the mouse wheel scroll the transcript without losing
157
+ the place you are reading.
158
+ - **Ctrl+O** expands or compacts the latest reasoning or tool-detail block.
159
+
160
+ The one-line footer keeps model, effort, and workspace on the left. Live work,
161
+ readiness guidance, and temporary feedback use the right edge without polluting
162
+ the transcript.
163
+
164
+ ## Configuration
165
+
166
+ Startup precedence is: command-line flags, environment variables, saved
167
+ settings, built-in defaults.
168
+
169
+ | Flag | Environment | Default |
170
+ |---|---|---|
171
+ | --provider | JECODE_PROVIDER | anthropic |
172
+ | --model | JECODE_MODEL | Provider default or interactive selection |
173
+ | --ollama-host | OLLAMA_HOST | Cloud with an Ollama key, local without one |
174
+ | --root | — | Current directory |
175
+ | --effort | JECODE_EFFORT | high |
176
+ | --max-tokens | JECODE_MAX_TOKENS | 64000 |
177
+ | --max-steps | JECODE_MAX_STEPS | 40 |
178
+ | --reduced-motion | JECODE_REDUCED_MOTION=1 | Off |
179
+ | --auto-approve | JECODE_AUTO_APPROVE=1 | Off |
180
+
181
+ Persistent preferences live in **~/.jecode/settings.json**. Explicitly saved
182
+ credentials live in **~/.jecode/credentials.json** with owner-only permissions
183
+ where the operating system supports them. Environment credentials always win.
184
+
185
+ Jecode has one interface theme: dark Steel. **NO_COLOR** is supported for
186
+ terminals and pipelines that disable colour.
187
+
188
+ ## Automation
189
+
190
+ When stdin or stdout is piped, Jecode switches to a plain line-oriented mode:
191
+
192
+ ~~~console
193
+ printf "explain this project\n" | jecode --root .
194
+ ~~~
195
+
196
+ Dangerous tools stay denied in batch mode unless **--auto-approve** is supplied
197
+ explicitly.
198
+
199
+ ## Safety model
200
+
201
+ Jecode treats model output, workspace content, tool output, and terminal text as
202
+ untrusted data.
203
+
204
+ - Tool paths are confined to the selected workspace. Writes reject symlink and
205
+ junction components, then revalidate the boundary during atomic replacement.
206
+ - Dangerous tools require approval unless the process was started with
207
+ **--auto-approve**.
208
+ - Credential fields are masked and excluded from transcripts. Approved shell
209
+ commands receive no credential-like environment variables, and recognized
210
+ credential values are redacted before tool output reaches the model, screen,
211
+ history, or export.
212
+ - Terminal control characters are neutralized before rendering.
213
+ - Remote Ollama endpoints require HTTPS. Provider HTTP redirects are rejected
214
+ rather than followed across an implicit trust boundary.
215
+ - Provider handshakes and idle response bodies have finite deadlines. Only
216
+ idempotent catalogue reads retry; generation requests are never replayed.
217
+ - Model and filesystem input are bounded before they reach the screen or
218
+ provider.
219
+
220
+ `run_command` is not an operating-system sandbox: an approved shell command can
221
+ still access files and account resources available to the current user. Review
222
+ commands carefully and reserve **--auto-approve** for controlled environments.
223
+
224
+ Please read [SECURITY.md](SECURITY.md) before reporting a vulnerability.
225
+
226
+ ## Community
227
+
228
+ Jecode is built around people using the product and telling us where the loop
229
+ can improve.
230
+
231
+ - Ask questions, share workflows, and explore ideas in
232
+ [GitHub Discussions](https://github.com/giovannijecha/jecode/discussions).
233
+ - Report reproducible bugs and focused feature requests through
234
+ [GitHub Issues](https://github.com/giovannijecha/jecode/issues).
235
+ - Report security concerns privately through the repository Security tab.
236
+
237
+ Public pull requests are not accepted at this stage; code changes remain a
238
+ maintainer/collaborator workflow. See [CONTRIBUTING.md](CONTRIBUTING.md) for the
239
+ short routing guide and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community
240
+ expectations.
241
+
242
+ ## Development
243
+
244
+ ~~~console
245
+ npm ci
246
+ npm run check
247
+ ~~~
248
+
249
+ The visual lab exercises the TUI without a provider, network access, tool
250
+ execution, or workspace writes:
251
+
252
+ ~~~console
253
+ npm run tui:lab
254
+ ~~~
255
+
256
+ For a manual long-session rendering probe, run **npm run bench:transcript**.
257
+
258
+ Architecture and security boundaries are documented in
259
+ [docs/architecture.md](docs/architecture.md). Brand assets and usage rules live
260
+ in [docs/brand.md](docs/brand.md).
261
+
262
+ ## License
263
+
264
+ Jecode is available under the [MIT License](LICENSE).
package/bin/jecode.js ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+
3
+ const major = Number.parseInt(process.versions.node, 10);
4
+ if (major < 24) {
5
+ process.stderr.write(
6
+ `jecode requires Node.js 24 or newer (current: ${process.versions.node})\n`,
7
+ );
8
+ process.exitCode = 1;
9
+ } else {
10
+ await import("../dist/main.js");
11
+ }
package/dist/atomic.js ADDED
@@ -0,0 +1,78 @@
1
+ // Crash-safe replacement of one file, using a verified temporary sibling and
2
+ // rename.
3
+ import { lstat, open, rename, rm, stat } from "node:fs/promises";
4
+ import { randomUUID } from "node:crypto";
5
+ import * as path from "node:path";
6
+ export async function atomicWrite(file, content, options = {}) {
7
+ const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
8
+ let handle;
9
+ let identity;
10
+ try {
11
+ await options.validate?.("before-open");
12
+ const permissions = options.mode ?? (await existingMode(file));
13
+ handle = await open(temporary, "wx", permissions);
14
+ identity = fileIdentity(await handle.stat());
15
+ await options.validate?.("before-write");
16
+ await assertNamedFile(temporary, identity);
17
+ await handle.writeFile(content, "utf8");
18
+ if (permissions !== undefined && process.platform !== "win32") {
19
+ await handle.chmod(permissions);
20
+ }
21
+ await handle.sync();
22
+ identity = fileIdentity(await handle.stat());
23
+ await options.validate?.("before-rename");
24
+ await assertNamedFile(temporary, identity);
25
+ await rename(temporary, file);
26
+ const completed = handle;
27
+ handle = undefined;
28
+ await completed.close().catch(() => undefined);
29
+ }
30
+ catch (error) {
31
+ if (handle !== undefined) {
32
+ await handle.truncate(0).catch(() => undefined);
33
+ await handle.sync().catch(() => undefined);
34
+ await handle.close().catch(() => undefined);
35
+ handle = undefined;
36
+ }
37
+ await removeTemporary(temporary, identity, options.validate);
38
+ throw error;
39
+ }
40
+ }
41
+ async function assertNamedFile(file, expected) {
42
+ const details = await lstat(file);
43
+ if (details.isSymbolicLink() || !sameFile(expected, fileIdentity(details))) {
44
+ throw new Error("atomic write target changed during replacement");
45
+ }
46
+ }
47
+ async function removeTemporary(file, expected, validate) {
48
+ if (expected === undefined)
49
+ return;
50
+ try {
51
+ await validate?.("before-cleanup");
52
+ await assertNamedFile(file, expected);
53
+ await rm(file);
54
+ }
55
+ catch {
56
+ // A changed path is no longer ours to remove.
57
+ }
58
+ }
59
+ function fileIdentity(details) {
60
+ return { dev: details.dev, ino: details.ino, birthtimeMs: details.birthtimeMs };
61
+ }
62
+ function sameFile(left, right) {
63
+ if (left.dev !== right.dev || left.ino !== right.ino)
64
+ return false;
65
+ return left.ino !== 0 || left.birthtimeMs === right.birthtimeMs;
66
+ }
67
+ async function existingMode(file) {
68
+ if (process.platform === "win32")
69
+ return undefined;
70
+ try {
71
+ return (await stat(file)).mode & 0o777;
72
+ }
73
+ catch (error) {
74
+ if (error.code === "ENOENT")
75
+ return undefined;
76
+ throw error;
77
+ }
78
+ }
@@ -0,0 +1,17 @@
1
+ // Project semantic blocks into a compact transcript for pipes.
2
+ import { render } from "./tui/blocks.js";
3
+ export function renderBatch(block, width, pal) {
4
+ const rows = [];
5
+ let lastWasBlank = false;
6
+ for (const rendered of render(block, width, pal)) {
7
+ const line = rendered.trimEnd();
8
+ const blank = line === "";
9
+ if (blank && lastWasBlank)
10
+ continue;
11
+ rows.push(line);
12
+ lastWasBlank = blank;
13
+ }
14
+ while (rows.at(-1) === "")
15
+ rows.pop();
16
+ return rows;
17
+ }
package/dist/batch.js ADDED
@@ -0,0 +1,100 @@
1
+ // The non-interactive path: stdin is a pipe, so there is no screen to own.
2
+ //
3
+ // It exists so the agent can be scripted and tested. It shares the controller,
4
+ // the tools and the block renderer with the TUI — only the surface differs.
5
+ import * as readline from "node:readline/promises";
6
+ import { stdin, stdout } from "node:process";
7
+ import { runTurn } from "./controller.js";
8
+ import { handleCommand } from "./commands.js";
9
+ import { renderBatch } from "./batch-view.js";
10
+ import { columns } from "./ui/render.js";
11
+ import { terminalText } from "./ui/terminal-text.js";
12
+ import { recordUsage } from "./usage.js";
13
+ export async function runBatch(session, environment = {}) {
14
+ const rl = environment.lines === undefined ? readline.createInterface({ input: stdin }) : undefined;
15
+ const lines = environment.lines ?? rl;
16
+ const write = environment.write ?? ((text) => stdout.write(text));
17
+ const width = environment.width ?? columns();
18
+ const emit = (block) => {
19
+ for (const line of renderBatch(block, width, session.palette))
20
+ write(`${line}\n`);
21
+ };
22
+ try {
23
+ for await (const raw of lines) {
24
+ const line = raw.trim();
25
+ if (line === "")
26
+ continue;
27
+ if (line.startsWith("/")) {
28
+ if ((await handleCommand(line, session, { emit })) === "exit")
29
+ break;
30
+ continue;
31
+ }
32
+ write(`> ${terminalText(line)}\n`);
33
+ session.history.push({ role: "user", content: [{ kind: "text", text: line }] });
34
+ const turn = events(emit, session);
35
+ try {
36
+ await runTurn(session.history, options(session), turn);
37
+ }
38
+ catch (error) {
39
+ emit({ kind: "notice", text: error.message, tone: "error" });
40
+ }
41
+ finally {
42
+ turn.flush();
43
+ }
44
+ }
45
+ }
46
+ finally {
47
+ rl?.close();
48
+ }
49
+ }
50
+ function options(session) {
51
+ return {
52
+ provider: session.provider,
53
+ tools: session.tools,
54
+ model: session.model,
55
+ system: session.system,
56
+ maxTokens: session.config.maxTokens,
57
+ effort: session.config.effort,
58
+ maxSteps: session.config.maxSteps,
59
+ toolContext: { root: session.config.root },
60
+ };
61
+ }
62
+ // Prose is buffered rather than streamed: without a screen to repaint there is
63
+ // nothing to gain from partial lines, and plenty to lose in readability.
64
+ function events(emit, session) {
65
+ let answer = "";
66
+ const flush = () => {
67
+ if (answer === "")
68
+ return;
69
+ emit({ kind: "answer", text: answer });
70
+ answer = "";
71
+ };
72
+ return {
73
+ flush,
74
+ onStream(event) {
75
+ if (event.kind === "text")
76
+ answer += event.text;
77
+ },
78
+ onToolCall() {
79
+ flush();
80
+ },
81
+ onToolResult(call, result, summary) {
82
+ emit({
83
+ kind: "tool",
84
+ name: call.name,
85
+ target: "",
86
+ right: summary ?? "",
87
+ tone: result.isError ? "fail" : "ok",
88
+ });
89
+ },
90
+ approve() {
91
+ flush();
92
+ // Nobody is watching a pipe. Approval has to be granted up front with
93
+ // --auto-approve, never inferred from silence.
94
+ return Promise.resolve(session.config.autoApprove);
95
+ },
96
+ onUsage(usage) {
97
+ recordUsage(session.usage, usage);
98
+ },
99
+ };
100
+ }
@@ -0,0 +1,40 @@
1
+ // Information requests that finish before configuration or terminal takeover.
2
+ import { readFile } from "node:fs/promises";
3
+ import * as path from "node:path";
4
+ const HELP = `jecode — an owned coding agent for the terminal
5
+
6
+ Usage:
7
+ jecode [options]
8
+
9
+ Options:
10
+ --root <path> workspace root (default: current directory)
11
+ --provider <id> anthropic, openai, or ollama
12
+ --model <id> model for the selected provider
13
+ --ollama-host <url> Ollama Cloud, local, or custom endpoint
14
+ --effort <level> low, medium, high, or max
15
+ --max-tokens <number> output-token ceiling
16
+ --max-steps <number> tool-loop ceiling
17
+ --reduced-motion disable animated terminal states
18
+ --auto-approve allow dangerous tools for this process
19
+ -h, --help show this help
20
+ -v, --version show the installed version
21
+
22
+ Inside Jecode, type / to discover interactive commands.`;
23
+ export async function showCliInfo(args, applicationRoot, write) {
24
+ if (args.includes("--help") || args.includes("-h")) {
25
+ write(`${HELP}\n`);
26
+ return true;
27
+ }
28
+ if (args.includes("--version") || args.includes("-v")) {
29
+ write(`${await packageVersion(applicationRoot)}\n`);
30
+ return true;
31
+ }
32
+ return false;
33
+ }
34
+ async function packageVersion(applicationRoot) {
35
+ const manifest = JSON.parse(await readFile(path.join(applicationRoot, "package.json"), "utf8"));
36
+ if (typeof manifest.version !== "string" || manifest.version === "") {
37
+ throw new Error("package version is missing");
38
+ }
39
+ return manifest.version;
40
+ }