plc-cli 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +386 -0
- package/dist/cli.js +5379 -0
- package/examples/config.yaml +95 -0
- package/examples/keybindings.yaml +27 -0
- package/package.json +90 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bruno Mariano
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# Plane Cockpit — CLI + TUI for Plane (Cloud and self-hosted)
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="docs/assets/img/plc-logo.png" alt="Plane Cockpit logo" width="340" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
Plane Cockpit is a terminal client for [Plane](https://github.com/makeplane/plane), inspired by `gh` and `gh dash`,
|
|
8
|
+
distributed as a `plc` binary.
|
|
9
|
+
It provides a fast CLI for daily operations on projects, issues (work items), and dashboards,
|
|
10
|
+
plus a TUI (`plc dash`) for visual exploration. It supports both Plane Cloud and Plane
|
|
11
|
+
self-hosted deployments behind reverse proxies, custom TLS, and custom headers.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
The published package exposes a `plc` binary. Pick whichever fits:
|
|
16
|
+
|
|
17
|
+
**From npm (global install):**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g plc-cli
|
|
21
|
+
plc --help
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**One-off with npx (no install):**
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx plc-cli --help
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**From a clone (Makefile):**
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
git clone https://github.com/brunoomariano/PlaneCockpit.git
|
|
34
|
+
cd PlaneCockpit
|
|
35
|
+
make bootstrap # installs the dev toolchain (mise) + dependencies
|
|
36
|
+
make install # builds, installs `plc` globally, and seeds an example config
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
End users installing from npm/npx do not need `mise` or a toolchain: the
|
|
40
|
+
published artifact is a plain Node binary and `better-sqlite3` ships a prebuilt
|
|
41
|
+
native binding. The `make install` path is for working from a checkout
|
|
42
|
+
(dogfooding / development); `make uninstall` removes it. Requires Node `>=24`.
|
|
43
|
+
|
|
44
|
+
## Quick start
|
|
45
|
+
|
|
46
|
+
1. Drop a config file at `~/.config/plane-cli/config.yaml`.
|
|
47
|
+
See [`examples/config.yaml`](examples/config.yaml). This file is safe to commit.
|
|
48
|
+
|
|
49
|
+
2. Authenticate:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
plc auth login
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The API key is prompted (masked) and stored at `~/.config/plane-cli/hosts.yaml`
|
|
56
|
+
with `0600` permissions — separate from `config.yaml` so the latter can live in
|
|
57
|
+
version control.
|
|
58
|
+
|
|
59
|
+
For a config file that carries the key directly, set `auth.api_key` under the
|
|
60
|
+
profile instead.
|
|
61
|
+
|
|
62
|
+
3. Run:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
plc auth status
|
|
66
|
+
plc project list
|
|
67
|
+
plc issue list --view "Current sprint"
|
|
68
|
+
plc dash
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Commands
|
|
72
|
+
|
|
73
|
+
| Command | What it does |
|
|
74
|
+
| -------------------------------------- | ------------------------------------------------------ |
|
|
75
|
+
| `plc auth login` / `logout` / `status` | manage the stored API key for the active profile |
|
|
76
|
+
| `plc project list` / `view <id>` | list projects, or show one by identifier |
|
|
77
|
+
| `plc issue list` | list issues, optionally via a configured `--view` |
|
|
78
|
+
| `plc issue view <key>` | show a single issue (e.g. `ENG-123`) |
|
|
79
|
+
| `plc issue open <key>` | open the issue in the default browser |
|
|
80
|
+
| `plc issue create` | create an issue (interactive, or headless via flags) |
|
|
81
|
+
| `plc issue edit <key>` | edit title, description, or priority |
|
|
82
|
+
| `plc issue assign <key> <user>` | assign an issue (use `me` for yourself) |
|
|
83
|
+
| `plc issue transition <key> <state>` | move an issue to a state (by name or id) |
|
|
84
|
+
| `plc issue label <key> [labels...]` | set an issue's labels (no labels clears them) |
|
|
85
|
+
| `plc issue comment <key>` | add a comment (inline, from a file, or interactive) |
|
|
86
|
+
| `plc issue delete <key>` | delete an issue (confirms unless `--yes`) |
|
|
87
|
+
| `plc config show` / `validate` | print the resolved config (keys masked) or validate it |
|
|
88
|
+
| `plc profile list` / `use <name>` | list profiles, or switch the active one |
|
|
89
|
+
| `plc cache status` / `warm` / `clear` | inspect, prime, or clear the cache |
|
|
90
|
+
| `plc log path` / `tail` / `clear` | locate, read, or remove the TUI log |
|
|
91
|
+
| `plc dash` | open the interactive TUI dashboard |
|
|
92
|
+
|
|
93
|
+
Run `plc <command> --help` for the full flag list of any command.
|
|
94
|
+
|
|
95
|
+
## Configuration
|
|
96
|
+
|
|
97
|
+
Plane Cockpit keeps two files apart, modeled after `gh`:
|
|
98
|
+
|
|
99
|
+
| File | Purpose | Safe to commit? |
|
|
100
|
+
| ------------- | -------------------------------------------- | --------------- |
|
|
101
|
+
| `config.yaml` | profiles, server URLs, views, cache settings | yes |
|
|
102
|
+
| `hosts.yaml` | API keys per host + profile (`chmod 0600`) | **no** |
|
|
103
|
+
|
|
104
|
+
The config file is YAML-first and validated with `zod`; invalid configs fail at
|
|
105
|
+
startup with the offending path.
|
|
106
|
+
|
|
107
|
+
For the complete list of options — server, auth, defaults, cache, and every view
|
|
108
|
+
filter and its accepted values — see [`docs/CONFIGURATION.md`](docs/CONFIGURATION.md).
|
|
109
|
+
|
|
110
|
+
`config.yaml` is read from a single location (after the `--config <path>` flag,
|
|
111
|
+
which overrides it):
|
|
112
|
+
|
|
113
|
+
`~/.config/plane-cli/config.yaml`
|
|
114
|
+
|
|
115
|
+
`hosts.yaml` is always read from `~/.config/plane-cli/hosts.yaml`.
|
|
116
|
+
|
|
117
|
+
All configuration lives in these two files — there are no environment variable
|
|
118
|
+
overrides. The active profile can be selected per invocation with `--profile`.
|
|
119
|
+
|
|
120
|
+
### API key resolution
|
|
121
|
+
|
|
122
|
+
In priority order:
|
|
123
|
+
|
|
124
|
+
1. `auth.api_key` inline in `config.yaml`.
|
|
125
|
+
2. Entry in `~/.config/plane-cli/hosts.yaml` (written by `plc auth login`).
|
|
126
|
+
|
|
127
|
+
`plc auth logout` removes the stored entry for the active profile.
|
|
128
|
+
|
|
129
|
+
### Profiles
|
|
130
|
+
|
|
131
|
+
A single config can declare multiple environments (e.g. `production`, `staging`).
|
|
132
|
+
Switch per invocation with `--profile`:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
plc --profile staging issue list
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or persist a new active profile:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
plc profile use staging
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Self-hosted
|
|
145
|
+
|
|
146
|
+
Plane Cockpit normalizes trailing slashes, supports custom headers, configurable timeouts, and
|
|
147
|
+
relaxed TLS for self-hosted clusters behind a reverse proxy:
|
|
148
|
+
|
|
149
|
+
```yaml
|
|
150
|
+
server:
|
|
151
|
+
base_url: https://plane.internal.company.com
|
|
152
|
+
workspace_slug: acme
|
|
153
|
+
timeout_ms: 10000
|
|
154
|
+
headers:
|
|
155
|
+
X-Forwarded-Proto: https
|
|
156
|
+
tls:
|
|
157
|
+
reject_unauthorized: false # only for internal CAs
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Cache
|
|
161
|
+
|
|
162
|
+
The cache is optional and pluggable. Providers:
|
|
163
|
+
|
|
164
|
+
- `memory` — in-process, the default.
|
|
165
|
+
- `sqlite` — local persistent cache, file at `~/.cache/plane-cli/cache.sqlite` by default.
|
|
166
|
+
- `redis` — shared cache (declare `cache.redis.url`).
|
|
167
|
+
- `noop` — disables caching entirely.
|
|
168
|
+
|
|
169
|
+
The CLI works fully without Redis. To bypass cache for a single invocation, pass
|
|
170
|
+
`--no-cache`.
|
|
171
|
+
|
|
172
|
+
Common subcommands:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
plc cache status
|
|
176
|
+
plc cache warm
|
|
177
|
+
plc cache clear --prefix plc:acme:project
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Views
|
|
181
|
+
|
|
182
|
+
Declare the universe of projects once under `defaults.projects`, then declare
|
|
183
|
+
views in YAML and reference them from the CLI or TUI:
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
defaults:
|
|
187
|
+
# The universe of projects this profile can reach. The TUI scans all of them
|
|
188
|
+
# by default; the CLI (`plc issue list` without `--project`) uses the first.
|
|
189
|
+
projects: ["ENG", "OPS", "DESIGN"]
|
|
190
|
+
# Profile-wide default sort, inherited by any view that omits its own `sort`.
|
|
191
|
+
sort: [{ priority: desc }, { updated_at: desc }]
|
|
192
|
+
|
|
193
|
+
views:
|
|
194
|
+
- name: "My open" # no `projects` => scans every project above
|
|
195
|
+
filters:
|
|
196
|
+
assignee: me
|
|
197
|
+
state_group: [unstarted, started]
|
|
198
|
+
sort: priority # scalar shorthand; uses the field's natural direction
|
|
199
|
+
|
|
200
|
+
- name: "Eng sprint"
|
|
201
|
+
projects: ["ENG"] # restricts to a subset of defaults.projects
|
|
202
|
+
filters:
|
|
203
|
+
cycle: current # cycle/module are only allowed on single-project views
|
|
204
|
+
state_group: [started]
|
|
205
|
+
sort: # multi-level: each key breaks ties of the one above
|
|
206
|
+
- priority: desc
|
|
207
|
+
- state: asc
|
|
208
|
+
- updated_at: desc
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
plc issue list --view "My open"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
A view without `projects` inherits the full `defaults.projects` set and
|
|
216
|
+
aggregates issues across all of them, reordered by the view's `sort`. A view
|
|
217
|
+
with `projects` restricts to that subset, which must be contained in
|
|
218
|
+
`defaults.projects`. Because `cycle` and `module` identify a single project,
|
|
219
|
+
they may only be used on views that resolve to exactly one project.
|
|
220
|
+
|
|
221
|
+
`sort` accepts either a scalar (`sort: priority`, applied in the field's natural
|
|
222
|
+
direction) or an ordered list of single-key maps (`- priority: desc`) for
|
|
223
|
+
multi-level sorting, where each entry breaks ties of the ones above it. Valid
|
|
224
|
+
fields are `priority`, `state`, `project`, `assign`, `created_at`, and
|
|
225
|
+
`updated_at`. A view inherits `defaults.sort` when it omits its own.
|
|
226
|
+
|
|
227
|
+
The full list of filters (`assignee`, `state_group`, `labels`, `priority`,
|
|
228
|
+
`cycle`, `module`, plus client-side `state_search` / `project_state_search`) and
|
|
229
|
+
their accepted values is documented in
|
|
230
|
+
[`docs/CONFIGURATION.md`](docs/CONFIGURATION.md#filters).
|
|
231
|
+
|
|
232
|
+
## TUI usage
|
|
233
|
+
|
|
234
|
+
`plc dash` opens a multi-panel dashboard. Views auto-refresh on a configurable
|
|
235
|
+
interval — set `defaults.auto_refresh_seconds` (default `15`; `0` disables it,
|
|
236
|
+
leaving manual `r` refresh intact).
|
|
237
|
+
|
|
238
|
+
### Keybindings
|
|
239
|
+
|
|
240
|
+
Press `?` inside the TUI to open the help modal — it lists every binding grouped
|
|
241
|
+
by context (Global, Issue list, Views, Filter, Help) and supports incremental
|
|
242
|
+
search by description, action id, or key.
|
|
243
|
+
|
|
244
|
+
Default bindings:
|
|
245
|
+
|
|
246
|
+
| Key | Action |
|
|
247
|
+
| ------------------ | -------------------------------------- |
|
|
248
|
+
| `?` | toggle help modal |
|
|
249
|
+
| `j` / `k` / arrows | navigate the issue list |
|
|
250
|
+
| `g` / `G` | jump to top / bottom |
|
|
251
|
+
| `PgUp` / `PgDn` | scroll one page |
|
|
252
|
+
| `[` / `]` | switch view |
|
|
253
|
+
| `Enter` | open issue detail modal (Markdown) |
|
|
254
|
+
| `Esc` (in modal) | close current modal back to list |
|
|
255
|
+
| `o` | open the selected issue in the browser |
|
|
256
|
+
| `e` | edit the selected issue |
|
|
257
|
+
| `n` | create a new issue |
|
|
258
|
+
| `>` / `<` | advance / retreat the issue's state |
|
|
259
|
+
| `r` | refresh |
|
|
260
|
+
| `/` | filter the loaded rows (see below) |
|
|
261
|
+
| `q` | quit |
|
|
262
|
+
|
|
263
|
+
Inside the detail modal, the same `j`/`k`/arrows/`PgUp`/`PgDn`/`g`/`G`
|
|
264
|
+
bindings scroll the Markdown description; `o` opens the issue in the
|
|
265
|
+
browser; `Esc` closes back to the list.
|
|
266
|
+
|
|
267
|
+
`e` (from the list or the detail modal) opens an edit modal for the selected
|
|
268
|
+
issue. `j`/`k`/arrows move focus across the editable fields (title, description,
|
|
269
|
+
state, assignee, priority, labels); `Enter` acts on the focused field. Title and
|
|
270
|
+
description open an inline text editor (`ctrl+s` applies, `Esc` cancels;
|
|
271
|
+
description is multiline with `Enter` for newline). State, assignee, priority and
|
|
272
|
+
labels open a picker — the assignee and label pickers are multi-select (`Enter`
|
|
273
|
+
toggles an entry, `ctrl+s` confirms the set, an empty set clears it), state and
|
|
274
|
+
priority confirm on `Enter`. Inside a picker, `Esc` returns to the form. `ctrl+s`
|
|
275
|
+
in the form saves every change in a single request; `Esc` closes, asking to
|
|
276
|
+
confirm first if there are unsaved changes.
|
|
277
|
+
|
|
278
|
+
`>` / `<` move the selected issue one step forward / back along its project's
|
|
279
|
+
workflow states, after a confirmation that names the move (`ENG-1: Todo → In
|
|
280
|
+
Progress?`); `y` applies it, `n`/`Esc` cancels. At the first/last state it is a
|
|
281
|
+
no-op with a hint.
|
|
282
|
+
|
|
283
|
+
`n` opens a create modal for a new issue, reusing the same form and pickers. When
|
|
284
|
+
the active view spans several projects it first asks which project to create in
|
|
285
|
+
(a single project is inferred). Fill the fields the same way as editing; a title
|
|
286
|
+
is required. `ctrl+s` creates the issue, `Esc` cancels.
|
|
287
|
+
|
|
288
|
+
`/` filters the loaded rows with a small `key:value` query (no refetch). Bare
|
|
289
|
+
words match the title/key; typed tokens narrow by field:
|
|
290
|
+
|
|
291
|
+
| Token | Matches | Example |
|
|
292
|
+
| -------- | ---------------------------- | --------------- |
|
|
293
|
+
| `ass:` | assignee display name / `me` | `ass:joe` |
|
|
294
|
+
| `state:` | state name (substring) | `state:prog` |
|
|
295
|
+
| `group:` | state group | `group:started` |
|
|
296
|
+
| `prio:` | priority | `prio:high` |
|
|
297
|
+
| `label:` | label name (substring) | `label:bug` |
|
|
298
|
+
| `proj:` | project identifier | `proj:ENG` |
|
|
299
|
+
|
|
300
|
+
Tokens of different keys combine with AND; repeating a key is an OR within it
|
|
301
|
+
(`label:bug label:ui`). An unknown `key:` falls back to a title/key substring.
|
|
302
|
+
The status bar shows how many of the loaded rows match, so an empty result reads
|
|
303
|
+
as "filtered", not "no data".
|
|
304
|
+
|
|
305
|
+
Issue descriptions are stored as HTML on Plane and rendered inline as
|
|
306
|
+
Markdown by a small custom renderer (headings, lists, code, links,
|
|
307
|
+
blockquotes, strikethrough).
|
|
308
|
+
|
|
309
|
+
### Customizing keybindings
|
|
310
|
+
|
|
311
|
+
Drop a `~/.config/plane-cli/keybindings.yaml` file. Each entry maps an action id
|
|
312
|
+
to a key spec. See [`examples/keybindings.yaml`](examples/keybindings.yaml).
|
|
313
|
+
|
|
314
|
+
```yaml
|
|
315
|
+
list.next: down
|
|
316
|
+
list.prev: up
|
|
317
|
+
list.refresh: ctrl+r
|
|
318
|
+
global.help: "?"
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The `?` modal flags overridden bindings with a green `*`.
|
|
322
|
+
|
|
323
|
+
## Logs
|
|
324
|
+
|
|
325
|
+
The TUI cannot print to stderr without corrupting the canvas, so `plc dash` writes
|
|
326
|
+
JSON Lines to `$XDG_STATE_HOME/plane-cli/log.jsonl` (default
|
|
327
|
+
`~/.local/state/plane-cli/log.jsonl`). Render errors caught by the React error boundary
|
|
328
|
+
go to the same file. Rotated to `log.jsonl.1` at ~1 MB.
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
plc log path # print the log file path
|
|
332
|
+
plc log tail -n 100 # last 100 entries
|
|
333
|
+
plc log clear # remove the file
|
|
334
|
+
plc --debug dash # raise log level to debug for the next run
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
CLI commands (everything other than `dash`) continue to log to stderr via `pino`.
|
|
338
|
+
|
|
339
|
+
## Output formats
|
|
340
|
+
|
|
341
|
+
Every list / view command supports `--json`, `--yaml`, and `--limit`. `--debug` enables
|
|
342
|
+
verbose logging and full stack traces.
|
|
343
|
+
|
|
344
|
+
## Development
|
|
345
|
+
|
|
346
|
+
The dev toolchain is managed by [`mise`](https://mise.jdx.dev):
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
mise install # node + pnpm versions pinned in mise.toml
|
|
350
|
+
make bootstrap # install dev toolchain + project deps
|
|
351
|
+
make ci # full pipeline: fmt-check + lint + typecheck + test-cov + build
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Common targets:
|
|
355
|
+
|
|
356
|
+
| Target | Description |
|
|
357
|
+
| ------------------------ | --------------------------------------- |
|
|
358
|
+
| `make bootstrap` | install dev toolchain and dependencies |
|
|
359
|
+
| `make dev ARGS="..."` | run the CLI from source |
|
|
360
|
+
| `make build` | build the production bundle |
|
|
361
|
+
| `make test` | run unit tests |
|
|
362
|
+
| `make test-cov` | run tests with coverage (95% threshold) |
|
|
363
|
+
| `make fmt` / `make lint` | format / lint |
|
|
364
|
+
| `make ci` | full pipeline |
|
|
365
|
+
| `make clean` | remove build artifacts |
|
|
366
|
+
|
|
367
|
+
Run `make help` for the full list.
|
|
368
|
+
|
|
369
|
+
## Troubleshooting
|
|
370
|
+
|
|
371
|
+
- **`api key not found`** — run `plc auth login` for the active profile, or set
|
|
372
|
+
`auth.api_key` inline under the profile.
|
|
373
|
+
- **`config validation failed`** — run `plc config validate` to see the offending path
|
|
374
|
+
reported by `zod`.
|
|
375
|
+
- **TLS errors against self-hosted** — set `server.tls.reject_unauthorized: false` only
|
|
376
|
+
if you intentionally use a private CA.
|
|
377
|
+
- **Stale data** — re-run with `--no-cache` to bypass the cache, or `plc cache clear`.
|
|
378
|
+
|
|
379
|
+
## Contributing
|
|
380
|
+
|
|
381
|
+
See [`docs/CONTRIBUTING.md`](docs/CONTRIBUTING.md) for commit, tag, and PR guidelines.
|
|
382
|
+
|
|
383
|
+
- [`ARCHITECTURE.md`](ARCHITECTURE.md) — how the codebase is organized and why.
|
|
384
|
+
- [`SECURITY.md`](SECURITY.md) — how to report a vulnerability.
|
|
385
|
+
- [`CHANGELOG.md`](CHANGELOG.md) — notable changes per release.
|
|
386
|
+
- [`AGENTS.md`](AGENTS.md) — conventions for humans and LLM agents.
|