Skip to content

feat(channels): add CLI channel — talk to your agent from the terminal#1853

Merged
gavrielc merged 1 commit intov2from
refactor/pr9-cli-channel
Apr 18, 2026
Merged

feat(channels): add CLI channel — talk to your agent from the terminal#1853
gavrielc merged 1 commit intov2from
refactor/pr9-cli-channel

Conversation

@gavrielc
Copy link
Copy Markdown
Collaborator

Summary

First default channel that ships with main. Unix-socket adapter on the daemon + thin one-shot client (pnpm run chat). Plugs into the running host rather than spawning its own — daemon is the only host, CLI is just another channel.

Replaces the one-shot scripts/cli-ingest.ts removed from PR #8. Done as a proper `ChannelAdapter` so it inherits the normal channel lifecycle — /clear and other session-level commands work identically to any other channel.

Changes

`src/channels/cli.ts` (new)

  • `ChannelAdapter` with `channelType='cli'`, `platformId='local'`, `supportsThreads=false`.
  • `setup()` unlinks any stale socket, listens on `$DATA_DIR/cli.sock` (mode 0600 — local user only).
  • On client connect: reads newline-delimited JSON (`{"text":"..."}`) and calls `config.onInbound('local', null, ...)`.
  • `deliver()` writes `{"text": }` back to the connected socket; silently no-ops when no client is attached (outbound row still persists).
  • Single-client policy: a second connection supersedes the first with a `[superseded]` notice.
  • `teardown()` closes client + server, removes the socket file.

`scripts/chat.ts` + `pnpm run chat`

One-shot client:

  • `pnpm run chat <message...>`
  • Connects to the socket, writes one JSON line with the message.
  • Reads replies; exits 2s after the first reply (hard timeout 120s).
  • ENOENT/ECONNREFUSED prints a hint to start the daemon.

`scripts/init-first-agent.ts`

  • Fix stale imports after earlier module extractions (permissions + agent-to-agent moved their DB helpers into modules/).
  • After wiring the DM channel, also create `cli/local` messaging_group (`unknown_sender_policy='public'` — local socket perms handle auth) and wire it to the same agent. User can `pnpm run chat` immediately.

`package.json`

  • Add `"chat": "tsx scripts/chat.ts"` script.

Test plan

  • `pnpm run build` clean
  • `pnpm test` — 137 host tests pass
  • `bun test` in container/agent-runner — 17 pass
  • Service boot logs: `CLI channel listening` + `Channel adapter started channel=cli type=cli`; clean SIGTERM shutdown; socket file removed
  • Manual end-to-end: start daemon, `pnpm run chat hi`, confirm reply prints
  • Manual first-boot UX: fresh DB + `pnpm exec tsx scripts/init-first-agent.ts ...`, confirm both DM channel and CLI are wired, then `pnpm run chat hi`

Scoped out (follow-ups)

  • Multi-agent CLI: per-agent messaging_groups, `-a ` flag on client
  • Interactive REPL mode (no-args invocation → readline loop)
  • Streaming / multi-reply rendering in one-shot mode
  • `nc` shortcut installation (bin + alias) — docs for users to self-alias in the meantime

🤖 Generated with Claude Code

First default channel that ships with main. Unix-socket adapter + thin
client; plugs into the running daemon rather than spawning its own host.

## src/channels/cli.ts

- ChannelAdapter with channelType='cli', platformId='local'.
- setup() unlinks any stale socket, listens on $DATA_DIR/cli.sock (mode 0600
  so only the local user can connect).
- On client connect: reads newline-delimited JSON ({"text": "..."}) and
  calls config.onInbound('local', null, {id, kind:'chat', content, ts}).
- deliver() writes {"text": <body>} back to the connected socket; silently
  no-ops when no client is attached (outbound row still persists).
- Single-client policy: a second connection supersedes the first with a
  [superseded] notice.
- teardown() closes the client, closes the server, removes the socket file.

## scripts/chat.ts + pnpm run chat

One-shot client:
- pnpm run chat <message...>
- Connects to the socket, writes one JSON line with the message.
- Reads replies; exits 2s after the first reply lands (hard timeout 120s).
- ENOENT/ECONNREFUSED prints a hint to start the daemon.

## scripts/init-first-agent.ts

- Fix stale imports after earlier module extractions (permissions +
  agent-to-agent moved their DB helpers into modules/).
- After wiring the DM channel, also create cli/local messaging_group
  (unknown_sender_policy='public' — local socket perms handle auth) and
  wire it to the same agent. User can `pnpm run chat` immediately.

## package.json

- Add "chat": "tsx scripts/chat.ts" script.

## Validation

- pnpm run build clean.
- pnpm test — 137 host tests pass.
- bun test in container/agent-runner — 17 pass.
- Service boot logs: "CLI channel listening" + "Channel adapter started
  channel=cli type=cli". Clean SIGTERM shutdown; socket file removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@gavrielc gavrielc requested a review from gabi-simons as a code owner April 18, 2026 18:51
@gavrielc gavrielc merged commit 60ffe34 into v2 Apr 18, 2026
1 check passed
@gavrielc gavrielc deleted the refactor/pr9-cli-channel branch April 18, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant