@procedures/shell 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/LICENSE +21 -0
  3. package/README.md +421 -2
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/command/index.d.ts +18 -0
  6. package/dist/command/index.d.ts.map +1 -0
  7. package/dist/command/index.js +17 -0
  8. package/dist/command/index.js.map +1 -0
  9. package/dist/command/output.d.ts +22 -0
  10. package/dist/command/output.d.ts.map +1 -0
  11. package/dist/command/output.js +67 -0
  12. package/dist/command/output.js.map +1 -0
  13. package/dist/command/processes.d.ts +89 -0
  14. package/dist/command/processes.d.ts.map +1 -0
  15. package/dist/command/processes.js +223 -0
  16. package/dist/command/processes.js.map +1 -0
  17. package/dist/command/run.d.ts +48 -0
  18. package/dist/command/run.d.ts.map +1 -0
  19. package/dist/command/run.js +89 -0
  20. package/dist/command/run.js.map +1 -0
  21. package/dist/command/spawn.d.ts +26 -0
  22. package/dist/command/spawn.d.ts.map +1 -0
  23. package/dist/command/spawn.js +28 -0
  24. package/dist/command/spawn.js.map +1 -0
  25. package/dist/command/stream.d.ts +42 -0
  26. package/dist/command/stream.d.ts.map +1 -0
  27. package/dist/command/stream.js +158 -0
  28. package/dist/command/stream.js.map +1 -0
  29. package/dist/command/tree-kill.d.ts +26 -0
  30. package/dist/command/tree-kill.d.ts.map +1 -0
  31. package/dist/command/tree-kill.js +122 -0
  32. package/dist/command/tree-kill.js.map +1 -0
  33. package/dist/index.d.ts +31 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +33 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/module.d.ts +19 -0
  38. package/dist/module.d.ts.map +1 -0
  39. package/dist/module.js +98 -0
  40. package/dist/module.js.map +1 -0
  41. package/dist/procedures/shell/exec.d.ts +21 -0
  42. package/dist/procedures/shell/exec.d.ts.map +1 -0
  43. package/dist/procedures/shell/exec.js +45 -0
  44. package/dist/procedures/shell/exec.js.map +1 -0
  45. package/dist/procedures/shell/index.d.ts +5 -0
  46. package/dist/procedures/shell/index.d.ts.map +1 -0
  47. package/dist/procedures/shell/index.js +5 -0
  48. package/dist/procedures/shell/index.js.map +1 -0
  49. package/dist/procedures/shell/run.d.ts +14 -0
  50. package/dist/procedures/shell/run.d.ts.map +1 -0
  51. package/dist/procedures/shell/run.js +40 -0
  52. package/dist/procedures/shell/run.js.map +1 -0
  53. package/dist/procedures/shell/stream.d.ts +20 -0
  54. package/dist/procedures/shell/stream.d.ts.map +1 -0
  55. package/dist/procedures/shell/stream.js +39 -0
  56. package/dist/procedures/shell/stream.js.map +1 -0
  57. package/dist/procedures/shell/which.d.ts +10 -0
  58. package/dist/procedures/shell/which.d.ts.map +1 -0
  59. package/dist/procedures/shell/which.js +21 -0
  60. package/dist/procedures/shell/which.js.map +1 -0
  61. package/dist/register.d.ts +7 -0
  62. package/dist/register.d.ts.map +1 -0
  63. package/dist/register.js +9 -0
  64. package/dist/register.js.map +1 -0
  65. package/dist/types.d.ts +79 -0
  66. package/dist/types.d.ts.map +1 -0
  67. package/dist/types.js +89 -0
  68. package/dist/types.js.map +1 -0
  69. package/package.json +81 -3
  70. package/src/command/index.ts +24 -0
  71. package/src/command/output.ts +67 -0
  72. package/src/command/processes.ts +271 -0
  73. package/src/command/run.ts +127 -0
  74. package/src/command/spawn.ts +44 -0
  75. package/src/command/stream.ts +186 -0
  76. package/src/command/tree-kill.ts +121 -0
  77. package/src/index.ts +53 -0
  78. package/src/module.ts +123 -0
  79. package/src/procedures/shell/exec.ts +55 -0
  80. package/src/procedures/shell/index.ts +4 -0
  81. package/src/procedures/shell/run.ts +47 -0
  82. package/src/procedures/shell/stream.ts +43 -0
  83. package/src/procedures/shell/which.ts +26 -0
  84. package/src/register.ts +11 -0
  85. package/src/types.ts +172 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # @procedures/shell
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 76d0d64: **Breaking:** an import of a package registers nothing. The `./register` entry of a package registers its procedures, and the procedures of the modules that they need, in the global registry (ARCHITECTURE-PROPOSALS-2026-10, P9).
8
+
9
+ - `import "@procedures/git"` gives the exports and registers nothing. Use `import "@procedures/git/register"`, or give `gitModule` (`@procedures/git/module`) to `createRegistry()`.
10
+ - `import "@procedures/client/register"` registers the control flow (`client.chain` and the others) and `procedure.define`/`get`/`list`/`delete`. `createRegistry()` adds them to each new registry, as before. The new `registerClientProcedures(registry?)` does the same for any registry.
11
+ - Each `register<Name>Procedures(registry?)` function is in the module of its package, takes a registry (the global one by default) and registers the modules that the package requires. `registerBridge` (splay) and `register` (store) are now `registerSplayProcedures` and `registerStoreProcedures`. The old names stay for one version.
12
+ - `sideEffects` names only `./dist/register.js`, so a bundler can drop the rest of a package.
13
+ - The bundles require `clientModule`, so `@procedures/bundle-mcp/register` gives the whole tool surface.
14
+ - `lib new` makes `src/module.ts` and a root without side effects, and `procedure new` adds procedures to the module.
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [0cf4bea]
19
+ - Updated dependencies [44d3e30]
20
+ - Updated dependencies [76d0d64]
21
+ - @procedures/client@0.2.0
22
+
23
+ ## 0.1.0
24
+
25
+ The first release on npm. The packages of the client monorepo have one version together.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Mark Russell
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 CHANGED
@@ -1,3 +1,422 @@
1
- # Temporary Holding Version
1
+ # @procedures/shell
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/@procedures/shell?color=2346c4&label=npm)](https://www.npmjs.com/package/@procedures/shell)
4
+
5
+ ```sh
6
+ npm install @procedures/shell
7
+ ```
8
+
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/)
11
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D22-green.svg)](https://nodejs.org/)
12
+
13
+ > Generic shell command execution procedures - the foundation layer for all CLI wrapper packages in the Mark ecosystem.
14
+
15
+ ## Table of Contents
16
+
17
+ - [Overview](#overview)
18
+ - [Installation](#installation)
19
+ - [Architecture](#architecture)
20
+ - [Quick Start](#quick-start)
21
+ - [API Reference](#api-reference)
22
+ - [shell.run](#shellrun)
23
+ - [shell.exec](#shellexec)
24
+ - [shell.which](#shellwhich)
25
+ - [Use Cases](#use-cases)
26
+ - [Cross-Platform Notes](#cross-platform-notes)
27
+ - [Integration](#integration)
28
+ - [Requirements](#requirements)
29
+ - [License](#license)
30
+
31
+ ---
32
+
33
+ ## Overview
34
+
35
+ **client-shell** provides low-level shell command execution as RPC procedures:
36
+
37
+ - **shell.run** - Spawn processes with arguments (no shell interpretation, safer)
38
+ - **shell.exec** - Execute command strings via shell (supports pipes, redirects)
39
+ - **shell.which** - Find command paths cross-platform
40
+ - **shell.stream** - Run a command with arguments, and give each output line while it runs
41
+
42
+ This package is the **foundation** for all CLI wrapper packages.
43
+
44
+ ### Command helpers
45
+
46
+ The wrapper packages start their programs with the helpers of `@procedures/shell/command`. This entry point registers no procedures.
47
+
48
+ | Helper | What it does |
49
+ |---|---|
50
+ | `runCommand(command, options)` | It runs a program to its end and gives `exitCode`, `stdout`, `stderr` and `duration`. It does not throw. |
51
+ | `streamCommand(command, options)` | It gives each output line while the program runs, then an `exit` item. |
52
+ | `processes` | It records a long-running program: `start`, `list`, `waitFor`, `output`, `stop`. |
53
+ | `killTree(child)` | It kills a child process and all the processes that it started. |
54
+
55
+ The rules of the helpers:
56
+
57
+ - A helper passes the arguments as a list. No shell reads them.
58
+ - The `timeout` and the `signal` kill the whole process tree. Windows uses `taskkill /T /F`. The other systems use a process group.
59
+ - Each output stream has a limit (`maxOutputBytes`, 64 MiB by default). The helper keeps the first bytes, or the last bytes with `keep: "tail"`.
60
+ - A program without an `input` gets an empty stdin.
61
+ - When the reader of `streamCommand` is slow, the helper stops reading the pipes, so the program waits.
62
+ - When the host ends, the helpers kill each child that still runs.
63
+
64
+ ```typescript
65
+ import { runCommand } from "@procedures/shell/command";
66
+
67
+ const result = await runCommand("git", { args: ["status", "--porcelain"], cwd, signal: ctx.signal });
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Installation
73
+
74
+ ```bash
75
+ npm install github:mark1russell7/client-shell#main
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Architecture
81
+
82
+ ### System Overview
83
+
84
+ ```mermaid
85
+ graph TB
86
+ subgraph "Application Layer"
87
+ App[Your Application]
88
+ end
89
+
90
+ subgraph "client-shell"
91
+ Run[shell.run<br/>Spawn with args]
92
+ Exec[shell.exec<br/>Shell string]
93
+ Which[shell.which<br/>Find command]
94
+ end
95
+
96
+ subgraph "Node.js"
97
+ Spawn[child_process.spawn]
98
+ ExecFn[child_process.exec]
99
+ WhichFn[which / where]
100
+ end
101
+
102
+ subgraph "Output"
103
+ Result[exitCode, stdout, stderr,<br/>success, duration]
104
+ end
105
+
106
+ App --> Run
107
+ App --> Exec
108
+ App --> Which
109
+ Run --> Spawn
110
+ Exec --> ExecFn
111
+ Which --> WhichFn
112
+ Spawn --> Result
113
+ ExecFn --> Result
114
+ ```
115
+
116
+ ### Command Execution Flow
117
+
118
+ ```mermaid
119
+ sequenceDiagram
120
+ participant App as Application
121
+ participant Shell as client-shell
122
+ participant Node as Node.js
123
+ participant OS as Operating System
124
+
125
+ App->>Shell: shell.run({ command: "git", args: ["status"] })
126
+ Shell->>Shell: Validate input
127
+ Shell->>Node: spawn("git", ["status"], options)
128
+ Node->>OS: Execute process
129
+ OS-->>Node: stdout, stderr, exitCode
130
+ Node-->>Shell: Process result
131
+ Shell->>Shell: Build output
132
+ Shell-->>App: { success, stdout, stderr, exitCode, duration }
133
+ ```
134
+
135
+ ### Dependency Tree
136
+
137
+ ```mermaid
138
+ graph TB
139
+ subgraph "CLI Wrapper Packages"
140
+ CLI[client-cli]
141
+ Pnpm[client-pnpm]
142
+ Git[client-git]
143
+ Docker[client-docker]
144
+ Vitest[client-vitest]
145
+ Cue[client-cue]
146
+ end
147
+
148
+ subgraph "Foundation"
149
+ Shell[client-shell]
150
+ end
151
+
152
+ subgraph "Core"
153
+ Client[client]
154
+ end
155
+
156
+ CLI --> Shell
157
+ Pnpm --> Shell
158
+ Git --> Shell
159
+ Docker --> Shell
160
+ Vitest --> Shell
161
+ Cue --> Shell
162
+ Shell --> Client
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Quick Start
168
+
169
+ ```typescript
170
+ import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
171
+ import "@procedures/shell/register"; // registers its procedures
172
+
173
+ const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
174
+
175
+ // Run a command with arguments
176
+ const result = await client.exec(["shell", "run"], {
177
+ command: "git",
178
+ args: ["status", "--short"],
179
+ cwd: "/path/to/repo",
180
+ });
181
+
182
+ console.log(result.stdout);
183
+ // M src/index.ts
184
+ // ?? new-file.ts
185
+ ```
186
+
187
+ ---
188
+
189
+ ## API Reference
190
+
191
+ ### Procedures Summary
192
+
193
+ | Path | Description |
194
+ |------|-------------|
195
+ | `shell.run` | Spawn process with arguments (no shell) |
196
+ | `shell.exec` | Execute command string through shell |
197
+ | `shell.which` | Find full path to a command |
198
+
199
+ ---
200
+
201
+ ### shell.run
202
+
203
+ Spawn a process directly without shell interpretation. **Safer for untrusted input.**
204
+
205
+ ```typescript
206
+ interface ShellRunInput {
207
+ command: string; // Command to run
208
+ args?: string[]; // Arguments (default: [])
209
+ cwd?: string; // Working directory
210
+ env?: Record<string, string>; // Environment variables
211
+ timeout?: number; // Timeout in ms
212
+ encoding?: string; // Output encoding (default: "utf8")
213
+ }
214
+
215
+ interface ShellRunOutput {
216
+ exitCode: number;
217
+ stdout: string;
218
+ stderr: string;
219
+ success: boolean; // true if exitCode === 0
220
+ duration: number; // ms
221
+ }
222
+ ```
223
+
224
+ **Example:**
225
+ ```typescript
226
+ await client.exec(["shell", "run"], {
227
+ command: "npm",
228
+ args: ["install", "--save-dev", "typescript"],
229
+ cwd: "/my/project",
230
+ timeout: 60000,
231
+ });
232
+ ```
233
+
234
+ ---
235
+
236
+ ### shell.exec
237
+
238
+ Execute a command string through the system shell. Supports pipes, redirects, etc.
239
+
240
+ ```typescript
241
+ interface ShellExecInput {
242
+ command: string; // Full command string
243
+ cwd?: string; // Working directory
244
+ env?: Record<string, string>; // Environment variables
245
+ timeout?: number; // Timeout in ms
246
+ shell?: boolean | string; // Shell to use (default: true)
247
+ maxBuffer?: number; // Max stdout/stderr buffer
248
+ stdin?: string; // Input to pipe to stdin
249
+ }
250
+
251
+ interface ShellExecOutput {
252
+ exitCode: number;
253
+ stdout: string;
254
+ stderr: string;
255
+ success: boolean;
256
+ duration: number;
257
+ signal?: string; // Signal that terminated process
258
+ }
259
+ ```
260
+
261
+ **Example:**
262
+ ```typescript
263
+ await client.exec(["shell", "exec"], {
264
+ command: "git log --oneline | head -5",
265
+ cwd: "/my/repo",
266
+ });
267
+ ```
268
+
269
+ ---
270
+
271
+ ### shell.which
272
+
273
+ Find the full path to a command. Uses `where` on Windows, `which` on Unix.
274
+
275
+ ```typescript
276
+ interface ShellWhichInput {
277
+ command: string; // Command to find
278
+ }
279
+
280
+ interface ShellWhichOutput {
281
+ path: string | null; // Full path or null if not found
282
+ found: boolean;
283
+ }
284
+ ```
285
+
286
+ **Example:**
287
+ ```typescript
288
+ const { path, found } = await client.exec(["shell", "which"], {
289
+ command: "node",
290
+ });
291
+ // { path: "/usr/local/bin/node", found: true }
292
+ ```
293
+
294
+ ---
295
+
296
+ ## Use Cases
297
+
298
+ ### Building CLI Wrappers
299
+
300
+ The `shell.run` procedure is the foundation for CLI wrapper packages:
301
+
302
+ ```typescript
303
+ // In client-git/src/procedures/git/status.ts
304
+ export async function gitStatus(input: GitStatusInput, ctx: ProcedureContext) {
305
+ const args = ["status"];
306
+ if (input.short) args.push("--short");
307
+
308
+ const result = await ctx.client.call(["shell", "run"], {
309
+ command: "git",
310
+ args,
311
+ cwd: input.cwd,
312
+ });
313
+
314
+ return parseGitStatus(result.stdout);
315
+ }
316
+ ```
317
+
318
+ ### Checking Command Availability
319
+
320
+ ```typescript
321
+ async function ensureGitAvailable() {
322
+ const { found } = await client.exec(["shell", "which"], { command: "git" });
323
+ if (!found) {
324
+ throw new Error("Git is not installed or not in PATH");
325
+ }
326
+ }
327
+ ```
328
+
329
+ ### Piping Commands
330
+
331
+ ```typescript
332
+ // Using shell.exec for pipes
333
+ const result = await client.exec(["shell", "exec"], {
334
+ command: "cat package.json | jq '.dependencies'",
335
+ cwd: "/my/project",
336
+ });
337
+ ```
338
+
339
+ ---
340
+
341
+ ## Cross-Platform Notes
342
+
343
+ ### shell.which Behavior
344
+
345
+ ```mermaid
346
+ graph LR
347
+ subgraph "Unix/macOS"
348
+ UnixWhich["which command"]
349
+ end
350
+
351
+ subgraph "Windows"
352
+ WinWhere["where command"]
353
+ end
354
+
355
+ subgraph "Result"
356
+ Path["/usr/bin/git<br/>or C:\\Program Files\\Git\\bin\\git.exe"]
357
+ end
358
+
359
+ UnixWhich --> Path
360
+ WinWhere --> Path
361
+ ```
362
+
363
+ ### shell.run vs shell.exec
364
+
365
+ | Feature | shell.run | shell.exec |
366
+ |---------|-----------|------------|
367
+ | Shell interpretation | No | Yes |
368
+ | Pipes & redirects | No | Yes |
369
+ | Argument safety | Safer | Less safe |
370
+ | Use case | Known commands | Shell scripts |
371
+ | Platform quoting | Handled | Manual |
372
+
373
+ ---
374
+
375
+ ## Integration
376
+
377
+ ### With Other Packages
378
+
379
+ ```mermaid
380
+ graph TB
381
+ subgraph "High-Level"
382
+ Lib[client-lib<br/>lib.refresh]
383
+ end
384
+
385
+ subgraph "Mid-Level"
386
+ Git[client-git<br/>git.commit]
387
+ Pnpm[client-pnpm<br/>pnpm.install]
388
+ end
389
+
390
+ subgraph "Foundation"
391
+ Shell[client-shell<br/>shell.run]
392
+ end
393
+
394
+ Lib --> Git
395
+ Lib --> Pnpm
396
+ Git --> Shell
397
+ Pnpm --> Shell
398
+ ```
399
+
400
+ ### With MCP (Claude)
401
+
402
+ When using the MCP server with bundle-dev, Claude has access to shell.* procedures:
403
+
404
+ ```
405
+ User: "Run npm install in /my/project"
406
+ Claude: [Uses shell.run with command: "npm", args: ["install"]]
407
+ ```
408
+
409
+ ---
410
+
411
+ ## Requirements
412
+
413
+ - **Node.js** >= 22
414
+ - **Dependencies:**
415
+ - `@procedures/client` (peer dependency)
416
+ - `zod` ^3.24.0
417
+
418
+ ---
419
+
420
+ ## License
421
+
422
+ MIT