octomux 1.0.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.
Files changed (56) hide show
  1. package/README.md +60 -0
  2. package/bin/octomux.js +139 -0
  3. package/cli/dist/client.js +56 -0
  4. package/cli/dist/commands/add-agent.js +20 -0
  5. package/cli/dist/commands/cancel-task.js +10 -0
  6. package/cli/dist/commands/close-task.js +16 -0
  7. package/cli/dist/commands/create-task.js +35 -0
  8. package/cli/dist/commands/delete-task.js +16 -0
  9. package/cli/dist/commands/get-task.js +37 -0
  10. package/cli/dist/commands/list-tasks.js +31 -0
  11. package/cli/dist/commands/resume-task.js +18 -0
  12. package/cli/dist/commands/send-message.js +18 -0
  13. package/cli/dist/format.js +40 -0
  14. package/cli/dist/index.js +36 -0
  15. package/cli/package.json +9 -0
  16. package/dist/assets/TaskDetail-GGGQ2C1J.js +1 -0
  17. package/dist/assets/TerminalView-CguHyqU9.js +3 -0
  18. package/dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  19. package/dist/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  20. package/dist/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  21. package/dist/assets/index-Br9dLOzs.css +1 -0
  22. package/dist/assets/index-Bsj_BLLM.js +2 -0
  23. package/dist/assets/vendor-react-BZ8ItZjw.js +49 -0
  24. package/dist/assets/vendor-router-DRLGqALp.js +12 -0
  25. package/dist/assets/vendor-ui-CWZtXYLx.js +31 -0
  26. package/dist/assets/vendor-xterm-DYP7pi_n.css +32 -0
  27. package/dist/assets/vendor-xterm-DvXGiZvM.js +9 -0
  28. package/dist/index.html +17 -0
  29. package/dist/logo.png +0 -0
  30. package/dist-server/api.d.ts +2 -0
  31. package/dist-server/api.js +447 -0
  32. package/dist-server/app.d.ts +2 -0
  33. package/dist-server/app.js +13 -0
  34. package/dist-server/db.d.ts +7 -0
  35. package/dist-server/db.js +107 -0
  36. package/dist-server/events.d.ts +13 -0
  37. package/dist-server/events.js +35 -0
  38. package/dist-server/hook-settings.d.ts +5 -0
  39. package/dist-server/hook-settings.js +195 -0
  40. package/dist-server/hooks.d.ts +2 -0
  41. package/dist-server/hooks.js +118 -0
  42. package/dist-server/index.d.ts +5 -0
  43. package/dist-server/index.js +81 -0
  44. package/dist-server/orchestrator.d.ts +4 -0
  45. package/dist-server/orchestrator.js +37 -0
  46. package/dist-server/poller.d.ts +17 -0
  47. package/dist-server/poller.js +170 -0
  48. package/dist-server/pr-template.d.ts +7 -0
  49. package/dist-server/pr-template.js +29 -0
  50. package/dist-server/task-runner.d.ts +28 -0
  51. package/dist-server/task-runner.js +359 -0
  52. package/dist-server/terminal.d.ts +13 -0
  53. package/dist-server/terminal.js +173 -0
  54. package/dist-server/types.d.ts +73 -0
  55. package/dist-server/types.js +1 -0
  56. package/package.json +113 -0
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # octomux
2
+
3
+ Orchestrate autonomous Claude Code agents from a web dashboard.
4
+ Create tasks, watch agents work in live terminals, get PRs.
5
+
6
+ ## Prerequisites
7
+
8
+ - **macOS** (ARM64 or x64)
9
+ - **Node.js 20+** — [nodejs.org](https://nodejs.org)
10
+ - **Xcode Command Line Tools** — `xcode-select --install`
11
+ - **tmux** — `brew install tmux`
12
+ - **git** — `brew install git`
13
+ - **Claude Code CLI** — `npm install -g @anthropic-ai/claude-code`
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install -g octomux
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```bash
24
+ cd your-project
25
+ octomux init # scaffold .claude/ settings
26
+ octomux start # open dashboard at http://localhost:7777
27
+ ```
28
+
29
+ ## CLI Commands
30
+
31
+ | Command | Description |
32
+ | ------------------------------------------------- | -------------------------------------------- |
33
+ | `octomux start` | Launch the web dashboard |
34
+ | `octomux init` | Scaffold `.claude/` settings in current repo |
35
+ | `octomux create-task` | Create a new task |
36
+ | `octomux list-tasks` | List all tasks |
37
+ | `octomux get-task <id>` | Get task details |
38
+ | `octomux close-task <id>` | Stop agents, preserve worktree |
39
+ | `octomux delete-task <id>` | Full cleanup (worktree, branch, DB) |
40
+ | `octomux resume-task <id>` | Resume a closed task |
41
+ | `octomux add-agent <task-id>` | Add an agent to a task |
42
+ | `octomux send-message <task-id> <agent-id> "msg"` | Send a message to an agent |
43
+
44
+ ## Configuration
45
+
46
+ | Option | Description | Default |
47
+ | --------------------- | -------------------------------- | ----------------------- |
48
+ | `--port <port>` | Port for the dashboard | `7777` |
49
+ | `--no-open` | Don't auto-open browser on start | — |
50
+ | `PORT` env var | Alternative to `--port` | `7777` |
51
+ | `OCTOMUX_URL` env var | Server URL for CLI commands | `http://localhost:7777` |
52
+
53
+ ## How It Works
54
+
55
+ Each task gets a **git worktree** for isolation, a **tmux session** for process management, and one or more **Claude Code agents** running in tmux windows. Watch it all from the web dashboard with live terminal streaming.
56
+
57
+ ## Links
58
+
59
+ - [octomux.dev](https://octomux.dev) (coming soon)
60
+ - [npm](https://www.npmjs.com/package/octomux)
package/bin/octomux.js ADDED
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFileSync, exec as execCb } from 'child_process';
4
+ import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+ import path from 'path';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+
10
+ // Read version from package.json at runtime
11
+ const pkg = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
12
+ const version = pkg.version;
13
+
14
+ // ─── Argument parsing ────────────────────────────────────────────────────────
15
+
16
+ const args = process.argv.slice(2);
17
+ const command = args[0];
18
+
19
+ if (command === 'start') {
20
+ await runStart(args.slice(1));
21
+ } else if (command === 'init') {
22
+ runInit();
23
+ } else {
24
+ // Delegate to CLI (commander-based) for all other commands
25
+ await import('../cli/dist/index.js');
26
+ }
27
+
28
+ // ─── start command ───────────────────────────────────────────────────────────
29
+
30
+ async function runStart(startArgs) {
31
+ // Parse --port and --no-open flags
32
+ let port = process.env.PORT || 7777;
33
+ let autoOpen = true;
34
+
35
+ for (let i = 0; i < startArgs.length; i++) {
36
+ if (startArgs[i] === '--port' && startArgs[i + 1]) {
37
+ port = parseInt(startArgs[i + 1], 10);
38
+ i++;
39
+ } else if (startArgs[i] === '--no-open') {
40
+ autoOpen = false;
41
+ }
42
+ }
43
+
44
+ // Welcome banner
45
+ console.log(`\n\uD83D\uDC19 octomux v${version}\n`);
46
+
47
+ // Preflight checks
48
+ const required = [
49
+ {
50
+ cmd: 'tmux',
51
+ args: ['-V'],
52
+ name: 'tmux',
53
+ hint: 'Install with: brew install tmux',
54
+ },
55
+ {
56
+ cmd: 'git',
57
+ args: ['--version'],
58
+ name: 'git',
59
+ hint: 'Install with: brew install git',
60
+ },
61
+ {
62
+ cmd: 'claude',
63
+ args: ['--version'],
64
+ name: 'Claude Code CLI',
65
+ hint: 'See: https://docs.anthropic.com/en/docs/claude-code',
66
+ },
67
+ ];
68
+
69
+ const missing = [];
70
+ for (const { cmd, args: checkArgs, name, hint } of required) {
71
+ try {
72
+ execFileSync(cmd, checkArgs, { stdio: 'ignore' });
73
+ } catch {
74
+ missing.push({ name, hint });
75
+ }
76
+ }
77
+
78
+ if (missing.length > 0) {
79
+ console.error('Missing required dependencies:\n');
80
+ for (const { name, hint } of missing) {
81
+ console.error(` - ${name}`);
82
+ console.error(` ${hint}\n`);
83
+ }
84
+ process.exit(1);
85
+ }
86
+
87
+ // Start server
88
+ process.env.NODE_ENV = 'production';
89
+ process.env.PORT = String(port);
90
+
91
+ const { server } = await import('../dist-server/index.js');
92
+
93
+ // The server is already listening (listen called in index.js).
94
+ // Attach handler for auto-open and status message.
95
+ const url = `http://localhost:${port}`;
96
+
97
+ if (server.listening) {
98
+ onReady();
99
+ } else {
100
+ server.on('listening', onReady);
101
+ }
102
+
103
+ function onReady() {
104
+ console.log(`Dashboard running at ${url} \u2014 press Ctrl+C to stop\n`);
105
+ if (autoOpen && process.platform === 'darwin') {
106
+ execCb(`open ${url}`);
107
+ }
108
+ }
109
+ }
110
+
111
+ // ─── init command ────────────────────────────────────────────────────────────
112
+
113
+ function runInit() {
114
+ // Verify git repo
115
+ if (!existsSync('.git')) {
116
+ console.error('Error: Current directory is not a git repository.');
117
+ console.error('Run this command from the root of a git repo.');
118
+ process.exit(1);
119
+ }
120
+
121
+ const settingsPath = path.join('.claude', 'settings.local.json');
122
+
123
+ if (existsSync(settingsPath)) {
124
+ console.log(`${settingsPath} already exists. No changes made.`);
125
+ return;
126
+ }
127
+
128
+ const settings = {
129
+ permissions: {
130
+ allow: ['Bash(git *)', 'Bash(npm *)', 'Bash(bun *)', 'Read', 'Write', 'Edit'],
131
+ },
132
+ };
133
+
134
+ mkdirSync('.claude', { recursive: true });
135
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
136
+
137
+ console.log(`Created ${settingsPath} with recommended agent permissions.`);
138
+ console.log(`Add to .gitignore: .claude/settings.local.json`);
139
+ }
@@ -0,0 +1,56 @@
1
+ async function request(baseUrl, path, options) {
2
+ let res;
3
+ try {
4
+ res = await fetch(`${baseUrl}${path}`, {
5
+ headers: { 'Content-Type': 'application/json' },
6
+ ...options,
7
+ });
8
+ }
9
+ catch (err) {
10
+ if (err.cause &&
11
+ err.cause.code === 'ECONNREFUSED') {
12
+ throw new Error(`Cannot connect to octomux server at ${baseUrl.replace('/api', '')}\nStart it with: octomux start`);
13
+ }
14
+ throw err;
15
+ }
16
+ if (!res.ok) {
17
+ const body = await res.json().catch(() => ({ error: res.statusText }));
18
+ throw new Error(body.error || res.statusText);
19
+ }
20
+ if (res.status === 204)
21
+ return undefined;
22
+ return res.json();
23
+ }
24
+ export function createClient(serverUrl) {
25
+ const baseUrl = serverUrl.replace(/\/$/, '') + '/api';
26
+ return {
27
+ createTask(data) {
28
+ return request(baseUrl, '/tasks', { method: 'POST', body: JSON.stringify(data) });
29
+ },
30
+ listTasks(params) {
31
+ const query = params?.repo_path ? `?repo_path=${encodeURIComponent(params.repo_path)}` : '';
32
+ return request(baseUrl, `/tasks${query}`);
33
+ },
34
+ getTask(id) {
35
+ return request(baseUrl, `/tasks/${encodeURIComponent(id)}`);
36
+ },
37
+ updateTask(id, data) {
38
+ return request(baseUrl, `/tasks/${encodeURIComponent(id)}`, {
39
+ method: 'PATCH',
40
+ body: JSON.stringify(data),
41
+ });
42
+ },
43
+ deleteTask(id) {
44
+ return request(baseUrl, `/tasks/${encodeURIComponent(id)}`, { method: 'DELETE' });
45
+ },
46
+ addAgent(taskId, data) {
47
+ return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/agents`, {
48
+ method: 'POST',
49
+ body: JSON.stringify(data || {}),
50
+ });
51
+ },
52
+ sendMessage(taskId, agentId, message) {
53
+ return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/agents/${encodeURIComponent(agentId)}/message`, { method: 'POST', body: JSON.stringify({ message }) });
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,20 @@
1
+ import { isJsonMode, outputJson, success, label } from '../format.js';
2
+ export function registerAddAgent(program) {
3
+ program
4
+ .command('add-agent <task-id>')
5
+ .description('Add a new agent to a running task')
6
+ .option('-p, --prompt <prompt>', 'initial prompt for the agent')
7
+ .action(async (taskId, opts, cmd) => {
8
+ const globals = cmd.optsWithGlobals();
9
+ const client = globals._client;
10
+ const agent = await client.addAgent(taskId, opts.prompt ? { prompt: opts.prompt } : undefined);
11
+ if (isJsonMode(globals.json)) {
12
+ outputJson(agent);
13
+ return;
14
+ }
15
+ success(`Added agent to task ${taskId}`);
16
+ console.log(label('Agent ID', agent.id));
17
+ console.log(label('Label', agent.label));
18
+ console.log(label('Window', String(agent.window_index)));
19
+ });
20
+ }
@@ -0,0 +1,10 @@
1
+ import { updateTask } from '../client.js';
2
+ export async function cancelTaskCommand(args) {
3
+ const id = args[0];
4
+ if (!id) {
5
+ console.error('Usage: octomux cancel-task <id>');
6
+ process.exit(1);
7
+ }
8
+ const task = await updateTask(id, { status: 'cancelled' });
9
+ console.log(`Task ${task.id} cancelled.`);
10
+ }
@@ -0,0 +1,16 @@
1
+ import { isJsonMode, outputJson, success } from '../format.js';
2
+ export function registerCloseTask(program) {
3
+ program
4
+ .command('close-task <id>')
5
+ .description('Close a running task')
6
+ .action(async (id, _opts, cmd) => {
7
+ const globals = cmd.optsWithGlobals();
8
+ const client = globals._client;
9
+ const task = await client.updateTask(id, { status: 'closed' });
10
+ if (isJsonMode(globals.json)) {
11
+ outputJson(task);
12
+ return;
13
+ }
14
+ success(`Closed task ${task.id} (${task.title})`);
15
+ });
16
+ }
@@ -0,0 +1,35 @@
1
+ import { isJsonMode, outputJson, label, success, colorStatus } from '../format.js';
2
+ export function registerCreateTask(program) {
3
+ program
4
+ .command('create-task')
5
+ .description('Create a new agent task')
6
+ .requiredOption('-t, --title <title>', 'task title')
7
+ .requiredOption('-d, --description <desc>', 'task description')
8
+ .requiredOption('-r, --repo-path <path>', 'repository path')
9
+ .option('-p, --initial-prompt <prompt>', 'initial prompt for the agent')
10
+ .option('-b, --branch <name>', 'branch name')
11
+ .option('--base-branch <name>', 'base branch name')
12
+ .option('--draft', 'create as draft without starting')
13
+ .action(async (opts, cmd) => {
14
+ const globals = cmd.optsWithGlobals();
15
+ const client = globals._client;
16
+ const task = await client.createTask({
17
+ title: opts.title,
18
+ description: opts.description,
19
+ repo_path: opts.repoPath,
20
+ initial_prompt: opts.initialPrompt,
21
+ branch: opts.branch,
22
+ base_branch: opts.baseBranch,
23
+ draft: opts.draft,
24
+ });
25
+ if (isJsonMode(globals.json)) {
26
+ outputJson(task);
27
+ return;
28
+ }
29
+ success(`Created task ${task.id}`);
30
+ console.log(label('Title', task.title));
31
+ console.log(label('Status', colorStatus(task.status)));
32
+ console.log(label('Branch', task.branch));
33
+ console.log(label('Repo', task.repo_path));
34
+ });
35
+ }
@@ -0,0 +1,16 @@
1
+ import { isJsonMode, success } from '../format.js';
2
+ export function registerDeleteTask(program) {
3
+ program
4
+ .command('delete-task <id>')
5
+ .description('Delete a task (removes worktree, branch, and tmux session)')
6
+ .action(async (id, _opts, cmd) => {
7
+ const globals = cmd.optsWithGlobals();
8
+ const client = globals._client;
9
+ await client.deleteTask(id);
10
+ if (isJsonMode(globals.json)) {
11
+ console.log(JSON.stringify({ deleted: id }));
12
+ return;
13
+ }
14
+ success(`Deleted task ${id}`);
15
+ });
16
+ }
@@ -0,0 +1,37 @@
1
+ import chalk from 'chalk';
2
+ import { isJsonMode, outputJson, label, heading, colorStatus, colorAgentStatus, } from '../format.js';
3
+ export function registerGetTask(program) {
4
+ program
5
+ .command('get-task <id>')
6
+ .alias('info')
7
+ .description('Get task details')
8
+ .action(async (id, _opts, cmd) => {
9
+ const globals = cmd.optsWithGlobals();
10
+ const client = globals._client;
11
+ const task = await client.getTask(id);
12
+ if (isJsonMode(globals.json)) {
13
+ outputJson(task);
14
+ return;
15
+ }
16
+ heading(`Task ${task.id}`);
17
+ console.log(label('Title', task.title));
18
+ console.log(label('Status', colorStatus(task.status)));
19
+ console.log(label('Repo', task.repo_path));
20
+ console.log(label('Branch', task.branch));
21
+ if (task.base_branch)
22
+ console.log(label('Base Branch', task.base_branch));
23
+ if (task.pr_url)
24
+ console.log(label('PR', task.pr_url));
25
+ if (task.error)
26
+ console.log(label('Error', chalk.red(task.error)));
27
+ console.log(label('Created', task.created_at));
28
+ console.log(label('Updated', task.updated_at));
29
+ if (task.agents && task.agents.length > 0) {
30
+ console.log('');
31
+ heading('Agents');
32
+ for (const agent of task.agents) {
33
+ console.log(` ${agent.label.padEnd(20)} ${colorAgentStatus(agent.status).padEnd(18 + 10)} window:${agent.window_index}`);
34
+ }
35
+ }
36
+ });
37
+ }
@@ -0,0 +1,31 @@
1
+ import chalk from 'chalk';
2
+ import { isJsonMode, outputJson, colorStatus, heading } from '../format.js';
3
+ export function registerListTasks(program) {
4
+ program
5
+ .command('list-tasks')
6
+ .alias('ls')
7
+ .description('List all tasks')
8
+ .option('--status <status>', 'filter by status (draft, setting_up, running, closed, error)')
9
+ .option('--repo-path <path>', 'filter by repository path')
10
+ .action(async (opts, cmd) => {
11
+ const globals = cmd.optsWithGlobals();
12
+ const client = globals._client;
13
+ let tasks = await client.listTasks(opts.repoPath ? { repo_path: opts.repoPath } : undefined);
14
+ if (opts.status) {
15
+ tasks = tasks.filter((t) => t.status === opts.status);
16
+ }
17
+ if (isJsonMode(globals.json)) {
18
+ outputJson(tasks);
19
+ return;
20
+ }
21
+ if (tasks.length === 0) {
22
+ console.log('No tasks found.');
23
+ return;
24
+ }
25
+ heading(`${'ID'.padEnd(14)}${'STATUS'.padEnd(14)}TITLE`);
26
+ console.log(chalk.dim('─'.repeat(60)));
27
+ for (const t of tasks) {
28
+ console.log(`${t.id.padEnd(14)}${colorStatus(t.status).padEnd(14 + 10)}${t.title}`);
29
+ }
30
+ });
31
+ }
@@ -0,0 +1,18 @@
1
+ import { isJsonMode, outputJson, success, colorStatus, label } from '../format.js';
2
+ export function registerResumeTask(program) {
3
+ program
4
+ .command('resume-task <id>')
5
+ .description('Resume a closed or errored task')
6
+ .action(async (id, _opts, cmd) => {
7
+ const globals = cmd.optsWithGlobals();
8
+ const client = globals._client;
9
+ const task = await client.updateTask(id, { status: 'running' });
10
+ if (isJsonMode(globals.json)) {
11
+ outputJson(task);
12
+ return;
13
+ }
14
+ success(`Resumed task ${task.id}`);
15
+ console.log(label('Title', task.title));
16
+ console.log(label('Status', colorStatus(task.status)));
17
+ });
18
+ }
@@ -0,0 +1,18 @@
1
+ import { isJsonMode, outputJson, success } from '../format.js';
2
+ export function registerSendMessage(program) {
3
+ program
4
+ .command('send-message <message>')
5
+ .description('Send a message to an agent via tmux send-keys')
6
+ .requiredOption('-t, --task <task-id>', 'task ID')
7
+ .requiredOption('-a, --agent <agent-id>', 'agent ID')
8
+ .action(async (message, opts, cmd) => {
9
+ const globals = cmd.optsWithGlobals();
10
+ const client = globals._client;
11
+ const result = await client.sendMessage(opts.task, opts.agent, message);
12
+ if (isJsonMode(globals.json)) {
13
+ outputJson(result);
14
+ return;
15
+ }
16
+ success(`Message sent to agent ${opts.agent} on task ${opts.task}`);
17
+ });
18
+ }
@@ -0,0 +1,40 @@
1
+ import chalk from 'chalk';
2
+ const STATUS_COLORS = {
3
+ draft: chalk.cyan,
4
+ setting_up: chalk.yellow,
5
+ running: chalk.green,
6
+ closed: chalk.dim,
7
+ error: chalk.red,
8
+ };
9
+ const AGENT_STATUS_COLORS = {
10
+ running: chalk.green,
11
+ idle: chalk.dim,
12
+ waiting: chalk.yellow,
13
+ stopped: chalk.red,
14
+ };
15
+ export function colorStatus(status) {
16
+ const colorFn = STATUS_COLORS[status] || chalk.white;
17
+ return colorFn(status);
18
+ }
19
+ export function colorAgentStatus(status) {
20
+ const colorFn = AGENT_STATUS_COLORS[status] || chalk.white;
21
+ return colorFn(status);
22
+ }
23
+ export function isJsonMode(json) {
24
+ return json === true || !process.stdout.isTTY;
25
+ }
26
+ export function outputJson(data) {
27
+ console.log(JSON.stringify(data, null, 2));
28
+ }
29
+ export function label(name, value) {
30
+ return `${chalk.bold(name + ':')} ${value ?? chalk.dim('—')}`;
31
+ }
32
+ export function heading(text) {
33
+ console.log(chalk.bold(text));
34
+ }
35
+ export function success(text) {
36
+ console.log(chalk.green('✓') + ' ' + text);
37
+ }
38
+ export function errorMessage(text) {
39
+ console.error(chalk.red('Error:') + ' ' + text);
40
+ }
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { createClient } from './client.js';
4
+ import { errorMessage } from './format.js';
5
+ import { registerCreateTask } from './commands/create-task.js';
6
+ import { registerListTasks } from './commands/list-tasks.js';
7
+ import { registerGetTask } from './commands/get-task.js';
8
+ import { registerCloseTask } from './commands/close-task.js';
9
+ import { registerDeleteTask } from './commands/delete-task.js';
10
+ import { registerResumeTask } from './commands/resume-task.js';
11
+ import { registerAddAgent } from './commands/add-agent.js';
12
+ import { registerSendMessage } from './commands/send-message.js';
13
+ const program = new Command();
14
+ program
15
+ .name('octomux')
16
+ .description('CLI for managing octomux agent tasks')
17
+ .version('0.1.0')
18
+ .option('-s, --server-url <url>', 'server URL', process.env.OCTOMUX_URL || 'http://localhost:7777')
19
+ .option('--json', 'output as JSON (auto-enabled when piped)');
20
+ registerCreateTask(program);
21
+ registerListTasks(program);
22
+ registerGetTask(program);
23
+ registerCloseTask(program);
24
+ registerDeleteTask(program);
25
+ registerResumeTask(program);
26
+ registerAddAgent(program);
27
+ registerSendMessage(program);
28
+ program.hook('preAction', (thisCommand) => {
29
+ const opts = thisCommand.optsWithGlobals();
30
+ const client = createClient(opts.serverUrl);
31
+ thisCommand.setOptionValue('_client', client);
32
+ });
33
+ program.parseAsync().catch((err) => {
34
+ errorMessage(err.message);
35
+ process.exit(1);
36
+ });
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "octomux-cli",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsc"
8
+ }
9
+ }
@@ -0,0 +1 @@
1
+ import{j as e,r as s}from"./vendor-react-BZ8ItZjw.js";import{c as ne,D as re,a as ae,b as ie,d as oe,e as le,L as y,T as q,B as u,f as p,I as Z,P as J,g as K,h as X,u as de,S as ce,i as ue}from"./index-Bsj_BLLM.js";import{TerminalView as Y}from"./TerminalView-CguHyqU9.js";import{b as xe,a as me,d as he}from"./vendor-router-DRLGqALp.js";import"./vendor-ui-CWZtXYLx.js";import"./vendor-xterm-DvXGiZvM.js";function pe({agents:i,activeIndex:x,onSelect:r,onAddAgent:t,onStopAgent:d,canAddAgent:m}){return e.jsxs("div",{className:"flex items-center gap-1 border-b border-border px-1 pb-1",children:[i.filter(a=>a.status!=="stopped").map(a=>e.jsxs("div",{className:"group flex items-center",children:[e.jsxs("button",{className:ne("rounded-t-md px-3 py-1.5 text-sm transition-colors",a.window_index===x?"bg-accent text-foreground":"text-muted-foreground hover:text-foreground"),onClick:()=>r(a.window_index),children:[a.label,a.status==="running"&&e.jsx("span",{className:`ml-1.5 inline-block h-1.5 w-1.5 rounded-full ${a.hook_activity==="waiting"?"bg-amber-500":a.hook_activity==="idle"?"bg-zinc-400":"animate-pulse bg-green-400"}`})]}),a.status==="running"&&e.jsx("button",{className:"ml-0.5 hidden rounded p-0.5 text-muted-foreground hover:text-destructive group-hover:inline-flex",onClick:l=>{l.stopPropagation(),d(a.id)},title:"Stop agent",children:e.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("path",{d:"M18 6 6 18"}),e.jsx("path",{d:"m6 6 12 12"})]})})]},a.id)),m&&e.jsx(fe,{onAdd:t})]})}function fe({onAdd:i}){const[x,r]=s.useState(!1),[t,d]=s.useState("");function m(){i(t.trim()||void 0),d(""),r(!1)}function a(){i()}return e.jsxs(re,{open:x,onOpenChange:r,children:[e.jsxs("div",{className:"flex items-center gap-0.5",children:[e.jsx("button",{className:"rounded-t-md px-2 py-1.5 text-sm text-muted-foreground hover:text-foreground",onClick:a,title:"Add agent without prompt",children:"+"}),e.jsx(ae,{render:e.jsx("button",{className:"rounded px-1 py-1 text-xs text-muted-foreground hover:text-foreground",title:"Add agent with prompt"}),children:"..."})]}),e.jsxs(ie,{className:"sm:max-w-md",children:[e.jsx(oe,{children:e.jsx(le,{children:"Add Agent"})}),e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(y,{htmlFor:"agent-prompt",children:"Initial Prompt (optional)"}),e.jsx(q,{id:"agent-prompt",placeholder:"Write tests for the authentication module...",rows:3,value:t,onChange:l=>d(l.target.value)})]}),e.jsxs("div",{className:"flex justify-end gap-2",children:[e.jsx(u,{variant:"ghost",onClick:()=>r(!1),children:"Cancel"}),e.jsx(u,{onClick:m,children:"Add Agent"})]})]})]})]})}function ge({task:i,onSaved:x,onStart:r}){const[t,d]=s.useState(i.title),[m,a]=s.useState(i.description),[l,g]=s.useState(i.repo_path),[C,R]=s.useState(i.branch??""),[f,E]=s.useState(i.base_branch??""),[h,S]=s.useState(i.initial_prompt??""),[v,O]=s.useState(!!i.initial_prompt),[I,j]=s.useState([]),[z,_]=s.useState(""),[b,w]=s.useState(!1),[B,U]=s.useState(!1),[A,M]=s.useState(null),[V,G]=s.useState(!1),[L,N]=s.useState(!1),[T,H]=s.useState(null),[Q,D]=s.useState(!1),P=s.useRef(f);P.current=f,s.useEffect(()=>{const n=l.trim();if(!n){j([]);return}let W=!1;const ee=setTimeout(async()=>{try{const[te,se]=await Promise.all([p.listBranches(n),p.getDefaultBranch(n)]);W||(j(te),P.current||E(se.branch))}catch{W||j([])}},300);return()=>{W=!0,clearTimeout(ee)}},[l]);const F=I.filter(n=>n.toLowerCase().includes(z.toLowerCase())),o=s.useCallback(async n=>{G(!0);try{const W=await p.browse(n);M(W)}catch{}finally{G(!1)}},[]);s.useEffect(()=>{B&&!A&&o()},[B,A,o]);function c(n){g(n),U(!1)}async function $(){if(!(!t.trim()||!m.trim()||!l.trim())){N(!0),H(null),D(!1);try{await p.updateTask(i.id,{title:t.trim(),description:m.trim(),repo_path:l.trim(),branch:C.trim()||void 0,base_branch:f.trim()||void 0,initial_prompt:h.trim()||void 0}),D(!0),x(),setTimeout(()=>D(!1),2e3)}catch(n){H(n.message)}finally{N(!1)}}}const k=t.trim()&&m.trim()&&l.trim()&&!L;return e.jsx("div",{className:"mx-auto max-w-2xl p-6",children:e.jsxs("div",{className:"flex flex-col gap-5",children:[e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(y,{htmlFor:"edit-title",children:"Title"}),e.jsx(Z,{id:"edit-title",value:t,onChange:n=>d(n.target.value),placeholder:"Task title"})]}),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(y,{htmlFor:"edit-description",children:"Description"}),e.jsx(q,{id:"edit-description",value:m,onChange:n=>a(n.target.value),placeholder:"Task description",rows:3})]}),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(y,{htmlFor:"edit-repo-path",children:"Repository Path"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(Z,{id:"edit-repo-path",className:"flex-1 font-mono text-sm",value:l,onChange:n=>g(n.target.value),placeholder:"/Users/you/projects/my-repo"}),e.jsxs(J,{open:B,onOpenChange:U,children:[e.jsx(K,{render:e.jsx(u,{type:"button",variant:"outline",className:"shrink-0",children:"Browse"})}),e.jsx(X,{align:"end",side:"bottom",sideOffset:4,className:"w-[420px] p-0",children:e.jsx(je,{data:A,loading:V,onNavigate:o,onSelect:c})})]})]})]}),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(y,{htmlFor:"edit-branch",children:"Branch Name"}),e.jsx(Z,{id:"edit-branch",className:"font-mono text-sm",value:C,onChange:n=>R(n.target.value),placeholder:"feat/my-feature"})]}),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(y,{children:"Base Branch"}),e.jsxs(J,{open:b,onOpenChange:w,children:[e.jsx(K,{render:e.jsxs("button",{type:"button",className:"flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",children:[e.jsx("span",{className:f?"font-mono text-xs":"text-muted-foreground",children:f||"Select base branch..."}),e.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"text-muted-foreground",children:e.jsx("path",{d:"m6 9 6 6 6-6"})})]})}),e.jsx(X,{align:"start",side:"bottom",sideOffset:4,className:"w-[--trigger-width] p-0",children:e.jsxs("div",{className:"flex flex-col",children:[e.jsx("div",{className:"border-b border-border px-3 py-2",children:e.jsx("input",{type:"text",placeholder:"Search branches...",className:"w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground",value:z,onChange:n=>_(n.target.value),autoFocus:!0})}),e.jsxs("div",{className:"max-h-[200px] overflow-y-auto",children:[F.length===0&&e.jsx("div",{className:"px-3 py-3 text-center text-xs text-muted-foreground",children:I.length===0?"Select a repository first":"No matching branches"}),F.map(n=>e.jsx("button",{type:"button",className:`flex w-full items-center px-3 py-1.5 text-left text-sm hover:bg-muted transition-colors ${n===f?"bg-muted font-medium":""}`,onClick:()=>{E(n),_(""),w(!1)},children:e.jsx("span",{className:"font-mono text-xs truncate",children:n})},n))]})]})})]})]}),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("button",{type:"button",className:"w-fit cursor-pointer border-0 bg-transparent p-0 text-xs text-muted-foreground outline-0 ring-0 hover:text-foreground transition-colors text-left focus:outline-0 focus:ring-0 focus-visible:outline-0 focus-visible:ring-0",onClick:()=>O(!v),children:v?"- Hide initial prompt":"+ Add initial prompt"}),v&&e.jsx(q,{id:"edit-initial-prompt",placeholder:"Custom prompt to send to the agent on start...",rows:4,value:h,onChange:n=>S(n.target.value)})]}),T&&e.jsx("p",{className:"text-sm text-destructive",children:T}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(u,{onClick:$,disabled:!k,children:L?"Saving...":"Save"}),e.jsx(u,{variant:"outline",onClick:r,children:"Start"}),Q&&e.jsx("span",{className:"text-sm text-emerald-500",children:"Saved"})]})]})})}function je({data:i,loading:x,onNavigate:r,onSelect:t}){return!i&&x?e.jsx("div",{className:"flex items-center justify-center py-8 text-sm text-muted-foreground",children:"Loading..."}):i?e.jsxs("div",{className:"flex flex-col",children:[e.jsxs("div",{className:"flex items-center gap-2 border-b border-border px-3 py-2",children:[e.jsx("button",{type:"button",disabled:!i.parent,className:"shrink-0 rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-30 disabled:hover:bg-transparent",onClick:()=>i.parent&&r(i.parent),children:e.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:e.jsx("path",{d:"m15 18-6-6 6-6"})})}),e.jsx("span",{className:"font-mono text-xs text-muted-foreground truncate direction-rtl text-left",children:i.current})]}),e.jsxs("div",{className:"max-h-[280px] overflow-y-auto",children:[i.entries.length===0&&e.jsx("div",{className:"px-3 py-4 text-center text-xs text-muted-foreground",children:"No subdirectories"}),i.entries.map(d=>e.jsxs("button",{type:"button",className:"flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm hover:bg-muted transition-colors",onClick:()=>r(d.path),onDoubleClick:()=>d.isGit&&t(d.path),children:[e.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:d.isGit?"text-emerald-500":"text-muted-foreground",children:e.jsx("path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"})}),e.jsxs("span",{className:"font-mono text-xs truncate",children:[d.name,e.jsx("span",{className:"text-muted-foreground",children:"/"})]}),d.isGit&&e.jsx("span",{className:"ml-auto shrink-0 rounded bg-emerald-500/10 px-1.5 py-0.5 text-[10px] font-medium text-emerald-600 dark:text-emerald-400",children:"git"})]},d.path))]}),e.jsxs("div",{className:"flex items-center justify-between border-t border-border px-3 py-2",children:[e.jsxs("div",{className:"flex flex-col truncate mr-2",children:[e.jsx("span",{className:"font-mono text-[10px] text-muted-foreground truncate",children:i.current}),e.jsx("span",{className:"text-[10px] text-muted-foreground/60",children:"Double-click a git repo to select it"})]}),e.jsx(u,{type:"button",size:"sm",className:"shrink-0 h-7 text-xs",onClick:()=>t(i.current),children:"Select"})]})]}):null}function Ce(){var P,F;const{id:i}=xe(),x=me(),r=i??"",{task:t,loading:d,error:m,refresh:a}=de(r),[l,g]=s.useState(null),[C,R]=s.useState(!1),[f,E]=s.useState(()=>window.innerWidth>=640),[h,S]=s.useState("agents"),[v,O]=s.useState(!1),[I]=he(),j=I.get("agent"),[z,_]=s.useState(null),b=(t==null?void 0:t.user_window_index)??z,w=((F=(P=t==null?void 0:t.agents)==null?void 0:P[0])==null?void 0:F.window_index)??null;s.useEffect(()=>{if(j&&(t!=null&&t.agents)){const o=t.agents.find(c=>c.id===j);if(o){g(o.window_index);return}}l===null&&w!==null&&g(w)},[w,l,j,t==null?void 0:t.agents]),s.useEffect(()=>{t&&t.status!=="running"&&(S("agents"),_(null))},[t==null?void 0:t.status]);const B=s.useCallback(async o=>{if(r)try{const c=await p.addAgent(r,o?{prompt:o}:void 0);g(c.window_index),a()}catch(c){console.error("Failed to add agent:",c)}},[r,a]),U=s.useCallback(async o=>{if(r)try{const c=(t==null?void 0:t.agents)||[],$=c.find(k=>k.id===o);if(await p.stopAgent(r,o),$&&$.window_index===l){const k=c.find(n=>n.id!==o&&n.status!=="stopped");k&&g(k.window_index)}a()}catch(c){console.error("Failed to stop agent:",c)}},[r,a,t,l]),A=s.useCallback(async()=>{if(r)try{await p.updateTask(r,{status:"closed"}),a()}catch(o){console.error("Failed to close task:",o)}},[r,a]),M=s.useCallback(async()=>{if(r)try{await p.startTask(r),a()}catch(o){console.error("Failed to start task:",o)}},[r,a]),V=s.useCallback(async()=>{if(r){R(!0);try{await p.updateTask(r,{status:"running"}),a()}catch(o){console.error("Failed to resume task:",o)}finally{R(!1)}}},[r,a]),G=s.useCallback(async()=>{if(h==="editor"){S("agents");return}if(b===null){if(v)return;O(!0);try{const o=await p.createUserTerminal(r);_(o.user_window_index),a()}catch(o){console.error("Failed to create user terminal:",o);return}finally{O(!1)}}S("editor")},[h,b,r,v,a]);if(d)return e.jsx("div",{className:"flex h-full items-center justify-center text-muted-foreground",children:"Loading..."});if(!t)return e.jsxs("div",{className:"flex h-full flex-col items-center justify-center gap-4",children:[e.jsx("p",{className:"text-destructive",children:m||"Task not found"}),e.jsx(u,{variant:"outline",onClick:()=>x("/"),children:"Back to Dashboard"})]});const L=t.agents||[],N=t.status==="running",T=t.status==="draft",H=(t.status==="closed"||t.status==="error")&&!!t.worktree,Q=t.status==="running"||t.status==="setting_up",D=!!t.tmux_session&&L.length>0&&l!==null&&Q;return e.jsxs("div",{className:"flex h-full flex-col",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-3 py-2 sm:px-4 sm:py-3",children:[e.jsxs("div",{className:"flex items-center gap-2 sm:gap-3 min-w-0",children:[e.jsxs(u,{variant:"ghost",size:"sm",className:"shrink-0",onClick:()=>x("/"),children:[e.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","data-icon":"inline-start",children:e.jsx("path",{d:"m15 18-6-6 6-6"})}),e.jsx("span",{className:"hidden sm:inline",children:"Back"})]}),e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("h1",{className:"truncate text-base font-semibold sm:text-lg",children:t.title}),e.jsx(ce,{status:t.derived_status||t.status})]}),e.jsx("p",{className:"hidden max-w-xl truncate text-xs text-muted-foreground sm:block",children:t.description})]})]}),e.jsxs("div",{className:"flex shrink-0 items-center gap-1 sm:gap-2",children:[t.pr_url&&e.jsxs("a",{href:t.pr_url,target:"_blank",rel:"noopener noreferrer",className:"text-xs text-blue-400 hover:underline sm:text-sm",children:["PR #",t.pr_number]}),H&&e.jsx(u,{variant:"outline",size:"sm",disabled:C,onClick:V,children:C?"...":"Resume"}),N&&!!t.tmux_session&&e.jsxs(u,{variant:h==="editor"?"default":"outline",size:"sm",onClick:G,children:[e.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("polyline",{points:"16 18 22 12 16 6"}),e.jsx("polyline",{points:"8 6 2 12 8 18"})]}),e.jsx("span",{className:"hidden sm:inline",children:"Editor"})]}),T&&e.jsx(u,{variant:"outline",size:"sm",onClick:M,children:"Start"}),N&&e.jsx(u,{variant:"outline",size:"sm",onClick:A,children:"Close"})]})]}),t.error&&e.jsx("div",{className:"border-b border-destructive/30 bg-destructive/10 px-4 py-2 text-sm text-destructive",children:t.error}),e.jsxs("div",{className:"border-b border-border",children:[e.jsxs("button",{onClick:()=>E(o=>!o),className:"flex w-full items-center gap-1.5 px-4 py-1.5 text-xs text-muted-foreground hover:bg-muted/50",children:[e.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:`transition-transform ${f?"rotate-90":""}`,children:e.jsx("path",{d:"m9 18 6-6-6-6"})}),"Details"]}),f&&e.jsxs("div",{className:"grid grid-cols-[auto_1fr] gap-x-6 gap-y-1 px-4 pb-3 text-xs",children:[t.repo_path&&e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-muted-foreground",children:"Repo"}),e.jsx("span",{className:"font-mono text-muted-foreground",children:t.repo_path})]}),t.branch&&e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-muted-foreground",children:"Branch"}),e.jsx("span",{className:"font-mono text-muted-foreground",children:t.branch})]}),t.base_branch&&e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-muted-foreground",children:"Base"}),e.jsx(ue,{variant:"outline",className:"w-fit text-xs font-normal",children:t.base_branch})]}),t.description&&e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-muted-foreground",children:"Description"}),e.jsx("span",{className:"whitespace-pre-wrap text-muted-foreground",children:t.description})]}),t.pr_url&&e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-muted-foreground",children:"PR"}),e.jsxs("a",{href:t.pr_url,target:"_blank",rel:"noopener noreferrer",className:"text-blue-400 hover:underline",children:["#",t.pr_number," — ",t.pr_url]})]}),t.created_at&&e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-muted-foreground",children:"Created"}),e.jsx("span",{className:"text-muted-foreground",children:new Date(t.created_at).toLocaleString()})]}),t.updated_at&&e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-muted-foreground",children:"Updated"}),e.jsx("span",{className:"text-muted-foreground",children:new Date(t.updated_at).toLocaleString()})]})]})]}),D?e.jsxs("div",{className:h==="agents"?"flex min-h-0 flex-1 flex-col":"hidden",children:[e.jsx(pe,{agents:L,activeIndex:l,onSelect:g,onAddAgent:B,onStopAgent:U,canAddAgent:N}),e.jsx("div",{className:"min-h-0 flex-1 overflow-hidden p-2",children:e.jsx(Y,{taskId:t.id,windowIndex:l,visible:h==="agents"})})]}):T?e.jsx("div",{className:"min-h-0 flex-1 overflow-y-auto",children:e.jsx(ge,{task:t,onSaved:a,onStart:M})}):e.jsx("div",{className:h==="agents"?"flex flex-1 items-center justify-center text-muted-foreground":"hidden",children:t.status==="setting_up"?"Setting up terminal...":t.status==="closed"||t.status==="error"?"Terminal session ended":"No terminal available"}),b!==null&&h==="editor"&&e.jsx("div",{className:"flex min-h-0 flex-1 flex-col",children:e.jsx("div",{className:"min-h-0 flex-1 overflow-hidden p-2",children:e.jsx(Y,{taskId:t.id,windowIndex:b})})})]})}export{Ce as default};
@@ -0,0 +1,3 @@
1
+ import{r as t,j as x}from"./vendor-react-BZ8ItZjw.js";import{x as g,a as h,b as F}from"./vendor-xterm-DvXGiZvM.js";const N=1e4,w=1e3;function C({taskId:E,windowIndex:b,wsUrl:d,visible:p=!0}){const c=t.useRef(null),o=t.useRef(null),s=t.useRef(null),f=t.useRef(null),u=t.useRef(null),a=t.useRef(w),i=t.useRef(!1),A=t.useCallback(()=>{const e=window.location.protocol==="https:"?"wss:":"ws:";return d?`${e}//${window.location.host}${d}`:`${e}//${window.location.host}/ws/terminal/${E}/${b}`},[E,b,d]),l=t.useCallback(e=>{if(!f.current||!o.current||!c.current)return;const{clientWidth:r,clientHeight:n}=c.current;r===0||n===0||(f.current.fit(),e.readyState===WebSocket.OPEN&&e.send(JSON.stringify({type:"resize",cols:o.current.cols,rows:o.current.rows})))},[]),m=t.useCallback(e=>{if(i.current)return;const r=new WebSocket(A());r.onopen=()=>{a.current=w,l(r),requestAnimationFrame(()=>{i.current||l(r)})},r.onmessage=n=>{e.write(n.data)},r.onclose=n=>{i.current||n.code!==1e3&&n.code!==1001&&(e.write(`\r
2
+ \x1B[31m[Terminal disconnected — reconnecting...]\x1B[0m\r
3
+ `),u.current=setTimeout(()=>{a.current=Math.min(a.current*2,N),m(e)},a.current))},r.onerror=()=>{},s.current=r,e.onData(n=>{r.readyState===WebSocket.OPEN&&r.send(n)})},[A]),R=t.useCallback(()=>{if(!c.current)return;u.current&&(clearTimeout(u.current),u.current=null),s.current&&(s.current.close(),s.current=null),o.current&&(o.current.dispose(),o.current=null);const e=new g.Terminal({cursorBlink:!0,fontSize:13,fontFamily:'Menlo, Monaco, "Courier New", monospace',theme:{background:"#09090b",foreground:"#fafafa",cursor:"#fafafa",selectionBackground:"#3f3f46"},scrollback:5e3}),r=new h.FitAddon;e.loadAddon(r),e.loadAddon(new F.WebLinksAddon),e.open(c.current);const n=c.current.querySelector(".xterm-viewport");n&&(n.style.overflowY="scroll"),o.current=e,f.current=r,a.current=w,requestAnimationFrame(()=>{i.current||r.fit()}),m(e)},[m]);return t.useEffect(()=>(i.current=!1,R(),()=>{var e,r;i.current=!0,u.current&&clearTimeout(u.current),(e=s.current)==null||e.close(),(r=o.current)==null||r.dispose()}),[R]),t.useEffect(()=>{let e=null;const r=()=>{e===null&&(e=requestAnimationFrame(()=>{e=null;const k=s.current;k&&l(k)}))};window.addEventListener("resize",r);const n=new ResizeObserver(r);return c.current&&n.observe(c.current),()=>{window.removeEventListener("resize",r),n.disconnect(),e!==null&&cancelAnimationFrame(e)}},[l]),t.useEffect(()=>{if(p&&f.current&&o.current){const e=requestAnimationFrame(()=>{requestAnimationFrame(()=>{const r=s.current;r&&l(r)})});return()=>cancelAnimationFrame(e)}},[p,l]),x.jsx("div",{ref:c,className:"h-full w-full overflow-hidden rounded-lg bg-[#09090b]"})}export{C as TerminalView};