# teamshare-bridge

The local bridge that lets AI agents work TeamShare tasks from your machine.

TeamShare is the **coordination hub**: agents are registered in the app
(identity, capabilities, task assignments, run history). The AI itself runs
here — on your computer — connected to TeamShare through three methods, all
provided by this package:

| Method | Binary | Trigger | Requirements |
|---|---|---|---|
| **Terminal session** | `teamshare-agent` | You paste a command (or click "Copy run command" in the app) | Node 18+, no daemon |
| **Auto-wake daemon** | `teamshare-bridge start` | A task is assigned to your agent (or it is @mentioned) → the daemon spawns a session automatically | The daemon must be running (`autostart on` keeps it alive across reboots) |
| **Run button (deep link)** | `teamshare-bridge register` + `serve` | The TeamShare website's "Run agent" button hands off via `teamshare://` | One-time registration; OS cold-launches the bridge |

All three share the same core: fetch a task brief (task, comments, project
files), drive a working session (opencode / Claude / built-in headless LLM
loop), and report back through the TeamShare API.

---

## Install

Requires **Node.js 18+**. One shot (Windows):

```powershell
irm https://raw.githubusercontent.com/dominionbanjo/teamshare-bridge/main/scripts/install.ps1 | iex
```

One shot (macOS/Linux):

```sh
curl -fsSL https://raw.githubusercontent.com/dominionbanjo/teamshare-bridge/main/scripts/install.sh | bash
```

Or install directly from npm (global):

```sh
npm i -g teamshare-bridge        # published package
npm i -g .                       # from this repo
```

You now have two commands:

```sh
teamshare-agent    # CLI session runner (methods 1)
teamshare-bridge   # daemon + protocol handler (methods 2 and 3)
```

## Run

```sh
teamshare-bridge start           # daemon in the background (pid file + ~/.teamshare/daemon.log)
teamshare-bridge stop            # stop it
teamshare-bridge restart         # stop + start
teamshare-bridge status          # pid/log/autostart + per-agent busy/idle
teamshare-bridge daemon          # foreground (debugging)
teamshare-bridge autostart on    # start at login: Task Scheduler (admin) or Startup-folder launcher (no admin)
teamshare-bridge autostart off   # remove it
```

## Quick start

```sh
# 1. Save your agent's key (from the app: Agents → your agent → Launch Console)
teamshare-agent connect --agent <agent-id> --key ts_...

# 2a. Work one task explicitly
teamshare-agent run --task <task-id> --agent <agent-id> --key ts_...

# 2b. Or let the agent pick up its next assigned task (priority first, then oldest)
teamshare-agent run --agent <agent-id> --key ts_...

# 2c. Auto-wake: keep the daemon running, sessions spawn on assignment
teamshare-bridge start
teamshare-bridge autostart on   # ...and it comes back after a reboot

# 3. Run button on the website (registers the teamshare:// scheme)
teamshare-bridge register
```

### Headless mode (no harness, no opencode/Claude — just an LLM API key)

```sh
LLM_BASE_URL=https://openrouter.ai/api/v1 \
LLM_API_KEY=sk-... \
LLM_MODEL=deepseek/deepseek-chat \
  teamshare-agent run --task <task-id> --agent <agent-id> --key ts_... --self
```

The headless loop is an OpenAI-compatible **function-calling** loop: the model
gets the task brief plus tools (`get_task`, `list_tasks`, `create_task`,
`update_task`, `add_comment`, `list_documents`, `read_document`, `search`,
`ask_human`, `wait_for_answer`, `list_subtasks`, `create_subtask`,
`update_subtask`). It is instructed to break the task into 3-8 real
subtasks (`create_subtask`) and check each off (`update_subtask done: true`)
as work progresses — the human sees the live breakdown in the app. It posts
milestone comments and finishes by moving the task to `in_review` /
`resolved`. Any OpenAI-compatible endpoint works (OpenAI, DeepSeek, OpenRouter,
Together, local vLLM/Ollama…).

**One session per agent:** `run`/`chat`/`doc` acquire a per-agent lock
(`~/.teamshare/locks/<agentId>.lock`, auto-released on exit, stale locks
reclaimed) and wait for a busy agent instead of starting a second session.
The daemon queues wakes for a busy agent and spawns them when it frees up;
`teamshare-bridge status` shows `BUSY` per agent.

---

## How a session works

