@better-fs-tools/shell 0.0.0-tegami-trusted-publish-setup → 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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +269 -6
  3. package/dist/contract/context.d.ts +10 -0
  4. package/dist/contract/context.js +0 -0
  5. package/dist/contract/deps.d.ts +43 -0
  6. package/dist/contract/deps.js +0 -0
  7. package/dist/contract/extensions.d.ts +94 -0
  8. package/dist/contract/extensions.js +0 -0
  9. package/dist/contract/format.d.ts +22 -0
  10. package/dist/contract/format.js +0 -0
  11. package/dist/contract/input.d.ts +15 -0
  12. package/dist/contract/input.js +0 -0
  13. package/dist/contract/limits.d.ts +16 -0
  14. package/dist/contract/limits.js +0 -0
  15. package/dist/contract/messages.d.ts +69 -0
  16. package/dist/contract/messages.js +0 -0
  17. package/dist/contract/result.d.ts +77 -0
  18. package/dist/contract/result.js +0 -0
  19. package/dist/contract/runner.d.ts +55 -0
  20. package/dist/contract/runner.js +0 -0
  21. package/dist/core/abort.d.ts +12 -0
  22. package/dist/core/abort.js +41 -0
  23. package/dist/core/capture.d.ts +11 -0
  24. package/dist/core/capture.js +171 -0
  25. package/dist/core/create-bash-tool.d.ts +8 -0
  26. package/dist/core/create-bash-tool.js +18 -0
  27. package/dist/core/deps.d.ts +5 -0
  28. package/dist/core/deps.js +103 -0
  29. package/dist/core/env.d.ts +14 -0
  30. package/dist/core/env.js +32 -0
  31. package/dist/core/execute.d.ts +11 -0
  32. package/dist/core/execute.js +265 -0
  33. package/dist/core/input.d.ts +12 -0
  34. package/dist/core/input.js +46 -0
  35. package/dist/core/limits.d.ts +10 -0
  36. package/dist/core/limits.js +44 -0
  37. package/dist/core/messages.d.ts +12 -0
  38. package/dist/core/messages.js +94 -0
  39. package/dist/core/pipeline.d.ts +4 -0
  40. package/dist/core/pipeline.js +179 -0
  41. package/dist/core/stages.d.ts +20 -0
  42. package/dist/core/stages.js +166 -0
  43. package/dist/core/stop.d.ts +17 -0
  44. package/dist/core/stop.js +50 -0
  45. package/dist/formatters/default.d.ts +20 -0
  46. package/dist/formatters/default.js +77 -0
  47. package/dist/formatters/index.d.ts +2 -0
  48. package/dist/formatters/index.js +1 -0
  49. package/dist/index.d.ts +17 -0
  50. package/dist/index.js +7 -0
  51. package/dist/signature/bash.d.ts +28 -0
  52. package/dist/signature/bash.js +158 -0
  53. package/dist/signature/index.d.ts +3 -0
  54. package/dist/signature/index.js +2 -0
  55. package/docs/architecture.md +74 -0
  56. package/docs/result-schema.md +151 -0
  57. package/package.json +41 -2
