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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/dist/api.js +17 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/commands/claude.js +292 -0
  6. package/dist/commands/claude.js.map +1 -0
  7. package/dist/commands/connect.js +55 -0
  8. package/dist/commands/connect.js.map +1 -0
  9. package/dist/commands/deploy.js +98 -0
  10. package/dist/commands/deploy.js.map +1 -0
  11. package/dist/commands/login.js +92 -0
  12. package/dist/commands/login.js.map +1 -0
  13. package/dist/commands/logout.js +10 -0
  14. package/dist/commands/logout.js.map +1 -0
  15. package/dist/commands/logs.js +26 -0
  16. package/dist/commands/logs.js.map +1 -0
  17. package/dist/commands/open.js +26 -0
  18. package/dist/commands/open.js.map +1 -0
  19. package/dist/commands/paste.js +63 -0
  20. package/dist/commands/paste.js.map +1 -0
  21. package/dist/commands/projects.js +31 -0
  22. package/dist/commands/projects.js.map +1 -0
  23. package/dist/commands/proxy.js +55 -0
  24. package/dist/commands/proxy.js.map +1 -0
  25. package/dist/commands/push.js +53 -0
  26. package/dist/commands/push.js.map +1 -0
  27. package/dist/commands/sessions.js +330 -0
  28. package/dist/commands/sessions.js.map +1 -0
  29. package/dist/commands/ssh-config.js +104 -0
  30. package/dist/commands/ssh-config.js.map +1 -0
  31. package/dist/commands/start-stop.js +37 -0
  32. package/dist/commands/start-stop.js.map +1 -0
  33. package/dist/commands/status.js +38 -0
  34. package/dist/commands/status.js.map +1 -0
  35. package/dist/commands/whoami.js +27 -0
  36. package/dist/commands/whoami.js.map +1 -0
  37. package/dist/config.js +34 -0
  38. package/dist/config.js.map +1 -0
  39. package/dist/index.js +154 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/lib/client-tools.js +175 -0
  42. package/dist/lib/client-tools.js.map +1 -0
  43. package/dist/lib/pick-project.js +176 -0
  44. package/dist/lib/pick-project.js.map +1 -0
  45. package/dist/lib/remote.js +161 -0
  46. package/dist/lib/remote.js.map +1 -0
  47. package/dist/terminal.js +80 -0
  48. package/dist/terminal.js.map +1 -0
  49. package/dist/version.js +17 -0
  50. package/dist/version.js.map +1 -0
  51. package/package.json +52 -0
