@glbgpt/cli 0.1.4 → 0.2.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/README.md +82 -40
- package/dist/src/coding/backgroundTasks.d.ts +34 -0
- package/dist/src/coding/backgroundTasks.js +154 -0
- package/dist/src/coding/backgroundTasks.js.map +1 -0
- package/dist/src/coding/backgroundTools.d.ts +9 -0
- package/dist/src/coding/backgroundTools.js +63 -0
- package/dist/src/coding/backgroundTools.js.map +1 -0
- package/dist/src/coding/bashTool.d.ts +27 -0
- package/dist/src/coding/bashTool.js +144 -0
- package/dist/src/coding/bashTool.js.map +1 -0
- package/dist/src/coding/dangerousCommands.d.ts +29 -0
- package/dist/src/coding/dangerousCommands.js +156 -0
- package/dist/src/coding/dangerousCommands.js.map +1 -0
- package/dist/src/coding/editDiff.d.ts +45 -0
- package/dist/src/coding/editDiff.js +156 -0
- package/dist/src/coding/editDiff.js.map +1 -0
- package/dist/src/coding/editTool.d.ts +14 -0
- package/dist/src/coding/editTool.js +154 -0
- package/dist/src/coding/editTool.js.map +1 -0
- package/dist/src/coding/index.d.ts +25 -0
- package/dist/src/coding/index.js +30 -0
- package/dist/src/coding/index.js.map +1 -0
- package/dist/src/coding/outputAccumulator.d.ts +34 -0
- package/dist/src/coding/outputAccumulator.js +111 -0
- package/dist/src/coding/outputAccumulator.js.map +1 -0
- package/dist/src/coding/permissionGate.d.ts +65 -0
- package/dist/src/coding/permissionGate.js +138 -0
- package/dist/src/coding/permissionGate.js.map +1 -0
- package/dist/src/coding/permissionStore.d.ts +36 -0
- package/dist/src/coding/permissionStore.js +56 -0
- package/dist/src/coding/permissionStore.js.map +1 -0
- package/dist/src/coding/permissions.d.ts +34 -0
- package/dist/src/coding/permissions.js +18 -0
- package/dist/src/coding/permissions.js.map +1 -0
- package/dist/src/coding/projectContext.d.ts +21 -0
- package/dist/src/coding/projectContext.js +118 -0
- package/dist/src/coding/projectContext.js.map +1 -0
- package/dist/src/coding/readState.d.ts +22 -0
- package/dist/src/coding/readState.js +30 -0
- package/dist/src/coding/readState.js.map +1 -0
- package/dist/src/coding/truncate.d.ts +39 -0
- package/dist/src/coding/truncate.js +83 -0
- package/dist/src/coding/truncate.js.map +1 -0
- package/dist/src/coding/writeTool.d.ts +14 -0
- package/dist/src/coding/writeTool.js +82 -0
- package/dist/src/coding/writeTool.js.map +1 -0
- package/dist/src/commands/execCommand.d.ts +13 -0
- package/dist/src/commands/execCommand.js +73 -6
- package/dist/src/commands/execCommand.js.map +1 -1
- package/dist/src/commands/updateCommand.d.ts +7 -0
- package/dist/src/commands/updateCommand.js +11 -0
- package/dist/src/commands/updateCommand.js.map +1 -0
- package/dist/src/core/catalog.d.ts +5 -0
- package/dist/src/core/catalog.js +31 -1
- package/dist/src/core/catalog.js.map +1 -1
- package/dist/src/core/compaction.d.ts +52 -0
- package/dist/src/core/compaction.js +151 -0
- package/dist/src/core/compaction.js.map +1 -0
- package/dist/src/core/contextPrune.js +8 -1
- package/dist/src/core/contextPrune.js.map +1 -1
- package/dist/src/core/runChat.d.ts +1 -1
- package/dist/src/core/runChat.js +1 -1
- package/dist/src/core/runChat.js.map +1 -1
- package/dist/src/core/session.d.ts +10 -1
- package/dist/src/core/session.js +11 -2
- package/dist/src/core/session.js.map +1 -1
- package/dist/src/core/turnLoop.d.ts +18 -2
- package/dist/src/core/turnLoop.js +79 -12
- package/dist/src/core/turnLoop.js.map +1 -1
- package/dist/src/core/updateCheck.d.ts +35 -0
- package/dist/src/core/updateCheck.js +71 -2
- package/dist/src/core/updateCheck.js.map +1 -1
- package/dist/src/core/updateRunner.d.ts +7 -0
- package/dist/src/core/updateRunner.js +26 -0
- package/dist/src/core/updateRunner.js.map +1 -0
- package/dist/src/exec/output.d.ts +3 -2
- package/dist/src/exec/output.js +17 -4
- package/dist/src/exec/output.js.map +1 -1
- package/dist/src/fs/index.d.ts +4 -1
- package/dist/src/fs/index.js +7 -2
- package/dist/src/fs/index.js.map +1 -1
- package/dist/src/fs/limits.d.ts +1 -1
- package/dist/src/fs/limits.js +3 -1
- package/dist/src/fs/limits.js.map +1 -1
- package/dist/src/fs/readFileTool.d.ts +2 -1
- package/dist/src/fs/readFileTool.js +4 -1
- package/dist/src/fs/readFileTool.js.map +1 -1
- package/dist/src/fs/workspaceGuard.d.ts +8 -0
- package/dist/src/fs/workspaceGuard.js +48 -0
- package/dist/src/fs/workspaceGuard.js.map +1 -1
- package/dist/src/index.js +44 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/server.js +17 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/tui/App.d.ts +10 -1
- package/dist/src/tui/App.js +114 -15
- package/dist/src/tui/App.js.map +1 -1
- package/dist/src/tui/UpdatePrompt.d.ts +17 -0
- package/dist/src/tui/UpdatePrompt.js +42 -0
- package/dist/src/tui/UpdatePrompt.js.map +1 -0
- package/dist/src/tui/approvalBridge.d.ts +21 -0
- package/dist/src/tui/approvalBridge.js +45 -0
- package/dist/src/tui/approvalBridge.js.map +1 -0
- package/dist/src/tui/chatController.d.ts +21 -0
- package/dist/src/tui/chatController.js +155 -16
- package/dist/src/tui/chatController.js.map +1 -1
- package/dist/src/tui/commands.js +16 -2
- package/dist/src/tui/commands.js.map +1 -1
- package/dist/src/tui/models.d.ts +4 -0
- package/dist/src/tui/models.js +8 -1
- package/dist/src/tui/models.js.map +1 -1
- package/dist/src/tui/run.d.ts +7 -0
- package/dist/src/tui/run.js +107 -22
- package/dist/src/tui/run.js.map +1 -1
- package/dist/src/web/webFetchTool.js +41 -5
- package/dist/src/web/webFetchTool.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -25,14 +25,15 @@ Run `glbgpt <command> --help` for the full flags of any command.
|
|
|
25
25
|
|
|
26
26
|
### Chat
|
|
27
27
|
|
|
28
|
-
| Command
|
|
29
|
-
|
|
30
|
-
| `glbgpt`
|
|
31
|
-
| `glbgpt "<prompt>"`
|
|
32
|
-
| `glbgpt resume last` · `glbgpt resume <id>` | Resume a previous conversation.
|
|
28
|
+
| Command | What it does |
|
|
29
|
+
| ------------------------------------------- | ----------------------------------- |
|
|
30
|
+
| `glbgpt` | Start the interactive chat TUI. |
|
|
31
|
+
| `glbgpt "<prompt>"` | Start the TUI with a first message. |
|
|
32
|
+
| `glbgpt resume last` · `glbgpt resume <id>` | Resume a previous conversation. |
|
|
33
33
|
|
|
34
34
|
Inside the TUI, type `/` for commands: `/model` `/models` `/image` `/video` `/tasks` `/status`
|
|
35
|
-
`/account` `/transactions` `/cost` `/login` `/logout` `/new` `/resume`
|
|
35
|
+
`/permissions` `/compact` `/account` `/transactions` `/cost` `/login` `/logout` `/new` `/resume`
|
|
36
|
+
`/help` `/exit`.
|
|
36
37
|
Attach images with `@image` (last generation), `Ctrl+V` or `@clipboard`, or `@./photo.png`.
|
|
37
38
|
Multi-line: end a line with `\` then Enter (or Ctrl+J); plain Enter sends; Esc interrupts.
|
|
38
39
|
On tool-capable models the assistant can also read files under the directory you started
|
|
@@ -40,22 +41,49 @@ On tool-capable models the assistant can also read files under the directory you
|
|
|
40
41
|
web when current information matters (a small per-search credit charge applies, at most a
|
|
41
42
|
few searches per message), and fetch public web pages by URL (free).
|
|
42
43
|
|
|
44
|
+
#### Coding tools
|
|
45
|
+
|
|
46
|
+
On tool-capable models the assistant can also **run shell commands, create files, and edit
|
|
47
|
+
files** in your workspace — every command and file change asks for your approval first
|
|
48
|
+
(`[y]` allow once, `[a]` allow similar actions for this session, `[p]` always allow for this
|
|
49
|
+
project, `[n]` deny). Diffs are shown before an edit runs. Project instructions in
|
|
50
|
+
`AGENTS.md` or `CLAUDE.md` (from the repository root down to the current directory) are
|
|
51
|
+
picked up automatically (`--no-project-context` to skip). Long-running commands (dev
|
|
52
|
+
servers, watchers) can run in the background — the assistant starts them detached, polls
|
|
53
|
+
their output, and they are stopped when the CLI exits.
|
|
54
|
+
|
|
55
|
+
Long conversations compact automatically: when the history approaches the model's context
|
|
56
|
+
limit the older part is summarized (one model call) and the conversation continues
|
|
57
|
+
seamlessly — `/compact` triggers it manually. The full history always stays on disk.
|
|
58
|
+
|
|
59
|
+
Permission modes — switch with `/permissions <mode>` or start with `--permission-mode <mode>`:
|
|
60
|
+
|
|
61
|
+
| Mode | Behavior |
|
|
62
|
+
| -------------- | ------------------------------------------------------------------------------ |
|
|
63
|
+
| `default` | Ask before every command and file write. |
|
|
64
|
+
| `accept-edits` | File writes run without asking; commands still ask. |
|
|
65
|
+
| `yolo` | Nothing asks (also `--yolo`). Dangerous — use only in disposable environments. |
|
|
66
|
+
|
|
67
|
+
Dangerous commands (`sudo`, forced/recursive `rm`, ...) always ask individually and can never
|
|
68
|
+
be remembered for the session; writes inside `.git/` always ask.
|
|
69
|
+
|
|
43
70
|
### Account
|
|
44
71
|
|
|
45
|
-
| Command
|
|
46
|
-
|
|
72
|
+
| Command | What it does |
|
|
73
|
+
| -------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
47
74
|
| `glbgpt login [--no-browser] [--code]` | Sign in. `--no-browser` prints the URL; `--code` pastes a one-time code (headless/SSH). |
|
|
48
|
-
| `glbgpt logout`
|
|
49
|
-
| `glbgpt whoami`
|
|
50
|
-
| `glbgpt account`
|
|
51
|
-
| `glbgpt account transactions`
|
|
75
|
+
| `glbgpt logout` | Sign out and remove stored credentials. |
|
|
76
|
+
| `glbgpt whoami` | Show the signed-in account. |
|
|
77
|
+
| `glbgpt account` | Membership and credit balance. |
|
|
78
|
+
| `glbgpt account transactions` | Recent spend (authoritative billing). |
|
|
79
|
+
| `glbgpt update` | Update the CLI to the latest version (runs `npm i -g` for you). |
|
|
52
80
|
|
|
53
81
|
### Images and video
|
|
54
82
|
|
|
55
|
-
| Command
|
|
56
|
-
|
|
83
|
+
| Command | What it does |
|
|
84
|
+
| ------------------------- | ----------------------------------------- |
|
|
57
85
|
| `glbgpt image "<prompt>"` | Generate an image (asks before spending). |
|
|
58
|
-
| `glbgpt video "<prompt>"` | Generate a video (asks before spending).
|
|
86
|
+
| `glbgpt video "<prompt>"` | Generate a video (asks before spending). |
|
|
59
87
|
|
|
60
88
|
Common flags: `-m/--model`, `--ar 16:9`, `--res 2K`, `--seed N`, `--negative-prompt "..."`,
|
|
61
89
|
`-i ./ref.png` (reference image), `--duration N` and `--audio/--no-audio` (video),
|
|
@@ -64,14 +92,14 @@ task id without waiting).
|
|
|
64
92
|
|
|
65
93
|
### Models and tasks
|
|
66
94
|
|
|
67
|
-
| Command
|
|
68
|
-
|
|
69
|
-
| `glbgpt model list [chat\|image\|video]`
|
|
70
|
-
| `glbgpt model show <model>`
|
|
71
|
-
| `glbgpt task list`
|
|
72
|
-
| `glbgpt task show last` · `glbgpt task show <id>` | A task's status.
|
|
73
|
-
| `glbgpt task wait last`
|
|
74
|
-
| `glbgpt task download last`
|
|
95
|
+
| Command | What it does |
|
|
96
|
+
| ------------------------------------------------- | ------------------------------------ |
|
|
97
|
+
| `glbgpt model list [chat\|image\|video]` | List available models. |
|
|
98
|
+
| `glbgpt model show <model>` | Parameters and limits for one model. |
|
|
99
|
+
| `glbgpt task list` | Recent generation tasks. |
|
|
100
|
+
| `glbgpt task show last` · `glbgpt task show <id>` | A task's status. |
|
|
101
|
+
| `glbgpt task wait last` | Block until a task finishes. |
|
|
102
|
+
| `glbgpt task download last` | Download a finished result. |
|
|
75
103
|
|
|
76
104
|
### exec — non-interactive
|
|
77
105
|
|
|
@@ -94,19 +122,27 @@ On tool-capable models the assistant can also read files under the current direc
|
|
|
94
122
|
search the web / fetch pages, and generate images/videos: exec waits for in-flight generations
|
|
95
123
|
before exiting and reports each as a `media_result` event (`--jsonl`) or a stderr line.
|
|
96
124
|
|
|
125
|
+
Coding tools are **off by default in exec** (no one is present to approve): the model sees the
|
|
126
|
+
tools but every command/write is refused with a message naming the enable flag. Opt in with
|
|
127
|
+
`--allow-write` (create/edit files), `--allow-bash` (shell commands), or `--yolo` (both):
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
glbgpt exec "fix the failing test in src/utils.test.ts and run the tests" --allow-write --allow-bash
|
|
131
|
+
```
|
|
132
|
+
|
|
97
133
|
### mcp — use from other agents
|
|
98
134
|
|
|
99
135
|
`glbgpt mcp` runs a [Model Context Protocol](https://modelcontextprotocol.io) server over stdio,
|
|
100
136
|
so agents like Claude Code, Codex, or Cursor can chat and generate media through your GlobalGPT
|
|
101
137
|
account. Sign in once with `glbgpt login` first.
|
|
102
138
|
|
|
103
|
-
| Tool
|
|
104
|
-
|
|
105
|
-
| `glbgpt_chat`
|
|
106
|
-
| `glbgpt_generate_image` | Generate an image; usually returns the finished result (URL + local file) in one call.
|
|
107
|
-
| `glbgpt_generate_video` | Generate a video; returns a `task_id` to check.
|
|
108
|
-
| `glbgpt_check_task`
|
|
109
|
-
| `glbgpt_list_models`
|
|
139
|
+
| Tool | What it does |
|
|
140
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
141
|
+
| `glbgpt_chat` | One chat turn: question in, answer out (system prompt, vision, JSON schema, and — on tool-capable models — live web search/fetch supported). |
|
|
142
|
+
| `glbgpt_generate_image` | Generate an image; usually returns the finished result (URL + local file) in one call. |
|
|
143
|
+
| `glbgpt_generate_video` | Generate a video; returns a `task_id` to check. |
|
|
144
|
+
| `glbgpt_check_task` | Check a generation task. Waits server-side — the agent can just call it again immediately until done. |
|
|
145
|
+
| `glbgpt_list_models` | List available chat/image/video models and what parameters each supports. |
|
|
110
146
|
|
|
111
147
|
Register it:
|
|
112
148
|
|
|
@@ -182,27 +218,33 @@ re-points the link and leaves nothing to lose.
|
|
|
182
218
|
|
|
183
219
|
**Which surface should you use?**
|
|
184
220
|
|
|
185
|
-
|
|
|
186
|
-
|
|
187
|
-
| **Skill**
|
|
188
|
-
| **MCP**
|
|
189
|
-
| **CLI / exec** | You are scripting it yourself, in CI, or piping output between tools.
|
|
221
|
+
| | Use it when |
|
|
222
|
+
| -------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
223
|
+
| **Skill** | Your agent runs shell commands (Claude Code, Cursor, Codex). Zero config — install once and ask for an image. |
|
|
224
|
+
| **MCP** | Your agent speaks MCP but cannot run shell commands, or you want the session credit budget enforced (`--max-credits`). |
|
|
225
|
+
| **CLI / exec** | You are scripting it yourself, in CI, or piping output between tools. |
|
|
190
226
|
|
|
191
227
|
### config
|
|
192
228
|
|
|
193
|
-
| Command
|
|
194
|
-
|
|
195
|
-
| `glbgpt config list`
|
|
196
|
-
| `glbgpt config get <key>` · `set <key> <value>` · `unset <key>` | Manage settings.
|
|
229
|
+
| Command | What it does |
|
|
230
|
+
| --------------------------------------------------------------- | ------------------------ |
|
|
231
|
+
| `glbgpt config list` | Show persisted settings. |
|
|
232
|
+
| `glbgpt config get <key>` · `set <key> <value>` · `unset <key>` | Manage settings. |
|
|
197
233
|
|
|
198
234
|
Keys: `lang`, `max-credits` (unattended media budget), `context-pruning` (set `false` to
|
|
199
235
|
keep old tool results in long chats), `update-check` (set `false` to disable the startup
|
|
200
|
-
|
|
236
|
+
update prompt and notices about newer versions). One-shot overrides via env vars (`GLBGPT_LANG`,
|
|
201
237
|
`GLBGPT_TOKEN`, `GLBGPT_MAX_CREDITS`, `GLBGPT_CONTEXT_PRUNING`, `GLBGPT_UPDATE_CHECK`)
|
|
202
238
|
always win.
|
|
203
239
|
|
|
204
240
|
## Notes
|
|
205
241
|
|
|
242
|
+
**Updates.** When a newer version exists, an interactive start shows a chooser (Enter
|
|
243
|
+
updates on the spot; "Skip until next version" mutes the chooser for that release), and
|
|
244
|
+
`exec`/MCP print a one-line notice on stderr so agent hosts can relay it. The check reads
|
|
245
|
+
a local cache refreshed in the background — it never blocks startup or touches the network
|
|
246
|
+
on the hot path. `glbgpt update` upgrades any time; `update-check false` disables it all.
|
|
247
|
+
|
|
206
248
|
**Usage telemetry.** The CLI reports anonymous usage events (session starts, turn latency,
|
|
207
249
|
tool-call counts, error categories) to the GlobalGPT backend to help us improve the product.
|
|
208
250
|
It never collects prompt text, file contents, or file paths. Set `GLBGPT_DISABLE_TELEMETRY=1`
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare class BackgroundBashTasks {
|
|
2
|
+
private readonly spillDir;
|
|
3
|
+
private readonly tasks;
|
|
4
|
+
private seq;
|
|
5
|
+
constructor(spillDir?: string);
|
|
6
|
+
/** Ids of tasks still running (for surfaces that want to report before exit). */
|
|
7
|
+
running(): Array<{
|
|
8
|
+
id: string;
|
|
9
|
+
command: string;
|
|
10
|
+
}>;
|
|
11
|
+
start(command: string, cwd: string): {
|
|
12
|
+
taskId: string;
|
|
13
|
+
outputPath: string;
|
|
14
|
+
} | {
|
|
15
|
+
error: string;
|
|
16
|
+
};
|
|
17
|
+
/** New output since the last read (tail-capped), plus liveness/exit status. */
|
|
18
|
+
readNew(taskId: string): Promise<{
|
|
19
|
+
output: string;
|
|
20
|
+
truncatedBytes: number;
|
|
21
|
+
status: 'running' | 'exited' | 'killed';
|
|
22
|
+
exitCode: number | null;
|
|
23
|
+
} | {
|
|
24
|
+
error: string;
|
|
25
|
+
}>;
|
|
26
|
+
kill(taskId: string): {
|
|
27
|
+
ok: true;
|
|
28
|
+
status: string;
|
|
29
|
+
} | {
|
|
30
|
+
error: string;
|
|
31
|
+
};
|
|
32
|
+
/** Kill every still-running task (CLI exit path). Synchronous — safe in an exit handler. */
|
|
33
|
+
killAll(): void;
|
|
34
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createWriteStream, promises as fs, existsSync } from 'node:fs';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { cutToBytesFromEnd } from './truncate.js';
|
|
7
|
+
/**
|
|
8
|
+
* Registry for background bash tasks (`bash` with run_in_background, P1 2026-08-21).
|
|
9
|
+
*
|
|
10
|
+
* Every task's stdout+stderr streams to a spill file from byte 0 (background tasks
|
|
11
|
+
* are long-lived by definition — memory holds nothing). `readNew` returns the bytes
|
|
12
|
+
* appended since the caller's last read (tail-capped per read), so the model polls
|
|
13
|
+
* incrementally like `tail -f`. Tasks die with the CLI (process-group kill on exit).
|
|
14
|
+
*/
|
|
15
|
+
const MAX_CONCURRENT_TASKS = 5;
|
|
16
|
+
const MAX_READ_BYTES = 50 * 1024;
|
|
17
|
+
export class BackgroundBashTasks {
|
|
18
|
+
spillDir;
|
|
19
|
+
tasks = new Map();
|
|
20
|
+
seq = 0;
|
|
21
|
+
constructor(spillDir = os.tmpdir()) {
|
|
22
|
+
this.spillDir = spillDir;
|
|
23
|
+
// Belt and suspenders: kill everything if the process exits without cleanup.
|
|
24
|
+
process.once('exit', () => this.killAll());
|
|
25
|
+
}
|
|
26
|
+
/** Ids of tasks still running (for surfaces that want to report before exit). */
|
|
27
|
+
running() {
|
|
28
|
+
return [...this.tasks.values()].filter((t) => !t.exited).map((t) => ({ id: t.id, command: t.command }));
|
|
29
|
+
}
|
|
30
|
+
start(command, cwd) {
|
|
31
|
+
const live = [...this.tasks.values()].filter((t) => !t.exited).length;
|
|
32
|
+
if (live >= MAX_CONCURRENT_TASKS) {
|
|
33
|
+
return {
|
|
34
|
+
error: `Too many background tasks (${live} running, max ${MAX_CONCURRENT_TASKS}). Kill one with bash_kill first.`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const shell = process.platform === 'win32' ? (process.env.ComSpec ?? 'cmd.exe') : pickShell();
|
|
38
|
+
const id = `bg-${++this.seq}`;
|
|
39
|
+
const outputPath = path.join(this.spillDir, `glbgpt-bgtask-${randomBytes(6).toString('hex')}.log`);
|
|
40
|
+
const out = createWriteStream(outputPath, { flags: 'w' });
|
|
41
|
+
const child = spawn(shell, ['-c', command], {
|
|
42
|
+
cwd,
|
|
43
|
+
detached: process.platform !== 'win32',
|
|
44
|
+
env: process.env,
|
|
45
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
46
|
+
windowsHide: true,
|
|
47
|
+
});
|
|
48
|
+
child.stdout?.pipe(out, { end: false });
|
|
49
|
+
child.stderr?.pipe(out, { end: false });
|
|
50
|
+
const task = {
|
|
51
|
+
id,
|
|
52
|
+
command,
|
|
53
|
+
pid: child.pid,
|
|
54
|
+
outputPath,
|
|
55
|
+
cursor: 0,
|
|
56
|
+
startedAt: Date.now(),
|
|
57
|
+
exitCode: null,
|
|
58
|
+
exited: false,
|
|
59
|
+
killed: false,
|
|
60
|
+
};
|
|
61
|
+
child.on('error', () => {
|
|
62
|
+
task.exited = true;
|
|
63
|
+
out.end();
|
|
64
|
+
});
|
|
65
|
+
child.on('close', (code) => {
|
|
66
|
+
task.exited = true;
|
|
67
|
+
task.exitCode = code;
|
|
68
|
+
out.end();
|
|
69
|
+
});
|
|
70
|
+
this.tasks.set(id, task);
|
|
71
|
+
return { taskId: id, outputPath };
|
|
72
|
+
}
|
|
73
|
+
/** New output since the last read (tail-capped), plus liveness/exit status. */
|
|
74
|
+
async readNew(taskId) {
|
|
75
|
+
const task = this.tasks.get(taskId);
|
|
76
|
+
if (!task)
|
|
77
|
+
return { error: `Unknown background task: ${taskId}` };
|
|
78
|
+
let data = '';
|
|
79
|
+
let truncatedBytes = 0;
|
|
80
|
+
try {
|
|
81
|
+
const stat = await fs.stat(task.outputPath);
|
|
82
|
+
if (stat.size > task.cursor) {
|
|
83
|
+
const newBytes = stat.size - task.cursor;
|
|
84
|
+
const fh = await fs.open(task.outputPath, 'r');
|
|
85
|
+
try {
|
|
86
|
+
const readFrom = newBytes > MAX_READ_BYTES ? stat.size - MAX_READ_BYTES : task.cursor;
|
|
87
|
+
truncatedBytes = readFrom - task.cursor;
|
|
88
|
+
const buf = Buffer.alloc(stat.size - readFrom);
|
|
89
|
+
await fh.read(buf, 0, buf.length, readFrom);
|
|
90
|
+
data =
|
|
91
|
+
truncatedBytes > 0
|
|
92
|
+
? cutToBytesFromEnd(buf.toString('utf8'), MAX_READ_BYTES)
|
|
93
|
+
: buf.toString('utf8');
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await fh.close();
|
|
97
|
+
}
|
|
98
|
+
task.cursor = stat.size;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Spill unreadable (already cleaned?) — report status only.
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
output: data,
|
|
106
|
+
truncatedBytes,
|
|
107
|
+
status: task.killed ? 'killed' : task.exited ? 'exited' : 'running',
|
|
108
|
+
exitCode: task.exitCode,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
kill(taskId) {
|
|
112
|
+
const task = this.tasks.get(taskId);
|
|
113
|
+
if (!task)
|
|
114
|
+
return { error: `Unknown background task: ${taskId}` };
|
|
115
|
+
if (task.exited)
|
|
116
|
+
return { ok: true, status: `already exited with code ${task.exitCode}` };
|
|
117
|
+
task.killed = true;
|
|
118
|
+
killTree(task.pid);
|
|
119
|
+
return { ok: true, status: 'killed' };
|
|
120
|
+
}
|
|
121
|
+
/** Kill every still-running task (CLI exit path). Synchronous — safe in an exit handler. */
|
|
122
|
+
killAll() {
|
|
123
|
+
for (const task of this.tasks.values()) {
|
|
124
|
+
if (!task.exited) {
|
|
125
|
+
task.killed = true;
|
|
126
|
+
killTree(task.pid);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function pickShell() {
|
|
132
|
+
return existsSync('/bin/bash') ? '/bin/bash' : 'sh';
|
|
133
|
+
}
|
|
134
|
+
function killTree(pid) {
|
|
135
|
+
if (!pid)
|
|
136
|
+
return;
|
|
137
|
+
try {
|
|
138
|
+
if (process.platform === 'win32') {
|
|
139
|
+
spawn('taskkill', ['/F', '/T', '/PID', String(pid)], { windowsHide: true });
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
process.kill(-pid, 'SIGKILL');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
try {
|
|
147
|
+
process.kill(pid, 'SIGKILL');
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Already gone.
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=backgroundTasks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backgroundTasks.js","sourceRoot":"","sources":["../../../src/coding/backgroundTasks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ClientTool } from '../core/types.js';
|
|
2
|
+
import type { BackgroundBashTasks } from './backgroundTasks.js';
|
|
3
|
+
/**
|
|
4
|
+
* Companion tools for background bash tasks: poll new output, stop a task.
|
|
5
|
+
* Neither is gated by the permission gate — the task itself was approved when
|
|
6
|
+
* it was started, and observing/stopping your own task needs no new authority.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createBashOutputTool(tasks: BackgroundBashTasks): ClientTool;
|
|
9
|
+
export declare function createBashKillTool(tasks: BackgroundBashTasks): ClientTool;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Companion tools for background bash tasks: poll new output, stop a task.
|
|
3
|
+
* Neither is gated by the permission gate — the task itself was approved when
|
|
4
|
+
* it was started, and observing/stopping your own task needs no new authority.
|
|
5
|
+
*/
|
|
6
|
+
export function createBashOutputTool(tasks) {
|
|
7
|
+
return {
|
|
8
|
+
name: 'bash_output',
|
|
9
|
+
readOnly: true,
|
|
10
|
+
description: 'Read NEW output from a background bash task since the last bash_output call (like tail -f), ' +
|
|
11
|
+
'plus its status (running/exited/killed and exit code). Poll this after starting a command ' +
|
|
12
|
+
'with run_in_background.',
|
|
13
|
+
parameters: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
task_id: { type: 'string', description: 'Background task id from the bash tool result.' },
|
|
17
|
+
},
|
|
18
|
+
required: ['task_id'],
|
|
19
|
+
},
|
|
20
|
+
async execute(args) {
|
|
21
|
+
if (typeof args.task_id !== 'string' || !args.task_id.trim()) {
|
|
22
|
+
return { error: 'Missing required "task_id" argument.' };
|
|
23
|
+
}
|
|
24
|
+
const r = await tasks.readNew(args.task_id.trim());
|
|
25
|
+
if ('error' in r)
|
|
26
|
+
return { error: r.error };
|
|
27
|
+
const parts = [];
|
|
28
|
+
if (r.truncatedBytes > 0)
|
|
29
|
+
parts.push(`[${r.truncatedBytes} bytes of new output skipped — showing the tail]`);
|
|
30
|
+
parts.push(r.output.length > 0 ? r.output : '(no new output)');
|
|
31
|
+
parts.push(r.status === 'running'
|
|
32
|
+
? 'Status: running'
|
|
33
|
+
: r.status === 'killed'
|
|
34
|
+
? 'Status: killed'
|
|
35
|
+
: `Status: exited with code ${r.exitCode}`);
|
|
36
|
+
return { output: parts.join('\n') };
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function createBashKillTool(tasks) {
|
|
41
|
+
return {
|
|
42
|
+
name: 'bash_kill',
|
|
43
|
+
readOnly: false,
|
|
44
|
+
description: 'Stop a background bash task (kills its whole process group).',
|
|
45
|
+
parameters: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
task_id: { type: 'string', description: 'Background task id from the bash tool result.' },
|
|
49
|
+
},
|
|
50
|
+
required: ['task_id'],
|
|
51
|
+
},
|
|
52
|
+
async execute(args) {
|
|
53
|
+
if (typeof args.task_id !== 'string' || !args.task_id.trim()) {
|
|
54
|
+
return { error: 'Missing required "task_id" argument.' };
|
|
55
|
+
}
|
|
56
|
+
const r = tasks.kill(args.task_id.trim());
|
|
57
|
+
if ('error' in r)
|
|
58
|
+
return { error: r.error };
|
|
59
|
+
return { output: `Task ${args.task_id}: ${r.status}.` };
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=backgroundTools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backgroundTools.js","sourceRoot":"","sources":["../../../src/coding/backgroundTools.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH,MAAM,UAAU,oBAAoB,CAAC,KAA0B;IAC7D,OAAO;QACL,IAAI,EAAE,aAAa;QACnB,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,8FAA8F;YAC9F,4FAA4F;YAC5F,yBAAyB;QAC3B,UAAU,EAAE;YACV,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+CAA+C,EAAE;aAC1F;YACD,QAAQ,EAAE,CAAC,SAAS,CAAC;SACtB;QACD,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC7D,OAAO,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC;YAC3D,CAAC;YACD,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YACnD,IAAI,OAAO,IAAI,CAAC;gBAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;YAC5C,MAAM,KAAK,GAAa,EAAE,CAAC;YAC3B,IAAI,CAAC,CAAC,cAAc,GAAG,CAAC;gBACtB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,cAAc,kDAAkD,CAAC,CAAC;YACrF,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;YAC/D,KAAK,CAAC,IAAI,CACR,CAAC,CAAC,MAAM,KAAK,SAAS;gBACpB,CAAC,CAAC,iBAAiB;gBACnB,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ;oBACrB,CAAC,CAAC,gBAAgB;oBAClB,CAAC,CAAC,4BAA4B,CAAC,CAAC,QAAQ,EAAE,CAC/C,CAAC;YACF,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAA0B;IAC3D,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,8DAA8D;QAC3E,UAAU,EAAE;YACV,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+CAA+C,EAAE;aAC1F;YACD,QAAQ,EAAE,CAAC,SAAS,CAAC;SACtB;QACD,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC7D,OAAO,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC;YAC3D,CAAC;YACD,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1C,IAAI,OAAO,IAAI,CAAC;gBAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;YAC5C,OAAO,EAAE,MAAM,EAAE,QAAQ,IAAI,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC;QAC1D,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ClientTool } from '../core/types.js';
|
|
2
|
+
import type { PermissionGate } from './permissions.js';
|
|
3
|
+
import type { BackgroundBashTasks } from './backgroundTasks.js';
|
|
4
|
+
export declare const BASH_DEFAULT_TIMEOUT_S = 120;
|
|
5
|
+
export declare const BASH_MAX_TIMEOUT_S = 600;
|
|
6
|
+
/**
|
|
7
|
+
* The `bash` tool: run a shell command in the workspace directory.
|
|
8
|
+
*
|
|
9
|
+
* Execution contract (2026-08-21 design, pi lineage):
|
|
10
|
+
* - New process per call; no shell state persists. Commands run in the session cwd —
|
|
11
|
+
* there is NO cwd parameter and `cd` does not carry over (prompt guidance says to use
|
|
12
|
+
* absolute paths or `cd` within a single command).
|
|
13
|
+
* - stdout+stderr merged in arrival order; tail-truncated at 2000 lines / 50KB with the
|
|
14
|
+
* full output spilled to a temp file (footnote names the path).
|
|
15
|
+
* - Non-zero exit is an error result but the collected output is still attached — the
|
|
16
|
+
* model needs the output to react.
|
|
17
|
+
* - Timeout (default 120s, cap 600s) kills the whole process GROUP (detached spawn), so
|
|
18
|
+
* children die with the shell.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createBashTool(opts: {
|
|
21
|
+
cwd: string;
|
|
22
|
+
gate: PermissionGate;
|
|
23
|
+
/** Background task registry; absent = run_in_background rejected. */
|
|
24
|
+
background?: BackgroundBashTasks;
|
|
25
|
+
/** Test hook: where spill files go. */
|
|
26
|
+
spillDir?: string;
|
|
27
|
+
}): ClientTool;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { OutputAccumulator } from './outputAccumulator.js';
|
|
4
|
+
export const BASH_DEFAULT_TIMEOUT_S = 120;
|
|
5
|
+
export const BASH_MAX_TIMEOUT_S = 600;
|
|
6
|
+
/**
|
|
7
|
+
* The `bash` tool: run a shell command in the workspace directory.
|
|
8
|
+
*
|
|
9
|
+
* Execution contract (2026-08-21 design, pi lineage):
|
|
10
|
+
* - New process per call; no shell state persists. Commands run in the session cwd —
|
|
11
|
+
* there is NO cwd parameter and `cd` does not carry over (prompt guidance says to use
|
|
12
|
+
* absolute paths or `cd` within a single command).
|
|
13
|
+
* - stdout+stderr merged in arrival order; tail-truncated at 2000 lines / 50KB with the
|
|
14
|
+
* full output spilled to a temp file (footnote names the path).
|
|
15
|
+
* - Non-zero exit is an error result but the collected output is still attached — the
|
|
16
|
+
* model needs the output to react.
|
|
17
|
+
* - Timeout (default 120s, cap 600s) kills the whole process GROUP (detached spawn), so
|
|
18
|
+
* children die with the shell.
|
|
19
|
+
*/
|
|
20
|
+
export function createBashTool(opts) {
|
|
21
|
+
return {
|
|
22
|
+
name: 'bash',
|
|
23
|
+
description: 'Execute a bash command in the workspace directory. Returns stdout and stderr combined. ' +
|
|
24
|
+
'Output is truncated to the last 2000 lines or 50KB (whichever is hit first); when truncated, ' +
|
|
25
|
+
'the full output is saved to a temp file whose path is included in the result. ' +
|
|
26
|
+
`Optional timeout in seconds (default ${BASH_DEFAULT_TIMEOUT_S}, max ${BASH_MAX_TIMEOUT_S}). ` +
|
|
27
|
+
'Shell state does not persist between calls; the working directory is always the workspace root. ' +
|
|
28
|
+
'For long-running commands (dev servers, watchers) set run_in_background: true — the result gives ' +
|
|
29
|
+
'a task id to poll with bash_output and stop with bash_kill.',
|
|
30
|
+
parameters: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
command: { type: 'string', description: 'Bash command to execute' },
|
|
34
|
+
timeout_seconds: {
|
|
35
|
+
type: 'number',
|
|
36
|
+
description: `Timeout in seconds (default ${BASH_DEFAULT_TIMEOUT_S}, max ${BASH_MAX_TIMEOUT_S})`,
|
|
37
|
+
},
|
|
38
|
+
run_in_background: {
|
|
39
|
+
type: 'boolean',
|
|
40
|
+
description: 'Run detached and return a task id immediately (for dev servers / long jobs). ' +
|
|
41
|
+
'Poll with bash_output; stop with bash_kill. No timeout applies.',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
required: ['command'],
|
|
45
|
+
},
|
|
46
|
+
readOnly: false,
|
|
47
|
+
async execute(args) {
|
|
48
|
+
const command = typeof args.command === 'string' ? args.command.trim() : '';
|
|
49
|
+
if (!command)
|
|
50
|
+
return { error: 'command is required' };
|
|
51
|
+
const rawTimeout = args.timeout_seconds;
|
|
52
|
+
let timeoutS = BASH_DEFAULT_TIMEOUT_S;
|
|
53
|
+
if (rawTimeout !== undefined && rawTimeout !== null) {
|
|
54
|
+
const n = Number(rawTimeout);
|
|
55
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
56
|
+
return { error: 'Invalid timeout_seconds: must be a positive number of seconds' };
|
|
57
|
+
}
|
|
58
|
+
timeoutS = Math.min(n, BASH_MAX_TIMEOUT_S);
|
|
59
|
+
}
|
|
60
|
+
const verdict = await opts.gate.check({ kind: 'bash', command });
|
|
61
|
+
if (!verdict.ok)
|
|
62
|
+
return { error: verdict.message };
|
|
63
|
+
if (args.run_in_background === true) {
|
|
64
|
+
if (!opts.background)
|
|
65
|
+
return { error: 'Background tasks are not available here.' };
|
|
66
|
+
const started = opts.background.start(command, opts.cwd);
|
|
67
|
+
if ('error' in started)
|
|
68
|
+
return { error: started.error };
|
|
69
|
+
return {
|
|
70
|
+
output: `Started background task ${started.taskId}. ` +
|
|
71
|
+
'Poll new output with bash_output; stop it with bash_kill. It dies when the CLI exits.',
|
|
72
|
+
task_id: started.taskId,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return runBash(command, opts.cwd, timeoutS, opts.spillDir);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function pickShell() {
|
|
80
|
+
if (process.platform === 'win32')
|
|
81
|
+
return process.env.ComSpec ?? 'cmd.exe';
|
|
82
|
+
if (existsSync('/bin/bash'))
|
|
83
|
+
return '/bin/bash';
|
|
84
|
+
return 'sh';
|
|
85
|
+
}
|
|
86
|
+
async function runBash(command, cwd, timeoutS, spillDir) {
|
|
87
|
+
if (!existsSync(cwd)) {
|
|
88
|
+
return { error: `Working directory does not exist: ${cwd}` };
|
|
89
|
+
}
|
|
90
|
+
const shell = pickShell();
|
|
91
|
+
const detached = process.platform !== 'win32';
|
|
92
|
+
const child = spawn(shell, ['-c', command], {
|
|
93
|
+
cwd,
|
|
94
|
+
detached,
|
|
95
|
+
env: process.env,
|
|
96
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
97
|
+
windowsHide: true,
|
|
98
|
+
});
|
|
99
|
+
const acc = new OutputAccumulator(undefined, undefined, spillDir);
|
|
100
|
+
child.stdout?.on('data', (b) => acc.append(b));
|
|
101
|
+
child.stderr?.on('data', (b) => acc.append(b));
|
|
102
|
+
let timedOut = false;
|
|
103
|
+
const timer = setTimeout(() => {
|
|
104
|
+
timedOut = true;
|
|
105
|
+
killTree(child.pid);
|
|
106
|
+
}, timeoutS * 1000);
|
|
107
|
+
const exitCode = await new Promise((resolve) => {
|
|
108
|
+
child.on('error', () => resolve(null));
|
|
109
|
+
// 'close' (not 'exit') so stdio has fully flushed before we snapshot the output.
|
|
110
|
+
child.on('close', (code) => resolve(code));
|
|
111
|
+
});
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
const { text } = await acc.finalize();
|
|
114
|
+
const output = text.length > 0 ? text : '(no output)';
|
|
115
|
+
if (timedOut) {
|
|
116
|
+
return { error: `${output}\n\nCommand timed out after ${timeoutS} seconds` };
|
|
117
|
+
}
|
|
118
|
+
if (exitCode !== 0 && exitCode !== null) {
|
|
119
|
+
return { error: `${output}\n\nCommand exited with code ${exitCode}` };
|
|
120
|
+
}
|
|
121
|
+
return { output };
|
|
122
|
+
}
|
|
123
|
+
/** Kill the whole process group (detached spawn) so children die with the shell. */
|
|
124
|
+
function killTree(pid) {
|
|
125
|
+
if (!pid)
|
|
126
|
+
return;
|
|
127
|
+
try {
|
|
128
|
+
if (process.platform === 'win32') {
|
|
129
|
+
spawn('taskkill', ['/F', '/T', '/PID', String(pid)], { windowsHide: true });
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
process.kill(-pid, 'SIGKILL');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
try {
|
|
137
|
+
process.kill(pid, 'SIGKILL');
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Already gone.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=bashTool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bashTool.js","sourceRoot":"","sources":["../../../src/coding/bashTool.ts"],"names":[],"mappings":"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"}
|