# Company OS plugin for Tern

This Tern plugin makes governed Company OS AgentChat the primary entry point
and adds a native operations cockpit for workspace health and the Beads work
queue. The Company Console is available as a separate browser action, and the
optional OMP MCP profile stays read-only.

## Install

From the repository root, link the plugin into Tern:

```sh
tern plugin link ./integrations/tern-companyos
tern plugin list
```

Tern reloads linked plugins when their files change. To remove the link, run:

```sh
tern plugin unlink companyos
```

Every probe and action runs the same Company OS command, resolved in this
order:

1. `HSM_COMPANY_OS_CLI`, when set.
2. The checkout's `scripts/company-os` through `/bin/bash`, where the checkout
   is `HSM_COMPANY_OS_REPO` when set, else the checkout this plugin is linked
   from. The bash launcher works even when a desktop-launched Tern does not
   inherit Node on `PATH`.
3. `$HOME/.local/bin/companyos`.

The same checkout is the Beads workspace. Actions open a new tab in the
focused pane's current directory, when available. For remote Tern sessions,
install the Company OS CLI on that session host or set `HSM_COMPANY_OS_CLI`
there.

## Palette actions

Open the command palette and search for **Company OS**:

| Action | What it does |
|---|---|
| Company OS AgentChat — Start governed chat | Runs `companyos chat` in a new Tern tab. Company OS admits the run and opens the OMP TUI. |
| Open Company OS Console | Opens `/workspace/start` on `127.0.0.1`, using `HSM_COMPANY_CONSOLE_PORT` or port `3050`. It does not start the Company OS services. |
| Check Company OS readiness | Runs `companyos status` in a new tab. |
| Open Company OS cockpit | Opens a native Tern block with workspace health (the API host and the Console checked separately, with a link to the launcher log), Beads queue counts, and a selectable Beads list (top five; `e` shows all, `v` or a count chip switches between ready, in progress, blocked and open). `↑`/`↓` (or `j`/`k`) select a bead and `Enter` (or a double-click) opens it in a governed OMP session, see **Working a bead in OMP**. Press `r` or `F5` (or **↻ Refresh**) to rerun the read-only checks, `l` to open the log, `w` to open the workspace. |
| Open Beads ready work | Opens `br ready` in a Tern tab at the linked checkout or `HSM_COMPANY_OS_REPO`. |
| Open blocked Beads | Opens `br blocked` in a Tern tab at the linked checkout or `HSM_COMPANY_OS_REPO`. |
| Connect OMP to Company OS (read-only) | Runs `companyos mcp connect omp`. This explicit action installs the read-only Company OS MCP profile in OMP. |
| Check OMP connection | Runs `companyos mcp doctor --client omp` in a new tab. |
| Connect Claude Code to Company OS | Runs `companyos mcp connect claude` in a new tab. |
| Connect Codex to Company OS | Runs `companyos mcp connect codex` in a new tab. |
| Start Company OS | Runs `companyos up`, which starts or reuses the local services and opens the Console. |
| Log this computer in to Company OS | Runs `companyos login` in a new tab. |
| Open Company OS approvals waiting on you | Lists pending approvals (`approvals --status pending --table`). Decide them in the Console or with `companyos approval approve\|reject ID --confirm`. |
| Open Company OS tasks | Lists 25 tasks (`task list --table`). |
| Open recent Company OS agent runs | Lists 20 runs (`ps --table`). |
| Open Company OS receipts | Lists receipts (`receipt list --table`). |
| Open Company OS usage and cost | Shows model spend (`cost`). |
| Run Company OS doctor | Runs the client preflight and host checks (`doctor`). |
| Company OS today (prototype): what needs me | Runs `scripts/company-os-today.mjs`: one list of what needs you (login or host problems, approvals, failed runs, tasks waiting on you, P0 Beads, P1 bugs), most urgent first, each with the command that acts on it; then running agents and the next ready Beads. It tries out the cockpit's content before it is ported to the block. |
| Open the Company OS guide | Opens [GUIDE.md](GUIDE.md) in Tern. |
| Ask Carly how Company OS can help me use AI | Opens Carly with that question. |

The approvals, tasks, runs, receipts, usage, and doctor actions run the
checkout's operator CLI, `services/company-os-mcp-server/dist/company-os-cli.js`,
with an explicit Node path, because a desktop-launched Tern has no Node on its
PATH. From a terminal, the same verbs also work as `companyos task list`,
`companyos approvals`, and so on.
Build it once with `npm --prefix services/company-os-mcp-server run build`.

