chore: rebrand companion-os to clanker-agent

- Rename all package names from companion-* to clanker-*
- Update npm scopes from @mariozechner to @harivansh-afk
- Rename config directories .companion -> .clanker
- Rename environment variables COMPANION_* -> CLANKER_*
- Update all documentation, README files, and install scripts
- Rename package directories (companion-channels, companion-grind, companion-teams)
- Update GitHub URLs to harivansh-afk/clanker-agent
- Preserve full git history from companion-cloud monorepo
This commit is contained in:
Harivansh Rathi 2026-03-26 16:22:52 -04:00
parent f93fe7d1a0
commit 67168d8289
356 changed files with 2249 additions and 10223 deletions

4
.gitignore vendored
View file

@ -23,13 +23,13 @@ packages/*/dist-firefox/
.npm/ .npm/
coverage/ coverage/
.nyc_output/ .nyc_output/
.companion_config/ .clanker_config/
tui-debug.log tui-debug.log
compaction-results/ compaction-results/
.opencode/ .opencode/
syntax.jsonl syntax.jsonl
out.jsonl out.jsonl
companion-*.html clanker-*.html
out.html out.html
packages/coding-agent/binaries/ packages/coding-agent/binaries/
todo.md todo.md

View file

@ -71,29 +71,29 @@ When closing issues via commit:
- GitHub CLI for issues/PRs - GitHub CLI for issues/PRs
- Add package labels to issues/PRs: pkg:agent, pkg:ai, pkg:coding-agent, pkg:mom, pkg:pods, pkg:tui, pkg:web-ui - Add package labels to issues/PRs: pkg:agent, pkg:ai, pkg:coding-agent, pkg:mom, pkg:pods, pkg:tui, pkg:web-ui
## Testing companion Interactive Mode with tmux ## Testing clanker Interactive Mode with tmux
To test companion's TUI in a controlled terminal environment: To test clanker's TUI in a controlled terminal environment:
```bash ```bash
# Create tmux session with specific dimensions # Create tmux session with specific dimensions
tmux new-session -d -s companion-test -x 80 -y 24 tmux new-session -d -s clanker-test -x 80 -y 24
# Start companion from source # Start clanker from source
tmux send-keys -t companion-test "cd /Users/badlogic/workspaces/companion-mono && ./companion-test.sh" Enter tmux send-keys -t clanker-test "cd /Users/badlogic/workspaces/clanker-agent && ./clanker-test.sh" Enter
# Wait for startup, then capture output # Wait for startup, then capture output
sleep 3 && tmux capture-pane -t companion-test -p sleep 3 && tmux capture-pane -t clanker-test -p
# Send input # Send input
tmux send-keys -t companion-test "your prompt here" Enter tmux send-keys -t clanker-test "your prompt here" Enter
# Send special keys # Send special keys
tmux send-keys -t companion-test Escape tmux send-keys -t clanker-test Escape
tmux send-keys -t companion-test C-o # ctrl+o tmux send-keys -t clanker-test C-o # ctrl+o
# Cleanup # Cleanup
tmux kill-session -t companion-test tmux kill-session -t clanker-test
``` ```
## Style ## Style
@ -127,8 +127,8 @@ Use these sections under `## [Unreleased]`:
### Attribution ### Attribution
- **Internal changes (from issues)**: `Fixed foo bar ([#123](https://github.com/badlogic/companion-mono/issues/123))` - **Internal changes (from issues)**: `Fixed foo bar ([#123](https://github.com/badlogic/clanker-agent/issues/123))`
- **External contributions**: `Added feature X ([#456](https://github.com/badlogic/companion-mono/pull/456) by [@username](https://github.com/username))` - **External contributions**: `Added feature X ([#456](https://github.com/badlogic/clanker-agent/pull/456) by [@username](https://github.com/username))`
## Adding a New LLM Provider (packages/ai) ## Adding a New LLM Provider (packages/ai)

View file

@ -1,4 +1,4 @@
# Contributing to companion # Contributing to clanker-agent
Thanks for wanting to contribute! This guide exists to save both of us time. Thanks for wanting to contribute! This guide exists to save both of us time.
@ -8,7 +8,7 @@ Thanks for wanting to contribute! This guide exists to save both of us time.
Using AI to write code is fine. You can gain understanding by interrogating an agent with access to the codebase until you grasp all edge cases and effects of your changes. What's not fine is submitting agent-generated slop without that understanding. Using AI to write code is fine. You can gain understanding by interrogating an agent with access to the codebase until you grasp all edge cases and effects of your changes. What's not fine is submitting agent-generated slop without that understanding.
If you use an agent, run it from the `companion` root directory so it picks up `AGENTS.md` automatically. Your agent must follow the rules and guidelines in that file. If you use an agent, run it from the `clanker` root directory so it picks up `AGENTS.md` automatically. Your agent must follow the rules and guidelines in that file.
## First-Time Contributors ## First-Time Contributors
@ -35,7 +35,7 @@ If you're adding a new provider to `packages/ai`, see `AGENTS.md` for required t
## Philosophy ## Philosophy
companion's core is minimal. If your feature doesn't belong in the core, it should be an extension. PRs that bloat the core will likely be rejected. clanker's core is minimal. If your feature doesn't belong in the core, it should be an extension. PRs that bloat the core will likely be rejected.
## Questions? ## Questions?

View file

@ -1,33 +1,33 @@
<p align="center"> <p align="center">
<a href="https://shittycodingagent.ai"> <a href="https://clanker.dev">
<img src="https://shittycodingagent.ai/logo.svg" alt="companion logo" width="128"> <img src="https://clanker.dev/logo.svg" alt="clanker logo" width="128">
</a> </a>
</p> </p>
<p align="center"> <p align="center">
<a href="https://discord.com/invite/3cU7Bz4UPx"><img alt="Discord" src="https://img.shields.io/badge/discord-community-5865F2?style=flat-square&logo=discord&logoColor=white" /></a> <a href="https://discord.com/invite/3cU7Bz4UPx"><img alt="Discord" src="https://img.shields.io/badge/discord-community-5865F2?style=flat-square&logo=discord&logoColor=white" /></a>
<a href="https://github.com/getcompanion-ai/co-mono/actions/workflows/ci.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/getcompanion-ai/co-mono/ci.yml?style=flat-square&branch=main" /></a> <a href="https://github.com/harivansh-afk/clanker-agent/actions/workflows/ci.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/harivansh-afk/clanker-agent/ci.yml?style=flat-square&branch=main" /></a>
</p> </p>
<p align="center"> <p align="center">
<a href="https://companion.dev">companion.dev</a> domain graciously donated by <a href="https://clanker.dev">clanker.dev</a> domain graciously donated by
<br /><br /> <br /><br />
<a href="https://exe.dev"><img src="packages/coding-agent/docs/images/exy.png" alt="Exy mascot" width="48" /><br />exe.dev</a> <a href="https://exe.dev"><img src="packages/coding-agent/docs/images/exy.png" alt="Exy mascot" width="48" /><br />exe.dev</a>
</p> </p>
# companion # clanker-agent
> **Looking for the companion coding agent?** See **[packages/coding-agent](packages/coding-agent)** for installation and usage. > **Looking for the clanker coding agent?** See **[packages/coding-agent](packages/coding-agent)** for installation and usage.
Tools for building AI agents and running the companion coding agent. Tools for building AI agents and running the clanker coding agent.
## Packages ## Packages
| Package | Description | | Package | Description |
| ---------------------------------------------------------- | ---------------------------------------------------------------- | | ---------------------------------------------------------- | ---------------------------------------------------------------- |
| **[@mariozechner/companion-ai](packages/ai)** | Unified multi-provider LLM API (OpenAI, Anthropic, Google, etc.) | | **[@mariozechner/clanker-ai](packages/ai)** | Unified multi-provider LLM API (OpenAI, Anthropic, Google, etc.) |
| **[@mariozechner/companion-agent-core](packages/agent)** | Agent runtime with tool calling and state management | | **[@mariozechner/clanker-agent-core](packages/agent)** | Agent runtime with tool calling and state management |
| **[@mariozechner/companion-coding-agent](packages/coding-agent)** | Interactive coding agent CLI | | **[@mariozechner/clanker-coding-agent](packages/coding-agent)** | Interactive coding agent CLI |
| **[@mariozechner/companion-tui](packages/tui)** | Terminal UI library with differential rendering | | **[@mariozechner/clanker-tui](packages/tui)** | Terminal UI library with differential rendering |
| **[@mariozechner/companion-web-ui](packages/web-ui)** | Web components for AI chat interfaces | | **[@mariozechner/clanker-web-ui](packages/web-ui)** | Web components for AI chat interfaces |
## Contributing ## Contributing
@ -40,27 +40,27 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines and [AGENTS.m
Use this for users on production machines where you don't want to expose source. Use this for users on production machines where you don't want to expose source.
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/getcompanion-ai/co-mono/main/public-install.sh | bash curl -fsSL https://raw.githubusercontent.com/harivansh-afk/clanker-agent/main/public-install.sh | bash
``` ```
Install everything and keep it always-on (recommended for new devices): Install everything and keep it always-on (recommended for new devices):
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/getcompanion-ai/co-mono/main/public-install.sh | bash -s -- --daemon --start curl -fsSL https://raw.githubusercontent.com/harivansh-afk/clanker-agent/main/public-install.sh | bash -s -- --daemon --start
``` ```
This installer: This installer:
- Downloads the latest release (or falls back to source when needed), - Downloads the latest release (or falls back to source when needed),
- writes `~/.local/bin/companion` launcher, - writes `~/.local/bin/clanker` launcher,
- populates `~/.companion/agent/settings.json` with package list, - populates `~/.clanker/agent/settings.json` with package list,
- installs packages (if `npm` is available), - installs packages (if `npm` is available),
- and can install a user service for `companion daemon` so it stays alive (`systemd` on Linux, `launchd` on macOS). - and can install a user service for `clanker daemon` so it stays alive (`systemd` on Linux, `launchd` on macOS).
Preinstalled package sources are: Preinstalled package sources are:
```json ```json
["npm:@e9n/companion-channels", "npm:companion-teams"] ["npm:@e9n/clanker-channels", "npm:clanker-teams"]
``` ```
If `npm` is available, it also installs these packages during install. If `npm` is available, it also installs these packages during install.
@ -68,48 +68,48 @@ If `npm` is available, it also installs these packages during install.
If no release asset is found, the installer falls back to source. If no release asset is found, the installer falls back to source.
```bash ```bash
COMPANION_FALLBACK_TO_SOURCE=0 \ CLANKER_FALLBACK_TO_SOURCE=0 \
curl -fsSL https://raw.githubusercontent.com/getcompanion-ai/co-mono/main/public-install.sh | bash -s -- --daemon --start curl -fsSL https://raw.githubusercontent.com/harivansh-afk/clanker-agent/main/public-install.sh | bash -s -- --daemon --start
``` ```
`public-install.sh` options: `public-install.sh` options:
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/getcompanion-ai/co-mono/main/public-install.sh | bash -s -- --help curl -fsSL https://raw.githubusercontent.com/harivansh-afk/clanker-agent/main/public-install.sh | bash -s -- --help
``` ```
### Local (source) ### Local (source)
```bash ```bash
git clone https://github.com/getcompanion-ai/co-mono.git git clone https://github.com/harivansh-afk/clanker-agent.git
cd co-mono cd clanker-agent
./install.sh ./install.sh
``` ```
Run: Run:
```bash ```bash
./companion ./clanker
``` ```
Run in background with extensions active: Run in background with extensions active:
```bash ```bash
./companion daemon ./clanker daemon
``` ```
For a user systemd setup, create `~/.config/systemd/user/companion.service` with: For a user systemd setup, create `~/.config/systemd/user/clanker.service` with:
```ini ```ini
[Unit] [Unit]
Description=companion daemon Description=clanker daemon
After=network-online.target After=network-online.target
[Service] [Service]
Type=simple Type=simple
Environment=CO_MONO_AGENT_DIR=%h/.companion/agent Environment=CLANKER_MONO_AGENT_DIR=%h/.clanker/agent
Environment=COMPANION_CODING_AGENT_DIR=%h/.companion/agent Environment=CLANKER_CODING_AGENT_DIR=%h/.clanker/agent
ExecStart=/absolute/path/to/repo/companion daemon ExecStart=/absolute/path/to/repo/clanker daemon
Restart=always Restart=always
RestartSec=5 RestartSec=5
@ -121,7 +121,7 @@ Then enable:
```bash ```bash
systemctl --user daemon-reload systemctl --user daemon-reload
systemctl --user enable --now companion systemctl --user enable --now clanker
``` ```
On macOS, `public-install.sh --daemon --start` now provisions a per-user `launchd` agent automatically. On macOS, `public-install.sh --daemon --start` now provisions a per-user `launchd` agent automatically.
@ -140,7 +140,7 @@ npm install # Install all dependencies
npm run build # Build all packages npm run build # Build all packages
npm run check # Lint, format, and type check npm run check # Lint, format, and type check
./test.sh # Run tests (skips LLM-dependent tests without API keys) ./test.sh # Run tests (skips LLM-dependent tests without API keys)
./companion-test.sh # Run companion from sources (must be run from repo root) ./clanker-test.sh # Run clanker from sources (must be run from repo root)
``` ```
> **Note:** `npm run check` requires `npm run build` to be run first. The web-ui package uses `tsc` which needs compiled `.d.ts` files from dependencies. > **Note:** `npm run check` requires `npm run build` to be run first. The web-ui package uses `tsc` which needs compiled `.d.ts` files from dependencies.

View file

@ -1,7 +1,7 @@
{ {
"folders": [ "folders": [
{ {
"name": "companion", "name": "clanker-agent",
"path": "." "path": "."
}, },
{ {

View file

@ -24,12 +24,12 @@ need npm
cd "$ROOT_DIR" cd "$ROOT_DIR"
if [[ "${COMPANION_SKIP_INSTALL:-${CO_MONO_SKIP_INSTALL:-0}}" != "1" ]]; then if [[ "${CLANKER_SKIP_INSTALL:-${CLANKER_MONO_SKIP_INSTALL:-0}}" != "1" ]]; then
log "Installing workspace dependencies" log "Installing workspace dependencies"
npm install npm install
fi fi
if [[ "${COMPANION_SKIP_BUILD:-${CO_MONO_SKIP_BUILD:-0}}" != "1" ]]; then if [[ "${CLANKER_SKIP_BUILD:-${CLANKER_MONO_SKIP_BUILD:-0}}" != "1" ]]; then
log "Building core packages" log "Building core packages"
BUILD_FAILED=0 BUILD_FAILED=0
for pkg in packages/tui packages/ai packages/agent packages/coding-agent; do for pkg in packages/tui packages/ai packages/agent packages/coding-agent; do
@ -46,7 +46,7 @@ if [[ "$BUILD_FAILED" == "1" ]] && [[ ! -f "$ROOT_DIR/packages/coding-agent/src/
fail "No usable coding-agent CLI source found for source launch fallback." fail "No usable coding-agent CLI source found for source launch fallback."
fi fi
LAUNCHER="$ROOT_DIR/companion" LAUNCHER="$ROOT_DIR/clanker"
cat > "$LAUNCHER" <<'EOF' cat > "$LAUNCHER" <<'EOF'
#!/usr/bin/env bash #!/usr/bin/env bash
@ -54,8 +54,8 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -x "$ROOT_DIR/packages/coding-agent/dist/companion" ]]; then if [[ -x "$ROOT_DIR/packages/coding-agent/dist/clanker" ]]; then
exec "$ROOT_DIR/packages/coding-agent/dist/companion" "$@" exec "$ROOT_DIR/packages/coding-agent/dist/clanker" "$@"
fi fi
if [[ -f "$ROOT_DIR/packages/coding-agent/dist/cli.js" ]]; then if [[ -f "$ROOT_DIR/packages/coding-agent/dist/cli.js" ]]; then
@ -66,10 +66,10 @@ if [[ -x "$ROOT_DIR/node_modules/.bin/tsx" ]] && [[ -f "$ROOT_DIR/packages/codin
exec "$ROOT_DIR/node_modules/.bin/tsx" "$ROOT_DIR/packages/coding-agent/src/cli.ts" "$@" exec "$ROOT_DIR/node_modules/.bin/tsx" "$ROOT_DIR/packages/coding-agent/src/cli.ts" "$@"
fi fi
echo "ERROR: no runnable companion binary found and tsx fallback is unavailable." >&2 echo "ERROR: no runnable clanker binary found and tsx fallback is unavailable." >&2
exit 1 exit 1
EOF EOF
chmod +x "$LAUNCHER" chmod +x "$LAUNCHER"
log "Created launcher: $LAUNCHER" log "Created launcher: $LAUNCHER"
log "Run with: ./companion" log "Run with: ./clanker"

7975
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,14 @@
{ {
"name": "companion", "name": "clanker-agent",
"private": true, "private": true,
"type": "module", "type": "module",
"homepage": "https://github.com/getcompanion-ai/co-mono#readme", "homepage": "https://github.com/harivansh-afk/clanker-agent#readme",
"bugs": { "bugs": {
"url": "https://github.com/getcompanion-ai/co-mono/issues" "url": "https://github.com/harivansh-afk/clanker-agent/issues"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/getcompanion-ai/co-mono.git" "url": "git+https://github.com/harivansh-afk/clanker-agent.git"
}, },
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
@ -19,7 +19,7 @@
"dev": "concurrently --names \"ai,agent,coding-agent,tui\" --prefix-colors \"cyan,yellow,red,magenta\" \"cd packages/ai && npm run dev\" \"cd packages/agent && npm run dev\" \"cd packages/coding-agent && npm run dev\" \"cd packages/tui && npm run dev\"", "dev": "concurrently --names \"ai,agent,coding-agent,tui\" --prefix-colors \"cyan,yellow,red,magenta\" \"cd packages/ai && npm run dev\" \"cd packages/agent && npm run dev\" \"cd packages/coding-agent && npm run dev\" \"cd packages/tui && npm run dev\"",
"dev:tsc": "cd packages/ai && npm run dev:tsc", "dev:tsc": "cd packages/ai && npm run dev:tsc",
"check": "biome lint --error-on-warnings . && tsgo --noEmit && npm run check:browser-smoke", "check": "biome lint --error-on-warnings . && tsgo --noEmit && npm run check:browser-smoke",
"check:browser-smoke": "sh -c 'esbuild scripts/browser-smoke-entry.ts --bundle --platform=browser --format=esm --log-limit=0 --outfile=/tmp/companion-browser-smoke.js > /tmp/companion-browser-smoke-errors.log 2>&1 || { echo \"Browser smoke check failed. See /tmp/companion-browser-smoke-errors.log\"; exit 1; }'", "check:browser-smoke": "sh -c 'esbuild scripts/browser-smoke-entry.ts --bundle --platform=browser --format=esm --log-limit=0 --outfile=/tmp/clanker-browser-smoke.js > /tmp/clanker-browser-smoke-errors.log 2>&1 || { echo \"Browser smoke check failed. See /tmp/clanker-browser-smoke-errors.log\"; exit 1; }'",
"test": "npm run test --workspaces --if-present", "test": "npm run test --workspaces --if-present",
"version:patch": "npm version patch -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install", "version:patch": "npm version patch -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
"version:minor": "npm version minor -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install", "version:minor": "npm version minor -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
@ -49,7 +49,7 @@
"version": "0.0.3", "version": "0.0.3",
"dependencies": { "dependencies": {
"@mariozechner/jiti": "^2.6.5", "@mariozechner/jiti": "^2.6.5",
"@mariozechner/companion-coding-agent": "^0.30.2", "@harivansh-afk/clanker-coding-agent": "^0.30.2",
"get-east-asian-width": "^1.4.0" "get-east-asian-width": "^1.4.0"
}, },
"overrides": { "overrides": {

View file

@ -1,18 +1,18 @@
# @mariozechner/companion-agent-core # @mariozechner/clanker-agent-core
Stateful agent with tool execution and event streaming. Built on `@mariozechner/companion-ai`. Stateful agent with tool execution and event streaming. Built on `@mariozechner/clanker-ai`.
## Installation ## Installation
```bash ```bash
npm install @mariozechner/companion-agent-core npm install @mariozechner/clanker-agent-core
``` ```
## Quick Start ## Quick Start
```typescript ```typescript
import { Agent } from "@mariozechner/companion-agent-core"; import { Agent } from "@mariozechner/clanker-agent-core";
import { getModel } from "@mariozechner/companion-ai"; import { getModel } from "@mariozechner/clanker-ai";
const agent = new Agent({ const agent = new Agent({
initialState: { initialState: {
@ -298,7 +298,7 @@ Follow-up messages are checked only when there are no more tool calls and no ste
Extend `AgentMessage` via declaration merging: Extend `AgentMessage` via declaration merging:
```typescript ```typescript
declare module "@mariozechner/companion-agent-core" { declare module "@mariozechner/clanker-agent-core" {
interface CustomAgentMessages { interface CustomAgentMessages {
notification: { role: "notification"; text: string; timestamp: number }; notification: { role: "notification"; text: string; timestamp: number };
} }
@ -378,7 +378,7 @@ Thrown errors are caught by the agent and reported to the LLM as tool errors wit
For browser apps that proxy through a backend: For browser apps that proxy through a backend:
```typescript ```typescript
import { Agent, streamProxy } from "@mariozechner/companion-agent-core"; import { Agent, streamProxy } from "@mariozechner/clanker-agent-core";
const agent = new Agent({ const agent = new Agent({
streamFn: (model, context, options) => streamFn: (model, context, options) =>
@ -395,7 +395,7 @@ const agent = new Agent({
For direct control without the Agent class: For direct control without the Agent class:
```typescript ```typescript
import { agentLoop, agentLoopContinue } from "@mariozechner/companion-agent-core"; import { agentLoop, agentLoopContinue } from "@mariozechner/clanker-agent-core";
const context: AgentContext = { const context: AgentContext = {
systemPrompt: "You are helpful.", systemPrompt: "You are helpful.",

View file

@ -1,5 +1,5 @@
{ {
"name": "@mariozechner/companion-agent-core", "name": "@harivansh-afk/clanker-agent-core",
"version": "0.56.2", "version": "0.56.2",
"description": "General-purpose agent with transport abstraction, state management, and attachment support", "description": "General-purpose agent with transport abstraction, state management, and attachment support",
"type": "module", "type": "module",
@ -17,7 +17,7 @@
"prepublishOnly": "npm run clean && npm run build" "prepublishOnly": "npm run clean && npm run build"
}, },
"dependencies": { "dependencies": {
"@mariozechner/companion-ai": "^0.56.2" "@harivansh-afk/clanker-ai": "^0.56.2"
}, },
"keywords": [ "keywords": [
"ai", "ai",
@ -30,7 +30,7 @@
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/getcompanion-ai/co-mono.git", "url": "git+https://github.com/harivansh-afk/clanker-agent.git",
"directory": "packages/agent" "directory": "packages/agent"
}, },
"engines": { "engines": {

View file

@ -10,7 +10,7 @@ import {
streamSimple, streamSimple,
type ToolResultMessage, type ToolResultMessage,
validateToolArguments, validateToolArguments,
} from "@mariozechner/companion-ai"; } from "@mariozechner/clanker-ai";
import type { import type {
AgentContext, AgentContext,
AgentEvent, AgentEvent,

View file

@ -12,7 +12,7 @@ import {
type TextContent, type TextContent,
type ThinkingBudgets, type ThinkingBudgets,
type Transport, type Transport,
} from "@mariozechner/companion-ai"; } from "@mariozechner/clanker-ai";
import { agentLoop, agentLoopContinue } from "./agent-loop.js"; import { agentLoop, agentLoopContinue } from "./agent-loop.js";
import type { import type {
AgentContext, AgentContext,

View file

@ -14,7 +14,7 @@ import {
type SimpleStreamOptions, type SimpleStreamOptions,
type StopReason, type StopReason,
type ToolCall, type ToolCall,
} from "@mariozechner/companion-ai"; } from "@mariozechner/clanker-ai";
// Create stream class matching ProxyMessageEventStream // Create stream class matching ProxyMessageEventStream
class ProxyMessageEventStream extends EventStream< class ProxyMessageEventStream extends EventStream<

View file

@ -8,7 +8,7 @@ import type {
TextContent, TextContent,
Tool, Tool,
ToolResultMessage, ToolResultMessage,
} from "@mariozechner/companion-ai"; } from "@mariozechner/clanker-ai";
import type { Static, TSchema } from "@sinclair/typebox"; import type { Static, TSchema } from "@sinclair/typebox";
/** Stream function - can return sync or Promise for async config lookup */ /** Stream function - can return sync or Promise for async config lookup */

