@unigent/cli 0.1.1
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 +21 -0
- package/README.md +470 -0
- package/dist/cli.js +132 -0
- package/dist/cli.js.map +1 -0
- package/dist/command.js +44 -0
- package/dist/command.js.map +1 -0
- package/dist/environment.js +20 -0
- package/dist/environment.js.map +1 -0
- package/dist/error_message.js +59 -0
- package/dist/error_message.js.map +1 -0
- package/dist/format.js +38 -0
- package/dist/format.js.map +1 -0
- package/dist/node_version.js +9 -0
- package/dist/node_version.js.map +1 -0
- package/dist/protocol.js +72 -0
- package/dist/protocol.js.map +1 -0
- package/dist/register.js +78 -0
- package/dist/register.js.map +1 -0
- package/dist/script_runtime.js +29 -0
- package/dist/script_runtime.js.map +1 -0
- package/dist/tui/app.js +453 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/clipboard.js +31 -0
- package/dist/tui/clipboard.js.map +1 -0
- package/dist/tui/runner.js +116 -0
- package/dist/tui/runner.js.map +1 -0
- package/dist/tui/scroll.js +33 -0
- package/dist/tui/scroll.js.map +1 -0
- package/dist/tui/store.js +99 -0
- package/dist/tui/store.js.map +1 -0
- package/dist/tui/theme.js +35 -0
- package/dist/tui/theme.js.map +1 -0
- package/dist/tui/tree.js +267 -0
- package/dist/tui/tree.js.map +1 -0
- package/dist/tui.js +63 -0
- package/dist/tui.js.map +1 -0
- package/node_modules/@unigent/core/dist/adapter_process.d.ts +25 -0
- package/node_modules/@unigent/core/dist/adapter_process.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/adapter_process.js +46 -0
- package/node_modules/@unigent/core/dist/adapter_process.js.map +1 -0
- package/node_modules/@unigent/core/dist/args.d.ts +17 -0
- package/node_modules/@unigent/core/dist/args.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/args.js +233 -0
- package/node_modules/@unigent/core/dist/args.js.map +1 -0
- package/node_modules/@unigent/core/dist/backend.d.ts +83 -0
- package/node_modules/@unigent/core/dist/backend.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/backend.js +2 -0
- package/node_modules/@unigent/core/dist/backend.js.map +1 -0
- package/node_modules/@unigent/core/dist/checkpoint.d.ts +26 -0
- package/node_modules/@unigent/core/dist/checkpoint.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/checkpoint.js +107 -0
- package/node_modules/@unigent/core/dist/checkpoint.js.map +1 -0
- package/node_modules/@unigent/core/dist/checkpoint_fingerprint.d.ts +4 -0
- package/node_modules/@unigent/core/dist/checkpoint_fingerprint.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/checkpoint_fingerprint.js +41 -0
- package/node_modules/@unigent/core/dist/checkpoint_fingerprint.js.map +1 -0
- package/node_modules/@unigent/core/dist/completion.d.ts +9 -0
- package/node_modules/@unigent/core/dist/completion.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/completion.js +4 -0
- package/node_modules/@unigent/core/dist/completion.js.map +1 -0
- package/node_modules/@unigent/core/dist/errors.d.ts +43 -0
- package/node_modules/@unigent/core/dist/errors.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/errors.js +48 -0
- package/node_modules/@unigent/core/dist/errors.js.map +1 -0
- package/node_modules/@unigent/core/dist/events.d.ts +101 -0
- package/node_modules/@unigent/core/dist/events.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/events.js +105 -0
- package/node_modules/@unigent/core/dist/events.js.map +1 -0
- package/node_modules/@unigent/core/dist/index.d.ts +19 -0
- package/node_modules/@unigent/core/dist/index.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/index.js +10 -0
- package/node_modules/@unigent/core/dist/index.js.map +1 -0
- package/node_modules/@unigent/core/dist/json_schema_validator.d.ts +8 -0
- package/node_modules/@unigent/core/dist/json_schema_validator.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/json_schema_validator.js +27 -0
- package/node_modules/@unigent/core/dist/json_schema_validator.js.map +1 -0
- package/node_modules/@unigent/core/dist/mcp.d.ts +12 -0
- package/node_modules/@unigent/core/dist/mcp.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/mcp.js +117 -0
- package/node_modules/@unigent/core/dist/mcp.js.map +1 -0
- package/node_modules/@unigent/core/dist/runtime.d.ts +109 -0
- package/node_modules/@unigent/core/dist/runtime.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/runtime.js +976 -0
- package/node_modules/@unigent/core/dist/runtime.js.map +1 -0
- package/node_modules/@unigent/core/dist/schema.d.ts +15 -0
- package/node_modules/@unigent/core/dist/schema.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/schema.js +71 -0
- package/node_modules/@unigent/core/dist/schema.js.map +1 -0
- package/node_modules/@unigent/core/dist/source_tools.d.ts +7 -0
- package/node_modules/@unigent/core/dist/source_tools.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/source_tools.js +450 -0
- package/node_modules/@unigent/core/dist/source_tools.js.map +1 -0
- package/node_modules/@unigent/core/dist/tool.d.ts +41 -0
- package/node_modules/@unigent/core/dist/tool.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/tool.js +43 -0
- package/node_modules/@unigent/core/dist/tool.js.map +1 -0
- package/node_modules/@unigent/core/dist/tools.d.ts +7 -0
- package/node_modules/@unigent/core/dist/tools.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/tools.js +3 -0
- package/node_modules/@unigent/core/dist/tools.js.map +1 -0
- package/node_modules/@unigent/core/dist/trace.d.ts +82 -0
- package/node_modules/@unigent/core/dist/trace.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/trace.js +235 -0
- package/node_modules/@unigent/core/dist/trace.js.map +1 -0
- package/node_modules/@unigent/core/dist/usage.d.ts +26 -0
- package/node_modules/@unigent/core/dist/usage.d.ts.map +1 -0
- package/node_modules/@unigent/core/dist/usage.js +47 -0
- package/node_modules/@unigent/core/dist/usage.js.map +1 -0
- package/node_modules/@unigent/core/package.json +68 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gintas Zenevskis
|
|
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,470 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://github.com/gintasz/unigent/raw/refs/heads/main/assets/icon.svg" alt="Unigent" width="96" height="96" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Unigent</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center"><strong>The cross-harness agent SDK for TypeScript.</strong></p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
Build stateful agent workflows with typed outputs, reusable tools, session forks, and ordinary TypeScript.
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://www.npmjs.com/package/@unigent/sdk"><img alt="npm version" src="https://img.shields.io/npm/v/@unigent/sdk?logo=npm&logoColor=white" /></a>
|
|
15
|
+
<a href="https://github.com/gintasz/unigent/blob/main/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-0A7EA4" /></a>
|
|
16
|
+
<a href="https://github.com/gintasz/unigent/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/gintasz/unigent?style=flat" /></a>
|
|
17
|
+
<img alt="Works with" src="https://img.shields.io/badge/works_with-555555" />
|
|
18
|
+
<a href="https://github.com/earendil-works/pi"><img alt="Pi SDK" src="https://img.shields.io/badge/Pi_SDK-7AA2F7" /></a>
|
|
19
|
+
<a href="https://docs.anthropic.com/en/docs/claude-code"><img alt="Claude CLI" src="https://img.shields.io/badge/Claude_Code-E5582B" /></a>
|
|
20
|
+
<a href="https://developers.openai.com/codex/cli/"><img alt="Codex CLI" src="https://img.shields.io/badge/Codex_CLI-10A37F" /></a>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p align="center"><a href="https://github.com/gintasz/unigent/tree/main/examples">Examples</a></p>
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
Unigent requires Node.js 24 or newer.
|
|
28
|
+
The live trace inspector (`unigent tui`) additionally requires [Bun](https://bun.sh/).
|
|
29
|
+
Published packages are ESM-only.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @unigent/sdk zod
|
|
33
|
+
npm install -g @unigent/cli
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The examples below use [Zod](https://zod.dev/), but Unigent accepts any
|
|
37
|
+
[Standard Schema](https://standardschema.dev/) validator.
|
|
38
|
+
|
|
39
|
+
### Drop-in Bun scripts
|
|
40
|
+
|
|
41
|
+
For small automations, a single Bun script is often more comfortable than adding a `package.json`,
|
|
42
|
+
lockfile, and local dependencies to a project. Start the file with `#!/usr/bin/env bun`; Unigent's
|
|
43
|
+
CLI will use Bun in both normal and TUI mode, with missing-package fallback enabled. Imports are
|
|
44
|
+
downloaded into Bun's global cache instead of creating project boilerplate.
|
|
45
|
+
|
|
46
|
+
Copy [examples/standalone.ts](https://github.com/gintasz/unigent/blob/main/examples/standalone.ts)
|
|
47
|
+
into a project and launch it through Unigent:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
unigent standalone.ts "Write a launch announcement"
|
|
51
|
+
unigent tui standalone.ts "Write a launch announcement"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The same file is directly executable when you do not need Unigent's trace transport or TUI:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
chmod +x standalone.ts
|
|
58
|
+
./standalone.ts "Write a launch announcement"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Pi reads models and authentication from your local Pi configuration. The Claude and Codex
|
|
62
|
+
adapters use the authenticated official CLIs installed on your machine.
|
|
63
|
+
|
|
64
|
+
👉 Your agent can invoke such workflow scripts with a bash command, isn't that cool?
|
|
65
|
+
|
|
66
|
+
## Your first agent
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { agent, piAgent } from "@unigent/sdk";
|
|
70
|
+
|
|
71
|
+
const writer = agent({
|
|
72
|
+
name: "writer",
|
|
73
|
+
backend: piAgent(),
|
|
74
|
+
model: "openrouter/deepseek/deepseek-v4-flash",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const result = await writer.run("Write one sentence about durable software.");
|
|
78
|
+
console.log(result.output);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`result` also contains token usage, any cost reported by the harness, and the full trace. The run
|
|
82
|
+
starts immediately, can be aborted, and exposes its events as an async iterable while you await the
|
|
83
|
+
final result.
|
|
84
|
+
|
|
85
|
+
See [`examples/hello.ts`](https://github.com/gintasz/unigent/blob/main/examples/hello.ts) for the
|
|
86
|
+
smallest complete script and
|
|
87
|
+
[`examples/pitch.ts`](https://github.com/gintasz/unigent/blob/main/examples/pitch.ts) for a workflow
|
|
88
|
+
using most of the API.
|
|
89
|
+
|
|
90
|
+
## Choose what comes back
|
|
91
|
+
|
|
92
|
+
Every run starts with `.run(prompt)`. The optional second argument controls what comes back: pass a
|
|
93
|
+
schema for a typed value or `done` for side-effect-only work.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { done } from "@unigent/sdk";
|
|
97
|
+
import { z } from "zod";
|
|
98
|
+
|
|
99
|
+
const prose = await writer.run("Explain the tradeoff.");
|
|
100
|
+
|
|
101
|
+
const structured = await writer.run(
|
|
102
|
+
"Choose the strongest title.",
|
|
103
|
+
z.object({ title: z.string(), score: z.number().min(0).max(100) }),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
await writer.run("Update CHANGELOG.md.", done);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- Omit the second argument to return prose.
|
|
110
|
+
- Pass a Standard Schema to return its inferred type.
|
|
111
|
+
- Pass `done` when the work ends with side effects and no prose result is useful.
|
|
112
|
+
|
|
113
|
+
`done` is a built-in completion sentinel schema. Passing it as the
|
|
114
|
+
second argument makes Unigent expose the reserved `unigent_return` tool with an empty object input
|
|
115
|
+
schema. The agent is instructed to call that tool after completing the requested side effects. An
|
|
116
|
+
accepted call returns `void` and terminates the run before the agent writes a final prose response,
|
|
117
|
+
saving tokens that the caller would otherwise discard.
|
|
118
|
+
|
|
119
|
+
Structured output uses the same completion-tool protocol. Instead of the empty sentinel schema,
|
|
120
|
+
`unigent_return` accepts a `value` matching the schema passed to `run()`.
|
|
121
|
+
|
|
122
|
+
Structured values therefore never need to be scraped from prose: Markdown, preambles, and trailing
|
|
123
|
+
commentary cannot corrupt the value. If the agent omits the completion call or returns an invalid
|
|
124
|
+
value, Unigent repairs the turn a bounded number of times and then throws
|
|
125
|
+
`AgentRepairExhaustedError`.
|
|
126
|
+
|
|
127
|
+
## Give agents tools
|
|
128
|
+
|
|
129
|
+
Pass ordinary named functions. Unigent reads their TypeScript signatures and JSDoc from the module
|
|
130
|
+
named by `source`, then builds the tool definitions for the harness.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
/** Return the number of words in some copy.
|
|
134
|
+
*
|
|
135
|
+
* @promptSnippet Use this before accepting copy with a word limit.
|
|
136
|
+
* @promptGuideline Do not call wordCount repeatedly with unchanged text.
|
|
137
|
+
*/
|
|
138
|
+
function wordCount(text: string): number {
|
|
139
|
+
return text.trim().split(/\s+/u).length;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const editor = agent({
|
|
143
|
+
name: "editor",
|
|
144
|
+
source: import.meta.url,
|
|
145
|
+
backend: piAgent(),
|
|
146
|
+
model: "openrouter/deepseek/deepseek-v4-flash",
|
|
147
|
+
tools: [wordCount],
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The opening JSDoc prose is the tool description; there is no `@description` tag. Both prompt tags
|
|
152
|
+
are optional. `@promptSnippet` adds a named entry to the system prompt's available-tools section.
|
|
153
|
+
Each `@promptGuideline` becomes a standalone bullet in its guidelines section, without an automatic
|
|
154
|
+
tool-name prefix, so every guideline must name its tool explicitly. This distinction follows Pi's
|
|
155
|
+
native semantics. Claude Code and Codex do not expose equivalent tool fields, so Unigent renders
|
|
156
|
+
both sections itself for every harness instead of appending either tag to the tool description.
|
|
157
|
+
Omit the tags when that extra guidance is not worth its token cost. When TypeScript source is
|
|
158
|
+
unavailable, the portable `tool({...})` helper accepts an explicit Standard Schema input.
|
|
159
|
+
|
|
160
|
+
For compiled deployments, bake source-derived schemas after compiling the entry:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
tsc && unigent bake src/worker.ts
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
This writes `worker.unigent-tools.json` beside the compiled `worker.js`. Production loads that
|
|
167
|
+
manifest without installing or loading TypeScript; development can continue reflecting directly
|
|
168
|
+
from a `.ts` entry when the optional `typescript` peer is installed.
|
|
169
|
+
|
|
170
|
+
Only values listed in `tools` are callable. Deliberate agent failure is also opt-in:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { fail } from "@unigent/sdk";
|
|
174
|
+
|
|
175
|
+
const reviewer = agent({ ...options, tools: [fail] });
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Unigent does not rename ordinary function tools: `wordCount` is advertised as `wordCount`. To the
|
|
179
|
+
agent, `unigent_fail` is another callable tool. It is special only at the TypeScript boundary:
|
|
180
|
+
ordinary tool errors are recoverable feedback for the agent, while `unigent_fail` terminates the
|
|
181
|
+
run and throws `AgentRaisedError` to the caller. Adding the `fail` sentinel opts the agent into that
|
|
182
|
+
control tool; agents cannot deliberately abort a run unless the developer enables it. User-defined
|
|
183
|
+
names beginning with `unigent_` are rejected because that namespace belongs to protocol tools such
|
|
184
|
+
as `unigent_return` and `unigent_fail`.
|
|
185
|
+
|
|
186
|
+
## Compose agents with functions
|
|
187
|
+
|
|
188
|
+
An agent becomes a nested agent when you call it from a tool function.
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
const researcher = agent({
|
|
192
|
+
name: "researcher",
|
|
193
|
+
backend: piAgent(),
|
|
194
|
+
model: "openrouter/deepseek/deepseek-v4-pro",
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
/** Research one question and return a concise finding. */
|
|
198
|
+
async function research(question: string): Promise<string> {
|
|
199
|
+
const result = await researcher.run(question);
|
|
200
|
+
return result.output;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const author = agent({
|
|
204
|
+
name: "author",
|
|
205
|
+
source: import.meta.url,
|
|
206
|
+
backend: piAgent(),
|
|
207
|
+
model: "openrouter/deepseek/deepseek-v4-flash",
|
|
208
|
+
tools: [research],
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
When the author calls `research`, the function opens another Unigent run. The nested run keeps its
|
|
213
|
+
trace parentage, and its usage rolls up into the parent. The surrounding program still decides how
|
|
214
|
+
many agents run, which work happens in parallel, and where results go next.
|
|
215
|
+
|
|
216
|
+
## Keep a conversation and branch it
|
|
217
|
+
|
|
218
|
+
Stateless runs are the default. A session keeps the harness conversation for later turns.
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
const review = reviewer.session();
|
|
222
|
+
|
|
223
|
+
await review.run(`
|
|
224
|
+
Read the repository and learn its architecture, conventions, and current behavior.
|
|
225
|
+
Trace the main execution paths and note the patterns that new code should follow.
|
|
226
|
+
Do not review or modify anything yet; reply when you have enough context.
|
|
227
|
+
`);
|
|
228
|
+
|
|
229
|
+
const correctnessReview = review.fork();
|
|
230
|
+
const maintainabilityReview = review.fork();
|
|
231
|
+
|
|
232
|
+
const [correctness, maintainability] = await Promise.all([
|
|
233
|
+
correctnessReview.run("Review the codebase only for correctness and edge-case failures."),
|
|
234
|
+
maintainabilityReview.run("Review the codebase only for maintainability and API clarity."),
|
|
235
|
+
]);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Both branches reuse the repository context gathered by the first turn instead of paying to rebuild
|
|
239
|
+
it independently. All three official harnesses support forks. A session can be forked after its
|
|
240
|
+
first completed turn, but not while a turn is active.
|
|
241
|
+
|
|
242
|
+
## Group a workflow with scopes
|
|
243
|
+
|
|
244
|
+
A scope groups related runs and owns their cumulative usage, traces, annotations, logs,
|
|
245
|
+
cancellation, and deadline.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
const release = writer.scope("release", {
|
|
249
|
+
duration: "10m",
|
|
250
|
+
retainTraces: 50,
|
|
251
|
+
limits: { budgetUsd: 1 },
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
release.annotate({ pullRequest: 42 });
|
|
255
|
+
release.log("drafting release notes");
|
|
256
|
+
|
|
257
|
+
const draft = await release.scope("draft").run("Draft the notes.");
|
|
258
|
+
await release.scope("publish").run(`Publish these notes: ${draft.output}`, done);
|
|
259
|
+
|
|
260
|
+
console.log(release.usage);
|
|
261
|
+
console.log(release.traces);
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`.with(overrides)` returns an immutable agent variation with different configuration, such as a
|
|
265
|
+
model, backend, system prompt, or per-turn limits. It does not create a new lifecycle or telemetry
|
|
266
|
+
group; runs still belong to the current scope.
|
|
267
|
+
|
|
268
|
+
`.scope(name, options)` creates a named workflow boundary. It can apply the same configuration
|
|
269
|
+
overrides, but it also owns aggregate usage and traces, annotations and logs, cancellation, and an
|
|
270
|
+
overall `duration` deadline. Nested scopes roll their usage and traces into their parent. Inherited
|
|
271
|
+
limits can only tighten: `limits.turnDuration` caps each agent turn, while the scope's `duration`
|
|
272
|
+
caps the whole grouped workflow. Explicit scopes retain the latest 50 completed root traces by
|
|
273
|
+
default; set `retainTraces` to another bound or `0` to disable retention. Each run still returns its
|
|
274
|
+
complete trace as `result.trace`.
|
|
275
|
+
|
|
276
|
+
## Run and inspect scripts
|
|
277
|
+
|
|
278
|
+
The CLI runs an ordinary TypeScript file as a child process. The file needs no TUI-specific code.
|
|
279
|
+
Everything after the script path is forwarded to the script. A conventional `--` separator is
|
|
280
|
+
also accepted for compatibility with command generators that insert one, but Unigent does not
|
|
281
|
+
need it: its own option parsing stops at the script path.
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
unigent examples/pitch.ts "Kebab shop app"
|
|
285
|
+
unigent tui examples/pitch.ts "Kebab shop app"
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Trace events travel over an isolated channel, leaving the script's stdout and stderr alone. The
|
|
289
|
+
TUI shows the live run tree, transcript, system prompt, tool calls, usage, errors, and process
|
|
290
|
+
output.
|
|
291
|
+
|
|
292
|
+
<p align="center">
|
|
293
|
+
<img src="https://github.com/gintasz/unigent/raw/refs/heads/main/assets/tui.png" alt="Unigent live trace inspector" width="1100" />
|
|
294
|
+
</p>
|
|
295
|
+
|
|
296
|
+
| Key | Action |
|
|
297
|
+
| --- | --- |
|
|
298
|
+
| `↑` / `↓` | Select a trace row |
|
|
299
|
+
| `Enter` | Expand or hide the selected agent's tool calls |
|
|
300
|
+
| `s` | Show or hide the system prompt |
|
|
301
|
+
| `o` | Switch between activity and process output |
|
|
302
|
+
| `[` / `]` | Page through older or newer trace history |
|
|
303
|
+
| `r` / `Ctrl-R` | Rerun or stop |
|
|
304
|
+
| `q` | Quit |
|
|
305
|
+
|
|
306
|
+
The trace retains its complete history in 250-row pages, and the activity pane keeps only its latest
|
|
307
|
+
250 entries mounted. Both panes use viewport culling so off-screen content is not painted.
|
|
308
|
+
|
|
309
|
+
## Parse script arguments
|
|
310
|
+
|
|
311
|
+
`args()` parses `process.argv` through any Standard Schema. A scalar schema joins positional
|
|
312
|
+
arguments into one natural value:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { args } from "@unigent/sdk";
|
|
316
|
+
import { z } from "zod";
|
|
317
|
+
|
|
318
|
+
const idea = await args(z.string().min(1));
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The second argument is optional. Add it only when the script needs more useful `--help` output:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
const idea = await args(z.string().min(1), {
|
|
325
|
+
description: "Turn a product idea into an elevator pitch.",
|
|
326
|
+
usage: '"Product idea"',
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Ask the script to print the generated help without starting an agent run:
|
|
331
|
+
|
|
332
|
+
```console
|
|
333
|
+
$ unigent examples/pitch.ts --help
|
|
334
|
+
Turn a product idea into a scored elevator pitch.
|
|
335
|
+
|
|
336
|
+
Usage: pitch.ts "Product idea"
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
`args()` prints this standardized usage for `-h` or `--help`. Invalid input prints the schema's
|
|
340
|
+
validation message and the same usage before exiting. Zod's default messages work without any
|
|
341
|
+
authored message; customize them only when the default is not clear enough for your script.
|
|
342
|
+
|
|
343
|
+
Object schemas accept named flags, defaults, repeated arrays, dotted paths, booleans, and `--no-*`
|
|
344
|
+
negation:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
const input = await args(
|
|
348
|
+
z.object({
|
|
349
|
+
keyword: z.string(),
|
|
350
|
+
count: z.number().int().positive().default(1),
|
|
351
|
+
cache: z.boolean().default(true),
|
|
352
|
+
}),
|
|
353
|
+
);
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
unigent task.ts --keyword kebab --count 3 --no-cache
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
`--no-cache` sets the `cache` field to `false`; `--cache` sets it to `true`. The `no-` prefix is a
|
|
361
|
+
conventional CLI negation for boolean flags, not part of the schema field name.
|
|
362
|
+
|
|
363
|
+
## Resume finished work with checkpoints
|
|
364
|
+
|
|
365
|
+
Checkpoints reuse successful stateless runs across reruns and deduplicate identical concurrent
|
|
366
|
+
calls. Their fingerprint covers the prompt, return schema, harness adapter, model, system
|
|
367
|
+
prompt, tools, and explicit checkpoint keys.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { agent, createFileCheckpointStore } from "@unigent/sdk";
|
|
371
|
+
|
|
372
|
+
const defaults = {
|
|
373
|
+
backend: piAgent(),
|
|
374
|
+
checkpoint: createFileCheckpointStore(".unigent/checkpoints.jsonl"),
|
|
375
|
+
limits: { turnDuration: "5m" as const },
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const writer = agent({ ...defaults, name: "writer", model: "model-a" });
|
|
379
|
+
const reviewer = agent({ ...defaults, name: "reviewer", model: "model-b" });
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Set `checkpointKey` when closed-over or ambient behavior changes the run's identity. Use
|
|
383
|
+
`.with({ checkpoint: false })` for a fresh branch. Stateful session turns always run fresh because
|
|
384
|
+
their result depends on the conversation transcript. A checkpoint replay restores the recorded
|
|
385
|
+
usage, including `costUsd`; scope telemetry and budgets therefore describe the logical workflow,
|
|
386
|
+
not only newly billed backend calls.
|
|
387
|
+
|
|
388
|
+
Budget enforcement is exact for sequential work. Parallel siblings can each begin below the same
|
|
389
|
+
remaining budget because their costs are unknown until they settle; a sibling that takes the scope
|
|
390
|
+
over budget rejects after settlement, but the provider spend has already occurred.
|
|
391
|
+
|
|
392
|
+
## Test without a model
|
|
393
|
+
|
|
394
|
+
The SDK includes a deterministic harness adapter under its `test` subpath, so tests need no second
|
|
395
|
+
Unigent package.
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { agent } from "@unigent/sdk";
|
|
399
|
+
import { createScriptedBackend } from "@unigent/sdk/test";
|
|
400
|
+
import { z } from "zod";
|
|
401
|
+
|
|
402
|
+
const backend = createScriptedBackend([
|
|
403
|
+
{
|
|
404
|
+
toolCalls: [
|
|
405
|
+
{ name: "unigent_return", input: { value: { title: "Unigent" } } },
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
]);
|
|
409
|
+
|
|
410
|
+
const subject = agent({ name: "subject", backend, model: "test" });
|
|
411
|
+
const result = await subject.run("Name the project.", z.object({ title: z.string() }));
|
|
412
|
+
|
|
413
|
+
expect(result.output).toEqual({ title: "Unigent" });
|
|
414
|
+
expect(backend.requests).toHaveLength(1);
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
`createTestBackend` accepts a programmable turn handler. `createScriptedBackend` consumes
|
|
418
|
+
declarative turns. Adapter authors can use `exerciseBackendContract` to run the shared prose,
|
|
419
|
+
session, and fork contract against a new harness adapter.
|
|
420
|
+
|
|
421
|
+
## Harnesses
|
|
422
|
+
|
|
423
|
+
Unigent talks to each harness through a small adapter that opens sessions, runs turns, executes
|
|
424
|
+
Unigent tools, streams events, reports usage, and forks conversations where supported.
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
import { claudeCli, codexCli, piAgent } from "@unigent/sdk";
|
|
428
|
+
|
|
429
|
+
const pi = piAgent();
|
|
430
|
+
const claude = claudeCli();
|
|
431
|
+
const codex = codexCli();
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
All three adapters default to `base: "clean"`, which starts without the machine's prompt,
|
|
435
|
+
plugins, skills, hooks, or MCP configuration. Authentication, model configuration, and each
|
|
436
|
+
harness's native tools remain available unless you restrict them. Set `base: "machine"` to inherit
|
|
437
|
+
ambient harness configuration.
|
|
438
|
+
|
|
439
|
+
| Capability | Pi agent SDK | Claude CLI | Codex CLI |
|
|
440
|
+
| --- | --- | --- | --- |
|
|
441
|
+
| Prose, structured output, `done`, `fail`, tools | Yes | Yes, through ephemeral local MCP | Yes, through ephemeral local MCP |
|
|
442
|
+
| Cost reporting and budget enforcement | Yes | Yes | No; budget configuration fails fast |
|
|
443
|
+
| Session continuation and forks | Yes | Yes, through CLI resume/fork flags | Yes; forks copy Codex's persisted session transcript under a new session ID |
|
|
444
|
+
| Native tools | Exact allowlist; `[]` disables | `--tools` allowlist; may retain `ToolSearch` for MCP | `[]` disables shell and web search; no exact allowlist |
|
|
445
|
+
| Plugins | Exact allowlist or disable | Exact installed-plugin allowlist or disable | No Unigent control |
|
|
446
|
+
| Skills | Exact allowlist or disable | Inherit all with machine base or disable all; no named allowlist | Exact allowlist or disable |
|
|
447
|
+
| Machine MCP servers and hooks | Pi has no built-in MCP; MCP extensions and hooks follow plugin selection | Inherit all with machine base or disable all; no named allowlist | Clean base ignores user config and rules; machine base inherits |
|
|
448
|
+
| Permissions | Harness execution is unrestricted | Bypassed by default; `permissions: "cli"` defers to Claude | Bypassed by default; `permissions: "cli"` defers to Codex |
|
|
449
|
+
|
|
450
|
+
Explicit adapter options override the inherited category where the harness can enforce them.
|
|
451
|
+
Unsupported controls fail during setup instead of becoming silent no-ops.
|
|
452
|
+
|
|
453
|
+
## Development
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
corepack pnpm install --frozen-lockfile
|
|
457
|
+
corepack pnpm run typecheck
|
|
458
|
+
corepack pnpm test
|
|
459
|
+
corepack pnpm run test:tui
|
|
460
|
+
corepack pnpm run test:e2e
|
|
461
|
+
corepack pnpm run check:full
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
The deterministic suite uses no model. `test:tui` drives a real terminal without a model.
|
|
465
|
+
`test:e2e` spends real Pi, Claude CLI, and Codex CLI turns and requires all three local harnesses
|
|
466
|
+
to be authenticated.
|
|
467
|
+
|
|
468
|
+
## License
|
|
469
|
+
|
|
470
|
+
[MIT](https://github.com/gintasz/unigent/blob/main/LICENSE) © 2026 Gintas Zenevskis
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { constants as operatingSystemConstants } from "node:os";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { bakeSourceTools } from "@unigent/core";
|
|
9
|
+
import packageManifest from "../package.json" with { type: "json" };
|
|
10
|
+
import { parseCommand } from "./command.js";
|
|
11
|
+
import { childProcessEnvironment } from "./environment.js";
|
|
12
|
+
import { assertSupportedNodeVersion } from "./node_version.js";
|
|
13
|
+
import { TRACE_TRANSPORT_ENVIRONMENT_VARIABLE } from "./protocol.js";
|
|
14
|
+
import { detectScriptRuntime, runtimeInvocation } from "./script_runtime.js";
|
|
15
|
+
const HELP = `unigent — run and inspect a Unigent script
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
unigent <file> [args...] run a TypeScript or JavaScript file
|
|
19
|
+
unigent run <file> [args...] same, explicitly
|
|
20
|
+
unigent tui <file> [args...] open the live trace inspector
|
|
21
|
+
unigent bake <entry> bake source-tool schemas for production
|
|
22
|
+
unigent --version print the installed version
|
|
23
|
+
|
|
24
|
+
Arguments after <file> are forwarded to the script. A conventional -- separator from command
|
|
25
|
+
generators is accepted but not required because Unigent stops parsing at <file>. Bun shebangs are
|
|
26
|
+
honored. TUI mode requires Bun.
|
|
27
|
+
`;
|
|
28
|
+
const DEVELOPMENT_LOADER_PREFIX = "--developmentLoader=";
|
|
29
|
+
const SIGNAL_EXIT_CODE_BASE = 128;
|
|
30
|
+
const INTERRUPTED_EXIT_CODE = 130;
|
|
31
|
+
function parseInternalArguments(arguments_) {
|
|
32
|
+
const [first, ...remaining] = arguments_;
|
|
33
|
+
return first?.startsWith(DEVELOPMENT_LOADER_PREFIX) === true
|
|
34
|
+
? {
|
|
35
|
+
developmentLoader: first.slice(DEVELOPMENT_LOADER_PREFIX.length),
|
|
36
|
+
commandArguments: remaining,
|
|
37
|
+
}
|
|
38
|
+
: { developmentLoader: undefined, commandArguments: arguments_ };
|
|
39
|
+
}
|
|
40
|
+
function packageEntry(name) {
|
|
41
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
for (const extension of ["js", "ts", "tsx"]) {
|
|
43
|
+
const candidate = resolve(here, `${name}.${extension}`);
|
|
44
|
+
if (existsSync(candidate)) {
|
|
45
|
+
return candidate;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
throw new Error(`unigent: ${name} entry not found`);
|
|
49
|
+
}
|
|
50
|
+
function signalExitCode(signal) {
|
|
51
|
+
return SIGNAL_EXIT_CODE_BASE + operatingSystemConstants.signals[signal];
|
|
52
|
+
}
|
|
53
|
+
function requireBun() {
|
|
54
|
+
const probe = spawnSync("bun", ["--version"], { stdio: "ignore" });
|
|
55
|
+
if (probe.error !== undefined) {
|
|
56
|
+
throw new Error("TUI mode requires Bun. Install it from https://bun.sh, then rerun `unigent tui`.");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function spawnExitCode(executable, arguments_) {
|
|
60
|
+
return await new Promise((resolveCode, reject) => {
|
|
61
|
+
const environment = childProcessEnvironment({
|
|
62
|
+
[TRACE_TRANSPORT_ENVIRONMENT_VARIABLE]: undefined,
|
|
63
|
+
});
|
|
64
|
+
const child = spawn(executable, arguments_, { env: environment, stdio: "inherit" });
|
|
65
|
+
let interrupted = false;
|
|
66
|
+
const forwardInterrupt = () => {
|
|
67
|
+
interrupted = true;
|
|
68
|
+
child.kill("SIGINT");
|
|
69
|
+
};
|
|
70
|
+
process.on("SIGINT", forwardInterrupt);
|
|
71
|
+
child.once("error", reject);
|
|
72
|
+
child.once("exit", (code, signal) => {
|
|
73
|
+
process.removeListener("SIGINT", forwardInterrupt);
|
|
74
|
+
resolveCode(interrupted
|
|
75
|
+
? INTERRUPTED_EXIT_CODE
|
|
76
|
+
: (code ?? (signal === null ? 1 : signalExitCode(signal))));
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function execute(command, developmentLoader) {
|
|
81
|
+
const runtime = runtimeInvocation(await detectScriptRuntime(command.sourceFile), process.execPath);
|
|
82
|
+
const developmentPreload = developmentLoader !== undefined && runtime.kind === "node"
|
|
83
|
+
? ["--import", developmentLoader]
|
|
84
|
+
: [];
|
|
85
|
+
if (command.mode === "run") {
|
|
86
|
+
return await spawnExitCode(runtime.executable, [
|
|
87
|
+
...runtime.arguments,
|
|
88
|
+
...developmentPreload,
|
|
89
|
+
"--import",
|
|
90
|
+
packageEntry("register"),
|
|
91
|
+
command.sourceFile,
|
|
92
|
+
...command.scriptArguments,
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
requireBun();
|
|
96
|
+
return await spawnExitCode("bun", [
|
|
97
|
+
packageEntry("tui"),
|
|
98
|
+
"--node",
|
|
99
|
+
process.execPath,
|
|
100
|
+
"--register",
|
|
101
|
+
packageEntry("register"),
|
|
102
|
+
...(developmentLoader === undefined ? [] : [`--developmentLoader=${developmentLoader}`]),
|
|
103
|
+
command.sourceFile,
|
|
104
|
+
"--",
|
|
105
|
+
...command.scriptArguments,
|
|
106
|
+
]);
|
|
107
|
+
}
|
|
108
|
+
async function main() {
|
|
109
|
+
assertSupportedNodeVersion(process.versions.node);
|
|
110
|
+
const internal = parseInternalArguments(process.argv.slice(2));
|
|
111
|
+
const parsed = parseCommand(internal.commandArguments, process.cwd());
|
|
112
|
+
if (parsed.kind === "help") {
|
|
113
|
+
process.stdout.write(HELP);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
if (parsed.kind === "version") {
|
|
117
|
+
process.stdout.write(`${packageManifest.version}\n`);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
if (parsed.kind === "bake") {
|
|
121
|
+
process.stdout.write(`${bakeSourceTools(parsed.command.sourceFile)}\n`);
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
return await execute(parsed.command, internal.developmentLoader);
|
|
125
|
+
}
|
|
126
|
+
main().then((code) => {
|
|
127
|
+
process.exitCode = code;
|
|
128
|
+
}, (error) => {
|
|
129
|
+
process.stderr.write(`unigent: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
130
|
+
process.exitCode = 1;
|
|
131
|
+
});
|
|
132
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { spawn, spawnSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { constants as operatingSystemConstants } from \"node:os\";\nimport { dirname, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { fileURLToPath } from \"node:url\";\nimport { bakeSourceTools } from \"@unigent/core\";\nimport packageManifest from \"../package.json\" with { type: \"json\" };\nimport { parseCommand, type RunCommand } from \"./command.js\";\nimport { childProcessEnvironment } from \"./environment.js\";\nimport { assertSupportedNodeVersion } from \"./node_version.js\";\nimport { TRACE_TRANSPORT_ENVIRONMENT_VARIABLE } from \"./protocol.js\";\nimport { detectScriptRuntime, runtimeInvocation } from \"./script_runtime.js\";\n\nconst HELP = `unigent — run and inspect a Unigent script\n\nUsage:\n unigent <file> [args...] run a TypeScript or JavaScript file\n unigent run <file> [args...] same, explicitly\n unigent tui <file> [args...] open the live trace inspector\n unigent bake <entry> bake source-tool schemas for production\n unigent --version print the installed version\n\nArguments after <file> are forwarded to the script. A conventional -- separator from command\ngenerators is accepted but not required because Unigent stops parsing at <file>. Bun shebangs are\nhonored. TUI mode requires Bun.\n`;\nconst DEVELOPMENT_LOADER_PREFIX = \"--developmentLoader=\";\nconst SIGNAL_EXIT_CODE_BASE = 128;\nconst INTERRUPTED_EXIT_CODE = 130;\n\ninterface InternalArguments {\n readonly developmentLoader: string | undefined;\n readonly commandArguments: readonly string[];\n}\n\nfunction parseInternalArguments(arguments_: readonly string[]): InternalArguments {\n const [first, ...remaining] = arguments_;\n return first?.startsWith(DEVELOPMENT_LOADER_PREFIX) === true\n ? {\n developmentLoader: first.slice(DEVELOPMENT_LOADER_PREFIX.length),\n commandArguments: remaining,\n }\n : { developmentLoader: undefined, commandArguments: arguments_ };\n}\n\nfunction packageEntry(name: \"register\" | \"tui\"): string {\n const here = dirname(fileURLToPath(import.meta.url));\n for (const extension of [\"js\", \"ts\", \"tsx\"] as const) {\n const candidate = resolve(here, `${name}.${extension}`);\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n throw new Error(`unigent: ${name} entry not found`);\n}\n\nfunction signalExitCode(signal: NodeJS.Signals): number {\n return SIGNAL_EXIT_CODE_BASE + operatingSystemConstants.signals[signal];\n}\n\nfunction requireBun(): void {\n const probe = spawnSync(\"bun\", [\"--version\"], { stdio: \"ignore\" });\n if (probe.error !== undefined) {\n throw new Error(\n \"TUI mode requires Bun. Install it from https://bun.sh, then rerun `unigent tui`.\",\n );\n }\n}\n\nasync function spawnExitCode(executable: string, arguments_: readonly string[]): Promise<number> {\n return await new Promise<number>((resolveCode, reject) => {\n const environment = childProcessEnvironment({\n [TRACE_TRANSPORT_ENVIRONMENT_VARIABLE]: undefined,\n });\n const child = spawn(executable, arguments_, { env: environment, stdio: \"inherit\" });\n let interrupted = false;\n const forwardInterrupt = (): void => {\n interrupted = true;\n child.kill(\"SIGINT\");\n };\n process.on(\"SIGINT\", forwardInterrupt);\n child.once(\"error\", reject);\n child.once(\"exit\", (code, signal) => {\n process.removeListener(\"SIGINT\", forwardInterrupt);\n resolveCode(\n interrupted\n ? INTERRUPTED_EXIT_CODE\n : (code ?? (signal === null ? 1 : signalExitCode(signal))),\n );\n });\n });\n}\n\nasync function execute(\n command: RunCommand,\n developmentLoader: string | undefined,\n): Promise<number> {\n const runtime = runtimeInvocation(\n await detectScriptRuntime(command.sourceFile),\n process.execPath,\n );\n const developmentPreload =\n developmentLoader !== undefined && runtime.kind === \"node\"\n ? [\"--import\", developmentLoader]\n : [];\n if (command.mode === \"run\") {\n return await spawnExitCode(runtime.executable, [\n ...runtime.arguments,\n ...developmentPreload,\n \"--import\",\n packageEntry(\"register\"),\n command.sourceFile,\n ...command.scriptArguments,\n ]);\n }\n requireBun();\n return await spawnExitCode(\"bun\", [\n packageEntry(\"tui\"),\n \"--node\",\n process.execPath,\n \"--register\",\n packageEntry(\"register\"),\n ...(developmentLoader === undefined ? [] : [`--developmentLoader=${developmentLoader}`]),\n command.sourceFile,\n \"--\",\n ...command.scriptArguments,\n ]);\n}\n\nasync function main(): Promise<number> {\n assertSupportedNodeVersion(process.versions.node);\n const internal = parseInternalArguments(process.argv.slice(2));\n const parsed = parseCommand(internal.commandArguments, process.cwd());\n if (parsed.kind === \"help\") {\n process.stdout.write(HELP);\n return 0;\n }\n if (parsed.kind === \"version\") {\n process.stdout.write(`${packageManifest.version}\\n`);\n return 0;\n }\n if (parsed.kind === \"bake\") {\n process.stdout.write(`${bakeSourceTools(parsed.command.sourceFile)}\\n`);\n return 0;\n }\n return await execute(parsed.command, internal.developmentLoader);\n}\n\nmain().then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n process.stderr.write(`unigent: ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n },\n);\n"]}
|