The Tern integration is available from the command palette. The cockpit shows
the Company OS readiness result and a read-only Beads snapshot: open, ready,
in-progress, and blocked counts plus a selectable list of ready, in-progress, blocked, or open issues. The Company OS
readiness probe reports workspace, API, and AgentChat together; the Beads
counts and ready list come from the linked checkout's Beads workspace. Each
section has an explicit loading or error state. Refresh reruns both checks.
Use **Open Beads ready work** or **Open blocked Beads** to continue in the
Beads CLI, and use AgentChat for governed conversational work.
CLI actions leave an interactive shell open after the command finishes so their
output remains visible. `companyos` in the table means the command resolved as
described under **Install**.

The OMP profile is useful for read-only Company OS tools. Use **Company OS
AgentChat — Start governed chat** when a turn needs the host-admitted terminal
run and its Company OS governance receipts.

To add a key binding, add an unused chord to Tern's `settings.json`:

```json
{
  "keybinds": {
    "cmd+shift+g": "plugin.companyos.agentchat"
  }
}
```

Choose a chord that is free in your active Tern preset. User key bindings take
precedence over plugin defaults.

## Carly

The plugin teaches Tern's assistant, Carly, to use Company OS:

- **Context.** Every Carly request carries one line saying Company OS is
  installed, its last known status, and to read the guide first.
- **Guide.** `plugins.companyos.guide()` returns the overview from
  [GUIDE.md](GUIDE.md): what Company OS is, how it helps people use AI, and
  the topic list. `guide("approvals")`, `guide("ai-help")`, and the other
  topics return one section each. Edit GUIDE.md to change what Carly knows;
  topics are the lowercase `## slug` headings.
- **`status()`** checks the API host (`127.0.0.1:3847/api/company/health`) and
  the Console (`/api/agent-chat-runtime` on the Console port) separately and
  says which one is down. Carly is told to state Company OS health only from
  it.
- **Read-only tools.** `tasks`, `task`, `approvals`, `runs`, `run`,
  `receipts`, `verify`, `memory`, `usage`, `beads`, `tools`, `connectors`, and
  `doctor` each run one read command and return
  `{ok, command, output, truncated, stderr, hint}`, with output cut at about
  9 KB. Arguments go to argv, never a shell, and a value starting with `-` is
  refused, so Carly cannot add flags such as `--confirm`.
- **Open.** `open(target, page?)` opens one of this plugin's palette actions or
  a Console page, so a person makes every decision.

Run `help("plugins.companyos")` in Carly to see each tool's signature. Tern
allows 16 exports per plugin; this plugin uses all 16.

Palette actions and Carly's tools register from timers just after load, not
during it: under machine load the window `load` hook has overrun its 50 ms
budget, and a failed load drops every palette action.

**Self-heal.** Tern disables any plugin hook or Carly tool that overruns its
time budget, until `tern plugin reload`, and logs it in
`~/Library/Logs/Tern/tern.log`. The host half checks that log every minute and,
when a Company OS hook was disabled, runs `tern plugin reload` itself: at most
once per 3 minutes and 6 times an hour, after which it stops and logs that the
machine is too loaded. Set `HSM_COMPANY_OS_TERN_SELF_HEAL=0` to turn it off, and
`HSM_TERN_BIN` if `tern` is not at `/Applications/Tern.app` or
`~/.local/bin/tern`. A reload reloads every linked Tern plugin.

Desktop-launched Tern has `PATH=/usr/bin:/bin:/usr/sbin:/sbin`. Carly's tools
find Node through `HSM_COMPANY_OS_NODE`, then `/opt/homebrew/bin/node`, then
`/usr/local/bin/node`.

## Status line

When Tern's status line is on (**Settings → Status line**, `"status_bar": true`),
the plugin adds a **Company OS** segment:

| Segment | Meaning | Click |
|---|---|---|
| `Company OS` | `company-os status` reported a ready workspace URL | Starts governed AgentChat |
| `Company OS not ready` | The last check failed or timed out | Opens the cockpit |
| `Company OS …` | No check has finished yet | Opens the cockpit |
| `Company OS status stale` | No new check result for 3 minutes, so the last one is no longer shown | Opens the cockpit |

The host half runs `company-os status` when the plugin loads and then every
60 seconds, and stores the result (`ready` or `error`, plus a counter) in the
plugin's key/value store. The cockpit's own readiness check stores its result
too. The window half never runs the check: it only re-reads that small store
every 5 seconds and on focus changes and finished commands, and the status
formatter only looks up a segment built at load, with no IO. That keeps it far
under Tern's 4 ms chrome budget in normal use, but not always under heavy
memory pressure: if the formatter overruns, Tern hides the segment until the
plugin reloads (`tern plugin reload`). The host keeps polling, and the cockpit
still works.

Process work stays out of the window half because it runs on Tern's UI thread.
There, spawning the check from a timer went over the 50 ms hook budget when
the machine was under memory pressure, and Tern disabled the timer until the
plugin reloaded, freezing the segment. Because three window hooks keep the
segment current, one disabled hook no longer freezes it, and a stopped host
poller shows as stale instead of an old result.

