dockai 0.2.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/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/api.js +17 -0
- package/dist/api.js.map +1 -0
- package/dist/commands/claude.js +292 -0
- package/dist/commands/claude.js.map +1 -0
- package/dist/commands/connect.js +55 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/deploy.js +98 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/login.js +92 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.js +10 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/logs.js +26 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/open.js +26 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/paste.js +63 -0
- package/dist/commands/paste.js.map +1 -0
- package/dist/commands/projects.js +31 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/proxy.js +55 -0
- package/dist/commands/proxy.js.map +1 -0
- package/dist/commands/push.js +53 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/sessions.js +330 -0
- package/dist/commands/sessions.js.map +1 -0
- package/dist/commands/ssh-config.js +104 -0
- package/dist/commands/ssh-config.js.map +1 -0
- package/dist/commands/start-stop.js +37 -0
- package/dist/commands/start-stop.js.map +1 -0
- package/dist/commands/status.js +38 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/whoami.js +27 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config.js +34 -0
- package/dist/config.js.map +1 -0
- package/dist/index.js +154 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/client-tools.js +175 -0
- package/dist/lib/client-tools.js.map +1 -0
- package/dist/lib/pick-project.js +176 -0
- package/dist/lib/pick-project.js.map +1 -0
- package/dist/lib/remote.js +161 -0
- package/dist/lib/remote.js.map +1 -0
- package/dist/terminal.js +80 -0
- package/dist/terminal.js.map +1 -0
- package/dist/version.js +17 -0
- package/dist/version.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { readdirSync } from "node:fs";
|
|
3
|
+
import { userInfo } from "node:os";
|
|
4
|
+
import { createApiClient } from "../api.js";
|
|
5
|
+
import { getToken } from "../config.js";
|
|
6
|
+
/** POSIX single-quote — safe for anything, including quotes themselves. */
|
|
7
|
+
export function sq(s) {
|
|
8
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
9
|
+
}
|
|
10
|
+
/** Wrap a command so it survives one round of double-quoted expansion. */
|
|
11
|
+
export function dq(s) {
|
|
12
|
+
return `"${s.replace(/([\\$`"])/g, "\\$1")}"`;
|
|
13
|
+
}
|
|
14
|
+
/** The ssh host `dockai ssh-config` registers for a project. */
|
|
15
|
+
export function sshHost(slug) {
|
|
16
|
+
return `dockai-${slug}`;
|
|
17
|
+
}
|
|
18
|
+
/** Where the project's code lives inside the worker. */
|
|
19
|
+
export function workspaceDir(slug) {
|
|
20
|
+
return `/workspace/${slug}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fail early with a readable reason.
|
|
24
|
+
*
|
|
25
|
+
* Without this the failure surfaces as an ssh error, because ProxyCommand's
|
|
26
|
+
* stderr is easy to miss behind ssh's own "Connection closed" message.
|
|
27
|
+
*/
|
|
28
|
+
export async function assertRunning(slug) {
|
|
29
|
+
if (!getToken()) {
|
|
30
|
+
console.error("Not logged in. Run `dockai login` first.");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
let projects;
|
|
34
|
+
try {
|
|
35
|
+
projects = (await createApiClient().project.list.query());
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return; // Server unreachable — let ssh produce the error instead.
|
|
39
|
+
}
|
|
40
|
+
const project = projects.find((p) => p.slug === slug);
|
|
41
|
+
if (!project) {
|
|
42
|
+
const known = projects.map((p) => p.slug).join(", ");
|
|
43
|
+
console.error(`No project "${slug}".${known ? ` Known projects: ${known}` : ""}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
if (project.status !== "RUNNING") {
|
|
47
|
+
console.error(`Project "${slug}" is ${project.status.toLowerCase()}. Start it with:`);
|
|
48
|
+
console.error(` dockai start ${slug}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** What to print when ssh itself failed (exit 255) rather than the far end. */
|
|
53
|
+
export function explainSshFailure(slug) {
|
|
54
|
+
console.error("");
|
|
55
|
+
console.error(`Could not reach ${sshHost(slug)}. Is the ssh config block installed?`);
|
|
56
|
+
console.error(" dockai ssh-config --print >> ~/.ssh/config");
|
|
57
|
+
console.error("");
|
|
58
|
+
console.error("If it is, re-run it — the ProxyCommand hardcodes the path");
|
|
59
|
+
console.error("to this CLI, so moving or reinstalling dockai breaks it.");
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Stream a local tree into a directory on the worker.
|
|
63
|
+
*
|
|
64
|
+
* tar over ssh rather than scp: a single connection through the ProxyCommand
|
|
65
|
+
* tunnel, modification times preserved (Claude picks `--continue` by them),
|
|
66
|
+
* and GNU tar on the far end can leave existing files alone. rsync would be
|
|
67
|
+
* the obvious tool, but the worker image does not ship it, and `scp -r`
|
|
68
|
+
* cannot skip what is already there.
|
|
69
|
+
*
|
|
70
|
+
* COPYFILE_DISABLE stops macOS bsdtar from adding `._*` AppleDouble entries
|
|
71
|
+
* for extended attributes, and --no-xattrs stops it encoding the attributes
|
|
72
|
+
* themselves as pax headers, which GNU tar on the worker reports line by
|
|
73
|
+
* line ("Ignoring unknown extended header keyword") for every file.
|
|
74
|
+
*
|
|
75
|
+
* Resolves with the exit status: 0 on success, 255 when ssh itself failed.
|
|
76
|
+
*/
|
|
77
|
+
export function pushTree(slug, opts) {
|
|
78
|
+
const remote = `mkdir -p ${sq(opts.remoteDir)} && ` +
|
|
79
|
+
`tar -C ${sq(opts.remoteDir)} -x${opts.overwrite ? "" : " --skip-old-files"} -f -`;
|
|
80
|
+
const local = `COPYFILE_DISABLE=1 tar --no-xattrs -C ${sq(opts.cwd)} -cf - ${opts.paths.map(sq).join(" ")}`;
|
|
81
|
+
// bash, not sh: pipefail is what makes a local tar failure visible.
|
|
82
|
+
const script = `set -o pipefail; ${local} | ssh ${sq(sshHost(slug))} ${sq(remote)}`;
|
|
83
|
+
const child = spawn("bash", ["-c", script], { stdio: ["ignore", "inherit", "inherit"] });
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
child.on("error", () => resolve(255));
|
|
86
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/** Run a command on the worker over ssh and capture its output. */
|
|
90
|
+
export function runRemote(slug, command) {
|
|
91
|
+
const child = spawn("ssh", ["-o", "BatchMode=yes", sshHost(slug), command], { stdio: ["ignore", "pipe", "inherit"] });
|
|
92
|
+
let stdout = "";
|
|
93
|
+
child.stdout.on("data", (d) => { stdout += d.toString(); });
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
child.on("error", () => resolve({ code: 255, stdout }));
|
|
96
|
+
child.on("close", (code) => resolve({ code: code ?? 1, stdout }));
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Put the terminal back the way a full-screen program leaves it when it exits
|
|
101
|
+
* cleanly. When the tunnel dies under Claude's TUI (a deploy restarts the
|
|
102
|
+
* orchestrator, the network drops) nothing sends the "off" sequences, and the
|
|
103
|
+
* shell is left with mouse reporting on — every pointer move types
|
|
104
|
+
* `35;43;29M` into the prompt. Harmless to send when nothing was on.
|
|
105
|
+
*/
|
|
106
|
+
export function resetTerminal() {
|
|
107
|
+
if (!process.stdout.isTTY)
|
|
108
|
+
return;
|
|
109
|
+
process.stdout.write("\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l" + // mouse reporting, all flavours
|
|
110
|
+
"\x1b[?2004l" + // bracketed paste
|
|
111
|
+
// No "leave alternate screen" (?1049l): Claude's TUI renders inline and
|
|
112
|
+
// never enters it, and on a terminal that is not in it the sequence
|
|
113
|
+
// restores a stale saved cursor, so the next output lands mid-scrollback.
|
|
114
|
+
"\x1b[?25h" + // cursor
|
|
115
|
+
"\x1b[0m");
|
|
116
|
+
}
|
|
117
|
+
/** True once the API answers again — polled after a dropped tunnel. */
|
|
118
|
+
export async function apiReachable() {
|
|
119
|
+
try {
|
|
120
|
+
await createApiClient().project.list.query();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Where the Claude in Chrome native host listens, on this machine. */
|
|
128
|
+
export const BROWSER_BRIDGE_REMOTE_DIR = "/tmp/claude-mcp-browser-bridge-claude";
|
|
129
|
+
/**
|
|
130
|
+
* The Chrome extension's native host (launched by Chrome, on the laptop)
|
|
131
|
+
* listens on a Unix socket per host process; the CLI's browser tools connect
|
|
132
|
+
* to whatever sockets sit in this directory. Forwarding those sockets over
|
|
133
|
+
* the ssh tunnel is all it takes for Claude inside a worker to drive the
|
|
134
|
+
* laptop's Chrome — verified 2026-09-14. Sockets of dead hosts are skipped.
|
|
135
|
+
*/
|
|
136
|
+
export function localBrowserBridgeSockets() {
|
|
137
|
+
const dir = `/tmp/claude-mcp-browser-bridge-${userInfo().username}`;
|
|
138
|
+
let names;
|
|
139
|
+
try {
|
|
140
|
+
names = readdirSync(dir);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
return names
|
|
146
|
+
.filter((n) => n.endsWith(".sock"))
|
|
147
|
+
.filter((n) => {
|
|
148
|
+
const pid = Number(n.slice(0, -5));
|
|
149
|
+
if (!Number.isInteger(pid))
|
|
150
|
+
return true;
|
|
151
|
+
try {
|
|
152
|
+
process.kill(pid, 0);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
.map((n) => `${dir}/${n}`);
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=remote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/lib/remote.ts"],"names":[],"mappings":"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"}
|
package/dist/terminal.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { WebSocket } from "ws";
|
|
2
|
+
import { getServerUrl, getToken } from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Opens a raw TTY WebSocket connection to the worker container.
|
|
5
|
+
* Pipes local stdin/stdout through the WebSocket.
|
|
6
|
+
*/
|
|
7
|
+
export async function connectTerminal(slug, options = {}) {
|
|
8
|
+
const serverUrl = getServerUrl();
|
|
9
|
+
const token = getToken();
|
|
10
|
+
// Build WebSocket URL
|
|
11
|
+
const wsBase = serverUrl.replace(/^http/, "ws");
|
|
12
|
+
const wsUrl = `${wsBase}/api/ws/terminal/${slug}`;
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const ws = new WebSocket(wsUrl, {
|
|
15
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
16
|
+
});
|
|
17
|
+
ws.binaryType = "arraybuffer";
|
|
18
|
+
ws.on("open", () => {
|
|
19
|
+
// Put terminal in raw mode
|
|
20
|
+
if (process.stdin.isTTY) {
|
|
21
|
+
process.stdin.setRawMode(true);
|
|
22
|
+
}
|
|
23
|
+
process.stdin.resume();
|
|
24
|
+
// Send initial resize
|
|
25
|
+
const cols = process.stdout.columns || 80;
|
|
26
|
+
const rows = process.stdout.rows || 24;
|
|
27
|
+
ws.send(JSON.stringify({ type: "resize", cols, rows }));
|
|
28
|
+
// If claude mode, send mode control message
|
|
29
|
+
if (options.claude) {
|
|
30
|
+
ws.send(JSON.stringify({ type: "mode", value: "claude" }));
|
|
31
|
+
}
|
|
32
|
+
// stdin → WebSocket
|
|
33
|
+
process.stdin.on("data", (data) => {
|
|
34
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
35
|
+
ws.send(data);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
// Handle terminal resize
|
|
39
|
+
process.stdout.on("resize", () => {
|
|
40
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
41
|
+
ws.send(JSON.stringify({
|
|
42
|
+
type: "resize",
|
|
43
|
+
cols: process.stdout.columns,
|
|
44
|
+
rows: process.stdout.rows,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
ws.on("message", (data) => {
|
|
50
|
+
if (data instanceof ArrayBuffer) {
|
|
51
|
+
process.stdout.write(Buffer.from(data));
|
|
52
|
+
}
|
|
53
|
+
else if (Buffer.isBuffer(data)) {
|
|
54
|
+
process.stdout.write(data);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
process.stdout.write(String(data));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
ws.on("close", (code) => {
|
|
61
|
+
// Restore terminal
|
|
62
|
+
if (process.stdin.isTTY) {
|
|
63
|
+
process.stdin.setRawMode(false);
|
|
64
|
+
}
|
|
65
|
+
process.stdin.pause();
|
|
66
|
+
if (code !== 1000) {
|
|
67
|
+
console.error(`\nConnection closed (code: ${code})`);
|
|
68
|
+
}
|
|
69
|
+
resolve();
|
|
70
|
+
});
|
|
71
|
+
ws.on("error", (err) => {
|
|
72
|
+
if (process.stdin.isTTY) {
|
|
73
|
+
process.stdin.setRawMode(false);
|
|
74
|
+
}
|
|
75
|
+
process.stdin.pause();
|
|
76
|
+
reject(new Error(`WebSocket error: ${err.message}`));
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=terminal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"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"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI version. Kept in sync with apps/cli/package.json by the release workflow.
|
|
3
|
+
* Hardcoded so it works whether the CLI is run via npx, global install, or local link.
|
|
4
|
+
*/
|
|
5
|
+
export const CLI_VERSION = "0.2.0";
|
|
6
|
+
export function compareSemver(a, b) {
|
|
7
|
+
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
8
|
+
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
|
9
|
+
for (let i = 0; i < 3; i++) {
|
|
10
|
+
const da = pa[i] ?? 0;
|
|
11
|
+
const db = pb[i] ?? 0;
|
|
12
|
+
if (da !== db)
|
|
13
|
+
return da - db;
|
|
14
|
+
}
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC;AAEnC,MAAM,UAAU,aAAa,CAAC,CAAS,EAAE,CAAS;IAChD,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACzD,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,EAAE,GAAG,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dockai",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "CLI for DockAI — self-hosted Claude Code session manager",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Pol Amoros",
|
|
7
|
+
"homepage": "https://github.com/polamoros/DockAI",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/polamoros/DockAI.git",
|
|
11
|
+
"directory": "apps/cli"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"claude",
|
|
15
|
+
"claude-code",
|
|
16
|
+
"dockai",
|
|
17
|
+
"docker",
|
|
18
|
+
"cli",
|
|
19
|
+
"remote",
|
|
20
|
+
"ssh",
|
|
21
|
+
"terminal"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"bin": {
|
|
32
|
+
"dockai": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@trpc/client": "^11.18.0",
|
|
36
|
+
"commander": "^15.0.0",
|
|
37
|
+
"open": "^11.0.0",
|
|
38
|
+
"superjson": "^2.2.6",
|
|
39
|
+
"ws": "^8.21.3"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.19.15",
|
|
43
|
+
"@types/ws": "^8.18.1",
|
|
44
|
+
"tsx": "^4.23.11",
|
|
45
|
+
"typescript": "^7.0.2"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"dev": "tsx src/index.ts",
|
|
49
|
+
"build": "tsc",
|
|
50
|
+
"typecheck": "tsc --noEmit"
|
|
51
|
+
}
|
|
52
|
+
}
|