View file

@ -5,7 +5,7 @@ import {
type Message, type Message,
type Model, type Model,
type UserMessage, type UserMessage,
} from "@mariozechner/companion-ai"; } from "@mariozechner/clanker-ai";
import { Type } from "@sinclair/typebox"; import { Type } from "@sinclair/typebox";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { agentLoop, agentLoopContinue } from "../src/agent-loop.js"; import { agentLoop, agentLoopContinue } from "../src/agent-loop.js";

View file

@ -3,7 +3,7 @@ import {
type AssistantMessageEvent, type AssistantMessageEvent,
EventStream, EventStream,
getModel, getModel,
} from "@mariozechner/companion-ai"; } from "@mariozechner/clanker-ai";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { Agent } from "../src/index.js"; import { Agent } from "../src/index.js";

View file

@ -9,7 +9,7 @@
* *
* You can run this test suite with: * You can run this test suite with:
* ```bash * ```bash
* $ AWS_REGION=us-east-1 BEDROCK_EXTENSIVE_MODEL_TEST=1 AWS_PROFILE=companion npm test -- ./test/bedrock-models.test.ts * $ AWS_REGION=us-east-1 BEDROCK_EXTENSIVE_MODEL_TEST=1 AWS_PROFILE=clanker npm test -- ./test/bedrock-models.test.ts
* ``` * ```
* *
* ## Known Issues by Category * ## Known Issues by Category
@ -21,8 +21,8 @@
* 5. **Invalid Signature Format**: Model validates signature format (Anthropic newer models). * 5. **Invalid Signature Format**: Model validates signature format (Anthropic newer models).
*/ */
import type { AssistantMessage } from "@mariozechner/companion-ai"; import type { AssistantMessage } from "@mariozechner/clanker-ai";
import { getModels } from "@mariozechner/companion-ai"; import { getModels } from "@mariozechner/clanker-ai";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { Agent } from "../src/index.js"; import { Agent } from "../src/index.js";
import { hasBedrockCredentials } from "./bedrock-utils.js"; import { hasBedrockCredentials } from "./bedrock-utils.js";

View file

@ -3,8 +3,8 @@ import type {
Model, Model,
ToolResultMessage, ToolResultMessage,
UserMessage, UserMessage,
} from "@mariozechner/companion-ai"; } from "@mariozechner/clanker-ai";
import { getModel } from "@mariozechner/companion-ai"; import { getModel } from "@mariozechner/clanker-ai";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { Agent } from "../src/index.js"; import { Agent } from "../src/index.js";
import { hasBedrockCredentials } from "./bedrock-utils.js"; import { hasBedrockCredentials } from "./bedrock-utils.js";

View file