## Working a bead in OMP

In the cockpit's Beads list, select a bead and press `Enter` (or double-click
it). A new Tern tab runs governed AgentChat with the bead as its first
message:

```sh
companyos chat --start "Work on Beads issue <id>: <title> …"
```

Company OS admits the run as usual, and OMP opens with that message sent: it
reads the bead (`br show`), its dependencies and the code, then posts a
preflight and a plan and waits for your go-ahead before editing. It claims
the bead through `node scripts/br-governed.mjs` only when work starts; the
cockpit itself changes nothing. The tab stays open when the command ends,
so a refusal such as "Not logged in" stays readable. `--start` is the
interactive counterpart of `--prompt`, which runs one turn and exits.

## Usage counts

To decide what the cockpit should show, the plugin counts what you use: each
palette action (`palette:<id>`, which includes clicks on the status segment),
each cockpit key or click (`cockpit:key:<name>`, `cockpit:click:refresh`), and
each Carly tool call (`carly:<name>`). A count is one line,
`{"t": <epoch seconds>, "e": "<event>"}`, in
`<Tern plugin data>/companyos/usage/<YYYY-MM-DD>.<window|host>.jsonl`
(`~/Library/Application Support/Tern/plugin-data/companyos/usage/` by
default). Nothing else is written, and nothing leaves this computer.

```sh
node scripts/company-os-tern-usage.mjs            # last 14 days, plus actions never used
node scripts/company-os-tern-usage.mjs --days 30 --json
```

Set `HSM_COMPANY_OS_TERN_USAGE=0` to stop recording. Opening the cockpit is
counted from the palette, not from the block, because Tern re-creates an open
cockpit on every plugin reload.

## Governed chat and the managed host

AgentChat admission, provider evidence, and receipt settlement always target
the managed Company OS runtime on `127.0.0.1:3847`, which
`scripts/company-os-agentchat.sh` pins. The terminal chat client ignores
ambient API-base aliases on purpose, so another loopback service cannot
receive a governed admission request.

## Trust and scope

Tern plugins run with the current user's access to files and programs; Tern
does not prompt for per-plugin permissions. This plugin opens the Console URL,
registers palette actions, adds one status-line segment, provides one
native block, and gives Carly one context line and 16 tools (15 read-only
tools and `open`). It does not read or store Company OS credentials or send
telemetry, and its only direct HTTP requests are the two unauthenticated
health checks behind Carly's `status()`, both to loopback. Review
`window.luau` and `host.luau` before linking it.

What runs, and when:

- **Palette actions** start the fixed command shown above in a new tab, through
  Tern's login shell, and leave a shell open afterwards so the output stays
  visible.
- **The status line** comes from the host half, which runs the read-only
  `company-os status` when the plugin loads and every 60 seconds after that,
  with a 15-second timeout.
- **The cockpit block** runs the read-only `company-os status`,
  `br stats --no-activity --json`, and the shown list (`br ready --limit 0`,
  `br list --status in_progress|open`, or `br blocked`, each `--json`) when
  it opens, when you refresh it, and when you switch lists, plus two 5-second GETs that say which part
  is down: the API host's `/api/company/health` and the Console's
  `/api/agent-chat-runtime`.
- **Carly's tools** run one read command each when Carly calls them, with a
  30-second timeout: the operator CLI's `task list|show|events`,
  `approvals`, `ps`, `what|why|timeline|inspect|events`, `receipt list|show|verify`,
  `verify task|run`, `memory search`, `cost|usage|stats|operator-status`,
  `mcp tools`, `mcp connectors list`, and `doctor`, or `br ready|blocked|stats|show`.
  None of them approves, sends, creates, or runs anything.
- **Carly's `status()`** sends `GET /api/company/health` to the API host and
  `GET /api/agent-chat-runtime` to the Console, 5-second timeout each.
- **Self-heal** (host half) runs `date`, `tail`, and `grep` on Tern's own log
  every minute, and `tern plugin reload` when a Company OS hook was disabled.
- **Opening a bead** (`Enter` or double-click in the Beads list) runs
  `tern new tab` with `companyos chat --start` for that bead; see
  **Working a bead in OMP**.
- **Usage counts** append one line to a local file per palette action,
  cockpit key or click, and Carly tool call (see **Usage counts**).
- **`br`** is `HSM_COMPANY_OS_BR` when set, else `~/.local/bin/br` when it
  exists, else `br` from `PATH`, for both the cockpit and the Beads tabs.

None of these probes change Beads issues or Company OS state. They run on the
current session host, so remote sessions need the CLI and Beads workspace at
the configured paths there.
