Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ Type a message, hit Enter, everyone sees it. Identity defaults to your hostname,
Launch an AI agent that listens on a channel and responds using Claude Code or Codex CLI:

```bash
# Start an agent (auto-detects claude or codex)
# Start an agent (auto-detects claude, codex or pi)
walkie agent mychannel

# Or pick explicitly
walkie agent mychannel --cli codex
walkie agent mychannel --cli claude --model haiku --name my-bot
walkie agent mychannel --cli pi --name pi-bot
```

Now anyone on that channel talks to your AI:
Expand Down Expand Up @@ -73,7 +74,7 @@ All channel args accept `channel:secret` format. No colon = secret defaults to c

```
walkie chat <channel> Interactive chat. Same name = same room
walkie agent <channel> AI agent that responds via claude/codex
walkie agent <channel> AI agent that responds via claude/codex/pi
walkie connect <channel> Join a channel programmatically
walkie send <channel> "message" Send a message (or pipe from stdin)
walkie read <channel> Read pending messages (--wait, --timeout)
Expand Down Expand Up @@ -132,7 +133,7 @@ npx skills add https://github.com/vikasprogrammer/walkie --skill walkie
### 1.5.0

- **`walkie chat <channel>`** — interactive terminal chat. Same channel name = same channel. Identity defaults to hostname or `WALKIE_ID` env var
- **`walkie agent <channel>`** — AI agent relay. Listens on a channel and responds via Claude Code or Codex CLI. Auto-detects which CLI is available, with `--cli`, `--model`, `--prompt`, `--name` options. Maintains conversation memory across messages via `--resume`
- **`walkie agent <channel>`** — AI agent relay. Listens on a channel and responds via Claude Code, Codex, or pi CLI. Auto-detects which CLI is available, with `--cli`, `--model`, `--prompt`, `--name` options. Maintains conversation memory across messages (via `--resume` for claude, `--session-id` for pi)
- **P2P identity fix** — remote peers now see the actual sender name (e.g. `vikas`, `my-bot`) instead of a daemon hash
- **P2P join/leave broadcasts** — `[system] alice joined` / `[system] alice left` now sent to remote peers, not just local subscribers
- **Auto-restart daemon on update** — daemon reports its version on ping; CLI auto-restarts it when a version mismatch is detected after `npm update`
Expand Down
54 changes: 46 additions & 8 deletions bin/walkie.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ program

Getting started:
$ walkie chat mychannel Interactive chat (same name = same channel)
$ walkie agent mychannel AI agent that responds via claude/codex
$ walkie agent mychannel AI agent that responds via claude/codex/pi
$ walkie agent mychannel --cli codex Use a specific AI CLI

Programmatic (for agents/scripts):
Expand Down Expand Up @@ -147,7 +147,7 @@ program

function detectCli() {
const { spawnSync } = require('child_process')
for (const cmd of ['claude', 'codex']) {
for (const cmd of ['claude', 'codex', 'pi']) {
const r = spawnSync('which', [cmd], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
if (r.status === 0) return cmd
}
Expand Down Expand Up @@ -246,23 +246,61 @@ function runCodex(prompt, sessionId, model, extraArgs) {
return { text, sessionId: threadId }
}

function runPi(prompt, sessionId, model, extraArgs) {
const { spawnSync } = require('child_process')
const args = ['-p', prompt, '--mode', 'json']
// --session-id reuses (or creates) a session, giving the agent memory across turns
if (sessionId) args.push('--session-id', sessionId)
if (model) args.push('--model', model)
if (extraArgs) args.push(...extraArgs)

const result = spawnSync('pi', args, {
timeout: 300000,
maxBuffer: 10 * 1024 * 1024,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
})

if (result.error) throw result.error
if (result.status !== 0) throw new Error(result.stderr || 'pi exited with code ' + result.status)

// pi --mode json emits newline-delimited events. The session id is on the
// "session" event; the reply is the text content of the final assistant
// message (accumulated across message_update/message_end events).
const out = { text: (result.stdout || '').trim(), sessionId: sessionId || null }
let assistantText = null
for (const line of (result.stdout || '').split('\n')) {
if (!line.trim()) continue
let obj
try { obj = JSON.parse(line) } catch { continue }
if (obj.type === 'session' && obj.id) out.sessionId = obj.id
const msg = obj.message
if (msg && msg.role === 'assistant' && Array.isArray(msg.content)) {
const t = msg.content.filter(c => c && c.type === 'text').map(c => c.text).join('')
if (t) assistantText = t
}
}
if (assistantText !== null) out.text = assistantText
return out
}

program
.command('agent <channel>')
.description('AI agent that listens and responds via claude or codex')
.description('AI agent that listens and responds via claude, codex or pi')
.option('--secret <secret>', 'Custom secret (default: channel name)')
.option('--cli <cli>', 'CLI to use: claude or codex (auto-detected if omitted)')
.option('--cli <cli>', 'CLI to use: claude, codex or pi (auto-detected if omitted)')
.option('--prompt <text>', 'System prompt for the agent')
.option('--model <model>', 'Model to use')
.option('--name <name>', 'Agent display name')
.option('--agent-args <args>', 'Extra CLI arguments passed to claude/codex (e.g. "--dangerously-skip-permissions")')
.action(async (channelArg, opts) => {
const cli = opts.cli || detectCli()
if (!cli) {
console.error('Error: neither "claude" nor "codex" CLI found. Install one first.')
console.error('Error: no supported CLI ("claude", "codex" or "pi") found. Install one first.')
process.exit(1)
}
if (cli !== 'claude' && cli !== 'codex') {
console.error(`Error: unsupported CLI "${cli}". Use "claude" or "codex".`)
if (cli !== 'claude' && cli !== 'codex' && cli !== 'pi') {
console.error(`Error: unsupported CLI "${cli}". Use "claude", "codex" or "pi".`)
process.exit(1)
}

Expand All @@ -272,7 +310,7 @@ program
const secret = opts.secret || parsed.secret
const cid = agentName
const extraArgs = opts.agentArgs ? opts.agentArgs.split(/\s+/) : null
const askFn = cli === 'claude' ? runClaude : runCodex
const askFn = cli === 'claude' ? runClaude : cli === 'codex' ? runCodex : runPi

try {
const resp = await request({ action: 'join', channel, secret, clientId: cid })
Expand Down