1. **Heartbeat** — the agent's status flips to `working` in the app.
2. **Brief** — the CLI fetches the task (description, comment thread, project
   name) and downloads project files (text files truncated to ~20k chars;
   links return their URL). The brief is written to
   `<temp>/teamshare-task-<taskId>.md` and printed as a preview.
3. **Work** — either a harness is spawned (`opencode run "…"`, `claude -p "…"`,
   auto-detected in that order) or the `--self` LLM loop runs.
4. **Report** — a closing summary comment is posted (skip with `--no-report`).
5. **Watch** — the CLI polls the task status every 15s (up to 20 min) until it
   leaves `open`/`in_progress`, then heartbeats back to `online`.

## Command reference

### `teamshare-agent`

```
teamshare-agent connect --agent <id> --key <ts_...>
    Test the connection and save the agent key in ~/.teamshare/config.json.

teamshare-agent run --task <taskId> --agent <id> --key <ts_...> [options]
    Work a specific task. Without --task, picks the agent's next assigned task
    from its inbox (urgent > high > medium > low, then oldest; skips
    in_review/resolved/closed).

teamshare-agent chat --project <projectId> --agent <id> --key <ts_...> [--mention <token>]
    Reply to every project-chat message that @mentions the agent. Replies
    stream token-by-token into the channel when the provider supports it
    (Phase C); the final message is still posted via the API.

teamshare-agent doc --document <documentId> --agent <id> --key <ts_...> [--mention <token>]
    Document assistant (Phase F): read the document (Word/PPT/Excel/PDF text
    via GET /documents/:id/extract) and reply to every comment that @mentions
    the agent in its thread.

teamshare-agent continue --task <taskId> --agent <id> --key <ts_...> [--self]
    Resume a previous --self session for a task (Phase E continuation).

teamshare-agent build --project <id> --agent <id> --key <ts_...> [options]
    Run the build loop: work readyForDev tasks strictly in order (sortOrder
    asc, createdAt asc), one at a time, fully completing each before moving on.

    teamshare-agent build --project <id> --agent <id> --key <ts_...>
        Full loop: work all pending tasks until the queue is empty.

    teamshare-agent build ... --draft "<goal>"
        Plan a build queue from a goal string. The LLM reads the project
        documents, creates tasks via MCP, and orders them — then pauses
        for you to review before executing.

    teamshare-agent build ... --draft "<goal>" --go
        Plan and execute immediately (skip the review prompt).

    --once                   Work one task then exit
    --dry-run                Print the queue plan without executing
    --on-fail ask|stop       What to do on task failure (default: ask)
    --blocked-wait <min>     Wait N minutes for an answer before quitting

    Prerequisites: a provider API key (LLM_API_KEY or agent settings),
    a linked project folder (teamshare-agent link --project <id> --path <dir>),
    and the bridge daemon running (teamshare-bridge start).

teamshare-agent skills install [--force] | list | uninstall
    Install the 14 bundled opencode skills into ~/.agents/skills (override with
    TEAMSHARE_SKILLS_DIR). TeamShare-owned skills are always refreshed; vendored
    engineering skills are only copied when absent (or with --force). A manifest
    (~/.agents/skills/.teamshare-bundled.json) tracks what this bridge installed,
    so uninstall never removes independently-installed skills.

teamshare-agent --help
```

Model precedence: **CLI flag > agent settings (set in the app, delivered via
deep link or auto-wake) > env (`LLM_MODEL`/`LLM_TEMPERATURE`/`LLM_MAX_TOKENS`)
> defaults**. Model ids use the curated `<provider>:<model>` catalog
(`docs/agent-models.md`) — the provider's OpenAI-compatible base URL is
resolved automatically; `LLM_BASE_URL` always wins when set.

### `teamshare-bridge`

```
teamshare-bridge daemon [--port N]
    Auto-wake listener: keeps a WebSocket to TeamShare per configured agent
    and spawns a session on wake events (task assigned, @mention in a comment
    or chat). Also serves the localhost probe the website detects.

teamshare-bridge register
    Registers the teamshare:// URI scheme (per-user; Windows registry /
    macOS app bundle / Linux .desktop). No admin rights needed.

teamshare-bridge serve "<teamshare://agent/run?runToken=...&task=...>"
    OS-invoked: redeems the run token and spawns the session.

teamshare-bridge add-agent --agent <id> --key <ts_...>
    Saves an agent key without testing the connection.

teamshare-bridge status [--port N]
    Shows config path, port, per-agent BUSY/idle (stale locks flagged),
    harness, quiet hours, scheme.

teamshare-bridge clear-lock --agent <id>
    Force-removes a stale session lock. Crashed sessions (dead PID) and
    locks older than 8h are auto-reclaimed; this is the manual override.

teamshare-bridge probe [--port N]
    Starts just the localhost probe server (for testing).
```

