dev-sessions 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # dev-sessions
2
+
3
+ A CLI tool for coding agents to spawn, manage, and communicate with other coding agent sessions. Built for agent-to-agent delegation — no MCP overhead, just a CLI that agents call via Bash.
4
+
5
+ ## Why
6
+
7
+ Coding agents (Claude Code, Codex) are increasingly capable of orchestrating parallel work. But the tooling for agent-to-agent communication is either over-engineered (MCP servers, HTTP gateways, SSH tunnels) or too primitive (raw tmux commands).
8
+
9
+ `dev-sessions` provides a clean CLI interface that lets agents:
10
+ - **Spawn** new coding agent sessions (Claude Code or Codex)
11
+ - **Send** tasks and messages to those sessions
12
+ - **Wait** for turns to complete (transcript-aware, not terminal scraping)
13
+ - **Read** structured responses (clean assistant text, not ANSI noise)
14
+ - **Check status** (idle, working, waiting for input)
15
+
16
+ ## Architecture
17
+
18
+ ### Claude Code Sessions
19
+ - **Backend**: tmux (human-attachable via `tmux attach -t dev-<id>`)
20
+ - **Session ID**: Pre-assigned UUID via `claude --session-id <uuid>`
21
+ - **Transcript**: `~/.claude/projects/<encoded-path>/<uuid>.jsonl`
22
+ - **Message delivery**: tmux send-keys (base64 encoded for safety)
23
+ - **Turn detection**: Watches for `system` entries in JSONL transcript (definitive turn-completion signal)
24
+
25
+ ### Codex Sessions
26
+ - **Backend**: Persistent `codex app-server` daemon (JSON-RPC 2.0 over WebSocket)
27
+ - **Session ID**: Thread ID from `thread/start` response
28
+ - **Conversation continuity**: Multiple sends share the same thread — full conversation history preserved
29
+ - **Message delivery**: `turn/start` JSON-RPC call
30
+ - **Turn detection**: `turn/completed` notification (streaming, no polling)
31
+ - **Daemon lifecycle**: Auto-started on first `create --cli codex`, auto-stopped when last Codex session is killed
32
+
33
+ ## Usage
34
+
35
+ ```bash
36
+ # Create a session (defaults: --cli claude, --mode yolo)
37
+ dev-sessions create --description "refactor auth module"
38
+ # => fizz-top
39
+
40
+ # Send a task
41
+ dev-sessions send fizz-top "Implement JWT auth. See AUTH-SPEC.md for details."
42
+ dev-sessions send fizz-top --file BRIEFING.md
43
+
44
+ # Wait for the agent to finish its turn
45
+ dev-sessions wait fizz-top --timeout 300
46
+
47
+ # Get the agent's response (clean text, not terminal noise)
48
+ dev-sessions last-message fizz-top
49
+
50
+ # Check what's happening
51
+ dev-sessions status fizz-top # idle | working | waiting_for_input
52
+ dev-sessions list # all active sessions
53
+
54
+ # Synchronous delegation (one-liner)
55
+ sid=$(dev-sessions create -q) && dev-sessions send $sid "run tests and fix failures" && dev-sessions wait $sid && dev-sessions last-message $sid
56
+
57
+ # Fan-out
58
+ s1=$(dev-sessions create -q --description "frontend")
59
+ s2=$(dev-sessions create -q --description "backend")
60
+ dev-sessions send $s1 "build React form per SPEC.md"
61
+ dev-sessions send $s2 "add /api/users endpoint per SPEC.md"
62
+ dev-sessions wait $s1
63
+ dev-sessions wait $s2
64
+ dev-sessions last-message $s1
65
+ dev-sessions last-message $s2
66
+
67
+ # Codex session (persistent app-server, conversation continuity)
68
+ sid=$(dev-sessions create --cli codex -q)
69
+ dev-sessions send $sid "hello"
70
+ dev-sessions send $sid "what did I just say?" # has context from first message
71
+ dev-sessions last-message $sid # "You said hello"
72
+
73
+ # Clean up
74
+ dev-sessions kill fizz-top
75
+ ```
76
+
77
+ ## Installation
78
+
79
+ ```bash
80
+ npm install -g dev-sessions
81
+ ```
82
+
83
+ Or clone and link:
84
+ ```bash
85
+ git clone <repo-url>
86
+ cd dev-sessions
87
+ npm install && npm run build && npm link
88
+ ```
89
+
90
+ ### Skill Installation (Optional)
91
+
92
+ Install the `/dev-sessions` skill for Claude Code and/or Codex:
93
+ ```bash
94
+ dev-sessions install-skill --global # Auto-detect available tools
95
+ dev-sessions install-skill --global --claude # Claude Code only
96
+ dev-sessions install-skill --global --codex # Codex CLI only
97
+ dev-sessions install-skill --local # Current directory only
98
+ ```
99
+
100
+ The skill teaches agents best practices for task delegation, polling strategies, and fan-out patterns.
101
+
102
+ ## Modes
103
+
104
+ | Mode | Flag | Description |
105
+ |------|------|-------------|
106
+ | `yolo` | `--mode yolo` | Runs CLI with permission bypass flags (default) |
107
+ | `native` | `--mode native` | Runs CLI normally (will prompt for permissions) |
108
+ | `docker` | `--mode docker` | Runs via `clauded` Docker wrapper (Claude Code only) |
109
+
110
+ ## Docker Integration
111
+
112
+ When running inside a Docker container (detected via `IS_SANDBOX=1`), the CLI automatically routes commands through an HTTP gateway relay on the host.
113
+
114
+ **Setup:**
115
+ 1. Start the gateway on the host: `dev-sessions gateway --port 6767`
116
+ 2. Inside Docker, the CLI detects `IS_SANDBOX=1` and uses `DEV_SESSIONS_GATEWAY_URL` (default `http://host.docker.internal:6767`)
117
+ 3. `HOST_PATH` maps the container workspace to the host filesystem
118
+
119
+ This is an optional integration for users of [claude-ting](https://github.com/anthropics/claude-ting) Docker workflows. The CLI works natively on the host without any gateway.
120
+
121
+ ## Commands
122
+
123
+ | Command | Description |
124
+ |---------|-------------|
125
+ | `create [options]` | Spawn a new agent session (`--cli claude\|codex`, `--mode yolo\|native\|docker`, `-q` for quiet) |
126
+ | `send <id> <msg>` | Send a message to a session (`--file` to send file contents) |
127
+ | `wait <id>` | Block until current turn completes (`--timeout` in seconds) |
128
+ | `last-message <id>` | Get last assistant message(s) from transcript (`--count N`) |
129
+ | `status <id>` | Get session status: `idle`, `working`, or `waiting_for_input` |
130
+ | `list` | List all active sessions |
131
+ | `kill <id>` | Terminate a session and clean up |
132
+ | `gateway` | Start the Docker relay gateway HTTP server (`--port`) |
133
+ | `install-skill` | Install the /dev-sessions skill (`--global\|--local`, `--claude\|--codex`) |
134
+
135
+ ## Session Lifecycle
136
+
137
+ ```
138
+ create → send → [wait / poll status] → last-message → kill
139
+ ↑ |
140
+ └────────────────────────────────────┘
141
+ (send follow-up)
142
+ ```
143
+
144
+ ## Development
145
+
146
+ ```bash
147
+ npm install
148
+ npm run build
149
+ npm test # unit + integration tests (83 tests)
150
+ npm run test:integration # integration tests only
151
+ npm link # for local CLI testing
152
+ ```
153
+
154
+ ## License
155
+
156
+ MIT
@@ -0,0 +1,19 @@
1
+ import { SessionMode } from '../types';
2
+ export declare class ClaudeTmuxBackend {
3
+ private readonly timeoutMs;
4
+ constructor(timeoutMs?: number);
5
+ createSession(tmuxSessionName: string, workspacePath: string, mode: SessionMode, sessionUuid: string): Promise<void>;
6
+ sendMessage(tmuxSessionName: string, message: string): Promise<void>;
7
+ submitInput(tmuxSessionName: string): Promise<void>;
8
+ killSession(tmuxSessionName: string): Promise<void>;
9
+ listSessions(): Promise<string[]>;
10
+ sessionExists(tmuxSessionName: string): Promise<boolean>;
11
+ isClaudeRunning(tmuxSessionName: string): Promise<boolean>;
12
+ isCliRunning(tmuxSessionName: string, commandPatterns?: RegExp[]): Promise<boolean>;
13
+ private getPaneCommands;
14
+ private isUserCliProcess;
15
+ private buildStartupCommand;
16
+ private execTmux;
17
+ private execCommand;
18
+ private sleep;
19
+ }
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ClaudeTmuxBackend = void 0;
7
+ const node_child_process_1 = require("node:child_process");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_util_1 = require("node:util");
10
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
11
+ const SHELL_COMMAND_PATTERN = /(^|\s|\/)-?(bash|zsh|sh|fish)(\s|$)/;
12
+ const CONTROL_COMMAND_PATTERN = /(^|\s|\/)(tmux|login)(\s|$)/;
13
+ function shellEscape(value) {
14
+ return `'${value.replace(/'/g, `'\\''`)}'`;
15
+ }
16
+ class ClaudeTmuxBackend {
17
+ timeoutMs;
18
+ constructor(timeoutMs = 15_000) {
19
+ this.timeoutMs = timeoutMs;
20
+ }
21
+ async createSession(tmuxSessionName, workspacePath, mode, sessionUuid) {
22
+ const startupCommand = this.buildStartupCommand(workspacePath, mode, sessionUuid);
23
+ await this.execTmux([
24
+ 'new-session',
25
+ '-d',
26
+ '-s',
27
+ tmuxSessionName,
28
+ '-n',
29
+ tmuxSessionName,
30
+ 'bash',
31
+ '-lc',
32
+ startupCommand
33
+ ]);
34
+ if (mode === 'docker') {
35
+ await this.sleep(5000);
36
+ await this.execTmux(['send-keys', '-t', tmuxSessionName, 'C-m']);
37
+ }
38
+ }
39
+ async sendMessage(tmuxSessionName, message) {
40
+ if (!(await this.isClaudeRunning(tmuxSessionName))) {
41
+ throw new Error('Claude is not running in this tmux session - refusing to send message');
42
+ }
43
+ const encodedMessage = Buffer.from(message, 'utf8').toString('base64');
44
+ const sessionTarget = shellEscape(tmuxSessionName);
45
+ const encoded = shellEscape(encodedMessage);
46
+ const script = [
47
+ `decoded=$( (printf '%s' ${encoded} | base64 --decode 2>/dev/null) || (printf '%s' ${encoded} | base64 -D) )`,
48
+ `tmux send-keys -l -t ${sessionTarget} "$decoded"`
49
+ ].join('\n');
50
+ await this.execCommand('bash', ['-lc', script], 30_000);
51
+ // Small gaps mirror the original SSH gateway timing and improve submit reliability.
52
+ await this.sleep(75);
53
+ // Keep Enter presses as separate tmux commands to match the original gateway behavior.
54
+ await this.execTmux(['send-keys', '-t', tmuxSessionName, 'C-m']);
55
+ await this.sleep(150);
56
+ await this.execTmux(['send-keys', '-t', tmuxSessionName, 'C-m']);
57
+ }
58
+ async submitInput(tmuxSessionName) {
59
+ if (!(await this.isClaudeRunning(tmuxSessionName))) {
60
+ throw new Error('Claude is not running in this tmux session - refusing to submit input');
61
+ }
62
+ await this.execTmux(['send-keys', '-t', tmuxSessionName, 'C-m']);
63
+ }
64
+ async killSession(tmuxSessionName) {
65
+ await this.execTmux(['kill-session', '-t', tmuxSessionName]);
66
+ }
67
+ async listSessions() {
68
+ try {
69
+ const output = await this.execTmux(['list-sessions', '-F', '#{session_name}']);
70
+ return output
71
+ .split('\n')
72
+ .map((line) => line.trim())
73
+ .filter((line) => line.length > 0);
74
+ }
75
+ catch {
76
+ return [];
77
+ }
78
+ }
79
+ async sessionExists(tmuxSessionName) {
80
+ try {
81
+ await this.execTmux(['has-session', '-t', tmuxSessionName]);
82
+ return true;
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
88
+ async isClaudeRunning(tmuxSessionName) {
89
+ return this.isCliRunning(tmuxSessionName, [
90
+ /(^|\s|\/)(claude|clauded)(\s|$)/,
91
+ /docker.*ubuntu-dev/
92
+ ]);
93
+ }
94
+ async isCliRunning(tmuxSessionName, commandPatterns = []) {
95
+ try {
96
+ const paneCommands = await this.getPaneCommands(tmuxSessionName);
97
+ if (commandPatterns.length > 0) {
98
+ return paneCommands.some((command) => commandPatterns.some((pattern) => pattern.test(command)));
99
+ }
100
+ return paneCommands.some((command) => this.isUserCliProcess(command));
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ }
106
+ async getPaneCommands(tmuxSessionName) {
107
+ const paneOutput = await this.execTmux(['list-panes', '-t', tmuxSessionName, '-F', '#{pane_tty}']);
108
+ const paneTtys = paneOutput
109
+ .split('\n')
110
+ .map((line) => line.trim())
111
+ .filter((line) => line.length > 0);
112
+ const commands = [];
113
+ for (const paneTty of paneTtys) {
114
+ const ttyName = node_path_1.default.basename(paneTty);
115
+ let psOutput = '';
116
+ try {
117
+ psOutput = await this.execCommand('ps', ['-t', ttyName, '-o', 'command=']);
118
+ }
119
+ catch {
120
+ continue;
121
+ }
122
+ const ttyCommands = psOutput
123
+ .split('\n')
124
+ .map((line) => line.trim())
125
+ .filter((line) => line.length > 0);
126
+ commands.push(...ttyCommands);
127
+ }
128
+ return commands;
129
+ }
130
+ isUserCliProcess(command) {
131
+ if (command.length === 0) {
132
+ return false;
133
+ }
134
+ return !SHELL_COMMAND_PATTERN.test(command) && !CONTROL_COMMAND_PATTERN.test(command);
135
+ }
136
+ buildStartupCommand(workspacePath, mode, sessionUuid) {
137
+ const binary = mode === 'docker' ? 'clauded' : 'claude';
138
+ const commandParts = [`${binary} --session-id ${shellEscape(sessionUuid)}`];
139
+ if (mode === 'yolo') {
140
+ commandParts.push('--dangerously-skip-permissions');
141
+ }
142
+ return `cd ${shellEscape(workspacePath)} && ${commandParts.join(' ')}`;
143
+ }
144
+ async execTmux(args) {
145
+ return this.execCommand('tmux', args);
146
+ }
147
+ async execCommand(command, args, timeoutMs = this.timeoutMs) {
148
+ const { stdout } = await execFileAsync(command, args, {
149
+ encoding: 'utf8',
150
+ timeout: timeoutMs,
151
+ maxBuffer: 1024 * 1024 * 4
152
+ });
153
+ return stdout;
154
+ }
155
+ async sleep(ms) {
156
+ await new Promise((resolve) => {
157
+ setTimeout(resolve, ms);
158
+ });
159
+ }
160
+ }
161
+ exports.ClaudeTmuxBackend = ClaudeTmuxBackend;
162
+ //# sourceMappingURL=claude-tmux.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-tmux.js","sourceRoot":"","sources":["../../src/backends/claude-tmux.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { AgentTurnStatus } from '../types';
2
+ type TurnCompletionStatus = 'completed' | 'failed' | 'interrupted';
3
+ export interface CodexAppServerInfo {
4
+ pid: number;
5
+ port: number;
6
+ url: string;
7
+ }
8
+ export interface CodexSessionCreateResult {
9
+ threadId: string;
10
+ model: string;
11
+ appServerPid: number;
12
+ appServerPort: number;
13
+ }
14
+ export interface CodexTurnWaitResult {
15
+ completed: boolean;
16
+ timedOut: boolean;
17
+ elapsedMs: number;
18
+ status: TurnCompletionStatus;
19
+ errorMessage?: string;
20
+ }
21
+ export interface CodexSendMessageOptions {
22
+ workspacePath: string;
23
+ model?: string;
24
+ timeoutMs?: number;
25
+ }
26
+ export interface CodexTurnSendResult extends CodexTurnWaitResult {
27
+ threadId: string;
28
+ assistantMessage: string;
29
+ appServerPid: number;
30
+ appServerPort: number;
31
+ }
32
+ export interface CodexRpcClient {
33
+ readonly currentTurnText: string;
34
+ readonly lastTurnStatus?: TurnCompletionStatus;
35
+ readonly lastTurnError?: string;
36
+ connectAndInitialize(): Promise<void>;
37
+ request(method: string, params?: unknown): Promise<unknown>;
38
+ waitForTurnCompletion(timeoutMs: number): Promise<CodexTurnWaitResult>;
39
+ close(): Promise<void>;
40
+ }
41
+ export interface CodexAppServerDaemonManager {
42
+ ensureServer(): Promise<CodexAppServerInfo>;
43
+ getServer(): Promise<CodexAppServerInfo | undefined>;
44
+ isServerRunning(pid?: number, port?: number): Promise<boolean>;
45
+ resetServer(server?: CodexAppServerInfo): Promise<void>;
46
+ stopServer(): Promise<void>;
47
+ }
48
+ export interface CodexAppServerBackendDependencies {
49
+ clientFactory?: (url: string) => CodexRpcClient;
50
+ daemonManager?: CodexAppServerDaemonManager;
51
+ }
52
+ export declare class CodexAppServerBackend {
53
+ private readonly sessionState;
54
+ private readonly daemonManager;
55
+ private readonly clientFactory;
56
+ constructor(dependencies?: CodexAppServerBackendDependencies);
57
+ createSession(championId: string, workspacePath: string, model?: string): Promise<CodexSessionCreateResult>;
58
+ sendMessage(championId: string, threadId: string, message: string, options?: CodexSendMessageOptions): Promise<CodexTurnSendResult>;
59
+ waitForTurn(championId: string, timeoutMs?: number): Promise<CodexTurnWaitResult>;
60
+ getLastAssistantMessages(championId: string, count: number): string[];
61
+ getSessionStatus(championId: string): AgentTurnStatus;
62
+ killSession(championId: string, pid?: number, threadId?: string, port?: number): Promise<void>;
63
+ stopAppServer(): Promise<void>;
64
+ sessionExists(_championId: string, pid?: number, port?: number): Promise<boolean>;
65
+ private ensureSessionState;
66
+ private withConnectedClient;
67
+ private withConnectedClientToServer;
68
+ private shouldResetDaemonAfterConnectionFailure;
69
+ private isResumeNotFoundError;
70
+ }
71
+ export {};