@@ -0,0 +1,151 @@
1
+ # Result schema
2
+
3
+ Every `bash` call returns a `ShellResult`: a `ShellReport` plus `content`, the parts that the formatter made for the model.
4
+
5
+ ```ts
6
+ type ShellResult = ShellReport & { readonly content: readonly ContentPart[] };
7
+
8
+ type ShellReport = ShellRunReport | ShellFailure;
9
+
10
+ interface ShellRunReport {
11
+ tool: "bash";
12
+ status: "ok" | "failed" | "timeout";
13
+ request: BashRequest;
14
+ run: ShellRun;
15
+ output: ShellOutput;
16
+ notes: readonly Note[];
17
+ }
18
+
19
+ interface ShellFailure {
20
+ tool: "bash";
21
+ status: "error";
22
+ error: ShellError;
23
+ request: BashRequest | null; // null only for INVALID_INPUT
24
+ run: ShellRun | null; // set only when the command started
25
+ output: ShellOutput | null; // set only when the command started
26
+ notes: readonly Note[];
27
+ }
28
+
29
+ // ToolError<ShellErrorCode, ShellPhase>: the same shape as the read and write errors.
30
+ interface ShellError {
31
+ code: ShellErrorCode;
32
+ phase: ShellPhase;
33
+ message: string;
34
+ data?: JsonObject;
35
+ }
36
+ ```
37
+
38
+ `ShellReport` is a discriminated union on `status`. Only the `error` variant has an `error` field, and it is never null there, so a plain `if (result.status === "error")` narrows it. For `ok`, `failed`, and `timeout`, `run` and `output` are never null. `error.message` and `error.data` are copied from the error note.
39
+
40
+ `textOf(result)` joins the text parts of `content` with `"\n"`. The report is plain data. Nothing in it comes from `ctx.host`.
41
+
42
+ The read and write tools have the same envelope. Their schemas are `docs/result-schema.md` in `@better-fs-tools/read` and in `@better-fs-tools/write`. The write one also has one table of the statuses and error codes of all five tools.
43
+
44
+ ## Statuses
45
+
46
+ | Status | When |
47
+ | --------- | -------------------------------------------------------------------------------------------------------------------------- |
48
+ | `ok` | The command exited with code 0 |
49
+ | `failed` | The command exited with another code, or a signal ended it. A normal result, not a tool error |
50
+ | `timeout` | The timeout stopped the command. The output so far is kept |
51
+ | `error` | The call stopped before the command ran, or the core stopped it for a reason other than its timeout. `error.code` says why |
52
+
53
+ There is no `aborted` or `refused` status. An abort is `error` with `ABORTED`, and a refusal is `error` with `DENIED` or `REFUSED`.
54
+
55
+ ## `BashRequest`
56
+
57
+ ```ts
58
+ {
59
+ command: string;
60
+ timeoutMs: number;
61
+ cwd: string | null;
62
+ }
63
+ ```
64
+
65
+ The validated canonical input. `timeoutMs` is concrete: the input value clamped to `limits.maxTimeoutMs`, or `limits.defaultTimeoutMs`. `cwd` is the requested cwd as the model sent it, or `null`. It uses canonical names even when the tool uses a renamed signature.
66
+
67
+ ## `ShellRun`
68
+
69
+ ```ts
70
+ {
71
+ command: string; // as run, after every beforeRun rewrite
72
+ cwd: string; // absolute
73
+ timeoutMs: number;
74
+ exitCode: number | null; // null when a signal ended it, or the exit was not confirmed
75
+ signal: string | null;
76
+ durationMs: number;
77
+ stoppedBy: "timeout" | "abort" | "output-cap" | null; // null when it ended by itself
78
+ unconfirmedStop: boolean; // the runner did not settle its exit in the grace time
79
+ }
80
+ ```
81
+
82
+ ## `ShellOutput`
83
+
84
+ ```ts
85
+ {
86
+ head: string; // all of the output when omittedBytes is 0
87
+ tail: string | null; // null when nothing was cut
88
+ totalBytes: number;
89
+ totalLines: number;
90
+ omittedBytes: number;
91
+ omittedLines: number;
92
+ stdoutBytes: number;
93
+ stderrBytes: number;
94
+ spill: string | null; // the spill sink's reference, or null without a sink or when the sink failed
95
+ }
96
+ ```
97
+
98
+ stdout and stderr are merged in arrival order. The view is `head`, a gap line, then `tail`. `headPercent` of `maxOutputBytes` and `maxOutputLines` goes to the head, and the rest to the tail. The core decodes UTF-8 with replacement characters and removes ANSI escape codes.
99
+
100
+ ## Error codes
101
+
102
+ | `error.code` | `error.phase` | Meaning | `run` and `output` |
103
+ | --------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
104
+ | `INVALID_INPUT` | `input` | The input failed validation. `request` is `null` | null |
105
+ | `CWD_NOT_FOUND` | `resolve`, `run` | The requested cwd does not exist. `resolve` when a resolver said so, `run` when the runner did | null |
106
+ | `CWD_NOT_A_DIRECTORY` | `run` | The requested cwd is not a directory | null |
107
+ | `REFUSED` | `beforeRun` | A `beforeRun` hook returned `{ allow: false }` | the hook note's data, with its own code in `source` |
108
+ | `DENIED` | `authorize` | The authorizer returned `{ allow: false }` | the authorizer note's data, with its own code in `source` |
109
+ | `ABORTED` | `input`, `resolve`, `beforeRun`, `authorize`, `env`, `run` | The caller's signal fired. A signal that had fired before the call is `input`, as in read and write. A later abort names the stage it hit | set if it started |
110
+ | `SPAWN_FAILED` | `run` | The runner could not start the command | null |
111
+ | `OUTPUT_CAP` | `run` | The command wrote more than `maxCaptureBytes`, so the core stopped it | set |
112
+ | `EXTENSION_FAILED` | `resolve`, `beforeRun`, `authorize`, `env`, `spill`, `run`, `afterRun` | Host code threw or returned a malformed value. `run` is a runner factory, `resolve` a cwd factory or resolver | set for `afterRun` |
113
+
114
+ `ShellPhase` also has `format`. A formatter failure is not an error: the default formatter runs, no note is added, and the status stays.
115
+
116
+ An `afterRun` hook that fails turns the result into `EXTENSION_FAILED`, and keeps `run`, `output`, and the notes so far. An abort during the run, and `OUTPUT_CAP`, keep them too. The formatter then shows the exit as the status line, and the reason in the error note.
117
+
118
+ ## Notes
119
+
120
+ ```ts
121
+ { code, severity: "info" | "warning", message, data?: JsonObject }
122
+ ```
123
+
124
+ `code` is stable and kebab case. The default formatter prints a note as `[bash:<code>] <message>`. Key your code on `code`, not on the wording. `messages` overrides change the wording only.
125
+
126
+ Every error result has one warning note for the error. The error note is always first in `notes`. Its code is the error code in kebab case, for example `output-cap` for `OUTPUT_CAP`. When an authorizer or a `beforeRun` hook refuses with its own `note`, the note keeps its message and data, its code becomes the error code in kebab case, its severity becomes `warning`, and the host's own code moves to `data.source`. So the error note is `denied` or `refused`, with `error.data.source` set to the host code when it differs. Read and write use the same rule.
127
+
128
+ Notes from the core:
129
+
130
+ | `code` | Severity | When | `data` |
131
+ | ----------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
132
+ | `clamped` | info | The input timeout was over `maxTimeoutMs` | `{ param: "timeoutMs", requested, max }` |
133
+ | `output-incomplete` | warning | The output stream failed, the runner gave chunks that were not output, or the stream had not ended `drainMs` after the exit. Not added after a stop the tool started (timeout, abort, output cap) | `{ skippedChunks, detail?, drainMs? }` |
134
+ | `unconfirmed-stop` | warning | The runner did not settle its exit in the grace time after a stop | none |
135
+ | `invalid-input`, `cwd-not-found`, `cwd-not-a-directory`, `refused`, `denied`, `aborted`, `spawn-failed`, `output-cap`, `extension-failed` | warning | The error note of each error code | none |
136
+
137
+ A resolver can add its own note, for example `path-repaired` from `unicodeRepair()`. An allow decision from the authorizer and a `beforeRun` hook can add notes. They keep their codes.
138
+
139
+ Order: the error note comes first, then `clamped`, then the resolver note, then the `beforeRun` and authorizer notes, then the run notes (`unconfirmed-stop`, `output-incomplete`). An `afterRun` hook can replace the notes.
140
+
141
+ ## Content
142
+
143
+ The formatter makes `content` once, with `mode: "model"`. A formatter that returns a string gives one text part. `defaultShellFormatter()` prints:
144
+
145
+ 1. The status line, when the command started: `Exit code 1 · 0.4 s`, `Ended by SIGKILL · 3 s`, or `Timed out after 2 min. The process tree was stopped.`
146
+ 2. The output view: `head`, the gap line `[… 11945 lines (1.8 MB) not shown] Full output: /tmp/bash-1.log`, then `tail`. `(no output)` when there is none.
147
+ 3. A blank line, then the note lines.
148
+
149
+ A call that never started prints the note lines only. In `mode: "view"` the notes are left out.
150
+
151
+ `defaultShellFormatter({ notes })` takes a function that rewrites a note, or returns `null` to hide it from the model text. `noteLine(note)` changes the text of one note line. The read and write formatters take the same `notes` option. The report keeps every note.
package/package.json CHANGED
@@ -1,5 +1,44 @@
1
1
  {
2
2
  "name": "@better-fs-tools/shell",
3
- "version": "0.0.0-tegami-trusted-publish-setup",
4
- "description": "Placeholder published by Tegami for npm trusted publishing setup."
3
+ "version": "0.2.0",
4
+ "description": "A bash tool for TypeScript agents: a small run core with hooks and defaults.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/gingerhendrix/better-fs-tools.git",
9
+ "directory": "packages/shell"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "docs"
14
+ ],
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ },
22
+ "./signature": {
23
+ "types": "./dist/signature/index.d.ts",
24
+ "import": "./dist/signature/index.js"
25
+ }
26
+ },
27
+ "publishConfig": {
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js"
32
+ },
33
+ "./signature": {
34
+ "types": "./dist/signature/index.d.ts",
35
+ "import": "./dist/signature/index.js"
36
+ }
37
+ },
38
+ "access": "public"
39
+ },
40
+ "dependencies": {
41
+ "@better-fs-tools/fs": "^0.2.0",
42
+ "@better-fs-tools/read": "^0.2.0"
43
+ }
5
44
  }