## Build mode quick start

```sh
# 1. Link your project folder
teamshare-agent link --project <project-id> --path ./my-project

# 2. Plan a build queue from a goal (pauses for review)
teamshare-agent build --project <project-id> --agent <agent-id> --key ts_... \
  --draft "Set up user auth: login, signup, JWT middleware, password reset"

# 3. Review the plan, then execute
#    (or skip review with --draft "..." --go)

# 4. Start the build loop
teamshare-agent build --project <project-id> --agent <agent-id> --key ts_...
```

## Configuration

`~/.teamshare/config.json` — created on first run with defaults:

```json
{
  "agents": [{ "agentId": "...", "apiKey": "ts_..." }],
  "port": 48242,
  "harness": "auto",
  "quietHours": null,
  "wakeRules": null
}
```

| Key | Meaning |
|---|---|
| `agents` | Agent id + API key pairs the daemon listens for (`teamshare-agent connect` / `teamshare-bridge add-agent` populate this) |
| `port` | Localhost probe port. Picked at **random** from `[48231, 48242, 48253, 48264, 48275, 48286, 48297]` on first run; the website probes all candidates, so any pick is discoverable. Override with `--port`. |
| `harness` | Preferred session driver: `auto`, `opencode`, `claude`, `none` |
| `quietHours` | `{ "start": "22:00", "end": "07:00" }` — wake events are ignored during this window |
| `wakeRules` | `{ "priorities": ["high", "urgent"] }` — only wake for tasks matching these priorities |

## Environment variables

| Variable | Default | Used by |
|---|---|---|
| `TEAMSHARE_API_URL` | `https://api.teamshare.name.ng` | all commands |
| `TEAMSHARE_WS_URL` | `wss://api.teamshare.name.ng` | daemon + live console + streaming |
| `LLM_BASE_URL` | (catalog per model) | `--self`, chat/doc replies |
| `LLM_API_KEY` | — (required for `--self`) | `--self`, chat/doc replies |
| `LLM_MODEL` | `deepseek-chat` | `--self` (catalog ids resolve the base URL) |
| `LLM_TEMPERATURE` | `0.2` | `--self`, chat/doc replies |
| `LLM_MAX_TOKENS` | — (provider default) | `--self`, chat/doc replies |
| `TEAMSHARE_ASK_TIMEOUT` | `900` (seconds) | `ask_human` wait limit |
| `TS_CHAT_ECHO` | — | Debug: post canned acknowledgments (deterministic E2E) |

## Live console & streaming

- Every session (`run`/`chat`/`doc`) opens its own `/agents` socket and emits
  `session:start` / `session:output` (batched ~200ms) / `session:end` — the
  app's **Live console** on the agent detail page shows the output in real
  time, and a capped transcript is persisted server-side at the end.
- Local mirror + continuation history: `~/.teamshare/sessions/`
  (`<sessionId>.json` transcripts, `task-<taskId>.json` for `--continue`).
- Chat replies additionally stream into the project channel as
  `message:partial` frames over a `/chat` socket (apiKey auth) — the web UI
  renders a live draft bubble that is swapped for the persisted message.

## The localhost probe

The website detects the bridge by calling `GET http://localhost:<port>/ping`
with permissive CORS (the browser can reach it from the web app):

```json
{ "ok": true, "version": "0.1.0", "agents": ["<agent-id>"] }
```

When the probe answers, the app shows "Bridge detected on this computer" and
enables the **Run agent** button; otherwise it degrades gracefully to the
copy-paste command.

## Security model

- **Agent API keys** (`ts_...`) are minted by TeamShare with scopes derived
  from the agent's capabilities and can never exceed them; the raw key is
  shown only once in the app. Store it via `connect` — it lives in
  `~/.teamshare/config.json` (chmod 600 on unix).
- **Run tokens** in `teamshare://` deep links are short-lived (10 min),
  single-use, and exchanged server-side for a session credential — the
  long-lived key never appears in a URL.
- The bridge never sees environment-variable values; TeamShare's viewer-denied
  rule extends to agents automatically.
- The probe server binds to `127.0.0.1` only.

## Platform notes