@@ -0,0 +1,92 @@
1
+ import { createInterface } from "node:readline";
2
+ import { saveConfig, getConfig } from "../config.js";
3
+ import { CLI_VERSION, compareSemver } from "../version.js";
4
+ function prompt(question) {
5
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
6
+ return new Promise((resolve) => {
7
+ rl.question(question, (answer) => {
8
+ rl.close();
9
+ resolve(answer.trim());
10
+ });
11
+ });
12
+ }
13
+ async function fetchSystemVersion(serverUrl) {
14
+ let healthOk = false;
15
+ try {
16
+ const res = await fetch(`${serverUrl}/api/health`);
17
+ healthOk = res.ok;
18
+ }
19
+ catch {
20
+ return { kind: "unreachable" };
21
+ }
22
+ if (!healthOk)
23
+ return { kind: "unreachable" };
24
+ try {
25
+ const res = await fetch(`${serverUrl}/api/trpc/system.version`);
26
+ if (!res.ok)
27
+ return { kind: "legacy" };
28
+ const json = (await res.json());
29
+ const data = json.result?.data?.json;
30
+ if (!data?.server || !data?.minCli)
31
+ return { kind: "legacy" };
32
+ return { kind: "ok", version: data };
33
+ }
34
+ catch {
35
+ return { kind: "legacy" };
36
+ }
37
+ }
38
+ export async function loginCommand(options) {
39
+ const config = getConfig();
40
+ const skipVersionCheck = options.versionCheck === false;
41
+ const prompted = options.server ?? (await prompt(`Server URL [${config.serverUrl}]: `));
42
+ const serverUrl = (prompted || config.serverUrl).replace(/\/$/, "");
43
+ let serverVersionLabel = "unknown";
44
+ if (!skipVersionCheck) {
45
+ const result = await fetchSystemVersion(serverUrl);
46
+ if (result.kind === "unreachable") {
47
+ console.error(`Cannot reach ${serverUrl}. Is the URL correct and the server running?`);
48
+ process.exit(1);
49
+ }
50
+ if (result.kind === "legacy") {
51
+ console.log(`Note: ${serverUrl} doesn't expose a version yet. Continuing anyway — ` +
52
+ `if calls fail, the server may need upgrading.`);
53
+ }
54
+ else {
55
+ const { server, minCli } = result.version;
56
+ serverVersionLabel = `v${server}`;
57
+ if (compareSemver(CLI_VERSION, minCli) < 0) {
58
+ console.error(`This CLI (v${CLI_VERSION}) is too old for ${serverUrl} (requires v${minCli}+).\n` +
59
+ `Update with: npm i -g dockai@latest`);
60
+ process.exit(1);
61
+ }
62
+ if (compareSemver(CLI_VERSION, server) < 0) {
63
+ console.log(`Note: server is v${server}, CLI is v${CLI_VERSION}. ` +
64
+ `Some features may be missing. Update with: npm i -g dockai@latest\n`);
65
+ }
66
+ }
67
+ }
68
+ else {
69
+ // Still verify reachability so we don't save a useless token.
70
+ try {
71
+ const res = await fetch(`${serverUrl}/api/health`);
72
+ if (!res.ok)
73
+ throw new Error(`HTTP ${res.status}`);
74
+ }
75
+ catch {
76
+ console.error(`Cannot reach ${serverUrl}.`);
77
+ process.exit(1);
78
+ }
79
+ }
80
+ console.log(`\nTo get your API token:`);
81
+ console.log(`1. Open ${serverUrl} in your browser`);
82
+ console.log(`2. Sign in`);
83
+ console.log(`3. Go to Settings → API Tokens → Create token\n`);
84
+ const token = await prompt("API token: ");
85
+ if (!token) {
86
+ console.error("No token provided. Aborting.");
87
+ process.exit(1);
88
+ }
89
+ saveConfig({ serverUrl, token });
90
+ console.log(`\nLogged in to ${serverUrl} (server ${serverVersionLabel}). Config saved.`);
91
+ }
92
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE3D,SAAS,MAAM,CAAC,QAAgB;IAC9B,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE;YAC/B,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAYD,KAAK,UAAU,kBAAkB,CAAC,SAAiB;IACjD,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,SAAS,aAAa,CAAC,CAAC;QACnD,QAAQ,GAAG,GAAG,CAAC,EAAE,CAAC;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IACjC,CAAC;IACD,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAE9C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,SAAS,0BAA0B,CAAC,CAAC;QAChE,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAqD,CAAC;QACpF,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC;QACrC,IAAI,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,IAAI,EAAE,MAAM;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC9D,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAGlC;IACC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,gBAAgB,GAAG,OAAO,CAAC,YAAY,KAAK,KAAK,CAAC;IAExD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,MAAM,CAAC,eAAe,MAAM,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC;IACxF,MAAM,SAAS,GAAG,CAAC,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAEpE,IAAI,kBAAkB,GAAG,SAAS,CAAC;IAEnC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAEnD,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YAClC,OAAO,CAAC,KAAK,CAAC,gBAAgB,SAAS,8CAA8C,CAAC,CAAC;YACvF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CACT,SAAS,SAAS,qDAAqD;gBACrE,+CAA+C,CAClD,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC;YAC1C,kBAAkB,GAAG,IAAI,MAAM,EAAE,CAAC;YAElC,IAAI,aAAa,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3C,OAAO,CAAC,KAAK,CACX,cAAc,WAAW,oBAAoB,SAAS,eAAe,MAAM,OAAO;oBAChF,qCAAqC,CACxC,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YAED,IAAI,aAAa,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3C,OAAO,CAAC,GAAG,CACT,oBAAoB,MAAM,aAAa,WAAW,IAAI;oBACpD,qEAAqE,CACxE,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;SAAM,CAAC;QACN,8DAA8D;QAC9D,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,SAAS,aAAa,CAAC,CAAC;YACnD,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,KAAK,CAAC,gBAAgB,SAAS,GAAG,CAAC,CAAC;YAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,WAAW,SAAS,kBAAkB,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC1B,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;IAE/D,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IAE1C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,UAAU,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,kBAAkB,SAAS,YAAY,kBAAkB,kBAAkB,CAAC,CAAC;AAC3F,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { saveConfig, getToken } from "../config.js";
2
+ export async function logoutCommand() {
3
+ if (!getToken()) {
4
+ console.log("Not logged in.");
5
+ return;
6
+ }
7
+ saveConfig({ token: null });
8
+ console.log("Logged out. Token cleared from local config.");
9
+ }
10
+ //# sourceMappingURL=logout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC9B,OAAO;IACT,CAAC;IACD,UAAU,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5B,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { createApiClient } from "../api.js";
2
+ export async function logsCommand(slug, options) {
3
+ const api = createApiClient();
4
+ const tail = parseInt(options.tail || "100", 10);
5
+ try {
6
+ const projects = (await api.project.list.query());
7
+ const project = projects.find((p) => p.slug === slug);
8
+ if (!project) {
9
+ console.error(`Project "${slug}" not found.`);
10
+ process.exit(1);
11
+ }
12
+ const result = (await api.project.logs.query({ id: project.id, tail }));
13
+ if (result.logs) {
14
+ process.stdout.write(result.logs);
15
+ }
16
+ else {
17
+ console.log("No logs available.");
18
+ }
19
+ }
20
+ catch (err) {
21
+ const msg = err instanceof Error ? err.message : String(err);
22
+ console.error("Failed to fetch logs:", msg);
23
+ process.exit(1);
24
+ }
25
+ }
26
+ //# sourceMappingURL=logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.js","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAgB,MAAM,WAAW,CAAC;AAE1D,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAY,EACZ,OAA0B;IAE1B,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC,CAAC;IAEjD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAc,CAAC;QAC/D,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,YAAY,IAAI,cAAc,CAAC,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAErE,CAAC;QACF,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;QAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ import open from "open";
2
+ import { createApiClient } from "../api.js";
3
+ export async function openCommand(slug) {
4
+ const api = createApiClient();
5
+ try {
6
+ const projects = (await api.project.list.query());
7
+ const project = projects.find((p) => p.slug === slug);
8
+ if (!project) {
9
+ console.error(`Project "${slug}" not found.`);
10
+ process.exit(1);
11
+ }
12
+ if (project.container?.sessionUrl) {
13
+ console.log(`Opening ${project.container.sessionUrl}...`);
14
+ await open(project.container.sessionUrl);
15
+ }
16
+ else {
17
+ console.log("No active session URL. Is the project running?");
18
+ }
19
+ }
20
+ catch (err) {
21
+ const msg = err instanceof Error ? err.message : String(err);
22
+ console.error("Failed:", msg);
23
+ process.exit(1);
24
+ }
25
+ }
26
+ //# sourceMappingURL=open.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.js","sourceRoot":"","sources":["../../src/commands/open.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,eAAe,EAAgB,MAAM,WAAW,CAAC;AAE1D,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAE9B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAc,CAAC;QAC/D,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,YAAY,IAAI,cAAc,CAAC,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,OAAO,CAAC,SAAS,EAAE,UAAU,EAAE,CAAC;YAClC,OAAO,CAAC,GAAG,CAAC,WAAW,OAAO,CAAC,SAAS,CAAC,UAAU,KAAK,CAAC,CAAC;YAC1D,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { spawn } from "node:child_process";
2
+ import { promises as fs } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { assertRunning, runRemote, sq, sshHost, workspaceDir } from "../lib/remote.js";
6
+ /**
7
+ * Paste the image on this Mac's clipboard into a `dockai claude` session.
8
+ *
9
+ * Claude reads the clipboard of the machine it runs on, which over SSH is
10
+ * the worker — so image paste in a `dockai claude` session cannot work on
11
+ * its own. This copies the image up and types the @path reference into the
12
+ * tmux session Claude is running in, which is the same thing a paste ends up
13
+ * as.
14
+ */
15
+ export async function pasteCommand(slug, options) {
16
+ if (process.platform !== "darwin") {
17
+ console.error("dockai paste reads the clipboard through macOS APIs; on other systems use `dockai push <slug> <file>` and reference it with @.");
18
+ process.exit(1);
19
+ }
20
+ await assertRunning(slug);
21
+ const tmp = path.join(os.tmpdir(), `dockai-paste-${Date.now()}.png`);
22
+ const script = [
23
+ `set f to open for access POSIX file "${tmp}" with write permission`,
24
+ "try",
25
+ " write (the clipboard as «class PNGf») to f",
26
+ "on error",
27
+ " close access f",
28
+ ' error "no image on the clipboard"',
29
+ "end try",
30
+ "close access f",
31
+ ].join("\n");
32
+ const ok = await new Promise((resolve) => {
33
+ const child = spawn("osascript", ["-e", script], { stdio: ["ignore", "ignore", "pipe"] });
34
+ let err = "";
35
+ child.stderr.on("data", (d) => { err += d.toString(); });
36
+ child.on("close", (code) => { if (code !== 0)
37
+ console.error(err.includes("no image") ? "No image on the clipboard." : err.trim()); resolve(code === 0); });
38
+ });
39
+ if (!ok)
40
+ process.exit(1);
41
+ const name = `paste-${new Date().toISOString().replace(/[:.]/g, "-")}.png`;
42
+ const remoteDir = `${workspaceDir(slug)}/.dockai/paste`;
43
+ const remotePath = `${remoteDir}/${name}`;
44
+ const data = await fs.readFile(tmp);
45
+ await fs.rm(tmp, { force: true });
46
+ const upload = spawn("ssh", ["-o", "BatchMode=yes", sshHost(slug), `mkdir -p ${sq(remoteDir)} && cat > ${sq(remotePath)}`], { stdio: ["pipe", "inherit", "inherit"] });
47
+ upload.stdin.end(data);
48
+ const code = await new Promise((resolve) => upload.on("close", (c) => resolve(c ?? 1)));
49
+ if (code !== 0) {
50
+ console.error(`Upload failed (exit ${code}).`);
51
+ process.exit(1);
52
+ }
53
+ // Type the reference into the pane, without a newline: the person decides
54
+ // when to send, and can add text around it.
55
+ const session = (options.session || "claude-cli").replace(/[^a-zA-Z0-9_-]/g, "-");
56
+ const ref = `@.dockai/paste/${name} `;
57
+ const typed = await runRemote(slug, `tmux send-keys -t ${sq(session)} -l ${sq(ref)}`);
58
+ if (typed.code === 0)
59
+ console.log(`Pasted into ${session}: ${ref.trim()}`);
60
+ else
61
+ console.log(`Uploaded ${remotePath} — no tmux session "${session}" to type into; reference it as ${ref.trim()}`);
62
+ }
63
+ //# sourceMappingURL=paste.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paste.js","sourceRoot":"","sources":["../../src/commands/paste.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { createApiClient } from "../api.js";
2
+ const statusIcons = {
3
+ RUNNING: "\x1b[32m●\x1b[0m",
4
+ STARTING: "\x1b[33m◐\x1b[0m",
5
+ STOPPING: "\x1b[33m◑\x1b[0m",
6
+ ERROR: "\x1b[31m●\x1b[0m",
7
+ STOPPED: "\x1b[90m○\x1b[0m",
8
+ };
9
+ export async function projectsCommand() {
10
+ const api = createApiClient();
11
+ try {
12
+ const projects = (await api.project.list.query());
13
+ if (!projects.length) {
14
+ console.log("No projects found. Create one in the dashboard.");
15
+ return;
16
+ }
17
+ console.log("\nProjects:\n");
18
+ for (const p of projects) {
19
+ const icon = statusIcons[p.status] || "?";
20
+ const session = p.container?.sessionUrl ? ` → ${p.container.sessionUrl}` : "";
21
+ console.log(` ${icon} ${p.name} (${p.slug}) [${p.status}]${session}`);
22
+ }
23
+ console.log();
24
+ }
25
+ catch (err) {
26
+ const msg = err instanceof Error ? err.message : String(err);
27
+ console.error("Failed to list projects:", msg);
28
+ process.exit(1);
29
+ }
30
+ }
31
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/commands/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAgB,MAAM,WAAW,CAAC;AAE1D,MAAM,WAAW,GAA2B;IAC1C,OAAO,EAAE,kBAAkB;IAC3B,QAAQ,EAAE,kBAAkB;IAC5B,QAAQ,EAAE,kBAAkB;IAC5B,KAAK,EAAE,kBAAkB;IACzB,OAAO,EAAE,kBAAkB;CAC5B,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAE9B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAc,CAAC;QAE/D,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;YAC/D,OAAO;QACT,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAC7B,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;YAC1C,MAAM,OAAO,GAAG,CAAC,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9E,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,IAAI,OAAO,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,CAAC,GAAG,EAAE,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,GAAG,CAAC,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,55 @@
1
+ import WebSocket from "ws";
2
+ import { getServerUrl, getToken } from "../config.js";
3
+ /**
4
+ * SSH ProxyCommand transport.
5
+ *
6
+ * Pipes stdin/stdout to DockAI's SSH tunnel endpoint, which forwards to the
7
+ * worker's sshd over the project's internal network. ssh speaks its own
8
+ * protocol across the pipe, so this works from any network DockAI is
9
+ * reachable from — no VPN, and no SSH port published on the host.
10
+ *
11
+ * Not meant to be run by hand:
12
+ *
13
+ * Host dockai-*
14
+ * User claude
15
+ * ProxyCommand dockai proxy %n
16
+ */
17
+ export async function proxyCommand(slug) {
18
+ // Accept both the bare slug and the `dockai-<slug>` host alias, since ssh
19
+ // passes whatever the Host pattern matched.
20
+ const project = slug.replace(/^dockai-/, "");
21
+ const token = getToken();
22
+ if (!token) {
23
+ process.stderr.write("Not logged in. Run `dockai login` first.\n");
24
+ process.exit(1);
25
+ }
26
+ const server = getServerUrl();
27
+ const wsUrl = `${server.replace(/^http/, "ws")}/api/ws/ssh/${encodeURIComponent(project)}`;
28
+ const ws = new WebSocket(wsUrl, {
29
+ headers: { Authorization: `Bearer ${token}` },
30
+ });
31
+ ws.on("open", () => {
32
+ process.stdin.on("data", (chunk) => {
33
+ if (ws.readyState === WebSocket.OPEN)
34
+ ws.send(chunk);
35
+ });
36
+ process.stdin.on("end", () => ws.close());
37
+ });
38
+ ws.on("message", (data) => {
39
+ process.stdout.write(data);
40
+ });
41
+ ws.on("unexpected-response", (_req, res) => {
42
+ const hint = res.statusCode === 401 ? "not authorised — try `dockai login`"
43
+ : res.statusCode === 404 ? `no project "${project}"`
44
+ : res.statusCode === 409 ? `project "${project}" is not running`
45
+ : `server returned ${res.statusCode}`;
46
+ process.stderr.write(`dockai proxy: ${hint}\n`);
47
+ process.exit(1);
48
+ });
49
+ ws.on("error", (err) => {
50
+ process.stderr.write(`dockai proxy: ${err.message}\n`);
51
+ process.exit(1);
52
+ });
53
+ ws.on("close", () => process.exit(0));
54
+ }
55
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/commands/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEtD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY;IAC7C,0EAA0E;IAC1E,4CAA4C;IAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAE7C,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;QACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;IAE3F,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,KAAK,EAAE;QAC9B,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;KAC9C,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACjB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YACjC,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI;gBAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAY,EAAE,EAAE;QAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,qBAAqB,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QACzC,MAAM,IAAI,GACR,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,CAAC,CAAC,qCAAqC;YAC9D,CAAC,CAAC,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,CAAC,CAAC,eAAe,OAAO,GAAG;gBACpD,CAAC,CAAC,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,CAAC,CAAC,YAAY,OAAO,kBAAkB;oBAChE,CAAC,CAAC,mBAAmB,GAAG,CAAC,UAAU,EAAE,CAAC;QACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,IAAI,IAAI,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { assertRunning, explainSshFailure, pushTree, workspaceDir } from "../lib/remote.js";
4
+ /**
5
+ * Copy local files into a project's workspace.
6
+ *
7
+ * For what a clone does not carry: the `.env`, the helper scripts, the local
8
+ * config that is gitignored on purpose. Paths are relative to the current
9
+ * directory and land at the same relative place under /workspace/<slug>, so
10
+ * running it from the repo root puts everything where the code expects it.
11
+ *
12
+ * Existing files on the worker are replaced — the files were named
13
+ * explicitly, and re-pushing an edited .env is the common case.
14
+ * `--keep-existing` turns that off.
15
+ */
16
+ export async function pushCommand(slug, paths, options) {
17
+ const cwd = process.cwd();
18
+ const relative = [];
19
+ for (const given of paths) {
20
+ const abs = path.resolve(cwd, given);
21
+ const rel = path.relative(cwd, abs);
22
+ if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) {
23
+ console.error(`"${given}" is outside the current directory. Run this from the project root.`);
24
+ process.exit(1);
25
+ }
26
+ if (!existsSync(abs)) {
27
+ console.error(`"${given}" does not exist.`);
28
+ process.exit(1);
29
+ }
30
+ relative.push(rel);
31
+ }
32
+ await assertRunning(slug);
33
+ const dest = workspaceDir(slug);
34
+ console.log(`Pushing to ${slug}:${dest}`);
35
+ for (const rel of relative)
36
+ console.log(` ${rel}`);
37
+ const code = await pushTree(slug, {
38
+ cwd,
39
+ paths: relative,
40
+ remoteDir: dest,
41
+ overwrite: !options.keepExisting,
42
+ });
43
+ if (code === 255) {
44
+ explainSshFailure(slug);
45
+ process.exit(1);
46
+ }
47
+ if (code !== 0) {
48
+ console.error(`Push failed (exit ${code}).`);
49
+ process.exit(1);
50
+ }
51
+ console.log("Done.");
52
+ }
53
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/commands/push.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAE5F;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAY,EACZ,KAAe,EACf,OAAmC;IAEnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACzD,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,qEAAqE,CAAC,CAAC;YAC9F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,mBAAmB,CAAC,CAAC;YAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAED,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;IAE1B,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;IAC1C,KAAK,MAAM,GAAG,IAAI,QAAQ;QAAE,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;IAEpD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE;QAChC,GAAG;QACH,KAAK,EAAE,QAAQ;QACf,SAAS,EAAE,IAAI;QACf,SAAS,EAAE,CAAC,OAAO,CAAC,YAAY;KACjC,CAAC,CAAC;IACH,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;QACjB,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,qBAAqB,IAAI,IAAI,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACvB,CAAC"}