@agfpd/iapeer 0.2.98 → 0.2.99
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 +1 -1
- package/docs/01-overview.md +86 -0
- package/docs/02-quickstart.md +110 -0
- package/docs/03-peers-runtimes-identity.md +117 -0
- package/docs/04-lifecycle-and-daemon.md +101 -0
- package/docs/05-system-prompt.md +82 -0
- package/docs/06-storage.md +115 -0
- package/docs/07-install-and-update.md +87 -0
- package/docs/08-peer-management.md +119 -0
- package/docs/09-runtimes.md +103 -0
- package/docs/10-messaging.md +69 -0
- package/docs/11-extensions-and-memory.md +60 -0
- package/docs/12-cli-reference.md +96 -0
- package/docs/13-architecture-and-contract.md +190 -0
- package/docs/14-configuration-and-logs.md +91 -0
- package/docs/README.md +97 -0
- package/docs/ru/01-/320/276/320/261/320/267/320/276/321/200.md +86 -0
- package/docs/ru/02-/320/261/321/213/321/201/321/202/321/200/321/213/320/271-/321/201/321/202/320/260/321/200/321/202.md +110 -0
- package/docs/ru/03-/320/277/320/270/321/200/321/213-/321/200/320/260/320/275/321/202/320/260/320/271/320/274/321/213-/320/270/320/264/320/265/320/275/321/202/320/270/321/207/320/275/320/276/321/201/321/202/321/214.md +117 -0
- package/docs/ru/04-/320/266/320/270/320/267/320/275/320/265/320/275/320/275/321/213/320/271-/321/206/320/270/320/272/320/273-/320/270-/320/264/320/265/320/274/320/276/320/275.md +101 -0
- package/docs/ru/05-/321/201/320/270/321/201/321/202/320/265/320/274/320/275/321/213/320/271-/320/277/321/200/320/276/320/274/320/277/321/202.md +82 -0
- package/docs/ru/06-/321/205/321/200/320/260/320/275/320/265/320/275/320/270/320/265.md +115 -0
- package/docs/ru/07-/321/203/321/201/321/202/320/260/320/275/320/276/320/262/320/272/320/260-/320/270-/320/276/320/261/320/275/320/276/320/262/320/273/320/265/320/275/320/270/320/265.md +87 -0
- package/docs/ru/08-/321/203/320/277/321/200/320/260/320/262/320/273/320/265/320/275/320/270/320/265-/320/277/320/270/321/200/320/260/320/274/320/270.md +119 -0
- package/docs/ru/09-/321/200/320/260/320/275/321/202/320/260/320/271/320/274/321/213.md +103 -0
- package/docs/ru/10-/320/276/320/261/320/274/320/265/320/275-/321/201/320/276/320/276/320/261/321/211/320/265/320/275/320/270/321/217/320/274/320/270.md +69 -0
- package/docs/ru/11-/321/200/320/260/321/201/321/210/320/270/321/200/320/265/320/275/320/270/321/217-/320/270-/320/277/320/260/320/274/321/217/321/202/321/214.md +60 -0
- package/docs/ru/12-cli-reference.md +96 -0
- package/docs/ru/13-/320/260/321/200/321/205/320/270/321/202/320/265/320/272/321/202/321/203/321/200/320/260-/320/270-/320/272/320/276/320/275/321/202/321/200/320/260/320/272/321/202.md +190 -0
- package/docs/ru/14-/320/272/320/276/320/275/321/204/320/270/320/263/321/203/321/200/320/260/321/206/320/270/321/217-/320/270-/320/266/321/203/321/200/320/275/320/260/320/273/321/213.md +91 -0
- package/docs/ru/README.md +97 -0
- package/package.json +4 -2
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# 07 — Install and update
|
|
2
|
+
|
|
3
|
+
[Русский](ru/07-установка-и-обновление.md) · **English**
|
|
4
|
+
|
|
5
|
+
Install is two steps: lay down the core and prepare the host. You don't bring the daemon up by hand — onboarding does that. Below: both steps, plus updating the core and rolling back.
|
|
6
|
+
|
|
7
|
+
## What you need first
|
|
8
|
+
|
|
9
|
+
- **macOS.** Processes are managed by launchd — there's no other platform yet.
|
|
10
|
+
- **[bun](https://bun.sh)** on `PATH`. The core runs through bun; the package ships no compiled JS.
|
|
11
|
+
- **`~/.local/bin` on `PATH`.** The `iapeer` binary is installed here. Not on `PATH` — the command won't be found after install.
|
|
12
|
+
- **At least one agent runtime** — Claude Code or Codex CLI. Without it, `iapeer create` and `iapeer init` refuse with an explicit error.
|
|
13
|
+
|
|
14
|
+
## Step 1. Install the core
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @agfpd/iapeer
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
A run with no arguments performs the install. It does exactly three things:
|
|
21
|
+
|
|
22
|
+
1. **Layout.** Creates `~/.iapeer/` with all its directories: registry, state, logs, cache, runtime spaces.
|
|
23
|
+
2. **Binary.** Builds `iapeer` and atomically places it in `~/.local/bin/iapeer`. The previous version stays beside it as `iapeer.prev` — a rollback reserve. Atomicity matters: between the old and new versions there's no moment when a stub sits at the path.
|
|
24
|
+
3. **Daemon plist.** Writes `~/Library/LaunchAgents/com.agfpd.iapeer.plist` but **doesn't load** it. Loading is taken on by `iapeer onboard` (step 2): on a running host the daemon may already be spinning, and migrating to a new binary is a deliberate act, not a side effect of install.
|
|
25
|
+
|
|
26
|
+
On install a local binary signature is created once (for macOS permissions). The signature is stable and survives updates.
|
|
27
|
+
|
|
28
|
+
The install is idempotent — a re-run is safe.
|
|
29
|
+
|
|
30
|
+
The same operation is available as `iapeer install` if the binary is already in place. But the very first time is always through `npx` or from source: a compiled binary can't rebuild itself.
|
|
31
|
+
|
|
32
|
+
## Step 2. Prepare the host
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
iapeer onboard
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`onboard` prepares the host for agents. First of all it **brings up the daemon** — loads the plist into launchd, after which launchd holds the daemon itself, bringing it back up on a crash and a reboot. No separate `launchctl` command is needed for this. Then onboard registers the extension marketplace in Claude and Codex, provisions the scheduler (`notifier`), creates a human peer on Telegram, and installs the memory provider. The daemon start always runs; the rest are on by default and turned off with flags (`--no-notifier`, `--no-telegram`, `--no-memory`).
|
|
39
|
+
|
|
40
|
+
Check the daemon is alive and the host is ready:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
iapeer status
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You'll see the core version, the daemon's health, and the memory-slot state. The exit code is non-zero if the daemon is unhealthy — handy for check scripts.
|
|
47
|
+
|
|
48
|
+
A breakdown of each onboard step is in [02 — Quick start](02-quickstart.md); runtimes and memory in detail — in [09](09-runtimes.md) and [11](11-extensions-and-memory.md).
|
|
49
|
+
|
|
50
|
+
## Updating the core
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
iapeer update # to the latest version
|
|
54
|
+
iapeer update 0.2.80 # to a specific version
|
|
55
|
+
iapeer update --force # reinstall even if the version is already in place
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
An update pulls the core version from npm and restarts the daemon onto it. Step by step:
|
|
59
|
+
|
|
60
|
+
1. Learns the target version from npm. If the installed one already matches and there's no `--force` — it does nothing.
|
|
61
|
+
2. Downloads and builds the new binary. This is done via `npm pack`, not `npx`: the binary doesn't rebuild itself.
|
|
62
|
+
3. Restarts the daemon onto the new binary.
|
|
63
|
+
4. Restarts the infrastructure launchd jobs the core owns (`com.iapeer.*` — the `timer`, `watcher`, Telegram routers), moving them onto the just-replaced binary. Their plists launch the same core binary as the daemon — without a restart a job would stay on the old code. It's the job that's restarted (booted out and back in); the runtimes' **package code** (telegram-runtime, notifier-runtime) is not updated by this — that's the separate `update-runtime` operation. The memory provider (`iapeer-memory`) and ordinary peers' plists aren't touched at all. If the daemon didn't come up in step 3, this step is skipped — no point moving jobs onto a binary the daemon didn't revive on.
|
|
64
|
+
5. Verifies the daemon actually came up healthy — not by the restart command exiting successfully, but by a probe: it connects to the daemon's socket (the one the daemon opens right before the readiness signal). The daemon counts as healthy only when the socket accepts a connection **twice in a row** — a single probe would also pass for a daemon that bound the socket and immediately crashed. The check is given 15 seconds; if a run of successful probes doesn't accumulate in that time, the command says so directly and advises rolling back.
|
|
65
|
+
|
|
66
|
+
An update touches **only the core**. The daemon plist and third-party packages stay as they are.
|
|
67
|
+
|
|
68
|
+
Naming a specific version (`iapeer update <version>`) is useful when you need to roll back deeper than one step, or pin a proven version.
|
|
69
|
+
|
|
70
|
+
## Rollback
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
iapeer rollback
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Restores the previous binary (that `iapeer.prev`), restarts the daemon onto it, and checks health. Rollback goes **one level back** — one previous version is kept. It's a local "undo the last update" while a fixed version is published to npm. To go deeper — `iapeer update <version>`.
|
|
77
|
+
|
|
78
|
+
## If the daemon didn't come up after an update
|
|
79
|
+
|
|
80
|
+
The sign — `iapeer status` shows an unhealthy daemon, or `update` finished with a warning. The order of action:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
iapeer rollback # return to the prior binary
|
|
84
|
+
# if the daemon is still down — bring it up by hand:
|
|
85
|
+
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.agfpd.iapeer.plist
|
|
86
|
+
iapeer status # confirm it's revived
|
|
87
|
+
```
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# 08 — Peer management
|
|
2
|
+
|
|
3
|
+
[Русский](ru/08-управление-пирами.md) · **English**
|
|
4
|
+
|
|
5
|
+
The everyday operations on peers. Full command signatures with flags are in [12 — CLI](12-cli-reference.md); here — when and why to use each.
|
|
6
|
+
|
|
7
|
+
## Creating a peer: create and init
|
|
8
|
+
|
|
9
|
+
A peer can be created with two commands, and the difference between them is where the working folder comes from.
|
|
10
|
+
|
|
11
|
+
**`iapeer create <name>`** creates a peer from anywhere. The command picks the folder itself (by default `~/.iapeer/peers/<name>`, or pass `--path`), scaffolds it, and initializes it. This is the main way.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
iapeer create assistant --runtime claude
|
|
15
|
+
iapeer create reviewer --runtime codex --description "Code and architecture review"
|
|
16
|
+
iapeer create helper --path ~/projects/helper --runtime claude
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**`iapeer init`** turns the current (or a given) folder into a peer. Handy when a peer already has its own project folder and you want `.iapeer/` to appear right inside it.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd ~/projects/myagent
|
|
23
|
+
iapeer init
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The peer name is taken from the folder name (the normalized `basename` — `myagent`); there's no separate flag for the name — to name a peer otherwise, rename the folder. The runtime is detected automatically from the installed ones (Claude/Codex), and set with `--runtime` when needed.
|
|
27
|
+
|
|
28
|
+
In essence `create` is "pick/create a folder, then `init`". Both commands perform the same peer setup:
|
|
29
|
+
|
|
30
|
+
- write the profile `peer-profile.json`;
|
|
31
|
+
- enter the peer into the registry;
|
|
32
|
+
- set up the connection to the daemon over MCP (so the agent gets `send_to_peer`);
|
|
33
|
+
- create the doctrine file `IAPEER.md` to be filled in;
|
|
34
|
+
- if the memory slot is claimed — attach shared memory and do the related runtime setup.
|
|
35
|
+
|
|
36
|
+
No-clobber protection: if another peer already lives in the target folder (a different name in the profile), the command refuses rather than overwrite.
|
|
37
|
+
|
|
38
|
+
The `--no-bootstrap` flag creates a peer without starting its service (relevant for infrastructure peers, which otherwise come up under launchd right away).
|
|
39
|
+
|
|
40
|
+
## Reviewing peers: list
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
iapeer list # a table
|
|
44
|
+
iapeer list --json # machine-readable format
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Each line is a peer, its runtimes, and the state of each: `●` alive, `○` asleep, `✕` stopped. Run in an interactive terminal, `list` opens a control panel (arrows to navigate, Enter to attach, `/` to search, `q` to quit); in non-interactive mode or with `--json` it prints a table for scripts.
|
|
48
|
+
|
|
49
|
+
## Integrity check: verify
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
iapeer verify # report divergences
|
|
53
|
+
iapeer verify --fix # repair
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`verify` checks two things: profiles' conformance to the standard, and divergences between the registry and the local profiles. With `--fix` the command self-heals the registry from the profiles (and migrates profiles from the old runtime-field format to the current one along the way). It's the standard way to put the registry in order if it's gone out of sync.
|
|
57
|
+
|
|
58
|
+
## Stop and start: stop, start
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
iapeer stop assistant # stop
|
|
62
|
+
iapeer start assistant # clear the stop
|
|
63
|
+
iapeer stop --all # stop all peers
|
|
64
|
+
iapeer start --all # clear the stop on all
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Behavior depends on the runtime class:
|
|
68
|
+
|
|
69
|
+
- **A claude/codex agent.** `stop` sets a persistent stop flag and closes the session — the daemon won't wake the peer until `start` is run. `start` clears the flag: the peer can be woken again, but **the session doesn't start right away** — the daemon brings it up on the first message.
|
|
70
|
+
- **An infrastructure peer.** `stop` removes the service from launchd, `start` loads it back.
|
|
71
|
+
|
|
72
|
+
You can address a specific runtime: `iapeer stop assistant codex`.
|
|
73
|
+
|
|
74
|
+
## Unconditional restart: new
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
iapeer new assistant
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`new` is the emergency lever for a hung or dead session. Unlike the soft path (where a live agent reads the command itself and restarts), `new` acts mechanically, around the agent: it cleanly tears down the current session and brings up a fresh one. The command reports success only when the fresh session has actually come up and is ready. It's the rescue for when an agent is hung so badly it reads nothing.
|
|
81
|
+
|
|
82
|
+
## Lazy soft restart: refresh
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
iapeer refresh assistant # mark a peer for a fresh start on its next wake
|
|
86
|
+
iapeer refresh --all # mark all peers
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`refresh` is the deferred lever for "the doctrine was updated, I want the team to re-read it, but without a jolt". It **only sets a marker**: it doesn't kill a live session, doesn't wake sleepers, doesn't do an immediate restart. The effect arrives on its own — on the peer's **next natural wake** (after a normal idle sleep, or on the next message to a sleeper): instead of resuming the prior conversation, a fresh session comes up that re-reads the doctrine and fragments from disk.
|
|
90
|
+
|
|
91
|
+
Why it's separate from `new`: `new` is immediate and hard (it tears the current work), and a bulk `new --all` would bring the whole team up at once. `refresh` is lazy and soft: it doesn't touch live work, there's no wake spike, freshness reaches each peer at its usual bring-up moment. The marker overrides "resume" for **all** peer types, including humans-on-Telegram, who otherwise resume the old session until an explicit `/new`. Routers (notifier/telegram) and absent runtimes are skipped — they have no doctrine. Using `refresh` is a deliberate operator step (e.g. after releasing an updated doctrine), not an automatic side effect.
|
|
92
|
+
|
|
93
|
+
## Removal: remove
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
iapeer remove assistant # remove the registry entry
|
|
97
|
+
iapeer remove assistant --force # remove even if the peer is currently alive
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`remove` deletes a peer's registry entry. Important properties:
|
|
101
|
+
|
|
102
|
+
- **It doesn't delete the folder.** The peer's data (its `.iapeer/`, history, memory) stays on disk — `remove` clears only the registry entry. If the folder was disposable, delete it yourself.
|
|
103
|
+
- **It refuses a live peer.** Removing a running peer's entry would sever its session from routing. The command refuses while the peer is alive; you can override with `--force`, but it's cleaner to stop it first.
|
|
104
|
+
- **Idempotent.** Removing a non-existent peer isn't an error — it's a successful no-op.
|
|
105
|
+
|
|
106
|
+
The typical use is to clean up "zombie" entries left from finished disposable peers.
|
|
107
|
+
|
|
108
|
+
## Attaching to a session: attach
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
iapeer attach assistant
|
|
112
|
+
iapeer attach assistant codex # a specific runtime
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`attach` brings the peer up if it's asleep, resumes its prior conversation, and opens its session under the supervisor right in your terminal — like an ordinary console. Detach with `Ctrl-]` — or simply close the terminal; the session keeps running in the background either way, and `iapeer attach` reconnects you whenever you need it.
|
|
116
|
+
|
|
117
|
+
## The boundary: a foreign launchd is off-limits
|
|
118
|
+
|
|
119
|
+
The commands `stop`, `start`, `new` **refuse to touch peers managed by a foreign launchd plist** (e.g. live peers under a separate lifecycle manager). For such peers the core is only a reader: intervening would mean a fight with launchd over process control. This is a deliberate guard, not a limitation — it keeps the core from accidentally breaking what it doesn't own.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# 09 — Runtimes
|
|
2
|
+
|
|
3
|
+
[Русский](ru/09-рантаймы.md) · **English**
|
|
4
|
+
|
|
5
|
+
A runtime is a peer's mode of presence. The core knows four: `claude`, `codex`, `telegram`, `notifier`. The first two are AI agents, the last two infrastructure routers.
|
|
6
|
+
|
|
7
|
+
## Agent runtimes: claude, codex
|
|
8
|
+
|
|
9
|
+
The daemon brings an agent up on an incoming message, in a session under the pty supervisor, and closes it after an hour idle. The system prompt ([05](05-system-prompt.md)) is swapped through the runtime's standard mechanism.
|
|
10
|
+
|
|
11
|
+
**Claude** launches with the `--system-prompt-file` flag, which replaces the built-in system prompt with the text assembled by iapeer. Conversation resume goes through `--continue` (the last session is taken), not by selecting a session by id. The conversation history lives in `~/.claude/projects/<folder>/*.jsonl`.
|
|
12
|
+
|
|
13
|
+
**Codex** gets the prompt via the `model_instructions_file` setting (`codex -c model_instructions_file=<file>`), which replaces the compiled base instructions. Resume is `codex resume --last`. History is in `~/.codex/sessions/`. Codex's connection to the daemon over MCP goes through a token header: Codex gates HTTP-MCP behind a token check, so the core supplies a non-functional "empty" token (it carries no protective role), and the daemon identifies the peer by the `X-IAPeer-Identity` header.
|
|
14
|
+
|
|
15
|
+
When setting up Codex, the core host-wide silences the startup update-check dialog (`check_for_update_on_startup = false` in `config.toml`). The interactive "an update is available, press Enter" is a gate at load: a Codex session the daemon woke headless would hang on it, never reaching readiness, until a human connected. Codex updates stay manual (`codex update` or brew) rather than a prompt at startup. A value the operator already set, the core doesn't rewrite.
|
|
16
|
+
|
|
17
|
+
Launch an agent in the current folder by hand, as a fresh session:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
iapeer claude # bring up the current folder's peer on Claude
|
|
21
|
+
iapeer codex # on Codex
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Fine launch flags for a specific peer+runtime pair are set in `<folder>/.iapeer/runtimes/<runtime>/launch.env` ([06](06-storage.md)).
|
|
25
|
+
|
|
26
|
+
### Which runtime is chosen when a peer is created
|
|
27
|
+
|
|
28
|
+
When a peer is created (`create` / `init`), the primary runtime is resolved by layers:
|
|
29
|
+
|
|
30
|
+
1. An explicit `--runtime` — it's taken, but first it's checked that the runtime is installed; if not, the command finishes with a clear error rather than silently accepting a non-existent runtime.
|
|
31
|
+
2. Otherwise — a marker in the folder (`.claude` / `.codex`): a folder already configured for a runtime keeps it. The marker takes priority and isn't re-checked for installedness.
|
|
32
|
+
3. Otherwise — from those installed on the host: exactly one installed — it; none — a clear error (not a silent Claude on a host with no runtime); both installed — Claude by default (choose Codex explicitly with `--runtime codex`).
|
|
33
|
+
|
|
34
|
+
The core determines a runtime's installedness by its launcher — the `IAPEER_<RUNTIME>_BIN` variable, the native installer location, then `PATH` — and by the global config directory (`~/.claude` / `~/.codex`).
|
|
35
|
+
|
|
36
|
+
## Infrastructure runtimes: telegram, notifier
|
|
37
|
+
|
|
38
|
+
These are long-lived router processes, not AI. They run continuously under launchd; the daemon doesn't bring them up or sleep them.
|
|
39
|
+
|
|
40
|
+
**telegram** — the Telegram ↔ peers bridge. It requires the peer to be of `natural` nature (a human): an attempt to give an AI agent a Telegram presence is rejected. It runs as `telegram-runtime run`.
|
|
41
|
+
|
|
42
|
+
**notifier** — the scheduler. It provisions the service peers `timer` (fires on a schedule) and `watcher` (watches for events). It runs as `notifier-runtime run`.
|
|
43
|
+
|
|
44
|
+
Infrastructure peers' plists, unlike the daemon's plist, are **loaded automatically** on `iapeer create --runtime telegram|notifier` and on `iapeer onboard` (unless `--no-bootstrap` is passed). So no separate `launchctl bootstrap` is needed for them.
|
|
45
|
+
|
|
46
|
+
## Several runtimes on a peer
|
|
47
|
+
|
|
48
|
+
A peer profile has two runtime fields: `runtimes` (all the peer can be present on) and `default_runtime` (the primary — where messages route and which comes up first). Extending the options and switching the primary runtime are different commands.
|
|
49
|
+
|
|
50
|
+
### add-runtime — add a runtime
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
iapeer add-runtime codex --peer assistant # for one peer
|
|
54
|
+
iapeer add-runtime codex --all # for all peers
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Adds an agent runtime to existing peers: appends it to `runtimes`, scaffolds the runtime space, and does the full Codex setup (pre-trusting the folder, the native-memory lever, attaching memory, MCP). The command **doesn't touch** `default_runtime` — this is extending options, not changing routing. Idempotent. Infrastructure peers are skipped: giving them an agent runtime is an operator decision via `create`, not a bulk operation.
|
|
58
|
+
|
|
59
|
+
### default-runtime — switch the primary
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
iapeer default-runtime codex --peer assistant # for one
|
|
63
|
+
iapeer default-runtime codex --all # all peers
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Switches the primary runtime — where routing goes and which runtime comes up first. A bulk `--all` switch is exactly the "transplant" of the whole team from one agent runtime to another; it's symmetric back (`default-runtime claude --all`). The command refuses if the peer doesn't declare that runtime (run `add-runtime` first), and skips infrastructure peers. The registry is fixed in the same command, so routing switches at once.
|
|
67
|
+
|
|
68
|
+
## Connecting Telegram: connect
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
iapeer connect telegram assistant --token <bot-token>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Connects a Telegram bot to a peer in one pass: registers the bot, writes it into the peer profile, restarts the router. From a human only the token is needed (from `@BotFather`). Afterward — send the bot the **first message**: Telegram doesn't let a bot start a chat first, so the channel comes alive only after an inbound message from a human.
|
|
75
|
+
|
|
76
|
+
The command is idempotent, and behavior depends on the token. **The same token** is a clean no-op: the bot's credentials file doesn't change, the live router already runs on these credentials, no restart. **A new or changed token** requires a router restart (credentials are read only at start) — a second's pause in delivery for all Telegram peers; inbound messages aren't lost (long-polling remembers the offset, Telegram holds messages for up to 24 hours). An invalid token is rejected at once, with the verbatim reason from Telegram.
|
|
77
|
+
|
|
78
|
+
## Runtime packages
|
|
79
|
+
|
|
80
|
+
Infrastructure runtimes ship as separate npm packages (`*-runtime`). The core installs and updates them:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
iapeer install-runtime notifier # install the package + provision its peers
|
|
84
|
+
iapeer update-runtime notifier # update with a peer restart
|
|
85
|
+
iapeer update-runtime --all # update all runtime packages
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`install-runtime` installs the package and provisions the peer set it declares (notifier provisions `timer` and `watcher`); the `--no-bootstrap` flag installs the package without loading its peers into launchd. A runtime with no declared set (telegram) is "operator-adds" mode: peers are set up by hand with `iapeer create <name> --runtime telegram`.
|
|
89
|
+
|
|
90
|
+
### Deploying a runtime package: always from the cloud, like `iapeer update`
|
|
91
|
+
|
|
92
|
+
A runtime package's delivery follows the same **release ≠ deploy** split as the core (see [07 — Install and update](07-install-and-update.md)). `iapeer update-runtime <runtime>` is the runtime analog of `iapeer update`: a deploy to the host **from the cloud (npm)**, not from a local working tree. It:
|
|
93
|
+
|
|
94
|
+
1. **version-gate** — compares npm-latest with the `version` stamp in the manifest (`~/.iapeer/runtimes/<runtime>/runtime.json`); latest installed → no-op (`already-latest`). With no stamp the gate is skipped and reinstall runs idempotently (and says so) — so stamping `version` in the manifest is the package's duty (otherwise both `update-runtime` gating and version observability in `iapeer status` and the per-peer version audit break).
|
|
95
|
+
2. **re-install** — a forced `npx @agfpd/<runtime>-runtime`: **pulls the package from npm** and lets it self-deploy the new binary + manifest. The source is the published package, **not** a working tree.
|
|
96
|
+
3. **re-provision** — idempotently the same path as `install-runtime` (sync descriptions, re-self-config; live peers aren't clobbered).
|
|
97
|
+
4. **restart** — restarts the runtime's registered peers with ordinary `stop`/`start`. They're **host-aware**: a peer under the supervisor is shut down correctly (kill the supervisor and the child) and brought back up on the new binary — no separate procedure needed.
|
|
98
|
+
|
|
99
|
+
Accordingly, **a local self-install from a working tree is a dev probe, not a deploy** (the analog of running the core from source, `bun src/...`). Deploy a runtime from the cloud: first release (`npm publish` — to the cloud, live hosts untouched), then deploy (`update-runtime` — pull from the cloud + restart). Bypassing npm (activating local code via self-install) desyncs npm and the host and isn't reproducible on a new host: a clean host brings a runtime up only from npm (`install-runtime`, then `update-runtime`). The core (`iapeer update`) deliberately does NOT touch runtime packages — the symmetry is intentional.
|
|
100
|
+
|
|
101
|
+
A runtime launcher's path resolves from `PATH` by default; override it (e.g. for launchd with a minimal `PATH`) with the `NOTIFIER_RUNTIME_BIN` / `TELEGRAM_RUNTIME_BIN` variables — see [14 — Configuration and logs](14-configuration-and-logs.md).
|
|
102
|
+
|
|
103
|
+
The runtime package contract — the manifest format, the duties, the commands — is in [13 — Architecture and contract](13-architecture-and-contract.md).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# 10 — Messaging
|
|
2
|
+
|
|
3
|
+
[Русский](ru/10-обмен-сообщениями.md) · **English**
|
|
4
|
+
|
|
5
|
+
Peers exchange messages over the IAP protocol. Agents do it with the `send_to_peer` tool, the operator with the `iapeer send` command. Both paths go through the same daemon routing.
|
|
6
|
+
|
|
7
|
+
## Two ways to send
|
|
8
|
+
|
|
9
|
+
**`send_to_peer`** — the MCP tool every claude/codex agent gets (through its connection to the daemon). This is the main channel between agents.
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
send_to_peer(personality="assistant", message="...", topic?, attachments?)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**`iapeer send`** — sending from the command line, the same delivery path around the agent session (works even when the daemon's HTTP listener is unavailable):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
iapeer send assistant --message "text"
|
|
19
|
+
iapeer send assistant --message-file ./report.md # body from a file; '-' = stdin
|
|
20
|
+
iapeer send assistant --message "see attachment" --attachment ./report.pdf
|
|
21
|
+
iapeer send assistant --message "..." --topic "release" --from claude-reviewer
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The `--from` flag sets the sender (`<runtime>-<name>`); by default it's the identity of the current folder's peer. `--attachment` is repeatable. `--message-file`, with the body from a file or stdin, is handy for long, multi-line messages.
|
|
25
|
+
|
|
26
|
+
## The delivery path
|
|
27
|
+
|
|
28
|
+
The daemon finds the recipient in the registry and looks at its state:
|
|
29
|
+
|
|
30
|
+
- **alive** — the daemon writes the message into the peer's session through the supervisor socket onto the session's input. The daemon waits for confirmation that the input was accepted (the input field cleared); the answer is `delivered`.
|
|
31
|
+
- **busy** (the agent is mid-turn, the input field isn't empty) — the message goes into the composer queue and is delivered as soon as the agent is free; the answer to the sender is `queued`. The queue waits up to 120 seconds, then force-delivers.
|
|
32
|
+
- **asleep** — the daemon brings the session up, re-checks the recipient, delivers; the answer is `delivered, woke`.
|
|
33
|
+
- **stopped** — the daemon refuses and tells the sender directly.
|
|
34
|
+
|
|
35
|
+
The key guarantee: `delivered` means the message actually landed in a live session (the daemon waited for confirmation), not that it was just sent into the void. If the session didn't confirm receipt in the allotted time, the daemon treats it as dead and doesn't lie about success.
|
|
36
|
+
|
|
37
|
+
In every message's envelope travels the sender's nature (`intelligence`) — the recipient knows whether a human, an agent, or a service process is writing to it.
|
|
38
|
+
|
|
39
|
+
## Message limits
|
|
40
|
+
|
|
41
|
+
The protocol has bounds: the message body — up to 16000 characters, the topic — up to 200, attachments — up to 20 at a time. A message over the limit must be split or its content passed as a file attachment.
|
|
42
|
+
|
|
43
|
+
## Sending into Telegram only with your own bot
|
|
44
|
+
|
|
45
|
+
A peer can't send messages into the Telegram domain if it has no bound Telegram bot. This is a guard: a reply to a human goes through a specific agent's bot, and a peer without such a binding doesn't write into Telegram — delivery to it is refused. The binding is made with `iapeer connect telegram` (see [09 — Runtimes](09-runtimes.md)).
|
|
46
|
+
|
|
47
|
+
## Control commands
|
|
48
|
+
|
|
49
|
+
Session control goes on a separate channel from messages — it's not text into the conversation but a control action:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
iapeer interrupt assistant # interrupt the current turn (Escape), context intact
|
|
53
|
+
iapeer compact assistant # compact the conversation
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`interrupt` stops a turn that's gone off-track or hung, without losing context. `compact` compacts the conversation; if the peer is asleep after a clean sleep, the daemon resumes it first, then compacts, and for an empty session it honestly answers that there's nothing to compact.
|
|
57
|
+
|
|
58
|
+
Separate are **aliases** — shortcuts like `/alias-new`, `/alias-compact`, defined in the peer profile. These aren't control but text templates: the runtime expands an alias into its full text and delivers it to the peer as an ordinary message. So with one short command the operator runs a pre-written scenario (e.g. "save state to memory and restart fresh").
|
|
59
|
+
|
|
60
|
+
## Ephemeral workers
|
|
61
|
+
|
|
62
|
+
A peer with the wake policy `wake_policy: "ephemeral"` in its profile works differently from an ordinary agent — it's meant for one-off tasks:
|
|
63
|
+
|
|
64
|
+
- **always fresh.** Each task brings up a fresh session, with no resume of the past conversation.
|
|
65
|
+
- **one at a time.** Tasks aren't delivered into a live session in parallel — they line up in a durable queue (it survives a daemon restart) and are processed strictly one by one. The daemon feeds the queue on supervisory ticks.
|
|
66
|
+
- **finish after the reply.** Having sent its single reply, the worker is marked for a quiet wind-down: a quiet window (20 seconds by default) — and the daemon closes the session, waking no one. If the worker stays silent and doesn't reply, a safety bound (600 seconds by default) winds it down anyway.
|
|
67
|
+
- **`iapeer self-done`** — a self-call by a worker that has nothing to send: it frees the queue for the next task without waking anyone. The doctrine is "nothing to send — `self-done` instead of an empty reply".
|
|
68
|
+
|
|
69
|
+
This model keeps a stream of one-off tasks orderly: one worker per task, with no accumulation of parallel sessions and no empty wakes of the recipient.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# 11 — Extensions and memory
|
|
2
|
+
|
|
3
|
+
[Русский](ru/11-расширения-и-память.md) · **English**
|
|
4
|
+
|
|
5
|
+
A peer's capabilities are extended by two kinds of package: capability plugins (abilities — tools, context) and runtime packages (modes of presence). Memory is a separate mechanism: one provider per host, through a slot. This section is how to use these; the contract for package developers is in [13 — Architecture and contract](13-architecture-and-contract.md).
|
|
6
|
+
|
|
7
|
+
## Capability plugins: enable
|
|
8
|
+
|
|
9
|
+
A plugin adds an ability to a peer without being a runtime — shared memory, speech synthesis, a set of skills. Attaching it:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
iapeer enable peer-voice assistant # attach a plugin to a peer
|
|
13
|
+
iapeer enable iapeer-memory # to the current folder's peer
|
|
14
|
+
iapeer enable peer-voice assistant --no-setup # without the plugin's setup step
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The command installs the plugin on each of the peer's agent runtimes (for Claude — into the project scope inside the peer's folder, for Codex — into the host's shared config), enables it, and, if the plugin declares a setup step, runs it. The operation is idempotent and doesn't disturb other peers: for Claude the binding is by the specific peer's folder.
|
|
18
|
+
|
|
19
|
+
A runtime's local marketplace snapshot may lag: a plugin registered in the marketplace after the host's last sync reads as an "unknown plugin" until then. So if the install doesn't go through the first time, `enable` refreshes the runtime's marketplace snapshot (Claude — `marketplace update`, Codex — `marketplace upgrade`) and retries once.
|
|
20
|
+
|
|
21
|
+
## The memory slot
|
|
22
|
+
|
|
23
|
+
Memory in iapeer is a first-class option, not a built-in feature. It's provided by a **memory provider** — a separate package that claims the single per-host slot. The slot is either occupied by exactly one provider or empty; an empty slot is a normal state.
|
|
24
|
+
|
|
25
|
+
The default provider is installed at onboarding:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
iapeer onboard # installs the default provider if the slot is empty
|
|
29
|
+
iapeer onboard --memory <package> # specify a different provider
|
|
30
|
+
iapeer onboard --no-memory # leave the slot empty
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The slot state is shown by `iapeer status` — the provider name, version, and the freshness of its daemon (or `none` if the slot is empty).
|
|
34
|
+
|
|
35
|
+
The reference provider is **iapeer-memory** (`@agfpd/iapeer-memory`), also the default. It's the team's shared memory: a knowledge canon (knowledge, decisions, ideas, projects, lists) and each agent's personal operative memory, with search over content and the link graph. Memory reaches an agent two ways — a map of its notes lands in the system prompt as a fragment layer ([05 — System prompt](05-system-prompt.md)), and it reads and writes through the provider's MCP tools. Details — in iapeer-memory's own documentation.
|
|
36
|
+
|
|
37
|
+
When the slot is claimed, the core calls the provider's commands at a peer's life events: at peer birth (`birth`) — to set up its memory, at removal (`remove`) — to wind it down. The provider manifest format, the commands, and the call moments — in [13](13-architecture-and-contract.md).
|
|
38
|
+
|
|
39
|
+
### A runtime's native memory
|
|
40
|
+
|
|
41
|
+
Claude and Codex have their own built-in memory. When the slot is occupied by a provider, keeping the runtime's native memory in parallel is harmful — a second, uncurated set accumulates. So at the birth of a peer with a claimed slot, the core automatically turns off its runtimes' native memory.
|
|
42
|
+
|
|
43
|
+
To manage this by hand:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
iapeer native-memory off --peer assistant # turn off native memory
|
|
47
|
+
iapeer native-memory on --peer assistant # restore it
|
|
48
|
+
iapeer native-memory off --all # across all peers
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Runtime packages
|
|
52
|
+
|
|
53
|
+
Modes of presence (Telegram, the scheduler) ship as runtime packages:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
iapeer install-runtime notifier # install + provision the declared peers
|
|
57
|
+
iapeer update-runtime --all # update all runtime packages
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`install-runtime` installs the package and provisions the peer set it declares (notifier — `timer` and `watcher`). A runtime with no declared set (telegram) has its peers set up by the operator with `iapeer create … --runtime telegram`. Telegram connection details — [09 — Runtimes](09-runtimes.md); the runtime package contract — [13](13-architecture-and-contract.md).
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# 12 — CLI
|
|
2
|
+
|
|
3
|
+
[Русский](ru/12-cli-reference.md) · **English**
|
|
4
|
+
|
|
5
|
+
The full list of `iapeer <command>`. General rules: `--help` / `-h` anywhere in the line prints help and runs nothing; flags take both `--key value` and `--key=value` forms.
|
|
6
|
+
|
|
7
|
+
## Core install and maintenance
|
|
8
|
+
|
|
9
|
+
| Command | What it does |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `install` | Core bootstrap: build the `~/.local/bin/iapeer` binary, create the `~/.iapeer/` layout, write the daemon plist (without loading). |
|
|
12
|
+
| `update [version] [--force]` | Update the core from npm, restart the daemon, restart the core's infra services. With no argument — to the latest version; with a version — to the exact one. `--force` reinstalls even when versions match. |
|
|
13
|
+
| `rollback` | Restore the previous binary (`.prev`), restart the daemon, check health. One level back. |
|
|
14
|
+
| `version` · `--version` · `-v` | The installed binary's version. |
|
|
15
|
+
| `help` · `--help` · `-h` | Command help. |
|
|
16
|
+
| `daemon [--install-plist]` | Run the router (normally held by launchd). `--install-plist` only writes the plist, without loading. |
|
|
17
|
+
|
|
18
|
+
`update` checks the daemon's health after the restart: if the daemon didn't revive, the command returns a non-zero code and advises `rollback`.
|
|
19
|
+
|
|
20
|
+
## Host
|
|
21
|
+
|
|
22
|
+
| Command | What it does |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `onboard [flags]` | Host setup: marketplace (Claude + Codex) → notifier → Telegram (human peer) → memory provider. All steps on by default. |
|
|
25
|
+
| `status` | Host snapshot: version, daemon health, memory slot. Non-zero exit code if the daemon is unhealthy. |
|
|
26
|
+
| `install-runtime <runtime> [--package <pkg>] [--npx] [--no-bootstrap]` | Install a runtime package and provision its declared peer set. `--no-bootstrap` — don't load peers into launchd. |
|
|
27
|
+
| `update-runtime <runtime> \| --all [--force]` | Update runtime package(s) with a restart of their peers. |
|
|
28
|
+
|
|
29
|
+
`onboard` flags: `--dry-run` (show without changes), `--no-notifier`, `--no-telegram`, `--no-memory`, `--telegram-human <name>`, `--telegram-user-id <id>`, `--memory <pkg>`, `--infra <list>`.
|
|
30
|
+
|
|
31
|
+
## Peers: creation and review
|
|
32
|
+
|
|
33
|
+
| Command | What it does |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `init [folder] [--runtime r] [--description d] [--bin path] [--no-bootstrap]` | Make the current (or a given) folder a peer: profile, MCP, doctrine. The peer name is the folder name; not set by a separate flag. |
|
|
36
|
+
| `create <name> [--runtime r] [--path folder] [--bin path] [--description d] [--intelligence i] [--no-bootstrap]` | Create a peer from anywhere (by default `~/.iapeer/peers/<name>`) and provision it. |
|
|
37
|
+
| `list [--json]` | The peer registry with per-runtime state. In an interactive terminal — a control panel; otherwise or with `--json` — a table. |
|
|
38
|
+
| `verify [--json] [--fix]` | Check profiles against the standard and registry↔profile divergences. `--fix` self-heals the registry and migrates the old runtime-field format. |
|
|
39
|
+
|
|
40
|
+
## Peers: lifecycle
|
|
41
|
+
|
|
42
|
+
| Command | What it does |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `stop <peer> [runtime] \| --all` | Agent: stop flag + close the session (the daemon won't wake it). Infra: remove from launchd. `--all` — all peers. |
|
|
45
|
+
| `start <peer> [runtime] \| --all` | Clear the stop flag (the session comes up on the first message). Infra: load into launchd. |
|
|
46
|
+
| `new <peer> [runtime]` | An unconditional fresh restart — the emergency lever for a hung/dead session, around the agent. Code 0 = a fresh session is up and ready. |
|
|
47
|
+
| `refresh <peer> [runtime] \| --all` | A lazy "soft restart": an agent peer comes up fresh (re-reads the doctrine/fragments from disk) on its NEXT natural wake. Doesn't kill a live session, doesn't wake the rest, doesn't do an immediate restart — only sets a marker. Routers (notifier/telegram) and absent runtimes are skipped (they have no doctrine). `--all` — all peers. |
|
|
48
|
+
| `remove <peer> [--force]` | Delete the registry entry (doesn't touch the peer's folder). Refuses a live peer without `--force`. Idempotent. |
|
|
49
|
+
| `attach <peer> [runtime]` | Bring up (if asleep), resume the conversation, open the session under the supervisor in the terminal (detach — `Ctrl-]`). |
|
|
50
|
+
| `interrupt <peer> [runtime]` | Interrupt the current turn (Escape), context intact. |
|
|
51
|
+
| `compact <peer> [runtime]` | Compact the conversation; a sleeper is resumed first after idle sleep. |
|
|
52
|
+
| `add-runtime <runtime> (--peer <p> \| --all)` | Add an agent runtime to existing peers. Doesn't touch `default_runtime`. |
|
|
53
|
+
| `default-runtime <runtime> (--peer <p> \| --all)` | Switch the primary runtime (routing / first bring-up). Refuses if the runtime isn't declared. |
|
|
54
|
+
|
|
55
|
+
`stop`/`start`/`new` refuse to touch peers under a foreign launchd plist — the core doesn't intervene in what it doesn't manage.
|
|
56
|
+
|
|
57
|
+
## Messages
|
|
58
|
+
|
|
59
|
+
| Command | What it does |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `send <target> (--message <text> \| --message-file <f\|->) [--from <id>] [--runtime <r>] [--attachment <path>]… [--topic <t>]` | Manual IAP send over the same path as `send_to_peer`. |
|
|
62
|
+
|
|
63
|
+
`--message-file -` reads the body from stdin. `--attachment` is repeatable. `--from` defaults to the current folder's peer identity.
|
|
64
|
+
|
|
65
|
+
## Launch and runtimes
|
|
66
|
+
|
|
67
|
+
| Command | What it does |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `<runtime>` (e.g. `iapeer claude`) | Launch the current folder's peer on this runtime — always as a fresh session. |
|
|
70
|
+
| `connect telegram <peer> [--token <t>]` | Connect a Telegram bot to a peer: register the bot → write into the profile → restart the router. Asks only for the token. |
|
|
71
|
+
|
|
72
|
+
## Extensions and memory
|
|
73
|
+
|
|
74
|
+
| Command | What it does |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `enable <plugin> [peer] [--no-setup]` | Install and enable a capability plugin for a peer (across runtimes), run its setup step. |
|
|
77
|
+
| `native-memory <off\|on> (--peer <p> \| --all)` | Turn off/restore a peer's runtimes' native memory. |
|
|
78
|
+
| `trust-hooks <hooks.json> [--check]` | Pre-trust codex hooks from a file (without the modal). `--check` — a divergence report. |
|
|
79
|
+
|
|
80
|
+
## Agent self-calls
|
|
81
|
+
|
|
82
|
+
Commands an agent runs in its own session (require `PEER_IDENTITY` in the environment):
|
|
83
|
+
|
|
84
|
+
| Command | What it does |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `self-fresh` | Mark itself for a fresh restart and close its session — the daemon brings up a fresh one. |
|
|
87
|
+
| `self-done` | Ephemeral worker: quietly finish, freeing the queue, waking no one. |
|
|
88
|
+
|
|
89
|
+
## Service
|
|
90
|
+
|
|
91
|
+
| Command | What it does |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `daemon` | The router daemon's entry point; launched from the `com.agfpd.iapeer` plist. |
|
|
94
|
+
| `run-infra <name> <runtime>` | An infrastructure peer's entry point; launched from its launchd plist. |
|
|
95
|
+
|
|
96
|
+
These two commands are invoked by launchd, not the operator directly.
|