@ -1,4 +1,4 @@
# @mariozechner/companion-ai # @mariozechner/clanker-ai
Unified LLM API with automatic model discovery, provider configuration, token and cost tracking, and simple context persistence and hand-off to other models mid-session. Unified LLM API with automatic model discovery, provider configuration, token and cost tracking, and simple context persistence and hand-off to other models mid-session.
@ -72,10 +72,10 @@ Unified LLM API with automatic model discovery, provider configuration, token an
## Installation ## Installation
```bash ```bash
npm install @mariozechner/companion-ai npm install @mariozechner/clanker-ai
``` ```
TypeBox exports are re-exported from `@mariozechner/companion-ai`: `Type`, `Static`, and `TSchema`. TypeBox exports are re-exported from `@mariozechner/clanker-ai`: `Type`, `Static`, and `TSchema`.
## Quick Start ## Quick Start
@ -88,7 +88,7 @@ import {
Context, Context,
Tool, Tool,
StringEnum, StringEnum,
} from "@mariozechner/companion-ai"; } from "@mariozechner/clanker-ai";
// Fully typed with auto-complete support for both providers and models // Fully typed with auto-complete support for both providers and models
const model = getModel("openai", "gpt-4o-mini"); const model = getModel("openai", "gpt-4o-mini");
@ -223,7 +223,7 @@ Tools enable LLMs to interact with external systems. This library uses TypeBox s
### Defining Tools ### Defining Tools
```typescript ```typescript
import { Type, Tool, StringEnum } from "@mariozechner/companion-ai"; import { Type, Tool, StringEnum } from "@mariozechner/clanker-ai";
// Define tool parameters with TypeBox // Define tool parameters with TypeBox
const weatherTool: Tool = { const weatherTool: Tool = {
@ -356,7 +356,7 @@ When using `agentLoop`, tool arguments are automatically validated against your
When implementing your own tool execution loop with `stream()` or `complete()`, use `validateToolCall` to validate arguments before passing them to your tools: When implementing your own tool execution loop with `stream()` or `complete()`, use `validateToolCall` to validate arguments before passing them to your tools:
```typescript ```typescript
import { stream, validateToolCall, Tool } from "@mariozechner/companion-ai"; import { stream, validateToolCall, Tool } from "@mariozechner/clanker-ai";
const tools: Tool[] = [weatherTool, calculatorTool]; const tools: Tool[] = [weatherTool, calculatorTool];
const s = stream(model, { messages, tools }); const s = stream(model, { messages, tools });
@ -410,7 +410,7 @@ Models with vision capabilities can process images. You can check if a model sup
```typescript ```typescript
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { getModel, complete } from "@mariozechner/companion-ai"; import { getModel, complete } from "@mariozechner/clanker-ai";
const model = getModel("openai", "gpt-4o-mini"); const model = getModel("openai", "gpt-4o-mini");
@ -449,7 +449,7 @@ Many models support thinking/reasoning capabilities where they can show their in
### Unified Interface (streamSimple/completeSimple) ### Unified Interface (streamSimple/completeSimple)
```typescript ```typescript
import { getModel, streamSimple, completeSimple } from "@mariozechner/companion-ai"; import { getModel, streamSimple, completeSimple } from "@mariozechner/clanker-ai";
// Many models across providers support thinking/reasoning // Many models across providers support thinking/reasoning
const model = getModel("anthropic", "claude-sonnet-4-20250514"); const model = getModel("anthropic", "claude-sonnet-4-20250514");
@ -491,7 +491,7 @@ for (const block of response.content) {
For fine-grained control, use the provider-specific options: For fine-grained control, use the provider-specific options:
```typescript ```typescript
import { getModel, complete } from "@mariozechner/companion-ai"; import { getModel, complete } from "@mariozechner/clanker-ai";
// OpenAI Reasoning (o1, o3, gpt-5) // OpenAI Reasoning (o1, o3, gpt-5)
const openaiModel = getModel("openai", "gpt-5-mini"); const openaiModel = getModel("openai", "gpt-5-mini");
@ -578,7 +578,7 @@ if (message.stopReason === "error" || message.stopReason === "aborted") {
The abort signal allows you to cancel in-progress requests. Aborted requests have `stopReason === 'aborted'`: The abort signal allows you to cancel in-progress requests. Aborted requests have `stopReason === 'aborted'`:
```typescript ```typescript
import { getModel, stream } from "@mariozechner/companion-ai"; import { getModel, stream } from "@mariozechner/clanker-ai";
const model = getModel("openai", "gpt-4o-mini"); const model = getModel("openai", "gpt-4o-mini");
const controller = new AbortController(); const controller = new AbortController();
@ -682,7 +682,7 @@ A **provider** offers models through a specific API. For example:
### Querying Providers and Models ### Querying Providers and Models
```typescript ```typescript
import { getProviders, getModels, getModel } from "@mariozechner/companion-ai"; import { getProviders, getModels, getModel } from "@mariozechner/clanker-ai";
// Get all available providers // Get all available providers
const providers = getProviders(); const providers = getProviders();
@ -708,7 +708,7 @@ console.log(`Using ${model.name} via ${model.api} API`);
You can create custom models for local inference servers or custom endpoints: You can create custom models for local inference servers or custom endpoints:
```typescript ```typescript
import { Model, stream } from "@mariozechner/companion-ai"; import { Model, stream } from "@mariozechner/clanker-ai";
// Example: Ollama using OpenAI-compatible API // Example: Ollama using OpenAI-compatible API
const ollamaModel: Model<"openai-completions"> = { const ollamaModel: Model<"openai-completions"> = {
@ -802,7 +802,7 @@ If `compat` is not set, the library falls back to URL-based detection. If `compa
Models are typed by their API, which keeps the model metadata accurate. Provider-specific option types are enforced when you call the provider functions directly. The generic `stream` and `complete` functions accept `StreamOptions` with additional provider fields. Models are typed by their API, which keeps the model metadata accurate. Provider-specific option types are enforced when you call the provider functions directly. The generic `stream` and `complete` functions accept `StreamOptions` with additional provider fields.
```typescript ```typescript
import { streamAnthropic, type AnthropicOptions } from "@mariozechner/companion-ai"; import { streamAnthropic, type AnthropicOptions } from "@mariozechner/clanker-ai";
// TypeScript knows this is an Anthropic model // TypeScript knows this is an Anthropic model
const claude = getModel("anthropic", "claude-sonnet-4-20250514"); const claude = getModel("anthropic", "claude-sonnet-4-20250514");
@ -831,7 +831,7 @@ When messages from one provider are sent to a different provider, the library au
### Example: Multi-Provider Conversation ### Example: Multi-Provider Conversation
```typescript ```typescript
import { getModel, complete, Context } from "@mariozechner/companion-ai"; import { getModel, complete, Context } from "@mariozechner/clanker-ai";
// Start with Claude // Start with Claude
const claude = getModel("anthropic", "claude-sonnet-4-20250514"); const claude = getModel("anthropic", "claude-sonnet-4-20250514");
@ -884,7 +884,7 @@ This enables flexible workflows where you can:
The `Context` object can be easily serialized and deserialized using standard JSON methods, making it simple to persist conversations, implement chat history, or transfer contexts between services: The `Context` object can be easily serialized and deserialized using standard JSON methods, making it simple to persist conversations, implement chat history, or transfer contexts between services:
```typescript ```typescript
import { Context, getModel, complete } from "@mariozechner/companion-ai"; import { Context, getModel, complete } from "@mariozechner/clanker-ai";
// Create and use a context // Create and use a context
const context: Context = { const context: Context = {
@ -922,7 +922,7 @@ const continuation = await complete(newModel, restored);
The library supports browser environments. You must pass the API key explicitly since environment variables are not available in browsers: The library supports browser environments. You must pass the API key explicitly since environment variables are not available in browsers:
```typescript ```typescript
import { getModel, complete } from "@mariozechner/companion-ai"; import { getModel, complete } from "@mariozechner/clanker-ai";
// API key must be passed explicitly in browser // API key must be passed explicitly in browser
const model = getModel("anthropic", "claude-3-5-haiku-20241022"); const model = getModel("anthropic", "claude-3-5-haiku-20241022");
@ -943,7 +943,7 @@ const response = await complete(
### Browser Compatibility Notes ### Browser Compatibility Notes
- Amazon Bedrock (`bedrock-converse-stream`) is not supported in browser environments. - Amazon Bedrock (`bedrock-converse-stream`) is not supported in browser environments.
- OAuth login flows are not supported in browser environments. Use the `@mariozechner/companion-ai/oauth` entry point in Node.js. - OAuth login flows are not supported in browser environments. Use the `@mariozechner/clanker-ai/oauth` entry point in Node.js.
- In browser builds, Bedrock can still appear in model lists. Calls to Bedrock models fail at runtime. - In browser builds, Bedrock can still appear in model lists. Calls to Bedrock models fail at runtime.
- Use a server-side proxy or backend service if you need Bedrock or OAuth-based auth from a web app. - Use a server-side proxy or backend service if you need Bedrock or OAuth-based auth from a web app.
@ -985,17 +985,17 @@ const response = await complete(model, context, {
#### Antigravity Version Override #### Antigravity Version Override
Set `COMPANION_AI_ANTIGRAVITY_VERSION` to override the Antigravity User-Agent version when Google updates their requirements: Set `CLANKER_AI_ANTIGRAVITY_VERSION` to override the Antigravity User-Agent version when Google updates their requirements:
```bash ```bash
export COMPANION_AI_ANTIGRAVITY_VERSION="1.23.0" export CLANKER_AI_ANTIGRAVITY_VERSION="1.23.0"
``` ```
#### Cache Retention #### Cache Retention
Set `COMPANION_CACHE_RETENTION=long` to extend prompt cache retention: Set `CLANKER_CACHE_RETENTION=long` to extend prompt cache retention:
| Provider | Default | With `COMPANION_CACHE_RETENTION=long` | | Provider | Default | With `CLANKER_CACHE_RETENTION=long` |
| --------- | --------- | ------------------------------ | | --------- | --------- | ------------------------------ |
| Anthropic | 5 minutes | 1 hour | | Anthropic | 5 minutes | 1 hour |
| OpenAI | in-memory | 24 hours | | OpenAI | in-memory | 24 hours |
@ -1007,7 +1007,7 @@ This only affects direct API calls to `api.anthropic.com` and `api.openai.com`.
### Checking Environment Variables ### Checking Environment Variables
```typescript ```typescript
import { getEnvApiKey } from "@mariozechner/companion-ai"; import { getEnvApiKey } from "@mariozechner/clanker-ai";
// Check if an API key is set in environment variables // Check if an API key is set in environment variables
const key = getEnvApiKey("openai"); // checks OPENAI_API_KEY const key = getEnvApiKey("openai"); // checks OPENAI_API_KEY
@ -1047,7 +1047,7 @@ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
``` ```
```typescript ```typescript
import { getModel, complete } from "@mariozechner/companion-ai"; import { getModel, complete } from "@mariozechner/clanker-ai";
(async () => { (async () => {
const model = getModel("google-vertex", "gemini-2.5-flash"); const model = getModel("google-vertex", "gemini-2.5-flash");
@ -1068,16 +1068,16 @@ Official docs: [Application Default Credentials](https://cloud.google.com/docs/a
The quickest way to authenticate: The quickest way to authenticate:
```bash ```bash
npx @mariozechner/companion-ai login # interactive provider selection npx @mariozechner/clanker-ai login # interactive provider selection
npx @mariozechner/companion-ai login anthropic # login to specific provider npx @mariozechner/clanker-ai login anthropic # login to specific provider
npx @mariozechner/companion-ai list # list available providers npx @mariozechner/clanker-ai list # list available providers
``` ```
Credentials are saved to `auth.json` in the current directory. Credentials are saved to `auth.json` in the current directory.
### Programmatic OAuth ### Programmatic OAuth
The library provides login and token refresh functions via the `@mariozechner/companion-ai/oauth` entry point. Credential storage is the caller's responsibility. The library provides login and token refresh functions via the `@mariozechner/clanker-ai/oauth` entry point. Credential storage is the caller's responsibility.
```typescript ```typescript
import { import {
@ -1095,13 +1095,13 @@ import {
// Types // Types
type OAuthProvider, // 'anthropic' | 'openai-codex' | 'github-copilot' | 'google-gemini-cli' | 'google-antigravity' type OAuthProvider, // 'anthropic' | 'openai-codex' | 'github-copilot' | 'google-gemini-cli' | 'google-antigravity'
type OAuthCredentials, type OAuthCredentials,
} from "@mariozechner/companion-ai/oauth"; } from "@mariozechner/clanker-ai/oauth";
``` ```
### Login Flow Example ### Login Flow Example
```typescript ```typescript
import { loginGitHubCopilot } from "@mariozechner/companion-ai/oauth"; import { loginGitHubCopilot } from "@mariozechner/clanker-ai/oauth";
import { writeFileSync } from "fs"; import { writeFileSync } from "fs";
const credentials = await loginGitHubCopilot({ const credentials = await loginGitHubCopilot({
@ -1125,8 +1125,8 @@ writeFileSync("auth.json", JSON.stringify(auth, null, 2));
Use `getOAuthApiKey()` to get an API key, automatically refreshing if expired: Use `getOAuthApiKey()` to get an API key, automatically refreshing if expired:
```typescript ```typescript
import { getModel, complete } from "@mariozechner/companion-ai"; import { getModel, complete } from "@mariozechner/clanker-ai";
import { getOAuthApiKey } from "@mariozechner/companion-ai/oauth"; import { getOAuthApiKey } from "@mariozechner/clanker-ai/oauth";
import { readFileSync, writeFileSync } from "fs"; import { readFileSync, writeFileSync } from "fs";
// Load your stored credentials // Load your stored credentials

View file

@ -1,5 +1,5 @@
{ {
"name": "@mariozechner/companion-ai", "name": "@harivansh-afk/clanker-ai",
"version": "0.56.2", "version": "0.56.2",
"description": "Unified LLM API with automatic model discovery and provider configuration", "description": "Unified LLM API with automatic model discovery and provider configuration",
"type": "module", "type": "module",
@ -20,7 +20,7 @@
} }
}, },
"bin": { "bin": {
"companion-ai": "./dist/cli.js" "clanker-ai": "./dist/cli.js"
}, },
"files": [ "files": [
"dist", "dist",
@ -66,7 +66,7 @@
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/getcompanion-ai/co-mono.git", "url": "git+https://github.com/harivansh-afk/clanker-agent.git",
"directory": "packages/ai" "directory": "packages/ai"
}, },
"engines": { "engines": {

View file

@ -78,7 +78,7 @@ async function main(): Promise<void> {
const providerList = PROVIDERS.map( const providerList = PROVIDERS.map(
(p) => ` ${p.id.padEnd(20)} ${p.name}`, (p) => ` ${p.id.padEnd(20)} ${p.name}`,
).join("\n"); ).join("\n");
console.log(`Usage: npx @mariozechner/companion-ai <command> [provider] console.log(`Usage: npx @mariozechner/clanker-ai <command> [provider]
Commands: Commands:
login [provider] Login to an OAuth provider login [provider] Login to an OAuth provider
@ -88,9 +88,9 @@ Providers:
${providerList} ${providerList}
Examples: Examples:
npx @mariozechner/companion-ai login # interactive provider selection npx @mariozechner/clanker-ai login # interactive provider selection
npx @mariozechner/companion-ai login anthropic # login to specific provider npx @mariozechner/clanker-ai login anthropic # login to specific provider
npx @mariozechner/companion-ai list # list providers npx @mariozechner/clanker-ai list # list providers
`); `);
return; return;
} }
@ -131,7 +131,7 @@ Examples:
if (!PROVIDERS.some((p) => p.id === provider)) { if (!PROVIDERS.some((p) => p.id === provider)) {
console.error(`Unknown provider: ${provider}`); console.error(`Unknown provider: ${provider}`);
console.error( console.error(
`Use 'npx @mariozechner/companion-ai list' to see available providers`, `Use 'npx @mariozechner/clanker-ai list' to see available providers`,
); );
process.exit(1); process.exit(1);
} }
@ -142,7 +142,7 @@ Examples:
} }
console.error(`Unknown command: ${command}`); console.error(`Unknown command: ${command}`);
console.error(`Use 'npx @mariozechner/companion-ai --help' for usage`); console.error(`Use 'npx @mariozechner/clanker-ai --help' for usage`);
process.exit(1); process.exit(1);
} }

View file

@ -514,7 +514,7 @@ function mapThinkingLevelToEffort(
/** /**
* Resolve cache retention preference. * Resolve cache retention preference.
* Defaults to "short" and uses COMPANION_CACHE_RETENTION for backward compatibility. * Defaults to "short" and uses CLANKER_CACHE_RETENTION for backward compatibility.
*/ */
function resolveCacheRetention( function resolveCacheRetention(
cacheRetention?: CacheRetention, cacheRetention?: CacheRetention,
@ -524,7 +524,7 @@ function resolveCacheRetention(
} }
if ( if (
typeof process !== "undefined" && typeof process !== "undefined" &&
process.env.COMPANION_CACHE_RETENTION === "long" process.env.CLANKER_CACHE_RETENTION === "long"
) { ) {
return "long"; return "long";
} }

View file

@ -40,7 +40,7 @@ import { transformMessages } from "./transform-messages.js";
/** /**
* Resolve cache retention preference. * Resolve cache retention preference.
* Defaults to "short" and uses COMPANION_CACHE_RETENTION for backward compatibility. * Defaults to "short" and uses CLANKER_CACHE_RETENTION for backward compatibility.
*/ */
function resolveCacheRetention( function resolveCacheRetention(
cacheRetention?: CacheRetention, cacheRetention?: CacheRetention,
@ -50,7 +50,7 @@ function resolveCacheRetention(
} }
if ( if (
typeof process !== "undefined" && typeof process !== "undefined" &&
process.env.COMPANION_CACHE_RETENTION === "long" process.env.CLANKER_CACHE_RETENTION === "long"
) { ) {
return "long"; return "long";
} }

View file

@ -88,7 +88,7 @@ const DEFAULT_ANTIGRAVITY_VERSION = "1.18.3";
function getAntigravityHeaders() { function getAntigravityHeaders() {
const version = const version =
process.env.COMPANION_AI_ANTIGRAVITY_VERSION || DEFAULT_ANTIGRAVITY_VERSION; process.env.CLANKER_AI_ANTIGRAVITY_VERSION || DEFAULT_ANTIGRAVITY_VERSION;
return { return {
"User-Agent": `antigravity/${version} darwin/arm64`, "User-Agent": `antigravity/${version} darwin/arm64`,
}; };
@ -1040,8 +1040,8 @@ export function buildRequest(
model: model.id, model: model.id,
request, request,
...(isAntigravity ? { requestType: "agent" } : {}), ...(isAntigravity ? { requestType: "agent" } : {}),
userAgent: isAntigravity ? "antigravity" : "companion-coding-agent", userAgent: isAntigravity ? "antigravity" : "clanker-coding-agent",
requestId: `${isAntigravity ? "agent" : "companion"}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`, requestId: `${isAntigravity ? "agent" : "clanker"}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
}; };
} }

View file

@ -997,10 +997,10 @@ function buildHeaders(
headers.set("Authorization", `Bearer ${token}`); headers.set("Authorization", `Bearer ${token}`);
headers.set("chatgpt-account-id", accountId); headers.set("chatgpt-account-id", accountId);
headers.set("OpenAI-Beta", "responses=experimental"); headers.set("OpenAI-Beta", "responses=experimental");
headers.set("originator", "companion"); headers.set("originator", "clanker");
const userAgent = _os const userAgent = _os
? `companion (${_os.platform()} ${_os.release()}; ${_os.arch()})` ? `clanker (${_os.platform()} ${_os.release()}; ${_os.arch()})`
: "companion (browser)"; : "clanker (browser)";
headers.set("User-Agent", userAgent); headers.set("User-Agent", userAgent);
headers.set("accept", "text/event-stream"); headers.set("accept", "text/event-stream");
headers.set("content-type", "application/json"); headers.set("content-type", "application/json");

View file

@ -33,7 +33,7 @@ const OPENAI_TOOL_CALL_PROVIDERS = new Set([
/** /**
* Resolve cache retention preference. * Resolve cache retention preference.
* Defaults to "short" and uses COMPANION_CACHE_RETENTION for backward compatibility. * Defaults to "short" and uses CLANKER_CACHE_RETENTION for backward compatibility.
*/ */
function resolveCacheRetention( function resolveCacheRetention(
cacheRetention?: CacheRetention, cacheRetention?: CacheRetention,
@ -43,7 +43,7 @@ function resolveCacheRetention(
} }
if ( if (
typeof process !== "undefined" && typeof process !== "undefined" &&
process.env.COMPANION_CACHE_RETENTION === "long" process.env.CLANKER_CACHE_RETENTION === "long"
) { ) {
return "long"; return "long";
} }

View file

@ -283,7 +283,7 @@ export interface OpenAICompletionsCompat {
supportsDeveloperRole?: boolean; supportsDeveloperRole?: boolean;
/** Whether the provider supports `reasoning_effort`. Default: auto-detected from URL. */ /** Whether the provider supports `reasoning_effort`. Default: auto-detected from URL. */
supportsReasoningEffort?: boolean; supportsReasoningEffort?: boolean;
/** Optional mapping from companion-ai reasoning levels to provider/model-specific `reasoning_effort` values. */ /** Optional mapping from clanker-ai reasoning levels to provider/model-specific `reasoning_effort` values. */
reasoningEffortMap?: Partial<Record<ThinkingLevel, string>>; reasoningEffortMap?: Partial<Record<ThinkingLevel, string>>;
/** Whether the provider supports `stream_options: { include_usage: true }` for token usage in streaming responses. Default: true. */ /** Whether the provider supports `stream_options: { include_usage: true }` for token usage in streaming responses. Default: true. */
supportsUsageInStreaming?: boolean; supportsUsageInStreaming?: boolean;

View file

@ -216,7 +216,7 @@ async function refreshAccessToken(refreshToken: string): Promise<TokenResult> {
} }
async function createAuthorizationFlow( async function createAuthorizationFlow(
originator: string = "companion", originator: string = "clanker",
): Promise<{ verifier: string; state: string; url: string }> { ): Promise<{ verifier: string; state: string; url: string }> {
const { verifier, challenge } = await generatePKCE(); const { verifier, challenge } = await generatePKCE();
const state = createState(); const state = createState();
@ -337,7 +337,7 @@ function getAccountId(accessToken: string): string | null {
* @param options.onManualCodeInput - Optional promise that resolves with user-pasted code. * @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.
* Races with browser callback - whichever completes first wins. * Races with browser callback - whichever completes first wins.
* Useful for showing paste input immediately alongside browser flow. * Useful for showing paste input immediately alongside browser flow.
* @param options.originator - OAuth originator parameter (defaults to "companion") * @param options.originator - OAuth originator parameter (defaults to "clanker")
*/ */
export async function loginOpenAICodex(options: { export async function loginOpenAICodex(options: {
onAuth: (info: { url: string; instructions?: string }) => void; onAuth: (info: { url: string; instructions?: string }) => void;

View file

@ -71,8 +71,8 @@ describe.skipIf(!oauthToken)("Anthropic OAuth tool name normalization", () => {
expect(toolCallName).toBe("todowrite"); expect(toolCallName).toBe("todowrite");
}); });
it("should handle companion's built-in tools (read, write, edit, bash)", async () => { it("should handle clanker's built-in tools (read, write, edit, bash)", async () => {
// Companion's tools use lowercase names, CC uses PascalCase // Clanker's tools use lowercase names, CC uses PascalCase
const readTool: Tool = { const readTool: Tool = {
name: "read", name: "read",
description: "Read a file", description: "Read a file",
@ -116,7 +116,7 @@ describe.skipIf(!oauthToken)("Anthropic OAuth tool name normalization", () => {
}); });
it("should NOT map find to Glob - find is not a CC tool name", async () => { it("should NOT map find to Glob - find is not a CC tool name", async () => {
// Companion has a "find" tool, CC has "Glob" - these are DIFFERENT tools // Clanker has a "find" tool, CC has "Glob" - these are DIFFERENT tools
// The old code incorrectly mapped find -> Glob, which broke the round-trip // The old code incorrectly mapped find -> Glob, which broke the round-trip
// because there's no tool named "glob" in context.tools // because there's no tool named "glob" in context.tools
const findTool: Tool = { const findTool: Tool = {

View file

@ -3,18 +3,18 @@ import { getModel } from "../src/models.js";
import { stream } from "../src/stream.js"; import { stream } from "../src/stream.js";
import type { Context } from "../src/types.js"; import type { Context } from "../src/types.js";
describe("Cache Retention (COMPANION_CACHE_RETENTION)", () => { describe("Cache Retention (CLANKER_CACHE_RETENTION)", () => {
const originalEnv = process.env.COMPANION_CACHE_RETENTION; const originalEnv = process.env.CLANKER_CACHE_RETENTION;
beforeEach(() => { beforeEach(() => {
delete process.env.COMPANION_CACHE_RETENTION; delete process.env.CLANKER_CACHE_RETENTION;
}); });
afterEach(() => { afterEach(() => {
if (originalEnv !== undefined) { if (originalEnv !== undefined) {
process.env.COMPANION_CACHE_RETENTION = originalEnv; process.env.CLANKER_CACHE_RETENTION = originalEnv;
} else { } else {
delete process.env.COMPANION_CACHE_RETENTION; delete process.env.CLANKER_CACHE_RETENTION;
} }
}); });
@ -25,7 +25,7 @@ describe("Cache Retention (COMPANION_CACHE_RETENTION)", () => {
describe("Anthropic Provider", () => { describe("Anthropic Provider", () => {
it.skipIf(!process.env.ANTHROPIC_API_KEY)( it.skipIf(!process.env.ANTHROPIC_API_KEY)(
"should use default cache TTL (no ttl field) when COMPANION_CACHE_RETENTION is not set", "should use default cache TTL (no ttl field) when CLANKER_CACHE_RETENTION is not set",
async () => { async () => {
const model = getModel("anthropic", "claude-3-5-haiku-20241022"); const model = getModel("anthropic", "claude-3-5-haiku-20241022");
let capturedPayload: any = null; let capturedPayload: any = null;
@ -51,9 +51,9 @@ describe("Cache Retention (COMPANION_CACHE_RETENTION)", () => {
); );
it.skipIf(!process.env.ANTHROPIC_API_KEY)( it.skipIf(!process.env.ANTHROPIC_API_KEY)(
"should use 1h cache TTL when COMPANION_CACHE_RETENTION=long", "should use 1h cache TTL when CLANKER_CACHE_RETENTION=long",
async () => { async () => {
process.env.COMPANION_CACHE_RETENTION = "long"; process.env.CLANKER_CACHE_RETENTION = "long";
const model = getModel("anthropic", "claude-3-5-haiku-20241022"); const model = getModel("anthropic", "claude-3-5-haiku-20241022");
let capturedPayload: any = null; let capturedPayload: any = null;
@ -79,7 +79,7 @@ describe("Cache Retention (COMPANION_CACHE_RETENTION)", () => {
); );
it("should not add ttl when baseUrl is not api.anthropic.com", async () => { it("should not add ttl when baseUrl is not api.anthropic.com", async () => {
process.env.COMPANION_CACHE_RETENTION = "long"; process.env.CLANKER_CACHE_RETENTION = "long";
// Create a model with a different baseUrl (simulating a proxy) // Create a model with a different baseUrl (simulating a proxy)
const baseModel = getModel("anthropic", "claude-3-5-haiku-20241022"); const baseModel = getModel("anthropic", "claude-3-5-haiku-20241022");
@ -210,7 +210,7 @@ describe("Cache Retention (COMPANION_CACHE_RETENTION)", () => {
describe("OpenAI Responses Provider", () => { describe("OpenAI Responses Provider", () => {
it.skipIf(!process.env.OPENAI_API_KEY)( it.skipIf(!process.env.OPENAI_API_KEY)(
"should not set prompt_cache_retention when COMPANION_CACHE_RETENTION is not set", "should not set prompt_cache_retention when CLANKER_CACHE_RETENTION is not set",
async () => { async () => {
const model = getModel("openai", "gpt-4o-mini"); const model = getModel("openai", "gpt-4o-mini");
let capturedPayload: any = null; let capturedPayload: any = null;
@ -232,9 +232,9 @@ describe("Cache Retention (COMPANION_CACHE_RETENTION)", () => {
); );
it.skipIf(!process.env.OPENAI_API_KEY)( it.skipIf(!process.env.OPENAI_API_KEY)(
"should set prompt_cache_retention to 24h when COMPANION_CACHE_RETENTION=long", "should set prompt_cache_retention to 24h when CLANKER_CACHE_RETENTION=long",
async () => { async () => {
process.env.COMPANION_CACHE_RETENTION = "long"; process.env.CLANKER_CACHE_RETENTION = "long";
const model = getModel("openai", "gpt-4o-mini"); const model = getModel("openai", "gpt-4o-mini");
let capturedPayload: any = null; let capturedPayload: any = null;
@ -255,7 +255,7 @@ describe("Cache Retention (COMPANION_CACHE_RETENTION)", () => {
); );
it("should not set prompt_cache_retention when baseUrl is not api.openai.com", async () => { it("should not set prompt_cache_retention when baseUrl is not api.openai.com", async () => {
process.env.COMPANION_CACHE_RETENTION = "long"; process.env.CLANKER_CACHE_RETENTION = "long";
// Create a model with a different baseUrl (simulating a proxy) // Create a model with a different baseUrl (simulating a proxy)
const baseModel = getModel("openai", "gpt-4o-mini"); const baseModel = getModel("openai", "gpt-4o-mini");

View file

@ -683,7 +683,7 @@ describe("Context overflow error handling", () => {
// Check if ollama is installed and local LLM tests are enabled // Check if ollama is installed and local LLM tests are enabled
let ollamaInstalled = false; let ollamaInstalled = false;
if (!process.env.COMPANION_NO_LOCAL_LLM) { if (!process.env.CLANKER_NO_LOCAL_LLM) {
try { try {
execSync("which ollama", { stdio: "ignore" }); execSync("which ollama", { stdio: "ignore" });
ollamaInstalled = true; ollamaInstalled = true;
@ -785,7 +785,7 @@ describe("Context overflow error handling", () => {
// ============================================================================= // =============================================================================
let lmStudioRunning = false; let lmStudioRunning = false;
if (!process.env.COMPANION_NO_LOCAL_LLM) { if (!process.env.CLANKER_NO_LOCAL_LLM) {
try { try {
execSync( execSync(
"curl -s --max-time 1 http://localhost:1234/v1/models > /dev/null", "curl -s --max-time 1 http://localhost:1234/v1/models > /dev/null",

View file

@ -227,7 +227,7 @@ function dumpFailurePayload(params: {
payload?: unknown; payload?: unknown;
messages: Message[]; messages: Message[];
}): void { }): void {
const filename = `/tmp/companion-handoff-${params.label}-${Date.now()}.json`; const filename = `/tmp/clanker-handoff-${params.label}-${Date.now()}.json`;
const body = { const body = {
label: params.label, label: params.label,
error: params.error, error: params.error,

View file

@ -765,7 +765,7 @@ describe("AI Providers Empty Message Tests", () => {
); );
// ========================================================================= // =========================================================================
// OAuth-based providers (credentials from ~/.companion/agent/oauth.json) // OAuth-based providers (credentials from ~/.clanker/agent/oauth.json)
// ========================================================================= // =========================================================================
describe("Anthropic OAuth Provider Empty Messages", () => { describe("Anthropic OAuth Provider Empty Messages", () => {

View file

@ -476,7 +476,7 @@ describe("Tool Results with Images", () => {
); );
// ========================================================================= // =========================================================================
// OAuth-based providers (credentials from ~/.companion/agent/oauth.json) // OAuth-based providers (credentials from ~/.clanker/agent/oauth.json)
// ========================================================================= // =========================================================================
describe("Anthropic OAuth Provider (claude-sonnet-4-5)", () => { describe("Anthropic OAuth Provider (claude-sonnet-4-5)", () => {
@ -584,7 +584,7 @@ describe("Tool Results with Images", () => {
}, },
); );
/** These two don't work, the model simply won't call the tool, works in companion /** These two don't work, the model simply won't call the tool, works in clanker
it.skipIf(!antigravityToken)( it.skipIf(!antigravityToken)(
"claude-sonnet-4-5 - should handle tool result with only image", "claude-sonnet-4-5 - should handle tool result with only image",
{ retry: 3, timeout: 30000 }, { retry: 3, timeout: 30000 },

View file

@ -1,5 +1,5 @@
/** /**
* Test helper for resolving API keys from ~/.companion/agent/auth.json * Test helper for resolving API keys from ~/.clanker/agent/auth.json
* *
* Supports both API key and OAuth credentials. * Supports both API key and OAuth credentials.
* OAuth tokens are automatically refreshed if expired and saved back to auth.json. * OAuth tokens are automatically refreshed if expired and saved back to auth.json.
@ -20,7 +20,7 @@ import type {
OAuthProvider, OAuthProvider,
} from "../src/utils/oauth/types.js"; } from "../src/utils/oauth/types.js";
const AUTH_PATH = join(homedir(), ".companion", "agent", "auth.json"); const AUTH_PATH = join(homedir(), ".clanker", "agent", "auth.json");
type ApiKeyCredential = { type ApiKeyCredential = {
type: "api_key"; type: "api_key";
@ -57,7 +57,7 @@ function saveAuthStorage(storage: AuthStorage): void {
} }
/** /**
* Resolve API key for a provider from ~/.companion/agent/auth.json * Resolve API key for a provider from ~/.clanker/agent/auth.json
* *
* For API key credentials, returns the key directly. * For API key credentials, returns the key directly.
* For OAuth credentials, returns the access token (refreshing if expired and saving back). * For OAuth credentials, returns the access token (refreshing if expired and saving back).

View file

@ -6,22 +6,22 @@ import { streamOpenAICodexResponses } from "../src/providers/openai-codex-respon
import type { Context, Model } from "../src/types.js"; import type { Context, Model } from "../src/types.js";
const originalFetch = global.fetch; const originalFetch = global.fetch;
const originalAgentDir = process.env.COMPANION_CODING_AGENT_DIR; const originalAgentDir = process.env.CLANKER_CODING_AGENT_DIR;
afterEach(() => { afterEach(() => {
global.fetch = originalFetch; global.fetch = originalFetch;
if (originalAgentDir === undefined) { if (originalAgentDir === undefined) {
delete process.env.COMPANION_CODING_AGENT_DIR; delete process.env.CLANKER_CODING_AGENT_DIR;
} else { } else {
process.env.COMPANION_CODING_AGENT_DIR = originalAgentDir; process.env.CLANKER_CODING_AGENT_DIR = originalAgentDir;
} }
vi.restoreAllMocks(); vi.restoreAllMocks();
}); });
describe("openai-codex streaming", () => { describe("openai-codex streaming", () => {
it("streams SSE responses into AssistantMessageEventStream", async () => { it("streams SSE responses into AssistantMessageEventStream", async () => {
const tempDir = mkdtempSync(join(tmpdir(), "companion-codex-stream-")); const tempDir = mkdtempSync(join(tmpdir(), "clanker-codex-stream-"));
process.env.COMPANION_CODING_AGENT_DIR = tempDir; process.env.CLANKER_CODING_AGENT_DIR = tempDir;
const payload = Buffer.from( const payload = Buffer.from(
JSON.stringify({ JSON.stringify({
@ -95,7 +95,7 @@ describe("openai-codex streaming", () => {
expect(headers?.get("Authorization")).toBe(`Bearer ${token}`); expect(headers?.get("Authorization")).toBe(`Bearer ${token}`);
expect(headers?.get("chatgpt-account-id")).toBe("acc_test"); expect(headers?.get("chatgpt-account-id")).toBe("acc_test");
expect(headers?.get("OpenAI-Beta")).toBe("responses=experimental"); expect(headers?.get("OpenAI-Beta")).toBe("responses=experimental");
expect(headers?.get("originator")).toBe("companion"); expect(headers?.get("originator")).toBe("clanker");
expect(headers?.get("accept")).toBe("text/event-stream"); expect(headers?.get("accept")).toBe("text/event-stream");
expect(headers?.has("x-api-key")).toBe(false); expect(headers?.has("x-api-key")).toBe(false);
return new Response(stream, { return new Response(stream, {
@ -149,8 +149,8 @@ describe("openai-codex streaming", () => {
}); });
it("sets conversation_id/session_id headers and prompt_cache_key when sessionId is provided", async () => { it("sets conversation_id/session_id headers and prompt_cache_key when sessionId is provided", async () => {
const tempDir = mkdtempSync(join(tmpdir(), "companion-codex-stream-")); const tempDir = mkdtempSync(join(tmpdir(), "clanker-codex-stream-"));
process.env.COMPANION_CODING_AGENT_DIR = tempDir; process.env.CLANKER_CODING_AGENT_DIR = tempDir;
const payload = Buffer.from( const payload = Buffer.from(
JSON.stringify({ JSON.stringify({
@ -272,8 +272,8 @@ describe("openai-codex streaming", () => {
it.each(["gpt-5.3-codex", "gpt-5.4"])( it.each(["gpt-5.3-codex", "gpt-5.4"])(
"clamps %s minimal reasoning effort to low", "clamps %s minimal reasoning effort to low",
async (modelId) => { async (modelId) => {
const tempDir = mkdtempSync(join(tmpdir(), "companion-codex-stream-")); const tempDir = mkdtempSync(join(tmpdir(), "clanker-codex-stream-"));
process.env.COMPANION_CODING_AGENT_DIR = tempDir; process.env.CLANKER_CODING_AGENT_DIR = tempDir;
const payload = Buffer.from( const payload = Buffer.from(
JSON.stringify({ JSON.stringify({
@ -393,8 +393,8 @@ describe("openai-codex streaming", () => {
); );
it("does not set conversation_id/session_id headers when sessionId is not provided", async () => { it("does not set conversation_id/session_id headers when sessionId is not provided", async () => {
const tempDir = mkdtempSync(join(tmpdir(), "companion-codex-stream-")); const tempDir = mkdtempSync(join(tmpdir(), "clanker-codex-stream-"));
process.env.COMPANION_CODING_AGENT_DIR = tempDir; process.env.CLANKER_CODING_AGENT_DIR = tempDir;
const payload = Buffer.from( const payload = Buffer.from(
JSON.stringify({ JSON.stringify({

View file

@ -1048,7 +1048,7 @@ describe("Generate E2E Tests", () => {
); );
// ========================================================================= // =========================================================================
// OAuth-based providers (credentials from ~/.companion/agent/oauth.json) // OAuth-based providers (credentials from ~/.clanker/agent/oauth.json)
// Tokens are resolved at module level (see oauthTokens above) // Tokens are resolved at module level (see oauthTokens above)
// ========================================================================= // =========================================================================
@ -1800,7 +1800,7 @@ describe("Generate E2E Tests", () => {
// Check if ollama is installed and local LLM tests are enabled // Check if ollama is installed and local LLM tests are enabled
let ollamaInstalled = false; let ollamaInstalled = false;
if (!process.env.COMPANION_NO_LOCAL_LLM) { if (!process.env.CLANKER_NO_LOCAL_LLM) {
try { try {
execSync("which ollama", { stdio: "ignore" }); execSync("which ollama", { stdio: "ignore" });
ollamaInstalled = true; ollamaInstalled = true;

View file

@ -294,7 +294,7 @@ describe("Token Statistics on Abort", () => {
); );
// ========================================================================= // =========================================================================
// OAuth-based providers (credentials from ~/.companion/agent/oauth.json) // OAuth-based providers (credentials from ~/.clanker/agent/oauth.json)
// ========================================================================= // =========================================================================
describe("Anthropic OAuth Provider", () => { describe("Anthropic OAuth Provider", () => {

View file

@ -7,7 +7,7 @@
* OpenAI Responses API generates IDs in format: {call_id}|{id} * OpenAI Responses API generates IDs in format: {call_id}|{id}
* where {id} can be 400+ chars with special characters (+, /, =). * where {id} can be 400+ chars with special characters (+, /, =).
* *
* Regression test for: https://github.com/badlogic/companion-mono/issues/1022 * Regression test for: https://github.com/badlogic/clanker-mono/issues/1022
*/ */
import { Type } from "@sinclair/typebox"; import { Type } from "@sinclair/typebox";

View file

@ -324,7 +324,7 @@ describe("Tool Call Without Result Tests", () => {
}); });
// ========================================================================= // =========================================================================
// OAuth-based providers (credentials from ~/.companion/agent/oauth.json) // OAuth-based providers (credentials from ~/.clanker/agent/oauth.json)
// ========================================================================= // =========================================================================
describe("Anthropic OAuth Provider", () => { describe("Anthropic OAuth Provider", () => {

View file

@ -472,7 +472,7 @@ describe("AI Providers Unicode Surrogate Pair Tests", () => {
); );
// ========================================================================= // =========================================================================
// OAuth-based providers (credentials from ~/.companion/agent/oauth.json) // OAuth-based providers (credentials from ~/.clanker/agent/oauth.json)
// ========================================================================= // =========================================================================
describe("Anthropic OAuth Provider Unicode Handling", () => { describe("Anthropic OAuth Provider Unicode Handling", () => {

View file

@ -1,6 +1,6 @@
# @e9n/companion-channels # @e9n/clanker-channels
Two-way channel extension for [companion](https://github.com/espennilsen/companion) — route messages between agents and Telegram, Slack, webhooks, or custom adapters. Two-way channel extension for [clanker](https://github.com/espennilsen/clanker) — route messages between agents and Telegram, Slack, webhooks, or custom adapters.
## Features ## Features
@ -13,11 +13,11 @@ Two-way channel extension for [companion](https://github.com/espennilsen/compani
## Settings ## Settings
Add to `~/.companion/agent/settings.json` or `.companion/settings.json`: Add to `~/.clanker/agent/settings.json` or `.clanker/settings.json`:
```json ```json
{ {
"companion-channels": { "clanker-channels": {
"adapters": { "adapters": {
"telegram": { "telegram": {
"type": "telegram", "type": "telegram",
@ -81,7 +81,7 @@ Use `"env:VAR_NAME"` to reference environment variables. Project settings overri
## Install ## Install
```bash ```bash
companion install npm:@e9n/companion-channels clanker install npm:@e9n/clanker-channels
``` ```
## License ## License

View file

@ -1,14 +1,14 @@
{ {
"name": "@e9n/companion-channels", "name": "@harivansh-afk/clanker-channels",
"version": "0.1.0", "version": "0.1.0",
"description": "Two-way channel extension for companion — route messages between agents and Telegram, webhooks, and custom adapters", "description": "Two-way channel extension for clanker - route messages between agents and Telegram, webhooks, and custom adapters",
"type": "module", "type": "module",
"keywords": [ "keywords": [
"companion-package" "clanker-package"
], ],
"license": "MIT", "license": "MIT",
"author": "Espen Nilsen <hi@e9n.dev>", "author": "Espen Nilsen <hi@e9n.dev>",
"companion": { "clanker": {
"extensions": [ "extensions": [
"./src/index.ts" "./src/index.ts"
] ]
@ -18,8 +18,8 @@
"typescript": "^5.0.0" "typescript": "^5.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@mariozechner/companion-ai": "*", "@harivansh-afk/clanker-ai": "*",
"@mariozechner/companion-coding-agent": "*", "@harivansh-afk/clanker-coding-agent": "*",
"@sinclair/typebox": "*" "@sinclair/typebox": "*"
}, },
"dependencies": { "dependencies": {
@ -34,7 +34,7 @@
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/espennilsen/companion.git", "url": "git+https://github.com/harivansh-afk/clanker-agent.git",
"directory": "extensions/companion-channels" "directory": "packages/clanker-channels"
} }
} }

View file

@ -1,5 +1,5 @@
/** /**
* companion-channels Built-in Slack adapter (bidirectional). * clanker-channels Built-in Slack adapter (bidirectional).
* *
* Outgoing: Slack Web API chat.postMessage. * Outgoing: Slack Web API chat.postMessage.
* Incoming: Socket Mode (WebSocket) for events + slash commands. * Incoming: Socket Mode (WebSocket) for events + slash commands.
@ -14,13 +14,13 @@
* - Channel allowlisting (optional) * - Channel allowlisting (optional)
* *
* Requires: * Requires:
* - App-level token (xapp-...) for Socket Mode in settings under companion-channels.slack.appToken * - App-level token (xapp-...) for Socket Mode in settings under clanker-channels.slack.appToken
* - Bot token (xoxb-...) for Web API in settings under companion-channels.slack.botToken * - Bot token (xoxb-...) for Web API in settings under clanker-channels.slack.botToken
* - Socket Mode enabled in app settings * - Socket Mode enabled in app settings
* *
* Config in ~/.companion/agent/settings.json: * Config in ~/.clanker/agent/settings.json:
* { * {
* "companion-channels": { * "clanker-channels": {
* "adapters": { * "adapters": {
* "slack": { * "slack": {
* "type": "slack", * "type": "slack",
@ -95,7 +95,7 @@ export function createSlackAdapter(
cwd?: string, cwd?: string,
log?: SlackAdapterLogger, log?: SlackAdapterLogger,
): ChannelAdapter { ): ChannelAdapter {
// Tokens live in settings under companion-channels.slack (not in the adapter config block) // Tokens live in settings under clanker-channels.slack (not in the adapter config block)
const appToken = const appToken =
(cwd ? (getChannelSetting(cwd, "slack.appToken") as string) : null) ?? (cwd ? (getChannelSetting(cwd, "slack.appToken") as string) : null) ??
(config.appToken as string); (config.appToken as string);
@ -109,11 +109,11 @@ export function createSlackAdapter(
if (!appToken) if (!appToken)
throw new Error( throw new Error(
"Slack adapter requires appToken (xapp-...) in settings under companion-channels.slack.appToken", "Slack adapter requires appToken (xapp-...) in settings under clanker-channels.slack.appToken",
); );
if (!botToken) if (!botToken)
throw new Error( throw new Error(
"Slack adapter requires botToken (xoxb-...) in settings under companion-channels.slack.botToken", "Slack adapter requires botToken (xoxb-...) in settings under clanker-channels.slack.botToken",
); );
let socketClient: SocketModeClient | null = null; let socketClient: SocketModeClient | null = null;

View file

@ -1,5 +1,5 @@
/** /**
* companion-channels Built-in Telegram adapter (bidirectional). * clanker-channels Built-in Telegram adapter (bidirectional).
* *
* Outgoing: Telegram Bot API sendMessage. * Outgoing: Telegram Bot API sendMessage.
* Incoming: Long-polling via getUpdates. * Incoming: Long-polling via getUpdates.
@ -14,7 +14,7 @@
* - File size validation (1MB for docs/photos, 10MB for voice/audio) * - File size validation (1MB for docs/photos, 10MB for voice/audio)
* - MIME type filtering (text-like files only for documents) * - MIME type filtering (text-like files only for documents)
* *
* Config (in settings.json under companion-channels.adapters.telegram): * Config (in settings.json under clanker-channels.adapters.telegram):
* { * {
* "type": "telegram", * "type": "telegram",
* "botToken": "your-telegram-bot-token", * "botToken": "your-telegram-bot-token",
@ -173,7 +173,7 @@ export function createTelegramAdapter(config: AdapterConfig): ChannelAdapter {
} catch (err: any) { } catch (err: any) {
transcriberError = err.message ?? "Unknown transcription config error"; transcriberError = err.message ?? "Unknown transcription config error";
console.error( console.error(
`[companion-channels] Transcription config error: ${transcriberError}`, `[clanker-channels] Transcription config error: ${transcriberError}`,
); );
} }
} }
@ -259,7 +259,7 @@ export function createTelegramAdapter(config: AdapterConfig): ChannelAdapter {
path.extname(info.result.file_path) || path.extname(info.result.file_path) ||
path.extname(suggestedName || "") || path.extname(suggestedName || "") ||
""; "";
const tmpDir = path.join(os.tmpdir(), "companion-channels"); const tmpDir = path.join(os.tmpdir(), "clanker-channels");
fs.mkdirSync(tmpDir, { recursive: true }); fs.mkdirSync(tmpDir, { recursive: true });
const localPath = path.join( const localPath = path.join(
tmpDir, tmpDir,

View file

@ -1,5 +1,5 @@
/** /**
* companion-channels Pluggable audio transcription. * clanker-channels Pluggable audio transcription.
* *
* Supports three providers: * Supports three providers:
* - "apple" macOS SFSpeechRecognizer (free, offline, no API key) * - "apple" macOS SFSpeechRecognizer (free, offline, no API key)

View file

@ -1,5 +1,5 @@
/** /**
* companion-channels Built-in webhook adapter. * clanker-channels Built-in webhook adapter.
* *
* POSTs message as JSON. The recipient field is the webhook URL. * POSTs message as JSON. The recipient field is the webhook URL.
* *

View file

@ -1,18 +1,18 @@
/** /**
* companion-channels Chat bridge. * clanker-channels Chat bridge.
* *
* Listens for incoming messages (channel:receive), serializes per sender, * Listens for incoming messages (channel:receive), serializes per sender,
* routes prompts into the live companion gateway runtime, and sends responses * routes prompts into the live clanker gateway runtime, and sends responses
* back via the same adapter. Each sender gets their own FIFO queue. * back via the same adapter. Each sender gets their own FIFO queue.
* Multiple senders run concurrently up to maxConcurrent. * Multiple senders run concurrently up to maxConcurrent.
*/ */
import { readFileSync } from "node:fs"; import { readFileSync } from "node:fs";
import type { ImageContent } from "@mariozechner/companion-ai"; import type { ImageContent } from "@mariozechner/clanker-ai";
import { import {
type EventBus, type EventBus,
getActiveGatewayRuntime, getActiveGatewayRuntime,
} from "@mariozechner/companion-coding-agent"; } from "@mariozechner/clanker-coding-agent";
import type { ChannelRegistry } from "../registry.js"; import type { ChannelRegistry } from "../registry.js";
import type { import type {
BridgeConfig, BridgeConfig,
@ -73,7 +73,7 @@ export class ChatBridge {
if (!getActiveGatewayRuntime()) { if (!getActiveGatewayRuntime()) {
this.log( this.log(
"bridge-unavailable", "bridge-unavailable",
{ reason: "no active companion gateway runtime" }, { reason: "no active clanker gateway runtime" },
"WARN", "WARN",
); );
return; return;
@ -195,7 +195,7 @@ export class ChatBridge {
this.sendReply( this.sendReply(
prompt.adapter, prompt.adapter,
prompt.sender, prompt.sender,
"❌ companion gateway is not running.", "❌ clanker gateway is not running.",
); );
return; return;
} }

View file

@ -1,5 +1,5 @@
/** /**
* companion-channels Bot command handler. * clanker-channels Bot command handler.
* *
* Detects messages starting with / and handles them without routing * Detects messages starting with / and handles them without routing
* to the agent. Provides built-in commands and a registry for custom ones. * to the agent. Provides built-in commands and a registry for custom ones.
@ -74,7 +74,7 @@ registerCommand({
name: "start", name: "start",
description: "Welcome message", description: "Welcome message",
handler: () => handler: () =>
"👋 Hi! I'm your companion assistant.\n\n" + "👋 Hi! I'm your clanker assistant.\n\n" +
"Send me a message and I'll process it. Use /help to see available commands.", "Send me a message and I'll process it. Use /help to see available commands.",
}); });

View file

@ -1,7 +1,7 @@
/** /**
* companion-channels Persistent RPC session runner. * clanker-channels Persistent RPC session runner.
* *
* Maintains a long-lived `companion --mode rpc` subprocess per sender, * Maintains a long-lived `clanker --mode rpc` subprocess per sender,
* enabling persistent conversation context across messages. * enabling persistent conversation context across messages.
* Falls back to stateless runner if RPC fails to start. * Falls back to stateless runner if RPC fails to start.
* *
@ -33,7 +33,7 @@ interface PendingRequest {
/** /**
* A persistent RPC session for a single sender. * A persistent RPC session for a single sender.
* Wraps a `companion --mode rpc` subprocess. * Wraps a `clanker --mode rpc` subprocess.
*/ */
export class RpcSession { export class RpcSession {
private child: ChildProcess | null = null; private child: ChildProcess | null = null;
@ -63,7 +63,7 @@ export class RpcSession {
} }
try { try {
this.child = spawn("companion", args, { this.child = spawn("clanker", args, {
cwd: this.options.cwd, cwd: this.options.cwd,
stdio: ["pipe", "pipe", "pipe"], stdio: ["pipe", "pipe", "pipe"],
env: { ...process.env }, env: { ...process.env },

View file

@ -1,9 +1,9 @@
/** /**
* companion-channels Subprocess runner for the chat bridge. * clanker-channels Subprocess runner for the chat bridge.
* *
* Spawns `companion -p --no-session [@files...] <prompt>` to process a single prompt. * Spawns `clanker -p --no-session [@files...] <prompt>` to process a single prompt.
* Supports file attachments (images, documents) via the @file syntax. * Supports file attachments (images, documents) via the @file syntax.
* Same pattern as companion-cron and companion-heartbeat. * Same pattern as clanker-cron and clanker-heartbeat.
*/ */
import { type ChildProcess, spawn } from "node:child_process"; import { type ChildProcess, spawn } from "node:child_process";
@ -49,7 +49,7 @@ export function runPrompt(options: RunOptions): Promise<RunResult> {
let child: ChildProcess; let child: ChildProcess;
try { try {
child = spawn("companion", args, { child = spawn("clanker", args, {
cwd, cwd,
stdio: ["ignore", "pipe", "pipe"], stdio: ["ignore", "pipe", "pipe"],
env: { ...process.env }, env: { ...process.env },

View file

@ -1,5 +1,5 @@
/** /**
* companion-channels Typing indicator manager. * clanker-channels Typing indicator manager.
* *
* Sends periodic typing chat actions via the adapter's sendTyping method. * Sends periodic typing chat actions via the adapter's sendTyping method.
* Telegram typing indicators expire after ~5s, so we refresh every 4s. * Telegram typing indicators expire after ~5s, so we refresh every 4s.

View file

@ -1,13 +1,13 @@
/** /**
* companion-channels Config from companion SettingsManager. * clanker-channels Config from clanker SettingsManager.
* *
* Reads the "companion-channels" key from settings via SettingsManager, * Reads the "clanker-channels" key from settings via SettingsManager,
* which merges global (~/.companion/agent/settings.json) and project * which merges global (~/.clanker/agent/settings.json) and project
* (.companion/settings.json) configs automatically. * (.clanker/settings.json) configs automatically.
* *
* Example settings.json: * Example settings.json:
* { * {
* "companion-channels": { * "clanker-channels": {
* "adapters": { * "adapters": {
* "telegram": { * "telegram": {
* "type": "telegram", * "type": "telegram",
@ -28,10 +28,10 @@
* } * }
*/ */
import { getAgentDir, SettingsManager } from "@mariozechner/companion-coding-agent"; import { getAgentDir, SettingsManager } from "@mariozechner/clanker-coding-agent";
import type { ChannelConfig } from "./types.js"; import type { ChannelConfig } from "./types.js";
const SETTINGS_KEY = "companion-channels"; const SETTINGS_KEY = "clanker-channels";
export function loadConfig(cwd: string): ChannelConfig { export function loadConfig(cwd: string): ChannelConfig {
const agentDir = getAgentDir(); const agentDir = getAgentDir();
@ -62,10 +62,10 @@ export function loadConfig(cwd: string): ChannelConfig {
} }
/** /**
* Read a setting from the "companion-channels" config by dotted key path. * Read a setting from the "clanker-channels" config by dotted key path.
* Useful for adapter-specific secrets that shouldn't live in the adapter config block. * Useful for adapter-specific secrets that shouldn't live in the adapter config block.
* *
* Example: getChannelSetting(cwd, "slack.appToken") reads companion-channels.slack.appToken * Example: getChannelSetting(cwd, "slack.appToken") reads clanker-channels.slack.appToken
*/ */
export function getChannelSetting(cwd: string, keyPath: string): unknown { export function getChannelSetting(cwd: string, keyPath: string): unknown {
const agentDir = getAgentDir(); const agentDir = getAgentDir();

View file

@ -1,5 +1,5 @@
/** /**
* companion-channels Event API registration. * clanker-channels Event API registration.
* *
* Events emitted: * Events emitted:
* channel:receive incoming message from an external adapter * channel:receive incoming message from an external adapter
@ -14,7 +14,7 @@
* bridge:* chat bridge lifecycle events * bridge:* chat bridge lifecycle events
*/ */
import type { ExtensionAPI } from "@mariozechner/companion-coding-agent"; import type { ExtensionAPI } from "@mariozechner/clanker-coding-agent";
import type { ChatBridge } from "./bridge/bridge.js"; import type { ChatBridge } from "./bridge/bridge.js";
import type { ChannelRegistry } from "./registry.js"; import type { ChannelRegistry } from "./registry.js";
import type { import type {
@ -31,13 +31,13 @@ export function setBridge(bridge: ChatBridge | null): void {
} }
export function registerChannelEvents( export function registerChannelEvents(
companion: ExtensionAPI, clanker: ExtensionAPI,
registry: ChannelRegistry, registry: ChannelRegistry,
): void { ): void {
// ── Incoming messages → channel:receive (+ bridge) ────── // ── Incoming messages → channel:receive (+ bridge) ──────
registry.setOnIncoming((message: IncomingMessage) => { registry.setOnIncoming((message: IncomingMessage) => {
companion.events.emit("channel:receive", message); clanker.events.emit("channel:receive", message);
// Route to bridge if active // Route to bridge if active
if (activeBridge?.isActive()) { if (activeBridge?.isActive()) {
@ -47,7 +47,7 @@ export function registerChannelEvents(
// ── Auto-route cron job output ────────────────────────── // ── Auto-route cron job output ──────────────────────────
companion.events.on("cron:job_complete", (raw: unknown) => { clanker.events.on("cron:job_complete", (raw: unknown) => {
const event = raw as { const event = raw as {
job: { name: string; channel: string; prompt: string }; job: { name: string; channel: string; prompt: string };
response?: string; response?: string;
@ -74,7 +74,7 @@ export function registerChannelEvents(
// ── channel:send — deliver a message ───────────────────── // ── channel:send — deliver a message ─────────────────────
companion.events.on("channel:send", (raw: unknown) => { clanker.events.on("channel:send", (raw: unknown) => {
const data = raw as ChannelMessage & { const data = raw as ChannelMessage & {
callback?: (result: { ok: boolean; error?: string }) => void; callback?: (result: { ok: boolean; error?: string }) => void;
}; };
@ -83,7 +83,7 @@ export function registerChannelEvents(
// ── channel:register — add a custom adapter ────────────── // ── channel:register — add a custom adapter ──────────────
companion.events.on("channel:register", (raw: unknown) => { clanker.events.on("channel:register", (raw: unknown) => {
const data = raw as { const data = raw as {
name: string; name: string;
adapter: ChannelAdapter; adapter: ChannelAdapter;
@ -99,14 +99,14 @@ export function registerChannelEvents(
// ── channel:remove — remove an adapter ─────────────────── // ── channel:remove — remove an adapter ───────────────────
companion.events.on("channel:remove", (raw: unknown) => { clanker.events.on("channel:remove", (raw: unknown) => {
const data = raw as { name: string; callback?: (ok: boolean) => void }; const data = raw as { name: string; callback?: (ok: boolean) => void };
data.callback?.(registry.unregister(data.name)); data.callback?.(registry.unregister(data.name));
}); });
// ── channel:list — list adapters + routes ──────────────── // ── channel:list — list adapters + routes ────────────────
companion.events.on("channel:list", (raw: unknown) => { clanker.events.on("channel:list", (raw: unknown) => {
const data = raw as { const data = raw as {
callback?: (items: ReturnType<ChannelRegistry["list"]>) => void; callback?: (items: ReturnType<ChannelRegistry["list"]>) => void;
}; };
@ -115,7 +115,7 @@ export function registerChannelEvents(
// ── channel:test — send a test ping ────────────────────── // ── channel:test — send a test ping ──────────────────────
companion.events.on("channel:test", (raw: unknown) => { clanker.events.on("channel:test", (raw: unknown) => {
const data = raw as { const data = raw as {
adapter: string; adapter: string;
recipient: string; recipient: string;
@ -125,7 +125,7 @@ export function registerChannelEvents(
.send({ .send({
adapter: data.adapter, adapter: data.adapter,
recipient: data.recipient ?? "", recipient: data.recipient ?? "",
text: `🏓 companion-channels test — ${new Date().toISOString()}`, text: `🏓 clanker-channels test — ${new Date().toISOString()}`,
source: "channel:test", source: "channel:test",
}) })
.then((r) => data.callback?.(r)); .then((r) => data.callback?.(r));

View file

@ -1,21 +1,21 @@
/** /**
* companion-channels Two-way channel extension for companion. * clanker-channels Two-way channel extension for clanker.
* *
* Routes messages between agents and external services * Routes messages between agents and external services
* (Telegram, webhooks, custom adapters). * (Telegram, webhooks, custom adapters).
* *
* Built-in adapters: telegram (bidirectional), webhook (outgoing) * Built-in adapters: telegram (bidirectional), webhook (outgoing)
* Custom adapters: register via companion.events.emit("channel:register", ...) * Custom adapters: register via clanker.events.emit("channel:register", ...)
* *
* Chat bridge: when enabled, incoming messages are routed to the agent * Chat bridge: when enabled, incoming messages are routed to the agent
* as isolated subprocess prompts and responses are sent back. Enable via: * as isolated subprocess prompts and responses are sent back. Enable via:
* - --chat-bridge flag * - --chat-bridge flag
* - /chat-bridge on command * - /chat-bridge on command
* - settings.json: { "companion-channels": { "bridge": { "enabled": true } } } * - settings.json: { "clanker-channels": { "bridge": { "enabled": true } } }
* *
* Config in settings.json under "companion-channels": * Config in settings.json under "clanker-channels":
* { * {
* "companion-channels": { * "clanker-channels": {
* "adapters": { * "adapters": {
* "telegram": { "type": "telegram", "botToken": "your-telegram-bot-token", "polling": true } * "telegram": { "type": "telegram", "botToken": "your-telegram-bot-token", "polling": true }
* }, * },
@ -34,7 +34,7 @@
* } * }
*/ */
import type { ExtensionAPI } from "@mariozechner/companion-coding-agent"; import type { ExtensionAPI } from "@mariozechner/clanker-coding-agent";
import { ChatBridge } from "./bridge/bridge.js"; import { ChatBridge } from "./bridge/bridge.js";
import { loadConfig } from "./config.js"; import { loadConfig } from "./config.js";
import { registerChannelEvents, setBridge } from "./events.js"; import { registerChannelEvents, setBridge } from "./events.js";
@ -42,15 +42,15 @@ import { createLogger } from "./logger.js";
import { ChannelRegistry } from "./registry.js"; import { ChannelRegistry } from "./registry.js";
import { registerChannelTool } from "./tool.js"; import { registerChannelTool } from "./tool.js";
export default function (companion: ExtensionAPI) { export default function (clanker: ExtensionAPI) {
const log = createLogger(companion); const log = createLoggerclanker;
const registry = new ChannelRegistry(); const registry = new ChannelRegistry();
registry.setLogger(log); registry.setLogger(log);
let bridge: ChatBridge | null = null; let bridge: ChatBridge | null = null;
// ── Flag: --chat-bridge ─────────────────────────────────── // ── Flag: --chat-bridge ───────────────────────────────────
companion.registerFlag("chat-bridge", { clanker.registerFlag("chat-bridge", {
description: description:
"Enable the chat bridge on startup (incoming messages → agent → reply)", "Enable the chat bridge on startup (incoming messages → agent → reply)",
type: "boolean", type: "boolean",
@ -59,17 +59,17 @@ export default function (companion: ExtensionAPI) {
// ── Event API + cron integration ────────────────────────── // ── Event API + cron integration ──────────────────────────
registerChannelEvents(companion, registry); registerChannelEvents(clanker, registry);
// ── Lifecycle ───────────────────────────────────────────── // ── Lifecycle ─────────────────────────────────────────────
companion.on("session_start", async (_event, ctx) => { clanker.on("session_start", async (_event, ctx) => {
const config = loadConfig(ctx.cwd); const config = loadConfig(ctx.cwd);
await registry.loadConfig(config, ctx.cwd); await registry.loadConfig(config, ctx.cwd);
const errors = registry.getErrors(); const errors = registry.getErrors();
for (const err of errors) { for (const err of errors) {
ctx.ui.notify(`companion-channels: ${err.adapter}: ${err.error}`, "warning"); ctx.ui.notify(`clanker-channels: ${err.adapter}: ${err.error}`, "warning");
log("adapter-error", { adapter: err.adapter, error: err.error }, "ERROR"); log("adapter-error", { adapter: err.adapter, error: err.error }, "ERROR");
} }
log("init", { log("init", {
@ -84,22 +84,22 @@ export default function (companion: ExtensionAPI) {
.getErrors() .getErrors()
.filter((e) => e.error.startsWith("Failed to start")); .filter((e) => e.error.startsWith("Failed to start"));
for (const err of startErrors) { for (const err of startErrors) {
ctx.ui.notify(`companion-channels: ${err.adapter}: ${err.error}`, "warning"); ctx.ui.notify(`clanker-channels: ${err.adapter}: ${err.error}`, "warning");
} }
// Initialize bridge // Initialize bridge
bridge = new ChatBridge(config.bridge, ctx.cwd, registry, companion.events, log); bridge = new ChatBridge(config.bridge, ctx.cwd, registry, clanker.events, log);
setBridge(bridge); setBridge(bridge);
const flagEnabled = companion.getFlag("--chat-bridge"); const flagEnabled = clanker.getFlag("--chat-bridge");
if (flagEnabled || config.bridge?.enabled) { if (flagEnabled || config.bridge?.enabled) {
bridge.start(); bridge.start();
log("bridge-start", {}); log("bridge-start", {});
ctx.ui.notify("companion-channels: Chat bridge started", "info"); ctx.ui.notify("clanker-channels: Chat bridge started", "info");
} }
}); });
companion.on("session_shutdown", async () => { clanker.on("session_shutdown", async () => {
if (bridge?.isActive()) log("bridge-stop", {}); if (bridge?.isActive()) log("bridge-stop", {});
bridge?.stop(); bridge?.stop();
setBridge(null); setBridge(null);
@ -108,7 +108,7 @@ export default function (companion: ExtensionAPI) {
// ── Command: /chat-bridge ───────────────────────────────── // ── Command: /chat-bridge ─────────────────────────────────
companion.registerCommand("chat-bridge", { clanker.registerCommand("chat-bridge", {
description: "Manage chat bridge: /chat-bridge [on|off|status]", description: "Manage chat bridge: /chat-bridge [on|off|status]",
getArgumentCompletions: (prefix: string) => { getArgumentCompletions: (prefix: string) => {
return ["on", "off", "status"] return ["on", "off", "status"]
@ -164,5 +164,5 @@ export default function (companion: ExtensionAPI) {
// ── LLM tool ────────────────────────────────────────────── // ── LLM tool ──────────────────────────────────────────────
registerChannelTool(companion, registry); registerChannelTool(clanker, registry);
} }

View file

@ -0,0 +1,8 @@
import type { ExtensionAPI } from "@mariozechner/clanker-coding-agent";
const CHANNEL = "channels";
export function createLogger(clanker: ExtensionAPI) {
return (event: string, data: unknown, level = "INFO") =>
clanker.events.emit("log", { channel: CHANNEL, event, level, data });
}

View file

@ -1,5 +1,5 @@
/** /**
* companion-channels Adapter registry + route resolution. * clanker-channels Adapter registry + route resolution.
*/ */
import type { import type {

View file

@ -1,9 +1,9 @@
/** /**
* companion-channels LLM tool registration. * clanker-channels LLM tool registration.
*/ */
import { StringEnum } from "@mariozechner/companion-ai"; import { StringEnum } from "@mariozechner/clanker-ai";
import type { ExtensionAPI } from "@mariozechner/companion-coding-agent"; import type { ExtensionAPI } from "@mariozechner/clanker-coding-agent";
import { Type } from "@sinclair/typebox"; import { Type } from "@sinclair/typebox";
import type { ChannelRegistry } from "./registry.js"; import type { ChannelRegistry } from "./registry.js";
@ -16,10 +16,10 @@ interface ChannelToolParams {
} }
export function registerChannelTool( export function registerChannelTool(
companion: ExtensionAPI, clanker: ExtensionAPI,
registry: ChannelRegistry, registry: ChannelRegistry,
): void { ): void {
companion.registerTool({ clanker.registerTool({
name: "notify", name: "notify",
label: "Channel", label: "Channel",
description: description:
@ -57,7 +57,7 @@ export function registerChannelTool(
const items = registry.list(); const items = registry.list();
if (items.length === 0) { if (items.length === 0) {
result = result =
'No adapters configured. Add "companion-channels" to your settings.json.'; 'No adapters configured. Add "clanker-channels" to your settings.json.';
} else { } else {
const lines = items.map((i) => const lines = items.map((i) =>
i.type === "route" i.type === "route"
@ -92,7 +92,7 @@ export function registerChannelTool(
const r = await registry.send({ const r = await registry.send({
adapter: params.adapter, adapter: params.adapter,
recipient: params.recipient ?? "", recipient: params.recipient ?? "",
text: `🏓 companion-channels test — ${new Date().toISOString()}`, text: `🏓 clanker-channels test — ${new Date().toISOString()}`,
source: "channel:test", source: "channel:test",
}); });
result = r.ok result = r.ok

View file

@ -1,5 +1,5 @@
/** /**
* companion-channels Shared types. * clanker-channels Shared types.
*/ */
// ── Channel message ───────────────────────────────────────────── // ── Channel message ─────────────────────────────────────────────
@ -17,7 +17,7 @@ export interface ChannelMessage {
metadata?: Record<string, unknown>; metadata?: Record<string, unknown>;
} }
// ── Incoming message (from external → companion) ─────────────────────── // ── Incoming message (from external → clanker) ───────────────────────
export interface IncomingAttachment { export interface IncomingAttachment {
/** Attachment type */ /** Attachment type */
@ -90,7 +90,7 @@ export interface ChannelAdapter {
sendTyping?(recipient: string): Promise<void>; sendTyping?(recipient: string): Promise<void>;
} }
// ── Config (lives under "companion-channels" key in companion settings.json) ── // ── Config (lives under "clanker-channels" key in clanker settings.json) ──
export interface AdapterConfig { export interface AdapterConfig {
type: string; type: string;
@ -103,8 +103,8 @@ export interface BridgeConfig {
/** /**
* Default session mode (default: "persistent"). * Default session mode (default: "persistent").
* *
* - "persistent" long-lived `companion --mode rpc` subprocess with conversation memory * - "persistent" long-lived `clanker --mode rpc` subprocess with conversation memory
* - "stateless" isolated `companion -p --no-session` subprocess per message (no memory) * - "stateless" isolated `clanker -p --no-session` subprocess per message (no memory)
* *
* Can be overridden per sender via `sessionRules`. * Can be overridden per sender via `sessionRules`.
*/ */
@ -144,7 +144,7 @@ export interface BridgeConfig {
* extensions that crash or conflict, e.g. webserver port collisions). * extensions that crash or conflict, e.g. webserver port collisions).
* List only the extensions the bridge agent actually needs. * List only the extensions the bridge agent actually needs.
* *
* Example: ["/Users/you/Dev/companion/extensions/companion-vault/src/index.ts"] * Example: ["/Users/you/Dev/clanker/extensions/clanker-vault/src/index.ts"]
*/ */
extensions?: string[]; extensions?: string[];
} }

View file

@ -1,12 +1,12 @@
# companion-grind # clanker-grind
Explicit grind mode for companion. Explicit grind mode for clanker.
Features: Features:
- Auto-activates only when the user uses explicit grind cues in a prompt - Auto-activates only when the user uses explicit grind cues in a prompt
- Persists run state in session custom entries - Persists run state in session custom entries
- Continues work on a heartbeat while running in `companion daemon` - Continues work on a heartbeat while running in `clanker daemon`
- Pauses automatically when the user sends a normal prompt - Pauses automatically when the user sends a normal prompt
Example prompts: Example prompts:
@ -26,7 +26,7 @@ Settings:
```json ```json
{ {
"companion-grind": { "clanker-grind": {
"enabled": true, "enabled": true,
"pollIntervalMs": 30000, "pollIntervalMs": 30000,
"cueMode": "explicit-only", "cueMode": "explicit-only",

View file

@ -1,10 +1,10 @@
{ {
"name": "companion-grind", "name": "clanker-grind",
"version": "0.1.0", "version": "0.1.0",
"description": "Explicit grind mode for companion with durable follow-up continuation in daemon mode", "description": "Explicit grind mode for clanker with durable follow-up continuation in daemon mode",
"type": "module", "type": "module",
"keywords": [ "keywords": [
"companion-package" "clanker-package"
], ],
"license": "MIT", "license": "MIT",
"author": "Mario Zechner", "author": "Mario Zechner",
@ -15,14 +15,14 @@
"package.json", "package.json",
"README.md" "README.md"
], ],
"companion": { "clanker": {
"extensions": [ "extensions": [
"./src/index.ts" "./src/index.ts"
] ]
}, },
"peerDependencies": { "peerDependencies": {
"@mariozechner/companion-agent-core": "*", "@harivansh-afk/clanker-agent-core": "*",
"@mariozechner/companion-coding-agent": "*" "@harivansh-afk/clanker-coding-agent": "*"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.3.0", "@types/node": "^24.3.0",

View file

@ -1,4 +1,4 @@
import { getAgentDir, SettingsManager } from "@mariozechner/companion-coding-agent"; import { getAgentDir, SettingsManager } from "@mariozechner/clanker-coding-agent";
import { DEFAULT_POLL_INTERVAL_MS, GRIND_SETTINGS_KEY, type GrindConfig } from "./types.js"; import { DEFAULT_POLL_INTERVAL_MS, GRIND_SETTINGS_KEY, type GrindConfig } from "./types.js";
const DEFAULT_CUE_PATTERNS = [ const DEFAULT_CUE_PATTERNS = [

View file

@ -1,5 +1,5 @@
import type { AgentMessage } from "@mariozechner/companion-agent-core"; import type { AgentMessage } from "@mariozechner/clanker-agent-core";
import type { ExtensionAPI, ExtensionContext, RegisteredCommand } from "@mariozechner/companion-coding-agent"; import type { ExtensionAPI, ExtensionContext, RegisteredCommand } from "@mariozechner/clanker-coding-agent";
import { loadConfig } from "./config.js"; import { loadConfig } from "./config.js";
import { parseAutoActivation, parseGrindStatus, parseStopCondition } from "./parser.js"; import { parseAutoActivation, parseGrindStatus, parseStopCondition } from "./parser.js";
import { buildContinuationPrompt, buildRepairPrompt, buildSystemPromptAddon } from "./prompts.js"; import { buildContinuationPrompt, buildRepairPrompt, buildSystemPromptAddon } from "./prompts.js";
@ -13,19 +13,19 @@ import {
} from "./types.js"; } from "./types.js";
function isDaemonRuntime(): boolean { function isDaemonRuntime(): boolean {
if (process.env.COMPANION_GRIND_FORCE_DAEMON === "1") { if (process.env.CLANKER_GRIND_FORCE_DAEMON === "1") {
return true; return true;
} }
if (process.env.COMPANION_GRIND_FORCE_DAEMON === "0") { if (process.env.CLANKER_GRIND_FORCE_DAEMON === "0") {
return false; return false;
} }
return ( return (
process.argv.includes("daemon") || process.argv.includes("daemon") ||
process.argv.includes("gateway") || process.argv.includes("gateway") ||
Boolean(process.env.COMPANION_GATEWAY_BIND) || Boolean(process.env.CLANKER_GATEWAY_BIND) ||
Boolean(process.env.COMPANION_GATEWAY_PORT) || Boolean(process.env.CLANKER_GATEWAY_PORT) ||
Boolean(process.env.COMPANION_GATEWAY_TOKEN) Boolean(process.env.CLANKER_GATEWAY_TOKEN)
); );
} }
@ -57,21 +57,21 @@ function readState(ctx: ExtensionContext): GrindRunState | null {
return getLatestRunState(ctx.sessionManager.getEntries()); return getLatestRunState(ctx.sessionManager.getEntries());
} }
function persistState(companion: ExtensionAPI, ctx: ExtensionContext, state: GrindRunState): GrindRunState { function persistState(clanker: ExtensionAPI, ctx: ExtensionContext, state: GrindRunState): GrindRunState {
companion.appendEntry(GRIND_STATE_ENTRY_TYPE, state); clanker.appendEntry(GRIND_STATE_ENTRY_TYPE, state);
if (ctx.hasUI) { if (ctx.hasUI) {
ctx.ui.setStatus("companion-grind", state.status === "active" ? "GRIND" : state.status.toUpperCase()); ctx.ui.setStatus("clanker-grind", state.status === "active" ? "GRIND" : state.status.toUpperCase());
} }
return state; return state;
} }
function clearUiStatus(ctx: ExtensionContext): void { function clearUiStatus(ctx: ExtensionContext): void {
if (ctx.hasUI) { if (ctx.hasUI) {
ctx.ui.setStatus("companion-grind", ""); ctx.ui.setStatus("clanker-grind", "");
} }
} }
function maybeExpireRun(companion: ExtensionAPI, ctx: ExtensionContext, state: GrindRunState): GrindRunState | null { function maybeExpireRun(clanker: ExtensionAPI, ctx: ExtensionContext, state: GrindRunState): GrindRunState | null {
if (!state.deadlineAt) { if (!state.deadlineAt) {
return state; return state;
} }
@ -85,7 +85,7 @@ function maybeExpireRun(companion: ExtensionAPI, ctx: ExtensionContext, state: G
lastNextAction: null, lastNextAction: null,
pendingRepair: false, pendingRepair: false,
}); });
persistState(companion, ctx, expired); persistState(clanker, ctx, expired);
note(ctx, "Grind mode stopped: deadline reached."); note(ctx, "Grind mode stopped: deadline reached.");
return expired; return expired;
} }
@ -164,7 +164,7 @@ function parseStartCommandArgs(args: string): {
} }
function startRun( function startRun(
companion: ExtensionAPI, clanker: ExtensionAPI,
ctx: ExtensionContext, ctx: ExtensionContext,
config: GrindConfig, config: GrindConfig,
input: { input: {
@ -181,17 +181,17 @@ function startRun(
} }
if (config.requireDaemon && !isDaemonRuntime()) { if (config.requireDaemon && !isDaemonRuntime()) {
note(ctx, "Durable grind mode requires `companion daemon`."); note(ctx, "Durable grind mode requires `clanker daemon`.");
return null; return null;
} }
const nextState = createRunState(input); const nextState = createRunState(input);
persistState(companion, ctx, nextState); persistState(clanker, ctx, nextState);
note(ctx, "Grind mode activated."); note(ctx, "Grind mode activated.");
return nextState; return nextState;
} }
export default function grind(companion: ExtensionAPI) { export default function grind(clanker: ExtensionAPI) {
let config: GrindConfig | null = null; let config: GrindConfig | null = null;
let state: GrindRunState | null = null; let state: GrindRunState | null = null;
let heartbeat: NodeJS.Timeout | null = null; let heartbeat: NodeJS.Timeout | null = null;
@ -221,18 +221,18 @@ export default function grind(companion: ExtensionAPI) {
return; return;
} }
const expired = maybeExpireRun(companion, ctx, state); const expired = maybeExpireRun(clanker, ctx, state);
state = expired; state = expired;
if (!state || state.status !== "active") { if (!state || state.status !== "active") {
return; return;
} }
if (state.pendingRepair) { if (state.pendingRepair) {
companion.sendUserMessage(buildRepairPrompt(state), { clanker.sendUserMessage(buildRepairPrompt(state), {
deliverAs: "followUp", deliverAs: "followUp",
}); });
} else { } else {
companion.sendUserMessage(buildContinuationPrompt(state), { clanker.sendUserMessage(buildContinuationPrompt(state), {
deliverAs: "followUp", deliverAs: "followUp",
}); });
} }
@ -241,7 +241,7 @@ export default function grind(companion: ExtensionAPI) {
}; };
const registerCommand = (name: string, command: Omit<RegisteredCommand, "name">) => { const registerCommand = (name: string, command: Omit<RegisteredCommand, "name">) => {
companion.registerCommand(name, command); clanker.registerCommand(name, command);
}; };
registerCommand("grind", { registerCommand("grind", {
@ -268,7 +268,7 @@ export default function grind(companion: ExtensionAPI) {
? parseStopCondition(`until ${parsed.until}`) ? parseStopCondition(`until ${parsed.until}`)
: { deadlineAt: null, completionCriterion: null }; : { deadlineAt: null, completionCriterion: null };
const nextState = startRun(companion, ctx, currentConfig, { const nextState = startRun(clanker, ctx, currentConfig, {
activation: "command", activation: "command",
goal: parsed.goal, goal: parsed.goal,
sourcePrompt: parsed.goal, sourcePrompt: parsed.goal,
@ -277,7 +277,7 @@ export default function grind(companion: ExtensionAPI) {
}); });
state = nextState; state = nextState;
if (state) { if (state) {
companion.sendUserMessage(parsed.goal, { deliverAs: "followUp" }); clanker.sendUserMessage(parsed.goal, { deliverAs: "followUp" });
} }
return; return;
} }
@ -312,7 +312,7 @@ export default function grind(companion: ExtensionAPI) {
note(ctx, "No grind run to pause."); note(ctx, "No grind run to pause.");
return; return;
} }
state = persistState(companion, ctx, withStatus(state, "paused", { pendingRepair: false })); state = persistState(clanker, ctx, withStatus(state, "paused", { pendingRepair: false }));
note(ctx, "Grind mode paused."); note(ctx, "Grind mode paused.");
return; return;
} }
@ -326,12 +326,12 @@ export default function grind(companion: ExtensionAPI) {
return; return;
} }
if (currentConfig.requireDaemon && !isDaemonRuntime()) { if (currentConfig.requireDaemon && !isDaemonRuntime()) {
note(ctx, "Durable grind mode requires `companion daemon`."); note(ctx, "Durable grind mode requires `clanker daemon`.");
return; return;
} }
state = persistState(companion, ctx, withStatus(state, "active")); state = persistState(clanker, ctx, withStatus(state, "active"));
note(ctx, "Grind mode resumed."); note(ctx, "Grind mode resumed.");
companion.sendUserMessage(buildContinuationPrompt(state), { clanker.sendUserMessage(buildContinuationPrompt(state), {
deliverAs: "followUp", deliverAs: "followUp",
}); });
return; return;
@ -346,7 +346,7 @@ export default function grind(companion: ExtensionAPI) {
return; return;
} }
state = persistState( state = persistState(
companion, clanker,
ctx, ctx,
withStatus(state, "stopped", { withStatus(state, "stopped", {
pendingRepair: false, pendingRepair: false,
@ -362,22 +362,22 @@ export default function grind(companion: ExtensionAPI) {
}, },
}); });
companion.on("session_start", async (_event, ctx) => { clanker.on("session_start", async (_event, ctx) => {
config = loadConfig(ctx.cwd); config = loadConfig(ctx.cwd);
state = readState(ctx); state = readState(ctx);
if (state && ctx.hasUI) { if (state && ctx.hasUI) {
ctx.ui.setStatus("companion-grind", state.status === "active" ? "GRIND" : state.status.toUpperCase()); ctx.ui.setStatus("clanker-grind", state.status === "active" ? "GRIND" : state.status.toUpperCase());
} }
if (config.enabled) { if (config.enabled) {
ensureHeartbeat(ctx); ensureHeartbeat(ctx);
} }
}); });
companion.on("session_shutdown", async () => { clanker.on("session_shutdown", async () => {
stopHeartbeat(); stopHeartbeat();
}); });
companion.on("input", async (event, ctx) => { clanker.on("input", async (event, ctx) => {
const currentConfig = getConfig(ctx.cwd); const currentConfig = getConfig(ctx.cwd);
if (!currentConfig.enabled || event.source === "extension") { if (!currentConfig.enabled || event.source === "extension") {
return { action: "continue" } as const; return { action: "continue" } as const;
@ -389,7 +389,7 @@ export default function grind(companion: ExtensionAPI) {
const activation = parseAutoActivation(event.text, currentConfig.cuePatterns); const activation = parseAutoActivation(event.text, currentConfig.cuePatterns);
if (!activation) { if (!activation) {
if (currentConfig.userIntervention === "pause") { if (currentConfig.userIntervention === "pause") {
state = persistState(companion, ctx, withStatus(currentState, "paused", { pendingRepair: false })); state = persistState(clanker, ctx, withStatus(currentState, "paused", { pendingRepair: false }));
note(ctx, "Grind mode paused for manual input."); note(ctx, "Grind mode paused for manual input.");
} }
return { action: "continue" } as const; return { action: "continue" } as const;
@ -401,7 +401,7 @@ export default function grind(companion: ExtensionAPI) {
return { action: "continue" } as const; return { action: "continue" } as const;
} }
state = startRun(companion, ctx, currentConfig, { state = startRun(clanker, ctx, currentConfig, {
activation: "explicit", activation: "explicit",
goal: event.text, goal: event.text,
sourcePrompt: event.text, sourcePrompt: event.text,
@ -412,13 +412,13 @@ export default function grind(companion: ExtensionAPI) {
return { action: "continue" } as const; return { action: "continue" } as const;
}); });
companion.on("before_agent_start", async (event, ctx) => { clanker.on("before_agent_start", async (event, ctx) => {
state = state ?? readState(ctx); state = state ?? readState(ctx);
if (!state || state.status !== "active") { if (!state || state.status !== "active") {
return; return;
} }
const expired = maybeExpireRun(companion, ctx, state); const expired = maybeExpireRun(clanker, ctx, state);
state = expired; state = expired;
if (!state || state.status !== "active") { if (!state || state.status !== "active") {
return; return;
@ -429,13 +429,13 @@ export default function grind(companion: ExtensionAPI) {
}; };
}); });
companion.on("turn_end", async (event, ctx) => { clanker.on("turn_end", async (event, ctx) => {
state = state ?? readState(ctx); state = state ?? readState(ctx);
if (!state || state.status !== "active") { if (!state || state.status !== "active") {
return; return;
} }
const expired = maybeExpireRun(companion, ctx, state); const expired = maybeExpireRun(clanker, ctx, state);
state = expired; state = expired;
if (!state || state.status !== "active") { if (!state || state.status !== "active") {
return; return;
@ -447,7 +447,7 @@ export default function grind(companion: ExtensionAPI) {
if (!parsed) { if (!parsed) {
if (state.consecutiveParseFailures + 1 >= MAX_PARSE_FAILURES) { if (state.consecutiveParseFailures + 1 >= MAX_PARSE_FAILURES) {
state = persistState( state = persistState(
companion, clanker,
ctx, ctx,
withStatus(state, "blocked", { withStatus(state, "blocked", {
pendingRepair: false, pendingRepair: false,
@ -460,7 +460,7 @@ export default function grind(companion: ExtensionAPI) {
return; return;
} }
state = persistState(companion, ctx, { state = persistState(clanker, ctx, {
...state, ...state,
pendingRepair: true, pendingRepair: true,
consecutiveParseFailures: state.consecutiveParseFailures + 1, consecutiveParseFailures: state.consecutiveParseFailures + 1,
@ -469,7 +469,7 @@ export default function grind(companion: ExtensionAPI) {
return; return;
} }
state = persistState(companion, ctx, withLoopStatus(state, parsed)); state = persistState(clanker, ctx, withLoopStatus(state, parsed));
if (state.status !== "active") { if (state.status !== "active") {
note(ctx, `Grind mode ${state.status}.`); note(ctx, `Grind mode ${state.status}.`);
if (state.status !== "paused") { if (state.status !== "paused") {

View file

@ -1,5 +1,5 @@
import { randomUUID } from "node:crypto"; import { randomUUID } from "node:crypto";
import type { SessionEntry } from "@mariozechner/companion-coding-agent"; import type { SessionEntry } from "@mariozechner/clanker-coding-agent";
import { import {
GRIND_STATE_ENTRY_TYPE, GRIND_STATE_ENTRY_TYPE,
type GrindActivation, type GrindActivation,

View file

@ -1,5 +1,5 @@
export const GRIND_SETTINGS_KEY = "companion-grind"; export const GRIND_SETTINGS_KEY = "clanker-grind";
export const GRIND_STATE_ENTRY_TYPE = "companion-grind/state"; export const GRIND_STATE_ENTRY_TYPE = "clanker-grind/state";
export const DEFAULT_COMPLETION_CRITERION = "finish the requested task"; export const DEFAULT_COMPLETION_CRITERION = "finish the requested task";
export const DEFAULT_POLL_INTERVAL_MS = 30_000; export const DEFAULT_POLL_INTERVAL_MS = 30_000;
export const MAX_PARSE_FAILURES = 2; export const MAX_PARSE_FAILURES = 2;

View file

@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { detectCue, parseAutoActivation, parseGrindStatus, parseStopCondition } from "../src/parser.js"; import { detectCue, parseAutoActivation, parseGrindStatus, parseStopCondition } from "../src/parser.js";
describe("companion-grind parser", () => { describe("clanker-grind parser", () => {
const now = new Date(2026, 2, 9, 9, 0, 0); const now = new Date(2026, 2, 9, 9, 0, 0);
const cues = ["don't stop", "keep going", "run until"]; const cues = ["don't stop", "keep going", "run until"];

View file

@ -1,9 +1,9 @@
import type { SessionEntry } from "@mariozechner/companion-coding-agent"; import type { SessionEntry } from "@mariozechner/clanker-coding-agent";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { createRunState, getLatestRunState, withLoopStatus, withStatus } from "../src/state.js"; import { createRunState, getLatestRunState, withLoopStatus, withStatus } from "../src/state.js";
import { GRIND_STATE_ENTRY_TYPE } from "../src/types.js"; import { GRIND_STATE_ENTRY_TYPE } from "../src/types.js";
describe("companion-grind state", () => { describe("clanker-grind state", () => {
it("creates active run state", () => { it("creates active run state", () => {
const state = createRunState({ const state = createRunState({
activation: "explicit", activation: "explicit",

View file

@ -1,5 +1,5 @@
node_modules node_modules
.DS_Store .DS_Store
.companion .clanker
dist dist
*.log *.log

View file

@ -1,12 +1,12 @@
# companion-teams: Agent Guide 🤖 # clanker-teams: Agent Guide 🤖
This guide explains how `companion-teams` transforms your single companion agent into a coordinated team of specialists. It covers the roles, capabilities, and coordination patterns available to you as the **Team Lead**. This guide explains how `clanker-teams` transforms your single clanker agent into a coordinated team of specialists. It covers the roles, capabilities, and coordination patterns available to you as the **Team Lead**.
--- ---
## 🎭 The Two Roles ## 🎭 The Two Roles
In a `companion-teams` environment, there are two distinct types of agents: In a `clanker-teams` environment, there are two distinct types of agents:
### 1. The Team Lead (You) ### 1. The Team Lead (You)
@ -87,13 +87,13 @@ Use this for refactoring or security work.
Use automated hooks to ensure standards. Use automated hooks to ensure standards.
1. Define a script at `.companion/team-hooks/task_completed.sh`. 1. Define a script at `.clanker/team-hooks/task_completed.sh`.
2. When any teammate marks a task as `completed`, the hook runs (e.g., runs `npm test`). 2. When any teammate marks a task as `completed`, the hook runs (e.g., runs `npm test`).
3. If the hook fails, you'll know the work isn't ready. 3. If the hook fails, you'll know the work isn't ready.
--- ---
## 🛑 When to Use companion-teams ## 🛑 When to Use clanker-teams
- **Complex Projects**: Tasks that involve multiple files and logic layers. - **Complex Projects**: Tasks that involve multiple files and logic layers.
- **Research & Execution**: One agent researches while another implements. - **Research & Execution**: One agent researches while another implements.

View file

@ -1,21 +1,21 @@
# companion-teams 🚀 # clanker-teams 🚀
**companion-teams** turns your single companion agent into a coordinated software engineering team. It allows you to spawn multiple "Teammate" agents in separate terminal panes that work autonomously, communicate with each other, and manage a shared task board-all mediated through tmux, Zellij, iTerm2, or WezTerm. **clanker-teams** turns your single clanker agent into a coordinated software engineering team. It allows you to spawn multiple "Teammate" agents in separate terminal panes that work autonomously, communicate with each other, and manage a shared task board-all mediated through tmux, Zellij, iTerm2, or WezTerm.
### 🖥️ companion-teams in Action ### 🖥️ clanker-teams in Action
| iTerm2 | tmux | Zellij | | iTerm2 | tmux | Zellij |
| :----------------------------------------------------------------------------------: | :----------------------------------------------------------------------------: | :----------------------------------------------------------------------------------: | | :----------------------------------------------------------------------------------: | :----------------------------------------------------------------------------: | :----------------------------------------------------------------------------------: |
| <a href="iTerm2.png"><img src="iTerm2.png" width="300" alt="companion-teams in iTerm2"></a> | <a href="tmux.png"><img src="tmux.png" width="300" alt="companion-teams in tmux"></a> | <a href="zellij.png"><img src="zellij.png" width="300" alt="companion-teams in Zellij"></a> | | <a href="iTerm2.png"><img src="iTerm2.png" width="300" alt="clanker-teams in iTerm2"></a> | <a href="tmux.png"><img src="tmux.png" width="300" alt="clanker-teams in tmux"></a> | <a href="zellij.png"><img src="zellij.png" width="300" alt="clanker-teams in Zellij"></a> |
_Also works with **WezTerm** (cross-platform support)_ _Also works with **WezTerm** (cross-platform support)_
## 🛠 Installation ## 🛠 Installation
Open your companion terminal and type: Open your clanker terminal and type:
```bash ```bash
companion install npm:companion-teams clanker install npm:clanker-teams
``` ```
## 🚀 Quick Start ## 🚀 Quick Start
@ -95,9 +95,9 @@ companion install npm:companion-teams
> **You:** "Spawn a teammate named 'architect-bot' using 'gpt-4o' with 'high' thinking level for deep reasoning." > **You:** "Spawn a teammate named 'architect-bot' using 'gpt-4o' with 'high' thinking level for deep reasoning."
**Smart Model Resolution:** **Smart Model Resolution:**
When you specify a model name without a provider (e.g., `gemini-2.5-flash`), companion-teams automatically: When you specify a model name without a provider (e.g., `gemini-2.5-flash`), clanker-teams automatically:
- Queries available models from `companion --list-models` - Queries available models from `clanker --list-models`
- Prioritizes **OAuth/subscription providers** (cheaper/free) over API-key providers: - Prioritizes **OAuth/subscription providers** (cheaper/free) over API-key providers:
- `google-gemini-cli` (OAuth) is preferred over `google` (API key) - `google-gemini-cli` (OAuth) is preferred over `google` (API key)
- `github-copilot`, `kimi-sub` are preferred over their API-key equivalents - `github-copilot`, `kimi-sub` are preferred over their API-key equivalents
@ -131,7 +131,7 @@ Teammates in `planning` mode will use `task_submit_plan`. As the lead, review th
## 🪟 Terminal Requirements ## 🪟 Terminal Requirements
To show multiple agents on one screen, **companion-teams** requires a way to manage terminal panes. It supports **tmux**, **Zellij**, **iTerm2**, and **WezTerm**. To show multiple agents on one screen, **clanker-teams** requires a way to manage terminal panes. It supports **tmux**, **Zellij**, **iTerm2**, and **WezTerm**.
### Option 1: tmux (Recommended) ### Option 1: tmux (Recommended)
@ -144,16 +144,16 @@ How to run:
```bash ```bash
tmux # Start tmux session tmux # Start tmux session
companion # Start companion inside tmux clanker # Start clanker inside tmux
``` ```
### Option 2: Zellij ### Option 2: Zellij
Simply start `companion` inside a Zellij session. **companion-teams** will detect it via the `ZELLIJ` environment variable and use `zellij run` to spawn teammates in new panes. Simply start `clanker` inside a Zellij session. **clanker-teams** will detect it via the `ZELLIJ` environment variable and use `zellij run` to spawn teammates in new panes.
### Option 3: iTerm2 (macOS) ### Option 3: iTerm2 (macOS)
If you are using **iTerm2** on macOS and are _not_ inside tmux or Zellij, **companion-teams** can manage your team in two ways: If you are using **iTerm2** on macOS and are _not_ inside tmux or Zellij, **clanker-teams** can manage your team in two ways:
1. **Panes (Default)**: Automatically split your current window into an optimized layout. 1. **Panes (Default)**: Automatically split your current window into an optimized layout.
2. **Windows**: Create true separate OS windows for each agent. 2. **Windows**: Create true separate OS windows for each agent.
@ -174,14 +174,14 @@ How to run:
```bash ```bash
wezterm # Start WezTerm wezterm # Start WezTerm
companion # Start companion inside WezTerm clanker # Start clanker inside WezTerm
``` ```
## 📜 Credits & Attribution ## 📜 Credits & Attribution
This project is a port of the excellent [claude-code-teams-mcp](https://github.com/cs50victor/claude-code-teams-mcp) by [cs50victor](https://github.com/cs50victor). This project is a port of the excellent [claude-code-teams-mcp](https://github.com/cs50victor/claude-code-teams-mcp) by [cs50victor](https://github.com/cs50victor).
We have adapted the original MCP coordination protocol to work natively as a **companion package**, adding features like auto-starting teammates, balanced vertical UI layouts, automatic inbox polling, plan approval mode, broadcast messaging, and quality gate hooks. We have adapted the original MCP coordination protocol to work natively as a **clanker package**, adding features like auto-starting teammates, balanced vertical UI layouts, automatic inbox polling, plan approval mode, broadcast messaging, and quality gate hooks.
## 📄 License ## 📄 License

View file

@ -2,7 +2,7 @@
## Problem ## Problem
WezTerm was not creating the correct panel layout for companion-teams. The desired layout is: WezTerm was not creating the correct panel layout for clanker-teams. The desired layout is:
- **Main controller panel** on the LEFT (takes 70% width) - **Main controller panel** on the LEFT (takes 70% width)
- **Teammate panels** stacked on the RIGHT (takes 30% width, divided vertically) - **Teammate panels** stacked on the RIGHT (takes 30% width, divided vertically)

View file

@ -2,7 +2,7 @@
## Summary ## Summary
Successfully added support for **WezTerm** terminal emulator to companion-teams, bringing the total number of supported terminals to **4**: Successfully added support for **WezTerm** terminal emulator to clanker-teams, bringing the total number of supported terminals to **4**:
- tmux (multiplexer) - tmux (multiplexer)
- Zellij (multiplexer) - Zellij (multiplexer)
@ -89,7 +89,7 @@ Total: **46 tests passing**, 0 failures
- ✅ CLI-based pane management (`wezterm cli split-pane`) - ✅ CLI-based pane management (`wezterm cli split-pane`)
- ✅ Auto-layout: left split for first pane (30%), bottom splits for subsequent (50%) - ✅ Auto-layout: left split for first pane (30%), bottom splits for subsequent (50%)
- ✅ Environment variable filtering (only `COMPANION_*` prefixed) - ✅ Environment variable filtering (only `CLANKER_*` prefixed)
- ✅ Graceful error handling - ✅ Graceful error handling
- ✅ Pane killing via Ctrl-C - ✅ Pane killing via Ctrl-C
- ✅ Tab title setting - ✅ Tab title setting
@ -102,7 +102,7 @@ WezTerm is cross-platform:
- Linux ✅ - Linux ✅
- Windows ✅ - Windows ✅
This means companion-teams now works out-of-the-box on **more platforms** without requiring multiplexers like tmux or Zellij. This means clanker-teams now works out-of-the-box on **more platforms** without requiring multiplexers like tmux or Zellij.
## Conclusion ## Conclusion

View file

@ -1,6 +1,6 @@
# companion-teams Usage Guide # clanker-teams Usage Guide
This guide provides detailed examples, patterns, and best practices for using companion-teams. This guide provides detailed examples, patterns, and best practices for using clanker-teams.
## Table of Contents ## Table of Contents
@ -22,10 +22,10 @@ First, make sure you're inside a tmux session, Zellij session, or iTerm2:
tmux # or zellij, or just use iTerm2 tmux # or zellij, or just use iTerm2
``` ```
Then start companion: Then start clanker:
```bash ```bash
companion clanker
``` ```
Create your first team: Create your first team:
@ -66,7 +66,7 @@ Approve or reject:
### 3. Testing with Automated Hooks ### 3. Testing with Automated Hooks
Create a hook script at `.companion/team-hooks/task_completed.sh`: Create a hook script at `.clanker/team-hooks/task_completed.sh`:
```bash ```bash
#!/bin/bash #!/bin/bash
@ -124,11 +124,11 @@ Hooks are shell scripts that run automatically at specific events. Currently sup
### Hook Location ### Hook Location
Hooks should be placed in `.companion/team-hooks/` in your project directory: Hooks should be placed in `.clanker/team-hooks/` in your project directory:
``` ```
your-project/ your-project/
├── .companion/ ├── .clanker/
│ └── team-hooks/ │ └── team-hooks/
│ └── task_completed.sh │ └── task_completed.sh
``` ```
@ -161,7 +161,7 @@ Example payload:
```bash ```bash
#!/bin/bash #!/bin/bash
# .companion/team-hooks/task_completed.sh # .clanker/team-hooks/task_completed.sh
TASK_DATA="$1" TASK_DATA="$1"
SUBJECT=$(echo "$TASK_DATA" | jq -r '.subject') SUBJECT=$(echo "$TASK_DATA" | jq -r '.subject')
@ -174,7 +174,7 @@ npm test
```bash ```bash
#!/bin/bash #!/bin/bash
# .companion/team-hooks/task_completed.sh # .clanker/team-hooks/task_completed.sh
TASK_DATA="$1" TASK_DATA="$1"
SUBJECT=$(echo "$TASK_DATA" | jq -r '.subject') SUBJECT=$(echo "$TASK_DATA" | jq -r '.subject')
@ -189,7 +189,7 @@ curl -X POST -H 'Content-type: application/json' \
```bash ```bash
#!/bin/bash #!/bin/bash
# .companion/team-hooks/task_completed.sh # .clanker/team-hooks/task_completed.sh
TASK_DATA="$1" TASK_DATA="$1"
SUBJECT=$(echo "$TASK_DATA" | jq -r '.subject') SUBJECT=$(echo "$TASK_DATA" | jq -r '.subject')
@ -304,15 +304,15 @@ This helps you catch blockers early and provide feedback.
**Problem**: tmux panes don't close when killing teammates. **Problem**: tmux panes don't close when killing teammates.
**Solution**: Make sure you started companion inside a tmux session. If you started companion outside tmux, it won't work properly. **Solution**: Make sure you started clanker inside a tmux session. If you started clanker outside tmux, it won't work properly.
```bash ```bash
# Correct way # Correct way
tmux tmux
companion clanker
# Incorrect way # Incorrect way
companion # Then try to use tmux commands clanker # Then try to use tmux commands
``` ```
### Hook Not Running ### Hook Not Running
@ -321,30 +321,30 @@ companion # Then try to use tmux commands
**Checklist**: **Checklist**:
1. File exists at `.companion/team-hooks/task_completed.sh` 1. File exists at `.clanker/team-hooks/task_completed.sh`
2. File is executable: `chmod +x .companion/team-hooks/task_completed.sh` 2. File is executable: `chmod +x .clanker/team-hooks/task_completed.sh`
3. Shebang line is present: `#!/bin/bash` 3. Shebang line is present: `#!/bin/bash`
4. Test manually: `.companion/team-hooks/task_completed.sh '{"test":"data"}'` 4. Test manually: `.clanker/team-hooks/task_completed.sh '{"test":"data"}'`
### Model Errors ### Model Errors
**Problem**: "Model not found" or similar errors. **Problem**: "Model not found" or similar errors.
**Solution**: Check the model name is correct and available in your companion config. Some model names vary between providers: **Solution**: Check the model name is correct and available in your clanker config. Some model names vary between providers:
- `gpt-4o` - OpenAI - `gpt-4o` - OpenAI
- `haiku` - Anthropic (usually `claude-3-5-haiku`) - `haiku` - Anthropic (usually `claude-3-5-haiku`)
- `glm-4.7` - Zhipu AI - `glm-4.7` - Zhipu AI
Check your companion config for available models. Check your clanker config for available models.
### Data Location ### Data Location
All team data is stored in: All team data is stored in:
- `~/.companion/teams/<team-name>/` - Team configuration, member list - `~/.clanker/teams/<team-name>/` - Team configuration, member list
- `~/.companion/tasks/<team-name>/` - Task files - `~/.clanker/tasks/<team-name>/` - Task files
- `~/.companion/messages/<team-name>/` - Message history - `~/.clanker/messages/<team-name>/` - Message history
You can manually inspect these JSON files to debug issues. You can manually inspect these JSON files to debug issues.
@ -386,9 +386,9 @@ To remove all team data:
> "Shut down the team named 'my-team'" > "Shut down the team named 'my-team'"
# Then delete data directory # Then delete data directory
rm -rf ~/.companion/teams/my-team/ rm -rf ~/.clanker/teams/my-team/
rm -rf ~/.companion/tasks/my-team/ rm -rf ~/.clanker/tasks/my-team/
rm -rf ~/.companion/messages/my-team/ rm -rf ~/.clanker/messages/my-team/
``` ```
Or use the delete command: Or use the delete command:

View file

@ -1,14 +1,14 @@
# companion-teams Core Features Implementation Plan # clanker-teams Core Features Implementation Plan
> **REQUIRED SUB-SKILL:** Use the executing-plans skill to implement this plan task-by-task. > **REQUIRED SUB-SKILL:** Use the executing-plans skill to implement this plan task-by-task.
**Goal:** Implement Plan Approval Mode, Broadcast Messaging, and Quality Gate Hooks for the `companion-teams` repository to achieve functional parity with Claude Code Agent Teams. **Goal:** Implement Plan Approval Mode, Broadcast Messaging, and Quality Gate Hooks for the `clanker-teams` repository to achieve functional parity with Claude Code Agent Teams.
**Architecture:** **Architecture:**
- **Plan Approval**: Add a `planning` status to `TaskFile.status`. Create `task_submit_plan` and `task_evaluate_plan` tools. Lead can approve/reject. - **Plan Approval**: Add a `planning` status to `TaskFile.status`. Create `task_submit_plan` and `task_evaluate_plan` tools. Lead can approve/reject.
- **Broadcast Messaging**: Add a `broadcast_message` tool that iterates through the team roster in `config.json` and sends messages to all active members. - **Broadcast Messaging**: Add a `broadcast_message` tool that iterates through the team roster in `config.json` and sends messages to all active members.
- **Quality Gate Hooks**: Introduce a simple hook system that triggers on `task_update` (specifically when status becomes `completed`). For now, it will look for a `.companion/team-hooks/task_completed.sh` or similar. - **Quality Gate Hooks**: Introduce a simple hook system that triggers on `task_update` (specifically when status becomes `completed`). For now, it will look for a `.clanker/team-hooks/task_completed.sh` or similar.
**Tech Stack:** Node.js, TypeScript, Vitest **Tech Stack:** Node.js, TypeScript, Vitest
@ -231,7 +231,7 @@ export function runHook(
): boolean { ): boolean {
const hookPath = path.join( const hookPath = path.join(
process.cwd(), process.cwd(),
".companion", ".clanker",
"team-hooks", "team-hooks",
`${hookName}.sh`, `${hookName}.sh`,
); );

View file

@ -1,4 +1,4 @@
# companion-teams Tool Reference # clanker-teams Tool Reference
Complete documentation of all tools, parameters, and automated behavior. Complete documentation of all tools, parameters, and automated behavior.
@ -95,7 +95,7 @@ Launch a new agent into a terminal pane with a role and instructions.
**Model Options**: **Model Options**:
- Any model available in your companion configuration - Any model available in your clanker configuration
- Common models: `gpt-4o`, `haiku` (Anthropic), `glm-4.7`, `glm-5` (Zhipu AI) - Common models: `gpt-4o`, `haiku` (Anthropic), `glm-4.7`, `glm-5` (Zhipu AI)
**Thinking Levels**: **Thinking Levels**:
@ -298,7 +298,7 @@ task_update({
}); });
``` ```
**Note**: When status changes to `completed`, any hook script at `.companion/team-hooks/task_completed.sh` will automatically run. **Note**: When status changes to `completed`, any hook script at `.clanker/team-hooks/task_completed.sh` will automatically run.
--- ---
@ -477,9 +477,9 @@ This ensures teammates stay responsive to new tasks, messages, and task reassign
### Automated Hooks ### Automated Hooks
When a task's status changes to `completed`, companion-teams automatically executes: When a task's status changes to `completed`, clanker-teams automatically executes:
`.companion/team-hooks/task_completed.sh` `.clanker/team-hooks/task_completed.sh`
The hook receives the task data as a JSON string as the first argument. The hook receives the task data as a JSON string as the first argument.
@ -536,10 +536,10 @@ Task is removed from the active task list. Still preserved in data history.
### Data Storage ### Data Storage
All companion-teams data is stored in your home directory under `~/.companion/`: All clanker-teams data is stored in your home directory under `~/.clanker/`:
``` ```
~/.companion/ ~/.clanker/
├── teams/ ├── teams/
│ └── <team-name>/ │ └── <team-name>/
│ └── config.json # Team configuration and member list │ └── config.json # Team configuration and member list
@ -618,11 +618,11 @@ All companion-teams data is stored in your home directory under `~/.companion/`:
## Environment Variables ## Environment Variables
companion-teams respects the following environment variables: clanker-teams respects the following environment variables:
- `ZELLIJ`: Automatically detected when running inside Zellij. Enables Zellij pane management. - `ZELLIJ`: Automatically detected when running inside Zellij. Enables Zellij pane management.
- `TMUX`: Automatically detected when running inside tmux. Enables tmux pane management. - `TMUX`: Automatically detected when running inside tmux. Enables tmux pane management.
- `COMPANION_DEFAULT_THINKING_LEVEL`: Default thinking level for spawned teammates if not specified (`off`, `minimal`, `low`, `medium`, `high`). - `CLANKER_DEFAULT_THINKING_LEVEL`: Default thinking level for spawned teammates if not specified (`off`, `minimal`, `low`, `medium`, `high`).
--- ---
@ -630,19 +630,19 @@ companion-teams respects the following environment variables:
### tmux Detection ### tmux Detection
If the `TMUX` environment variable is set, companion-teams uses `tmux split-window` to create panes. If the `TMUX` environment variable is set, clanker-teams uses `tmux split-window` to create panes.
**Layout**: Large lead pane on the left, teammates stacked on the right. **Layout**: Large lead pane on the left, teammates stacked on the right.
### Zellij Detection ### Zellij Detection
If the `ZELLIJ` environment variable is set, companion-teams uses `zellij run` to create panes. If the `ZELLIJ` environment variable is set, clanker-teams uses `zellij run` to create panes.
**Layout**: Same as tmux - large lead pane on left, teammates on right. **Layout**: Same as tmux - large lead pane on left, teammates on right.
### iTerm2 Detection ### iTerm2 Detection
If neither tmux nor Zellij is detected, and you're on macOS with iTerm2, companion-teams uses AppleScript to split the window. If neither tmux nor Zellij is detected, and you're on macOS with iTerm2, clanker-teams uses AppleScript to split the window.
**Layout**: Same as tmux/Zellij - large lead pane on left, teammates on right. **Layout**: Same as tmux/Zellij - large lead pane on left, teammates on right.
@ -658,12 +658,12 @@ If neither tmux nor Zellij is detected, and you're on macOS with iTerm2, compani
### Lock Files ### Lock Files
companion-teams uses lock files to prevent concurrent modifications: clanker-teams uses lock files to prevent concurrent modifications:
``` ```
~/.companion/teams/<team-name>/.lock ~/.clanker/teams/<team-name>/.lock
~/.companion/tasks/<team-name>/.lock ~/.clanker/tasks/<team-name>/.lock
~/.companion/messages/<team-name>/.lock ~/.clanker/messages/<team-name>/.lock
``` ```
If a lock file is stale (process no longer running), it's automatically removed after 60 seconds. If a lock file is stale (process no longer running), it's automatically removed after 60 seconds.
@ -678,7 +678,7 @@ If a lock file persists beyond 60 seconds, it's automatically cleaned up. For ma
```bash ```bash
# Remove stale lock # Remove stale lock
rm ~/.companion/teams/my-team/.lock rm ~/.clanker/teams/my-team/.lock
``` ```
--- ---
@ -699,5 +699,5 @@ Messages are stored as JSON. For teams with extensive message history, consider
```bash ```bash
# Archive old messages # Archive old messages
mv ~/.companion/messages/my-team/ ~/.companion/messages-archive/my-team-2024-02-22/ mv ~/.clanker/messages/my-team/ ~/.clanker/messages-archive/my-team-2024-02-22/
``` ```

View file

@ -269,7 +269,7 @@ While not tested in this research, iTerm2 is known to have:
## Recommendations ## Recommendations
### For the companion-teams Project ### For the clanker-teams Project
**Primary Recommendation:** **Primary Recommendation:**

View file

@ -39,7 +39,7 @@ Prompt:
Test the shell-based hook system. First, create a hook script, then mark a task as completed. Test the shell-based hook system. First, create a hook script, then mark a task as completed.
Prompt: Prompt:
"Create a shell script at '.companion/team-hooks/task_completed.sh' that echoes the task ID and status to a file called 'hook_results.txt'. Then, mark task #1 as 'completed' and verify that 'hook_results.txt' has been created." "Create a shell script at '.clanker/team-hooks/task_completed.sh' that echoes the task ID and status to a file called 'hook_results.txt'. Then, mark task #1 as 'completed' and verify that 'hook_results.txt' has been created."
--- ---

View file

@ -31,10 +31,10 @@ Prompt:
### 4. Test Environment Variable Propagation ### 4. Test Environment Variable Propagation
Verify that the COMPANION_DEFAULT_THINKING_LEVEL environment variable is correctly set for each spawned process. Verify that the CLANKER_DEFAULT_THINKING_LEVEL environment variable is correctly set for each spawned process.
Prompt (run in terminal): Prompt (run in terminal):
"Run 'ps aux | grep COMPANION_DEFAULT_THINKING_LEVEL' to check that the environment variables were passed to the spawned teammate processes." "Run 'ps aux | grep CLANKER_DEFAULT_THINKING_LEVEL' to check that the environment variables were passed to the spawned teammate processes."
--- ---
@ -43,7 +43,7 @@ Prompt (run in terminal):
Create tasks appropriate for each teammate's thinking level. Create tasks appropriate for each teammate's thinking level.
Prompt: Prompt:
"Create a task for DeepThinker: 'Analyze the companion-teams codebase architecture and suggest improvements for scalability'. Set it to in_progress. "Create a task for DeepThinker: 'Analyze the clanker-teams codebase architecture and suggest improvements for scalability'. Set it to in_progress.
Create a task for FastWorker: 'List all TypeScript files in the src directory'. Set it to in_progress." Create a task for FastWorker: 'List all TypeScript files in the src directory'. Set it to in_progress."
--- ---

View file

@ -2,13 +2,13 @@
## Executive Summary ## Executive Summary
After researching VS Code and Cursor integrated terminal capabilities, **I recommend AGAINST implementing direct VS Code/Cursor terminal support for companion-teams at this time**. The fundamental issue is that VS Code does not provide a command-line API for spawning or managing terminal panes from within an integrated terminal. While a VS Code extension could theoretically provide this functionality, it would require users to install an additional extension and would not work "out of the box" like the current tmux/Zellij/iTerm2 solutions. After researching VS Code and Cursor integrated terminal capabilities, **I recommend AGAINST implementing direct VS Code/Cursor terminal support for clanker-teams at this time**. The fundamental issue is that VS Code does not provide a command-line API for spawning or managing terminal panes from within an integrated terminal. While a VS Code extension could theoretically provide this functionality, it would require users to install an additional extension and would not work "out of the box" like the current tmux/Zellij/iTerm2 solutions.
--- ---
## Research Scope ## Research Scope
This document investigates whether companion-teams can work with VS Code and Cursor integrated terminals, specifically: This document investigates whether clanker-teams can work with VS Code and Cursor integrated terminals, specifically:
1. Detecting when running inside VS Code/Cursor integrated terminal 1. Detecting when running inside VS Code/Cursor integrated terminal
2. Programmatically creating new terminal instances 2. Programmatically creating new terminal instances
@ -163,8 +163,8 @@ Extensions can register custom terminal profiles:
"terminal": { "terminal": {
"profiles": [ "profiles": [
{ {
"title": "Companion Teams Terminal", "title": "Clanker Teams Terminal",
"id": "companion-teams-terminal" "id": "clanker-teams-terminal"
} }
] ]
} }
@ -172,10 +172,10 @@ Extensions can register custom terminal profiles:
} }
// Register provider // Register provider
vscode.window.registerTerminalProfileProvider('companion-teams-terminal', { vscode.window.registerTerminalProfileProvider('clanker-teams-terminal', {
provideTerminalProfile(token) { provideTerminalProfile(token) {
return { return {
name: "Companion Teams Agent", name: "Clanker Teams Agent",
shellPath: "bash", shellPath: "bash",
cwd: "/project/path" cwd: "/project/path"
}; };
@ -248,17 +248,17 @@ vscode.window.registerTerminalProfileProvider('companion-teams-terminal', {
### ⚠️ Approach 4: VS Code Extension (Partial Solution) ### ⚠️ Approach 4: VS Code Extension (Partial Solution)
**Investigated**: Create a VS Code extension that companion-teams can communicate with **Investigated**: Create a VS Code extension that clanker-teams can communicate with
**Feasible Design**: **Feasible Design**:
1. companion-teams detects VS Code environment (`TERM_PROGRAM=vscode`) 1. clanker-teams detects VS Code environment (`TERM_PROGRAM=vscode`)
2. companion-teams spawns child processes that communicate with the extension 2. clanker-teams spawns child processes that communicate with the extension
3. Extension receives requests and creates terminals via VS Code API 3. Extension receives requests and creates terminals via VS Code API
**Communication Mechanisms**: **Communication Mechanisms**:
- **Local WebSocket server**: Extension starts server, companion-teams connects - **Local WebSocket server**: Extension starts server, clanker-teams connects
- **Named pipes/Unix domain sockets**: On Linux/macOS - **Named pipes/Unix domain sockets**: On Linux/macOS
- **File system polling**: Write request files, extension reads them - **File system polling**: Write request files, extension reads them
- **Local HTTP server**: Easier cross-platform - **Local HTTP server**: Easier cross-platform
@ -267,15 +267,15 @@ vscode.window.registerTerminalProfileProvider('companion-teams-terminal', {
``` ```
┌─────────────┐ ┌─────────────┐
│ companion-teams │ ← Running in integrated terminal │ clanker-teams │ ← Running in integrated terminal
│ (node.js) │ │ (node.js) │
└──────┬──────┘ └──────┬──────┘
│ 1. HTTP POST /create-terminal │ 1. HTTP POST /create-terminal
│ { name: "agent-1", cwd: "/path", command: "companion ..." } │ { name: "agent-1", cwd: "/path", command: "clanker ..." }
┌───────────────────────────┐ ┌───────────────────────────┐
│ companion-teams VS Code Extension │ ← Running in extension host │ clanker-teams VS Code Extension │ ← Running in extension host
│ (TypeScript) │ │ (TypeScript) │
└───────┬───────────────────┘ └───────┬───────────────────┘
@ -283,7 +283,7 @@ vscode.window.registerTerminalProfileProvider('companion-teams-terminal', {
┌───────────────────────────┐ ┌───────────────────────────┐
│ VS Code Terminal Pane │ ← New terminal created │ VS Code Terminal Pane │ ← New terminal created
│ (running companion) │ │ (running clanker) │
└───────────────────────────┘ └───────────────────────────┘
``` ```
@ -298,7 +298,7 @@ vscode.window.registerTerminalProfileProvider('companion-teams-terminal', {
- ❌ Users must install extension (additional dependency) - ❌ Users must install extension (additional dependency)
- ❌ Extension adds ~5-10MB to install - ❌ Extension adds ~5-10MB to install
- ❌ Extension must be maintained alongside companion-teams - ❌ Extension must be maintained alongside clanker-teams
- ❌ Extension adds startup overhead - ❌ Extension adds startup overhead
- ❌ Extension permissions/security concerns - ❌ Extension permissions/security concerns
- ❌ Not "plug and play" like tmux/Zellij - ❌ Not "plug and play" like tmux/Zellij
@ -307,7 +307,7 @@ vscode.window.registerTerminalProfileProvider('companion-teams-terminal', {
--- ---
## 6. Comparison with Existing companion-teams Adapters ## 6. Comparison with Existing clanker-teams Adapters
| Feature | tmux | Zellij | iTerm2 | VS Code (CLI) | VS Code (Extension) | | Feature | tmux | Zellij | iTerm2 | VS Code (CLI) | VS Code (Extension) |
| ----------------- | ------------------------ | ------------------------- | ------------------------ | --------------------- | ----------------------- | | ----------------- | ------------------------ | ------------------------- | ------------------------ | --------------------- | ----------------------- |
@ -407,7 +407,7 @@ The fundamental blocker: **VS Code provides no command-line or shell interface f
1. **No native CLI support**: VS Code provides no command-line API for terminal management 1. **No native CLI support**: VS Code provides no command-line API for terminal management
2. **Extension required**: Would require users to install and configure an extension 2. **Extension required**: Would require users to install and configure an extension
3. **User friction**: Adds setup complexity vs. "just use tmux" 3. **User friction**: Adds setup complexity vs. "just use tmux"
4. **Maintenance burden**: Extension must be maintained alongside companion-teams 4. **Maintenance burden**: Extension must be maintained alongside clanker-teams
5. **Limited benefit**: Users can simply run `tmux` inside VS Code integrated terminal 5. **Limited benefit**: Users can simply run `tmux` inside VS Code integrated terminal
6. **Alternative exists**: tmux/Zellij work perfectly fine inside VS Code terminals 6. **Alternative exists**: tmux/Zellij work perfectly fine inside VS Code terminals
@ -417,9 +417,9 @@ The fundamental blocker: **VS Code provides no command-line or shell interface f
```bash ```bash
# Option 1: Run tmux inside VS Code integrated terminal # Option 1: Run tmux inside VS Code integrated terminal
tmux new -s companion-teams tmux new -s clanker-teams
companion create-team my-team clanker create-team my-team
companion spawn-teammate ... clanker spawn-teammate ...
# Option 2: Start tmux from terminal, then open VS Code # Option 2: Start tmux from terminal, then open VS Code
tmux new -s my-session tmux new -s my-session
@ -445,27 +445,27 @@ If there's strong user demand for native VS Code integration:
#### Architecture #### Architecture
``` ```
1. companion-teams detects VS Code (TERM_PROGRAM=vscode) 1. clanker-teams detects VS Code (TERM_PROGRAM=vscode)
2. companion-teams spawns a lightweight HTTP server 2. clanker-teams spawns a lightweight HTTP server
- Port: Random free port (e.g., 34567) - Port: Random free port (e.g., 34567)
- Endpoint: POST /create-terminal - Endpoint: POST /create-terminal
- Payload: { name, cwd, command, env } - Payload: { name, cwd, command, env }
3. User installs "companion-teams" VS Code extension 3. User installs "clanker-teams" VS Code extension
- Extension starts HTTP client on activation - Extension starts HTTP client on activation
- Finds companion-teams server port via shared file or env var - Finds clanker-teams server port via shared file or env var
4. Extension receives create-terminal requests 4. Extension receives create-terminal requests
- Calls vscode.window.createTerminal() - Calls vscode.window.createTerminal()
- Returns terminal ID - Returns terminal ID
5. companion-teams tracks terminal IDs via extension responses 5. clanker-teams tracks terminal IDs via extension responses
``` ```
#### Implementation Sketch #### Implementation Sketch
**companion-teams (TypeScript)**: **clanker-teams (TypeScript)**:
```typescript ```typescript
class VSCodeAdapter implements TerminalAdapter { class VSCodeAdapter implements TerminalAdapter {
@ -482,7 +482,7 @@ class VSCodeAdapter implements TerminalAdapter {
// Write request file // Write request file
const requestId = uuidv4(); const requestId = uuidv4();
await fs.writeFile( await fs.writeFile(
`/tmp/companion-teams-request-${requestId}.json`, `/tmp/clanker-teams-request-${requestId}.json`,
JSON.stringify({ ...options, requestId }), JSON.stringify({ ...options, requestId }),
); );
@ -514,7 +514,7 @@ export function activate(context: vscode.ExtensionContext) {
// Watch for request files // Watch for request files
const watcher = vscode.workspace.createFileSystemWatcher( const watcher = vscode.workspace.createFileSystemWatcher(
"/tmp/companion-teams-request-*.json", "/tmp/clanker-teams-request-*.json",
); );
watcher.onDidChange(async (uri) => { watcher.onDidChange(async (uri) => {
@ -564,7 +564,7 @@ export function activate(context: vscode.ExtensionContext) {
### Could We Detect Existing Terminal Tabs? ### Could We Detect Existing Terminal Tabs?
**Investigated**: Can companion-teams detect existing VS Code terminal tabs and use them? **Investigated**: Can clanker-teams detect existing VS Code terminal tabs and use them?
**Findings**: **Findings**:
@ -639,9 +639,9 @@ export class VSCodeAdapter implements TerminalAdapter {
spawn(options: SpawnOptions): string { spawn(options: SpawnOptions): string {
throw new Error( throw new Error(
"VS Code integrated terminals do not support spawning " + "VS Code integrated terminals do not support spawning " +
"new terminals from command line. Please run companion-teams " + "new terminals from command line. Please run clanker-teams " +
"inside tmux, Zellij, or iTerm2 for terminal management. " + "inside tmux, Zellij, or iTerm2 for terminal management. " +
"Alternatively, install the companion-teams VS Code extension " + "Alternatively, install the clanker-teams VS Code extension " +
"(if implemented).", "(if implemented).",
); );
} }
@ -665,22 +665,22 @@ export class VSCodeAdapter implements TerminalAdapter {
``` ```
❌ Cannot spawn terminal in VS Code integrated terminal ❌ Cannot spawn terminal in VS Code integrated terminal
companion-teams requires a terminal multiplexer to create multiple panes. clanker-teams requires a terminal multiplexer to create multiple panes.
For VS Code users, we recommend one of these options: For VS Code users, we recommend one of these options:
Option 1: Run tmux inside VS Code integrated terminal Option 1: Run tmux inside VS Code integrated terminal
┌────────────────────────────────────────┐ ┌────────────────────────────────────────┐
│ $ tmux new -s companion-teams │ │ $ tmux new -s clanker-teams │
│ $ companion create-team my-team │ │ $ clanker create-team my-team │
│ $ companion spawn-teammate security-bot ... │ │ $ clanker spawn-teammate security-bot ... │
└────────────────────────────────────────┘ └────────────────────────────────────────┘
Option 2: Open VS Code from tmux session Option 2: Open VS Code from tmux session
┌────────────────────────────────────────┐ ┌────────────────────────────────────────┐
│ $ tmux new -s my-session │ │ $ tmux new -s my-session │
│ $ code . │ │ $ code . │
│ $ companion create-team my-team │ │ $ clanker create-team my-team │
└────────────────────────────────────────┘ └────────────────────────────────────────┘
Option 3: Use a terminal with multiplexer support Option 3: Use a terminal with multiplexer support
@ -690,7 +690,7 @@ Option 3: Use a terminal with multiplexer support
│ • Zellij - Install: cargo install ... │ │ • Zellij - Install: cargo install ... │
└────────────────────────────────────────┘ └────────────────────────────────────────┘
Learn more: https://github.com/your-org/companion-teams#terminal-support Learn more: https://github.com/your-org/clanker-teams#terminal-support
``` ```
--- ---
@ -717,7 +717,7 @@ For VS Code/Cursor users, recommend:
```bash ```bash
# Option 1: Run tmux inside VS Code (simplest) # Option 1: Run tmux inside VS Code (simplest)
tmux new -s companion-teams tmux new -s clanker-teams
# Option 2: Start tmux first, then open VS Code # Option 2: Start tmux first, then open VS Code
tmux new -s dev tmux new -s dev
@ -726,19 +726,19 @@ code .
### Documentation Update ### Documentation Update
Add to companion-teams README.md: Add to clanker-teams README.md:
````markdown ````markdown
## Using companion-teams with VS Code or Cursor ## Using clanker-teams with VS Code or Cursor
companion-teams works great with VS Code and Cursor! Simply run tmux clanker-teams works great with VS Code and Cursor! Simply run tmux
or Zellij inside the integrated terminal: or Zellij inside the integrated terminal:
```bash ```bash
# Start tmux in VS Code integrated terminal # Start tmux in VS Code integrated terminal
$ tmux new -s companion-teams $ tmux new -s clanker-teams
$ companion create-team my-team $ clanker create-team my-team
$ companion spawn-teammate security-bot "Scan for vulnerabilities" $ clanker spawn-teammate security-bot "Scan for vulnerabilities"
``` ```
```` ````
@ -898,12 +898,12 @@ process.env.TERM_PROGRAM === 'iTerm.app'
```bash ```bash
# Step 1: Start tmux # Step 1: Start tmux
tmux new -s companion-teams tmux new -s clanker-teams
# Step 2: Use companion-teams # Step 2: Use clanker-teams
companion create-team my-team clanker create-team my-team
companion spawn-teammate frontend-dev clanker spawn-teammate frontend-dev
companion spawn-teammate backend-dev clanker spawn-teammate backend-dev
# Step 3: Enjoy multi-pane coordination # Step 3: Enjoy multi-pane coordination
┌──────────────────┬──────────────────┬──────────────────┐ ┌──────────────────┬──────────────────┬──────────────────┐

View file

@ -1,6 +1,6 @@
import type { ExtensionAPI } from "@mariozechner/companion-coding-agent"; import type { ExtensionAPI } from "@mariozechner/clanker-coding-agent";
import { Type } from "@sinclair/typebox"; import { Type } from "@sinclair/typebox";
import { StringEnum } from "@mariozechner/companion-ai"; import { StringEnum } from "@mariozechner/clanker-ai";
import * as paths from "../src/utils/paths"; import * as paths from "../src/utils/paths";
import * as teams from "../src/utils/teams"; import * as teams from "../src/utils/teams";
import * as tasks from "../src/utils/tasks"; import * as tasks from "../src/utils/tasks";
@ -19,7 +19,7 @@ let modelsCacheTime = 0;
const MODELS_CACHE_TTL = 60000; // 1 minute const MODELS_CACHE_TTL = 60000; // 1 minute
/** /**
* Query available models from companion --list-models * Query available models from clanker --list-models
*/ */
function getAvailableModels(): Array<{ provider: string; model: string }> { function getAvailableModels(): Array<{ provider: string; model: string }> {
const now = Date.now(); const now = Date.now();
@ -28,7 +28,7 @@ function getAvailableModels(): Array<{ provider: string; model: string }> {
} }
try { try {
const result = spawnSync("companion", ["--list-models"], { const result = spawnSync("clanker", ["--list-models"], {
encoding: "utf-8", encoding: "utf-8",
timeout: 10000, timeout: 10000,
}); });
@ -142,14 +142,14 @@ function resolveModelWithProvider(modelName: string): string | null {
return null; return null;
} }
export default function (companion: ExtensionAPI) { export default function (clanker: ExtensionAPI) {
const isTeammate = !!process.env.COMPANION_AGENT_NAME; const isTeammate = !!process.env.CLANKER_AGENT_NAME;
const agentName = process.env.COMPANION_AGENT_NAME || "team-lead"; const agentName = process.env.CLANKER_AGENT_NAME || "team-lead";
const teamName = process.env.COMPANION_TEAM_NAME; const teamName = process.env.CLANKER_TEAM_NAME;
const terminal = getTerminalAdapter(); const terminal = getTerminalAdapter();
companion.on("session_start", async (_event, ctx) => { clanker.on("session_start", async (_event, ctx) => {
paths.ensureDirs(); paths.ensureDirs();
if (isTeammate) { if (isTeammate) {
if (teamName) { if (teamName) {
@ -157,7 +157,7 @@ export default function (companion: ExtensionAPI) {
fs.writeFileSync(pidFile, process.pid.toString()); fs.writeFileSync(pidFile, process.pid.toString());
} }
ctx.ui.notify(`Teammate: ${agentName} (Team: ${teamName})`, "info"); ctx.ui.notify(`Teammate: ${agentName} (Team: ${teamName})`, "info");
ctx.ui.setStatus("00-companion-teams", `[${agentName.toUpperCase()}]`); ctx.ui.setStatus("00-clanker-teams", `[${agentName.toUpperCase()}]`);
if (terminal) { if (terminal) {
const fullTitle = teamName ? `${teamName}: ${agentName}` : agentName; const fullTitle = teamName ? `${teamName}: ${agentName}` : agentName;
@ -172,7 +172,7 @@ export default function (companion: ExtensionAPI) {
} }
setTimeout(() => { setTimeout(() => {
companion.sendUserMessage( clanker.sendUserMessage(
`I am starting my work as '${agentName}' on team '${teamName}'. Checking my inbox for instructions...`, `I am starting my work as '${agentName}' on team '${teamName}'. Checking my inbox for instructions...`,
); );
}, 1000); }, 1000);
@ -186,18 +186,18 @@ export default function (companion: ExtensionAPI) {
false, false,
); );
if (unread.length > 0) { if (unread.length > 0) {
companion.sendUserMessage( clanker.sendUserMessage(
`I have ${unread.length} new message(s) in my inbox. Reading them now...`, `I have ${unread.length} new message(s) in my inbox. Reading them now...`,
); );
} }
} }
}, 30000); }, 30000);
} else if (teamName) { } else if (teamName) {
ctx.ui.setStatus("companion-teams", `Lead @ ${teamName}`); ctx.ui.setStatus("clanker-teams", `Lead @ ${teamName}`);
} }
}); });
companion.on("turn_start", async (_event, ctx) => { clanker.on("turn_start", async (_event, ctx) => {
if (isTeammate) { if (isTeammate) {
const fullTitle = teamName ? `${teamName}: ${agentName}` : agentName; const fullTitle = teamName ? `${teamName}: ${agentName}` : agentName;
if ((ctx.ui as any).setTitle) (ctx.ui as any).setTitle(fullTitle); if ((ctx.ui as any).setTitle) (ctx.ui as any).setTitle(fullTitle);
@ -206,7 +206,7 @@ export default function (companion: ExtensionAPI) {
}); });
let firstTurn = true; let firstTurn = true;
companion.on("before_agent_start", async (event, ctx) => { clanker.on("before_agent_start", async (event, ctx) => {
if (isTeammate && firstTurn) { if (isTeammate && firstTurn) {
firstTurn = false; firstTurn = false;
@ -259,7 +259,7 @@ export default function (companion: ExtensionAPI) {
} }
// Tools // Tools
companion.registerTool({ clanker.registerTool({
name: "team_create", name: "team_create",
label: "Create Team", label: "Create Team",
description: "Create a new agent team.", description: "Create a new agent team.",
@ -290,7 +290,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "spawn_teammate", name: "spawn_teammate",
label: "Spawn Teammate", label: "Spawn Teammate",
description: "Spawn a new teammate in a terminal pane or separate window.", description: "Spawn a new teammate in a terminal pane or separate window.",
@ -324,7 +324,7 @@ export default function (companion: ExtensionAPI) {
// Resolve model to provider/model format // Resolve model to provider/model format
if (chosenModel) { if (chosenModel) {
if (!chosenModel.includes("/")) { if (!chosenModel.includes("/")) {
// Try to resolve using available models from companion --list-models // Try to resolve using available models from clanker --list-models
const resolved = resolveModelWithProvider(chosenModel); const resolved = resolveModelWithProvider(chosenModel);
if (resolved) { if (resolved) {
chosenModel = resolved; chosenModel = resolved;
@ -371,7 +371,7 @@ export default function (companion: ExtensionAPI) {
"Initial prompt", "Initial prompt",
); );
const piBinary = "companion"; const piBinary = "clanker";
let piCmd = piBinary; let piCmd = piBinary;
if (chosenModel) { if (chosenModel) {
@ -387,8 +387,8 @@ export default function (companion: ExtensionAPI) {
const env: Record<string, string> = { const env: Record<string, string> = {
...process.env, ...process.env,
COMPANION_TEAM_NAME: safeTeamName, CLANKER_TEAM_NAME: safeTeamName,
COMPANION_AGENT_NAME: safeName, CLANKER_AGENT_NAME: safeName,
}; };
let terminalId = ""; let terminalId = "";
@ -452,7 +452,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "spawn_lead_window", name: "spawn_lead_window",
label: "Spawn Lead Window", label: "Spawn Lead Window",
description: "Open the team lead in a separate OS window.", description: "Open the team lead in a separate OS window.",
@ -469,7 +469,7 @@ export default function (companion: ExtensionAPI) {
const teamConfig = await teams.readConfig(safeTeamName); const teamConfig = await teams.readConfig(safeTeamName);
const cwd = params.cwd || process.cwd(); const cwd = params.cwd || process.cwd();
const piBinary = "companion"; const piBinary = "clanker";
let piCmd = piBinary; let piCmd = piBinary;
if (teamConfig.defaultModel) { if (teamConfig.defaultModel) {
// Use the combined --model provider/model format // Use the combined --model provider/model format
@ -478,8 +478,8 @@ export default function (companion: ExtensionAPI) {
const env = { const env = {
...process.env, ...process.env,
COMPANION_TEAM_NAME: safeTeamName, CLANKER_TEAM_NAME: safeTeamName,
COMPANION_AGENT_NAME: "team-lead", CLANKER_AGENT_NAME: "team-lead",
}; };
try { try {
const windowId = terminal.spawnWindow({ const windowId = terminal.spawnWindow({
@ -500,7 +500,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "send_message", name: "send_message",
label: "Send Message", label: "Send Message",
description: "Send a message to a teammate.", description: "Send a message to a teammate.",
@ -527,7 +527,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "broadcast_message", name: "broadcast_message",
label: "Broadcast Message", label: "Broadcast Message",
description: "Broadcast a message to all team members except the sender.", description: "Broadcast a message to all team members except the sender.",
@ -554,7 +554,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "read_inbox", name: "read_inbox",
label: "Read Inbox", label: "Read Inbox",
description: "Read messages from an agent's inbox.", description: "Read messages from an agent's inbox.",
@ -581,7 +581,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "task_create", name: "task_create",
label: "Create Task", label: "Create Task",
description: "Create a new team task.", description: "Create a new team task.",
@ -603,7 +603,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "task_submit_plan", name: "task_submit_plan",
label: "Submit Plan", label: "Submit Plan",
description: "Submit a plan for a task, updating its status to 'planning'.", description: "Submit a plan for a task, updating its status to 'planning'.",
@ -627,7 +627,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "task_evaluate_plan", name: "task_evaluate_plan",
label: "Evaluate Plan", label: "Evaluate Plan",
description: "Evaluate a submitted plan for a task.", description: "Evaluate a submitted plan for a task.",
@ -658,7 +658,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "task_list", name: "task_list",
label: "List Tasks", label: "List Tasks",
description: "List all tasks for a team.", description: "List all tasks for a team.",
@ -674,7 +674,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "task_update", name: "task_update",
label: "Update Task", label: "Update Task",
description: "Update a task's status or owner.", description: "Update a task's status or owner.",
@ -704,7 +704,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "team_shutdown", name: "team_shutdown",
label: "Shutdown Team", label: "Shutdown Team",
description: "Shutdown the entire team and close all panes/windows.", description: "Shutdown the entire team and close all panes/windows.",
@ -732,7 +732,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "task_read", name: "task_read",
label: "Read Task", label: "Read Task",
description: "Read details of a specific task.", description: "Read details of a specific task.",
@ -749,7 +749,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "check_teammate", name: "check_teammate",
label: "Check Teammate", label: "Check Teammate",
description: "Check a single teammate's status.", description: "Check a single teammate's status.",
@ -789,7 +789,7 @@ export default function (companion: ExtensionAPI) {
}, },
}); });
companion.registerTool({ clanker.registerTool({
name: "process_shutdown_approved", name: "process_shutdown_approved",
label: "Process Shutdown Approved", label: "Process Shutdown Approved",
description: "Process a teammate's shutdown.", description: "Process a teammate's shutdown.",

View file

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Before After
Before After

View file

@ -1,15 +1,16 @@
{ {
"name": "companion-teams", "name": "clanker-teams",
"version": "0.8.6", "version": "0.8.6",
"description": "Agent teams for companion, ported from claude-code-teams-mcp", "description": "Agent teams for clanker, ported from claude-code-teams-mcp",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/burggraf/companion-teams.git" "url": "git+https://github.com/harivansh-afk/clanker-agent.git",
"directory": "packages/clanker-teams"
}, },
"author": "Mark Burggraf", "author": "Mark Burggraf",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
"companion-package" "clanker-package"
], ],
"scripts": { "scripts": {
"test": "vitest run" "test": "vitest run"
@ -26,11 +27,11 @@
"uuid": "^11.1.0" "uuid": "^11.1.0"
}, },
"peerDependencies": { "peerDependencies": {
"@mariozechner/companion-coding-agent": "*", "@harivansh-afk/clanker-coding-agent": "*",
"@sinclair/typebox": "*" "@sinclair/typebox": "*"
}, },
"companion": { "clanker": {
"image": "https://raw.githubusercontent.com/burggraf/companion-teams/main/companion-team-in-action.png", "image": "https://raw.githubusercontent.com/burggraf/clanker-teams/main/clanker-team-in-action.png",
"extensions": [ "extensions": [
"extensions/index.ts" "extensions/index.ts"
], ],

View file

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Before After
Before After

View file

@ -26,7 +26,7 @@ export class CmuxAdapter implements TerminalAdapter {
// Construct the command with environment variables // Construct the command with environment variables
const envPrefix = Object.entries(options.env) const envPrefix = Object.entries(options.env)
.filter(([k]) => k.startsWith("COMPANION_")) .filter(([k]) => k.startsWith("CLANKER_"))
.map(([k, v]) => `${k}=${v}`) .map(([k, v]) => `${k}=${v}`)
.join(" "); .join(" ");
@ -124,7 +124,7 @@ export class CmuxAdapter implements TerminalAdapter {
// Wait a bit for the window to be ready? // Wait a bit for the window to be ready?
const envPrefix = Object.entries(options.env) const envPrefix = Object.entries(options.env)
.filter(([k]) => k.startsWith("COMPANION_")) .filter(([k]) => k.startsWith("CLANKER_"))
.map(([k, v]) => `${k}=${v}`) .map(([k, v]) => `${k}=${v}`)
.join(" "); .join(" ");

Some files were not shown because too many files have changed in this diff Show more