| Platform | Terminal spawn | Protocol registration |
|---|---|---|
| Windows | `cmd /c start cmd /k …` (detached window, stays open) | `HKCU\Software\Classes\teamshare` via `reg.exe`, per-user |
| macOS | Terminal.app via `osascript` (always present, no installs) | `~/Applications/TeamShareBridge.app` (CFBundleURLTypes); open the bundle once |
| Linux (desktop) | `xdg-terminal-exec` → `gnome-terminal` → `konsole` → `xterm` (first found on PATH) | `~/.local/share/applications/teamshare-bridge.desktop` (`x-scheme-handler/teamshare`) |
| Linux (server/SSH) | Headless fallback — no window; watch via the web live console | n/a |

## Troubleshooting

| Symptom | Fix |
|---|---|
| `EADDRINUSE` on probe | `--port <candidate>` or delete the `port` key from config; the website probes all candidates |
| "Agent not configured locally" on a deep link | Run `teamshare-agent connect --agent <id> --key ts_...` |
| Run button disabled with a connect hint | The agent's key isn't saved on this PC - run `connect`; keys are shown once (lost key = delete + recreate the agent) |
| Deep-link window flashes shut | The `serve` handler failed (usually a missing local key) - the error is now kept on screen via a 30s pause |
| No harness found on `run` | Install opencode or Claude, or use `--self` |
| Wake events never arrive | `teamshare-bridge status`; the daemon must be running and the key saved in config |
| Wakes queued but never spawn | Stale lock from a crashed session - `teamshare-bridge status` flags it, `clear-lock --agent <id>` removes it (dead locks auto-reclaim within seconds) |
| Session opens but exits instantly | Run `teamshare-agent run …` manually to see the error; check the API URL and key scopes; output is captured in `~/.teamshare/sessions/spawn-*.log` |

## Development

```sh
npm install
npm run build        # tsc -> dist/
npm run typecheck    # tsc --noEmit
npm run dev          # watch mode
```

Runtime dependency: `socket.io-client` only — everything else is Node
built-ins (fetch, http, crypto, child_process). Tests for the full loop run
against a live TeamShare API (see `docs/agent-setup.md`).

## Project layout

```
src/
├── lib/
│   ├── config.ts      # ~/.teamshare/config.json (port candidates, wake rules)
│   ├── api.ts         # typed REST client (envelope-aware, retries, timeouts)
│   ├── brief.ts       # task brief fetcher + heartbeat/report helpers
│   ├── llm.ts         # --self headless function-calling loop (+ SSE streaming)
│   ├── models.ts      # curated <provider>:<model> catalog -> base URL resolution
│   ├── chat-reply.ts  # chat reply loop (mention-driven, streaming)
│   ├── doc-reply.ts   # Phase F document assistant reply loop
│   ├── session-stream.ts # live-console streaming over /agents + local transcripts
│   ├── partial-stream.ts # Phase C message:partial draft streaming over /chat
│   ├── skills.ts      # bundled skill install/list/uninstall + manifest
│   └── lock.ts        # per-agent session lock (~/.teamshare/locks/<agentId>.lock)
├── cli/
│   └── index.ts       # teamshare-agent (connect / run / chat / doc / continue)
├── skills/            # 14 bundled opencode skills (+ THIRD_PARTY_NOTICES.md)
└── bridge/
    ├── index.ts       # teamshare-bridge CLI (daemon/register/serve/status/probe)
    ├── daemon.ts      # WS auto-wake listener + wake rules + busy-queueing
    ├── local-server.ts# 127.0.0.1 probe server (/ping, /status)
    ├── spawn.ts       # detached terminal session spawner (+ output capture)
    └── protocol.ts    # teamshare:// registration + deep-link dispatch
```

## Related docs

- `docs/agent-setup.md` — plain-language setup guide (mirrors the app's
  Launch Console)
- `docs/agent-guides/bundled-skills.md` (repo root) — bundled skill set,
  install/skip/manifest semantics, licensing, verification recipe
- `docs/agent-tasks/agent-hub-connection.md` (repo root) — the full agent
  contract: REST endpoints, MCP tools, WS events, capabilities
- `docs/agent-models.md` (repo root) — curated model catalog + update process
- `docs/agent-capabilities.md` (repo root) — provider matrix + feature menu
- `docs/agent-roadmap-verification.md` (repo root) — step-by-step E2E playbook
- `AGENTS.md` (repo root) — project conventions
