@sibylline-labs/eris 0.0.0-stage → 0.3.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 (69) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +146 -2
  4. package/bin/eris.ts +42 -0
  5. package/docs/ARCHITECTURE.md +188 -0
  6. package/docs/COMPLETION.md +106 -0
  7. package/docs/ERRORS.md +632 -0
  8. package/docs/OUTPUT-CONTRACT.md +219 -0
  9. package/docs/README.md +30 -0
  10. package/docs/UPDATE.md +159 -0
  11. package/docs/YAK.md +395 -0
  12. package/docs/dev/ADDING-A-COMMAND.md +79 -0
  13. package/docs/dev/DEVELOPMENT.md +88 -0
  14. package/docs/dev/LAYOUT.md +74 -0
  15. package/docs/dev/PLUGINS.md +382 -0
  16. package/docs/dev/RELEASING.md +157 -0
  17. package/docs/dev/TESTING.md +84 -0
  18. package/docs/dev/surfaces/secrets.md +95 -0
  19. package/package.json +55 -4
  20. package/packages/yak-contract/package.json +28 -0
  21. package/packages/yak-contract/schema/yak-v2.schema.json +416 -0
  22. package/packages/yak-contract/src/index.ts +1 -0
  23. package/packages/yak-contract/src/v2.ts +375 -0
  24. package/plugins/index.ts +12 -0
  25. package/plugins/yak/handlers.ts +128 -0
  26. package/plugins/yak/index.ts +51 -0
  27. package/plugins/yak/login.ts +300 -0
  28. package/plugins/yak/yak-context.ts +280 -0
  29. package/plugins/yak/yak-identity.ts +399 -0
  30. package/plugins/yak/yak-redact.ts +686 -0
  31. package/plugins/yak/yak-setup.ts +133 -0
  32. package/plugins/yak/yak-submit.ts +1166 -0
  33. package/plugins/yak/yak.ts +485 -0
  34. package/src/build.ts +248 -0
  35. package/src/cli/args.ts +434 -0
  36. package/src/cli/commands/completion/bash.ts +24 -0
  37. package/src/cli/commands/completion/domain.ts +203 -0
  38. package/src/cli/commands/completion/emit.ts +246 -0
  39. package/src/cli/commands/completion/shell.ts +24 -0
  40. package/src/cli/commands/completion/zsh.ts +24 -0
  41. package/src/cli/commands/completion.ts +48 -0
  42. package/src/cli/commands/update/github.ts +328 -0
  43. package/src/cli/commands/update/opt-in.ts +32 -0
  44. package/src/cli/commands/update/refusal.ts +21 -0
  45. package/src/cli/commands/update/release.ts +351 -0
  46. package/src/cli/commands/update/replace.ts +143 -0
  47. package/src/cli/commands/update/semver.ts +48 -0
  48. package/src/cli/commands/update.ts +47 -0
  49. package/src/cli/commands/usage.ts +68 -0
  50. package/src/cli/commands/version.ts +29 -0
  51. package/src/cli/compose.ts +273 -0
  52. package/src/cli/output.ts +81 -0
  53. package/src/cli/plugin.ts +85 -0
  54. package/src/cli/registry.ts +180 -0
  55. package/src/cli/router.ts +31 -0
  56. package/src/cli/ts-verb.ts +126 -0
  57. package/src/main.ts +223 -0
  58. package/src/runtime/env.ts +16 -0
  59. package/src/runtime/eris-assets.d.ts +5 -0
  60. package/src/runtime/home.ts +306 -0
  61. package/src/runtime/logging.ts +43 -0
  62. package/src/runtime/process.ts +388 -0
  63. package/src/runtime/settings.ts +191 -0
  64. package/src/sdk.ts +62 -0
  65. package/testing/build-child.ts +55 -0
  66. package/testing/fake-runner.ts +23 -0
  67. package/testing/index.ts +16 -0
  68. package/ts/lib/eris-fail.ts +180 -0
  69. package/ts/lib/pool.ts +63 -0
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
202
+
package/NOTICE ADDED
@@ -0,0 +1,2 @@
1
+ eris
2
+ Copyright 2026 Sibylline Labs
package/README.md CHANGED
@@ -1,3 +1,147 @@
1
- # Temporary Holding Version
1
+ # eris
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
+ eris is a small, plugin-composed command-line tool written in TypeScript on
4
+ [Bun](https://bun.sh). Core supplies the router, the verb registry, argument
5
+ parsing, the output contract (`--output text|json`, slugged failure blocks)
6
+ and shell completion; every verb group beyond that is a plugin composed in at
7
+ build time.
8
+
9
+ This build ships one plugin, `eris yak`, which AI coding agents (and people)
10
+ use to file observations about their own tooling: a broken command, a
11
+ misleading doc, a missing capability. A yak is redacted, given a dedup key and
12
+ sent in one bounded request to a Yaks endpoint you configure. It never fails
13
+ the task that filed it.
14
+
15
+ ## Install
16
+
17
+ eris is published to npm as `@sibylline-labs/eris` and runs on Bun, which must be on
18
+ PATH. npm is the only install path:
19
+
20
+ ```bash
21
+ npx @sibylline-labs/eris@latest --help # run the latest release, no install
22
+ bunx @sibylline-labs/eris@latest --help # the same, through bun
23
+ bun add -g @sibylline-labs/eris@latest # install `eris` globally
24
+ ```
25
+
26
+ ### From source
27
+
28
+ You need Bun (the version in `.bun-version`) and git.
29
+
30
+ ```bash
31
+ git clone https://github.com/sibylline-labs/eris && cd eris
32
+ bun install --frozen-lockfile
33
+ ./eris --help
34
+ ```
35
+
36
+ Put the checkout on your `PATH` to run it as `eris`. To build a standalone binary,
37
+ run `bun scripts/build.ts`; it writes `dist/eris-<os>-<arch>`, which runs without Bun.
38
+
39
+ ### Version and updates
40
+
41
+ ```bash
42
+ eris --version # eris v0.3.0 (sibylline-labs/eris)
43
+ eris update # prints the npx / bunx / bun add -g commands for the latest release
44
+ ```
45
+
46
+ `eris update` changes nothing: the package manager that installed eris updates it.
47
+ [docs/UPDATE.md](docs/UPDATE.md) has the details.
48
+
49
+ ## Quickstart
50
+
51
+ ```bash
52
+ eris --help # the commands this build composes
53
+ source <(eris completion zsh) # or bash; see docs/COMPLETION.md
54
+ eris yak --help
55
+ ```
56
+
57
+ ### Filing a yak
58
+
59
+ Sign in once with a device code. `eris login` shows a code to approve in the
60
+ browser and writes the token to `~/.eris/yaks.json` (mode `0600`):
61
+
62
+ ```bash
63
+ eris login
64
+ ```
65
+
66
+ That signs in to `https://api.sibylline.group`. For another endpoint, pass
67
+ `--url`, or configure it with environment variables:
68
+
69
+ ```bash
70
+ export ERIS_YAKS_URL=https://yaks.example.com # your Yaks endpoint
71
+ export ERIS_YAKS_TOKEN=<bearer token>
72
+ ```
73
+
74
+ Or, for a self-hosted endpoint without device login, mint a bearer into
75
+ `~/.eris/yaks.json` from a checkout. The script prints the entry your endpoint's
76
+ operator needs to register:
77
+
78
+ ```bash
79
+ bun plugins/yak/yak-setup.ts --url https://yaks.example.com
80
+ ```
81
+
82
+ Then file reports:
83
+
84
+ ```bash
85
+ eris yak issue --summary "completion fails on Alpine" --body "busybox sh lacks compgen"
86
+ eris yak suggest --summary "a --dry-run for completion" --body-file notes.md
87
+ eris yak fatal --summary "eris exits before any verb runs" --body-file - < log.txt
88
+ eris yak issue --summary "…" --body "…" --repro "bun test tests/foo.test.ts" --repo acme/widgets
89
+ eris --output json yak issue --summary "…" --body "…" # one JSON record on stdout
90
+ ```
91
+
92
+ A delivered yak prints `recorded: <id>`. A missing URL or token, an
93
+ unreachable endpoint or a timeout prints a `WARN` line with its slug and still
94
+ exits 0. Only misuse (a missing `--summary`, an over-long field, an unknown
95
+ flag) exits 1. [docs/YAK.md](docs/YAK.md) has the full reference.
96
+
97
+ ### Agent skill
98
+
99
+ [`skills/eris-yaks`](skills/eris-yaks/SKILL.md) is an agent skill that walks a coding agent
100
+ through setting up Yaks for its user: running eris, the browser steps to connect Linear and
101
+ create a token, a test yak, and the `AGENTS.md` block that teaches later sessions to file. Install
102
+ it with the [skills](https://github.com/vercel-labs/skills) CLI:
103
+
104
+ ```bash
105
+ npx skills add sibylline-labs/eris --skill eris-yaks
106
+ ```
107
+
108
+ ## Writing a plugin
109
+
110
+ A plugin is registry rows plus lazily loaded handlers, written against the SDK
111
+ in `src/cli/plugin.ts`. Start with [docs/dev/PLUGINS.md](docs/dev/PLUGINS.md)
112
+ and copy the worked example in [`examples/plugins/widgets/`](examples/plugins/widgets).
113
+ A build's plugin list is `plugins/index.ts`.
114
+
115
+ To ship your own binary with plugins of your own, write a distribution: depend
116
+ on this repository, import `eris/sdk`, compose, and build with `build()`
117
+ ([docs/dev/PLUGINS.md#distributions](docs/dev/PLUGINS.md#distributions); the
118
+ worked example is [`examples/distribution/`](examples/distribution)).
119
+
120
+ ## The Yaks wire contract
121
+
122
+ [`packages/yak-contract`](packages/yak-contract) is `@sibylline-labs/yak-contract`:
123
+ the Yaks submission contract v2 as Zod schemas, their inferred TypeScript
124
+ types, and the JSON Schema they emit (`schema/yak-v2.schema.json`) for
125
+ consumers in other languages. A repository that depends on this one (a git
126
+ dependency pinned to a merge commit on `main`) imports the TypeScript contract
127
+ as `eris/yak-contract`. The `eris yak` client in this build imports it and
128
+ posts contract v2 submissions.
129
+
130
+ The client's redaction pass, [`plugins/yak/yak-redact.ts`](plugins/yak/yak-redact.ts), is
131
+ exported as `eris/yak-redact` (`redact`, `redactDeep`), so a server that redacts a submission
132
+ before it commits runs the same rules. The module imports nothing.
133
+
134
+ ## Documentation
135
+
136
+ [docs/README.md](docs/README.md) indexes the references: the architecture,
137
+ the output contract every command follows, every error slug, and the
138
+ developer guides.
139
+
140
+ ## Contributing and security
141
+
142
+ We do not accept outside contributions; see [CONTRIBUTING.md](CONTRIBUTING.md).
143
+ Report vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
144
+
145
+ ## Licence
146
+
147
+ Apache License, Version 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
package/bin/eris.ts ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env -S bun --no-env-file --config=/dev/null
2
+ /**
3
+ * eris.ts — the `eris` command an install of this package links (package.json
4
+ * `bin`): `npm i -g`, `bun add -g`, `npx`, `bunx`.
5
+ *
6
+ * A checkout starts through the POSIX shim, `eris`. An installed package has no
7
+ * shell to run, so this file's shebang is the entrypoint, and it keeps the
8
+ * shim's invariants (the shim's header says why each exists):
9
+ *
10
+ * - bun never reads the caller's cwd: `--no-env-file` stops it loading a
11
+ * service dir's generated `.env`, and `--config=/dev/null` stops a cwd
12
+ * `bunfig.toml` preload from running. A bare `#!/usr/bin/env bun` does
13
+ * neither. `env -S` is what splits the flags out of one shebang line.
14
+ * - BUN_CONFIG_VERBOSE_FETCH never reaches a fetch. The shim removes it
15
+ * before bun starts; this file refuses to run under it
16
+ * (`eris-verbose-fetch-set`) rather than print a bearer token.
17
+ * - The install root is the package: src/runtime/home.ts resolves it from its
18
+ * own location, so the symlink an install links this file under changes
19
+ * nothing. Only the shim sets `_ERIS_SHIM_DIR`, so a stray value in the
20
+ * caller's environment is removed before the program reads it.
21
+ *
22
+ * `src/main.ts` runs itself only when it is the entry, so this calls
23
+ * `runProgram`. The refusal comes first and `src/main` is imported after it:
24
+ * nothing of the program is evaluated under the variable.
25
+ *
26
+ * tests/bin.test.ts pins each of these.
27
+ */
28
+
29
+ import { reportFailure } from "../ts/lib/eris-fail";
30
+
31
+ if (process.env.BUN_CONFIG_VERBOSE_FETCH !== undefined) {
32
+ // Not `process.exit`: the exit code is set and the stream left to drain, as src/main.ts does.
33
+ process.exitCode = reportFailure({
34
+ slug: "eris-verbose-fetch-set",
35
+ what: "BUN_CONFIG_VERBOSE_FETCH is set — under it bun prints every fetch, Authorization bearer and body included, to stderr",
36
+ fix: "unset BUN_CONFIG_VERBOSE_FETCH, or run: env -u BUN_CONFIG_VERBOSE_FETCH eris <command>",
37
+ });
38
+ } else {
39
+ delete process.env._ERIS_SHIM_DIR;
40
+ const { runProgram } = await import("../src/main");
41
+ runProgram();
42
+ }
@@ -0,0 +1,188 @@
1
+ # Eris architecture
2
+
3
+ The current-state map: what eris is made of, what calls what, and the constraints any change has to
4
+ survive. A claim here that no longer resolves against the tree is a bug in this file; fixing it
5
+ belongs to the change that moved the code.
6
+
7
+ ---
8
+
9
+ ## 1. What eris is
10
+
11
+ Eris is a TypeScript program run by bun, behind a POSIX `sh` shim: a small core (the router, the
12
+ verb registry, the output contract, the plugin host and its SDK, shell completion and the top-level
13
+ usage) plus the plugins a build composes in. This build's plugin is `eris yak`
14
+ ([YAK.md](YAK.md)).
15
+
16
+ - **`eris`** finds bun and execs `src/main.ts`. Nothing else.
17
+ - **`bin/eris.ts`** is the same entry for an installed package: the `eris` command that `npm i -g
18
+ @sibylline-labs/eris`, `bun add -g`, `npx` and `bunx` link (`bin` in `package.json`). It starts under bun
19
+ through its shebang and calls `runProgram`.
20
+ - **`src/main.ts`** routes every command line.
21
+ - **`plugins/index.ts`** lists the plugins this build compiles in.
22
+ - **`src/sdk.ts`** is `eris/sdk`, the one module through which another repository reaches core
23
+ (the `exports` map names `eris/sdk/testing`, `eris/yak-contract` and `eris/yak-redact` beside it,
24
+ nothing deeper).
25
+ A **distribution** depends on this package, composes core with this build's plugins and its own,
26
+ and compiles its own binary with `build()` ([`dev/PLUGINS.md`](dev/PLUGINS.md#distributions)).
27
+ - **`packages/yak-contract`** is the Yaks wire contract v2, a bun workspace package that builds
28
+ standalone. The `exports` map also names it, as `eris/yak-contract`, so a repository pinned to
29
+ this one imports the contract without core.
30
+
31
+ eris installs from npm only (`npx`, `bunx`, `bun add -g @sibylline-labs/eris`); the package's
32
+ `bin/eris.ts` is the command. A checkout needs `bun install --frozen-lockfile` and no build step:
33
+ `bash <checkout>/eris` and `sh <checkout>/eris` run it as it stands.
34
+
35
+ `bun scripts/build.ts` also compiles the same program into one executable,
36
+ `dist/eris-<os>-<arch>`, through `build()` (`src/build.ts`), the same call a distribution makes
37
+ with its own entry. It carries the docs the source names and writes them once per
38
+ build to `${ERIS_CACHE_DIR:-${XDG_CACHE_HOME:-~/.cache}/eris}/<build id>/`, its install root
39
+ (`src/runtime/home.ts`), so a failure block's `docs:` line names a file that exists.
40
+
41
+ A binary also knows which build it is, from the same call: `build({ tag })` bakes the version,
42
+ the release repository, an optional version line and the docs base of a failure block
43
+ (`VERSION` and `identity()`, `src/runtime/home.ts`); a distribution run from source hands the
44
+ same identity to `runProgram({ identity })`, and a binary keeps what its build baked. `eris --version`
45
+ prints them. Core's `eris update` (`src/cli/commands/update.ts`) prints the npm commands for the
46
+ latest release. A distribution that publishes binaries composes `RELEASE_UPDATE`
47
+ (`src/cli/commands/update/`) in its place, which replaces the binary with the newest release of
48
+ its own repository, after checking it ([`UPDATE.md`](UPDATE.md)). The release is cut by
49
+ `.github/workflows/release.yml`, which builds and tests both binaries, publishes them with a
50
+ `SHA256SUMS`, and publishes the package to npm
51
+ ([`dev/RELEASING.md`](dev/RELEASING.md)). The npm package is the source, run by bun: `exports` and
52
+ `bin` name `.ts` files, and `files` in `package.json` is the allow-list of what the tarball holds
53
+ (`tests/package.test.ts`).
54
+
55
+ ---
56
+
57
+ ## 2. The layers, as built
58
+
59
+ ```
60
+ eris POSIX sh shim: unset BUN_CONFIG_VERBOSE_FETCH; exec bun src/main.ts
61
+ bin/eris.ts the npm bin: refuse BUN_CONFIG_VERBOSE_FETCH; runProgram()
62
+ └─ src/main.ts settings fill → route → handler (lazy import) | unknown
63
+ ├─ src/sdk.ts eris/sdk: what a distribution imports · src/build.ts: build()
64
+ ├─ src/cli/ registry.ts (core's verbs) · compose.ts (core + plugins) · plugin.ts
65
+ │ (the SDK) · router.ts · args.ts (argv + the global --output pre-pass)
66
+ │ · output.ts (the json allow-list) · ts-verb.ts
67
+ ├─ src/cli/commands/ completion/ · usage.ts · version.ts · update.ts + update/
68
+ ├─ src/runtime/ logging · process · settings · env · home
69
+ ├─ ts/lib/eris-fail.ts the failure block
70
+ └─ plugins/ index.ts (the build's list) · yak/
71
+ ```
72
+
73
+ ### The shim
74
+
75
+ `eris` is POSIX `sh` because consumers run it as `bash eris`, `sh eris` and `exec <repo>/eris`. It:
76
+
77
+ - removes `BUN_CONFIG_VERBOSE_FETCH` before bun starts. Under it bun prints every fetch, bearer and
78
+ body included, to stderr, and bun reads it from its own environment at fetch time, so
79
+ `delete process.env.X` inside `src/main.ts` cannot turn it off;
80
+ - refuses a missing bun with the `eris-bun-missing` failure block, rendered by the shim itself;
81
+ - execs `bun --no-env-file --config=/dev/null src/main.ts`, so bun never loads a service
82
+ directory's generated `.env` or runs a cwd `bunfig.toml` preload;
83
+ - exports nothing. It hands the router its logical directory through `_ERIS_SHIM_DIR`, which
84
+ `src/main.ts` removes before anything is spawned.
85
+
86
+ `tests/cli-shim.test.ts` pins each of these.
87
+
88
+ ### The package's bin
89
+
90
+ `bin/eris.ts` is what an install links as `eris`. It has no shell to run, so its shebang,
91
+ `#!/usr/bin/env -S bun --no-env-file --config=/dev/null`, does what the shim's `exec` does: bun never
92
+ reads the caller's cwd `.env` or `bunfig.toml` preload. A bare `#!/usr/bin/env bun` does neither, so
93
+ `npx eris` run in a service directory would load that directory's `.env` and run its preload.
94
+ `env -S` splits the flags out of the one shebang line (GNU coreutils 8.30 and newer, macOS).
95
+
96
+ The bin does not remove `BUN_CONFIG_VERBOSE_FETCH`; it refuses to run under it, with the
97
+ `eris-verbose-fetch-set` failure block, before it imports the program. With bun absent the operating
98
+ system's own `env: bun: No such file or directory` is the diagnostic; there is no `eris-bun-missing`
99
+ block, because nothing of eris has started to print one. The install root is the package directory,
100
+ whatever symlink the install put the bin behind. `tests/bin.test.ts` pins each of these.
101
+
102
+ ### The router
103
+
104
+ `src/cli/registry.ts` `REGISTRY` has a row per group, sub and nested sub: `impl` (`ts`; a sub
105
+ inherits its group's), long flags with value arity, `json`, and a summary. From it
106
+ are derived:
107
+
108
+ - the router table (`src/cli/router.ts`, `deriveTable`): every group, empty argv, `-h`, `--help`,
109
+ and a `"group sub"` key where a sub's impl differs from its group's (the longer key wins);
110
+ - the `--output json` allow-list (`src/cli/output.ts`);
111
+ - the shell completion tree (`src/cli/commands/completion/emit.ts`);
112
+ - every TS verb's argv parse (`src/cli/args.ts`): `readVerbArgs` accepts exactly the row's flags
113
+ and `pos` operands, refuses anything else uniformly, and types the handler's args from the row.
114
+
115
+ The registry those planes read is core's rows composed with the build's plugins
116
+ (`src/cli/compose.ts`, over the list in `plugins/index.ts`): a plugin is rows plus lazily loaded
117
+ handlers, written against the SDK `src/cli/plugin.ts`, composed at build time, with collisions, a
118
+ non-`ts` row and a json group with subs refused, and a group whose product the workspace has not
119
+ bought locked per command line. [`dev/PLUGINS.md`](dev/PLUGINS.md).
120
+
121
+ A `ts` route runs the composed handler for the verb (`HANDLERS` in `src/main.ts`, plus each
122
+ plugin's), imported lazily, behind the global `--output` pre-pass. A command line whose verb is not in the table is refused by the router as an unknown
123
+ command. Adding or flipping a verb is a registry edit; `tests/cli-registry.test.ts` and
124
+ `tests/cli-router.test.ts` hold the rows against `HANDLERS`.
125
+
126
+ `runTsVerb` (`src/cli/ts-verb.ts`) turns a package that cannot be resolved into the
127
+ `eris-deps-not-installed` block, read from the error's `code` and `specifier`. Nothing on the path
128
+ `src/main.ts` → `src/cli/ts-verb.ts` → `src/cli/output.ts` may import a third-party package, or that
129
+ block cannot be reached.
130
+
131
+ The top-level usage (`src/cli/commands/usage.ts`) is a `HANDLERS` row too: bare `eris`, `-h` and
132
+ `--help` all print it. `--version` is the fourth argv shape (`ARGV_SHAPES`, `src/cli/registry.ts`):
133
+ it is not a verb, but it renders JSON under `--output` and reads no file from the install root.
134
+
135
+ ---
136
+
137
+ ## 3. Dependency direction
138
+
139
+ - **A plugin imports the SDK and nothing else of core.** `src/cli/plugin.ts` is the one core module
140
+ under `plugins/` may import (`plugin-imports-sdk-only`, in `rules/`).
141
+ - **A distribution imports `eris/sdk` and nothing else of core.** The `exports` map refuses a
142
+ deeper path; `distribution-imports-sdk-only` holds the example distribution to it. Its suites
143
+ may also import `eris/sdk/testing` (`testing/`), which no source the binary bundles imports
144
+ (`runtime-imports-no-testing`). `eris/yak-contract` is not core: it is the workspace package
145
+ `packages/yak-contract`, which imports nothing of core (`tests/yak-contract-v2.test.ts`).
146
+ `eris/yak-redact` is `plugins/yak/yak-redact.ts`, the client's redaction pass, so a server that
147
+ redacts a submission runs the client's rules; it imports nothing (`tests/distribution.test.ts`).
148
+ - **Completion imports the registry and nothing else.** `source <(eris completion bash)` runs on
149
+ every shell start, so a broken source file elsewhere must not break a login shell.
150
+ `tests/completion.test.ts` corrupts every other verb and every plugin's handler code in a copied
151
+ tree and asserts byte-identical emission. Plugin rows reach completion through their manifests,
152
+ which import nothing but the SDK (`plugin-manifest-lazy-handlers`); `tests/plugins.test.ts`
153
+ composes a plugin whose handler module throws on evaluation and completes past it.
154
+ - **Only `src/runtime/process.ts` spawns.** Argv arrays only, an explicit
155
+ timeout, and the whole tree killed on expiry.
156
+
157
+ ---
158
+
159
+ ## 4. Constraints
160
+
161
+ **C1 — `bash <checkout>/eris` runs from a git checkout with no build step**, with bun at
162
+ `.bun-version` and `bun install --frozen-lockfile`.
163
+
164
+ **C2 — the output contract and the error slugs are stable.** The failure block
165
+ (`ts/lib/eris-fail.ts`, one `### <slug>` heading per slug in [`ERRORS.md`](ERRORS.md),
166
+ harvested by `tests/errors-doc.test.ts`); exit `1` = failure and `2` = idempotent no-op; stdout
167
+ carries the payload and nothing else. Stated in [`OUTPUT-CONTRACT.md`](OUTPUT-CONTRACT.md) and pinned
168
+ by `tests/output-contract.test.ts`.
169
+
170
+ **C3 — `eris completion` emits without loading any other verb**, and its output is
171
+ byte-deterministic for a given composed registry. A row's completion domain is data on the row:
172
+ its helper text is checked at composition (`src/cli/commands/completion/domain.ts`) so sourcing
173
+ the script runs nothing and no helper can swallow core's text.
174
+
175
+ **C4 — the shell only shrinks.** The shim is frozen as one total (`.sh` files,
176
+ code lines, function definitions) by `tests/source-is-text.test.ts` against
177
+ `tests/fixtures/ratchet-baseline.json` `FREEZE`. Growth fails; a shrink fails until the baseline is
178
+ lowered in the same change, so freed room is never re-spent. The same file bans the quadratic
179
+ `${v//pattern/repl}` blank test in that shell. New logic goes to TypeScript.
180
+
181
+ ---
182
+
183
+ ## See also
184
+
185
+ - [`OUTPUT-CONTRACT.md`](OUTPUT-CONTRACT.md): what eris guarantees to a program.
186
+ - [`dev/ADDING-A-COMMAND.md`](dev/ADDING-A-COMMAND.md): adding a verb behind the registry.
187
+ - [`dev/PLUGINS.md`](dev/PLUGINS.md): adding a verb group as a build-time plugin.
188
+ - [`ERRORS.md`](ERRORS.md): the error-slug catalogue.