pairpod-bot 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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -0
  3. package/dist/access.d.ts +2 -0
  4. package/dist/access.js +14 -0
  5. package/dist/access.js.map +1 -0
  6. package/dist/agents.d.ts +7 -0
  7. package/dist/agents.js +10 -0
  8. package/dist/agents.js.map +1 -0
  9. package/dist/attach.d.ts +2 -0
  10. package/dist/attach.js +65 -0
  11. package/dist/attach.js.map +1 -0
  12. package/dist/bot.d.ts +1 -0
  13. package/dist/bot.js +357 -0
  14. package/dist/bot.js.map +1 -0
  15. package/dist/config.d.ts +18 -0
  16. package/dist/config.js +39 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/db.d.ts +2 -0
  19. package/dist/db.js +87 -0
  20. package/dist/db.js.map +1 -0
  21. package/dist/docker.d.ts +15 -0
  22. package/dist/docker.js +113 -0
  23. package/dist/docker.js.map +1 -0
  24. package/dist/env.d.ts +2 -0
  25. package/dist/env.js +36 -0
  26. package/dist/env.js.map +1 -0
  27. package/dist/errors.d.ts +7 -0
  28. package/dist/errors.js +25 -0
  29. package/dist/errors.js.map +1 -0
  30. package/dist/local/sessions.d.ts +5 -0
  31. package/dist/local/sessions.js +83 -0
  32. package/dist/local/sessions.js.map +1 -0
  33. package/dist/main.d.ts +1 -0
  34. package/dist/main.js +8 -0
  35. package/dist/main.js.map +1 -0
  36. package/dist/naming.d.ts +3 -0
  37. package/dist/naming.js +19 -0
  38. package/dist/naming.js.map +1 -0
  39. package/dist/network.d.ts +1 -0
  40. package/dist/network.js +11 -0
  41. package/dist/network.js.map +1 -0
  42. package/dist/notifier.d.ts +4 -0
  43. package/dist/notifier.js +47 -0
  44. package/dist/notifier.js.map +1 -0
  45. package/dist/notify.d.ts +2 -0
  46. package/dist/notify.js +19 -0
  47. package/dist/notify.js.map +1 -0
  48. package/dist/paths.d.ts +9 -0
  49. package/dist/paths.js +18 -0
  50. package/dist/paths.js.map +1 -0
  51. package/dist/routes/attach.d.ts +13 -0
  52. package/dist/routes/attach.js +49 -0
  53. package/dist/routes/attach.js.map +1 -0
  54. package/dist/server.d.ts +1 -0
  55. package/dist/server.js +51 -0
  56. package/dist/server.js.map +1 -0
  57. package/dist/ssh.d.ts +2 -0
  58. package/dist/ssh.js +84 -0
  59. package/dist/ssh.js.map +1 -0
  60. package/dist/store.d.ts +65 -0
  61. package/dist/store.js +337 -0
  62. package/dist/store.js.map +1 -0
  63. package/dist/targets/docker.d.ts +7 -0
  64. package/dist/targets/docker.js +14 -0
  65. package/dist/targets/docker.js.map +1 -0
  66. package/dist/targets/index.d.ts +4 -0
  67. package/dist/targets/index.js +33 -0
  68. package/dist/targets/index.js.map +1 -0
  69. package/dist/targets/ssh.d.ts +25 -0
  70. package/dist/targets/ssh.js +121 -0
  71. package/dist/targets/ssh.js.map +1 -0
  72. package/dist/targets/types.d.ts +15 -0
  73. package/dist/targets/types.js +2 -0
  74. package/dist/targets/types.js.map +1 -0
  75. package/dist/telegram-auth.d.ts +7 -0
  76. package/dist/telegram-auth.js +41 -0
  77. package/dist/telegram-auth.js.map +1 -0
  78. package/dist/vault.d.ts +4 -0
  79. package/dist/vault.js +57 -0
  80. package/dist/vault.js.map +1 -0
  81. package/miniapp/index.html +597 -0
  82. package/miniapp/ssh.html +251 -0
  83. package/package.json +44 -0
  84. package/scripts/fix-pty.cjs +15 -0
@@ -0,0 +1,49 @@
1
+ export async function wireAttach(socket, query, target, sessionId, log) {
2
+ const cols = query.cols ? parseInt(query.cols, 10) : 80;
3
+ const rows = query.rows ? parseInt(query.rows, 10) : 24;
4
+ log(`attach start session=${sessionId} cols=${cols} rows=${rows}`);
5
+ let pty;
6
+ try {
7
+ pty = await target.openPty(["tmux", "attach", "-d", "-t", sessionId], cols, rows);
8
+ }
9
+ catch (e) {
10
+ log("exec failed", e);
11
+ socket.close(4500, "exec failed");
12
+ return;
13
+ }
14
+ const { stream, resize } = pty;
15
+ stream.on("data", (chunk) => {
16
+ if (socket.readyState === socket.OPEN)
17
+ socket.send(chunk);
18
+ });
19
+ stream.on("end", () => {
20
+ log("stream ended");
21
+ if (socket.readyState === socket.OPEN)
22
+ socket.close(1000, "exec ended");
23
+ });
24
+ stream.on("error", (e) => {
25
+ log("stream error", e);
26
+ if (socket.readyState === socket.OPEN)
27
+ socket.close(1011, "stream error");
28
+ });
29
+ socket.on("message", async (data) => {
30
+ const raw = typeof data === "string" ? data : data.toString("utf8");
31
+ if (raw.charCodeAt(0) === 0x7b) {
32
+ try {
33
+ const msg = JSON.parse(raw);
34
+ if (msg.type === "resize" && typeof msg.cols === "number" && typeof msg.rows === "number") {
35
+ await resize(msg.cols, msg.rows);
36
+ return;
37
+ }
38
+ }
39
+ catch { }
40
+ }
41
+ if (stream.writable) {
42
+ stream.write(typeof data === "string" ? Buffer.from(data) : data);
43
+ }
44
+ });
45
+ socket.on("close", () => {
46
+ stream.destroy();
47
+ });
48
+ }
49
+ //# sourceMappingURL=attach.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attach.js","sourceRoot":"","sources":["../../src/routes/attach.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function startServer(): Promise<void>;
package/dist/server.js ADDED
@@ -0,0 +1,51 @@
1
+ import Fastify from "fastify";
2
+ import fastifyWebsocket from "@fastify/websocket";
3
+ import fastifyStatic from "@fastify/static";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { config, botConfig } from "./config.js";
7
+ import { getDb } from "./db.js";
8
+ import { ensureHome } from "./paths.js";
9
+ import { ensurePairpodNetwork } from "./network.js";
10
+ import { errorHandler } from "./errors.js";
11
+ import { attachRoutes } from "./attach.js";
12
+ import { notifyRoutes } from "./notify.js";
13
+ import { sshRoutes } from "./ssh.js";
14
+ import { pruneLocalSessions } from "./store.js";
15
+ import { startBot } from "./bot.js";
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+ export async function startServer() {
18
+ ensureHome();
19
+ const app = Fastify({
20
+ logger: {
21
+ level: config.logLevel,
22
+ transport: process.env.NODE_ENV !== "production"
23
+ ? { target: "pino-pretty", options: { colorize: true } }
24
+ : undefined,
25
+ },
26
+ });
27
+ app.setErrorHandler(errorHandler);
28
+ await app.register(fastifyWebsocket);
29
+ await app.register(attachRoutes);
30
+ await app.register(notifyRoutes);
31
+ await app.register(sshRoutes);
32
+ const miniappDir = path.resolve(__dirname, "../miniapp");
33
+ await app.register(fastifyStatic, {
34
+ root: miniappDir,
35
+ prefix: "/",
36
+ wildcard: true,
37
+ decorateReply: false,
38
+ });
39
+ getDb();
40
+ pruneLocalSessions();
41
+ // Docker is optional: only needed for Docker pods. Don't block startup on it.
42
+ try {
43
+ await ensurePairpodNetwork();
44
+ }
45
+ catch (e) {
46
+ app.log.warn(`Docker unavailable — Docker pods disabled (${e.message})`);
47
+ }
48
+ await app.listen({ port: botConfig.port, host: "0.0.0.0" });
49
+ startBot();
50
+ }
51
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,gBAAgB,MAAM,oBAAoB,CAAC;AAClD,OAAO,aAAa,MAAM,iBAAiB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,UAAU,EAAE,CAAC;IAEb,MAAM,GAAG,GAAG,OAAO,CAAC;QAClB,MAAM,EAAE;YACN,KAAK,EAAE,MAAM,CAAC,QAAQ;YACtB,SAAS,EACP,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;gBACnC,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;gBACxD,CAAC,CAAC,SAAS;SAChB;KACF,CAAC,CAAC;IAEH,GAAG,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;IAElC,MAAM,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IACrC,MAAM,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IACjC,MAAM,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IACjC,MAAM,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAE9B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IACzD,MAAM,GAAG,CAAC,QAAQ,CAAC,aAAa,EAAE;QAChC,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,GAAG;QACX,QAAQ,EAAE,IAAI;QACd,aAAa,EAAE,KAAK;KACrB,CAAC,CAAC;IAEH,KAAK,EAAE,CAAC;IACR,kBAAkB,EAAE,CAAC;IACrB,8EAA8E;IAC9E,IAAI,CAAC;QACH,MAAM,oBAAoB,EAAE,CAAC;IAC/B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,8CAA+C,CAAW,CAAC,OAAO,GAAG,CAAC,CAAC;IACtF,CAAC;IAED,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAC5D,QAAQ,EAAE,CAAC;AACb,CAAC"}
package/dist/ssh.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ export declare function sshRoutes(app: FastifyInstance): Promise<void>;
package/dist/ssh.js ADDED
@@ -0,0 +1,84 @@
1
+ import { validateInitData } from "./telegram-auth.js";
2
+ import { isAllowed } from "./access.js";
3
+ import { botConfig } from "./config.js";
4
+ import { createSshPod, updateSshPod, getSshEndpoint } from "./store.js";
5
+ import { vaultEnabled } from "./vault.js";
6
+ function authed(tgData) {
7
+ const auth = validateInitData(tgData ?? "", botConfig.token, botConfig.authMaxAgeSec);
8
+ return auth.ok && isAllowed(auth.userId, auth.username);
9
+ }
10
+ function parseFields(b) {
11
+ const host = (b.host ?? "").trim();
12
+ const username = (b.username ?? "").trim();
13
+ const method = (b.auth ?? "");
14
+ if (!host || !username)
15
+ return { error: "host and username are required" };
16
+ if (method !== "agent" && method !== "key_path" && method !== "vault") {
17
+ return { error: "invalid auth method" };
18
+ }
19
+ if (method === "vault" && b.privateKey && !vaultEnabled()) {
20
+ return { error: "vault disabled (PAIRPOD_VAULT_KEY unset)" };
21
+ }
22
+ return {
23
+ fields: {
24
+ label: (b.label ?? "").trim() || undefined,
25
+ host,
26
+ port: Number(b.port) || 22,
27
+ username,
28
+ remoteCwd: (b.remoteCwd ?? "").trim() || "~",
29
+ auth: method,
30
+ keyPath: method === "key_path" ? (b.keyPath ?? "").trim() : undefined,
31
+ privateKey: method === "vault" ? b.privateKey || undefined : undefined,
32
+ passphrase: method === "vault" || method === "key_path" ? (b.passphrase ?? "") || undefined : undefined,
33
+ },
34
+ };
35
+ }
36
+ export async function sshRoutes(app) {
37
+ app.get("/ssh/config", async (_req, reply) => {
38
+ return reply.send({ vaultEnabled: vaultEnabled() });
39
+ });
40
+ app.get("/ssh/endpoints/:id", async (req, reply) => {
41
+ const q = req.query;
42
+ if (!authed(q.tgData))
43
+ return reply.status(403).send({ ok: false, error: "unauthorized" });
44
+ const ep = getSshEndpoint(req.params.id);
45
+ if (!ep)
46
+ return reply.status(404).send({ ok: false, error: "not found" });
47
+ return reply.send({ ok: true, endpoint: ep });
48
+ });
49
+ app.post("/ssh/endpoints", async (req, reply) => {
50
+ const b = (req.body || {});
51
+ if (!authed(b.tgData))
52
+ return reply.status(403).send({ ok: false, error: "unauthorized" });
53
+ const parsed = parseFields(b);
54
+ if ("error" in parsed)
55
+ return reply.status(400).send({ ok: false, error: parsed.error });
56
+ if (parsed.fields.auth === "vault" && !parsed.fields.privateKey) {
57
+ return reply.status(400).send({ ok: false, error: "paste a private key" });
58
+ }
59
+ try {
60
+ const id = await createSshPod(parsed.fields);
61
+ return reply.send({ ok: true, id });
62
+ }
63
+ catch (e) {
64
+ return reply.status(400).send({ ok: false, error: e.message });
65
+ }
66
+ });
67
+ app.put("/ssh/endpoints/:id", async (req, reply) => {
68
+ const b = (req.body || {});
69
+ if (!authed(b.tgData))
70
+ return reply.status(403).send({ ok: false, error: "unauthorized" });
71
+ const parsed = parseFields(b);
72
+ if ("error" in parsed)
73
+ return reply.status(400).send({ ok: false, error: parsed.error });
74
+ try {
75
+ const id = req.params.id;
76
+ await updateSshPod(id, parsed.fields);
77
+ return reply.send({ ok: true, id });
78
+ }
79
+ catch (e) {
80
+ return reply.status(400).send({ ok: false, error: e.message });
81
+ }
82
+ });
83
+ }
84
+ //# sourceMappingURL=ssh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh.js","sourceRoot":"","sources":["../src/ssh.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import { type SshAuth } from "./targets/ssh.js";
2
+ export type SessionMode = "danger" | "regular" | "terminal";
3
+ export interface PodRow {
4
+ id: string;
5
+ container_id: string;
6
+ kind: string;
7
+ label: string | null;
8
+ status: string;
9
+ created_at: string;
10
+ ssh_host: string | null;
11
+ ssh_port: number | null;
12
+ ssh_user: string | null;
13
+ ssh_auth: string | null;
14
+ ssh_key_path: string | null;
15
+ ssh_vault_ref: string | null;
16
+ host_fingerprint: string | null;
17
+ remote_cwd: string | null;
18
+ }
19
+ export interface SessionRow {
20
+ id: string;
21
+ pod_id: string;
22
+ label: string | null;
23
+ status: string;
24
+ created_at: string;
25
+ }
26
+ export interface PodView extends PodRow {
27
+ sessions: SessionRow[];
28
+ }
29
+ export declare function getPodRow(id: string): PodRow | undefined;
30
+ export declare function listPods(): PodView[];
31
+ export declare function setPodLabel(id: string, label: string | null): void;
32
+ export declare function setSessionLabel(podId: string, sessionId: string, label: string | null): void;
33
+ export declare function getPod(id: string): PodView | undefined;
34
+ export declare function createPod(): Promise<string>;
35
+ export declare function createLocalPod(cwd?: string): string;
36
+ export declare function pruneLocalSessions(): void;
37
+ export interface SshFields {
38
+ label?: string;
39
+ host: string;
40
+ port: number;
41
+ username: string;
42
+ remoteCwd: string;
43
+ auth: SshAuth;
44
+ keyPath?: string;
45
+ privateKey?: string;
46
+ passphrase?: string;
47
+ }
48
+ export interface SshEndpointView {
49
+ id: string;
50
+ label: string | null;
51
+ host: string | null;
52
+ port: number | null;
53
+ username: string | null;
54
+ remoteCwd: string | null;
55
+ auth: string | null;
56
+ keyPath: string | null;
57
+ hasKey: boolean;
58
+ }
59
+ export declare function getSshEndpoint(id: string): SshEndpointView | undefined;
60
+ export declare function createSshPod(opts: SshFields): Promise<string>;
61
+ export declare function updateSshPod(id: string, opts: SshFields): Promise<void>;
62
+ export declare function testPod(id: string): Promise<string>;
63
+ export declare function deletePod(id: string): Promise<void>;
64
+ export declare function createSession(podId: string, mode: SessionMode): Promise<string>;
65
+ export declare function deleteSession(podId: string, sessionId: string): Promise<void>;
package/dist/store.js ADDED
@@ -0,0 +1,337 @@
1
+ import path from "node:path";
2
+ import os from "node:os";
3
+ import fs from "node:fs/promises";
4
+ import { getDb } from "./db.js";
5
+ import { config } from "./config.js";
6
+ import { agents } from "./agents.js";
7
+ import { nextPodId, nextSessionId, nextTerminalId } from "./naming.js";
8
+ import { createContainer, removeContainer } from "./docker.js";
9
+ import { botConfig } from "./config.js";
10
+ import { targetForPod, disposeTarget } from "./targets/index.js";
11
+ import { SshTarget } from "./targets/ssh.js";
12
+ import { vaultEnabled, vaultPut, vaultRemove } from "./vault.js";
13
+ import { createLocalSession, killLocalSession } from "./local/sessions.js";
14
+ const POD_COLS = "id, container_id, kind, label, status, created_at, ssh_host, ssh_port, ssh_user, ssh_auth, " +
15
+ "ssh_key_path, ssh_vault_ref, host_fingerprint, remote_cwd";
16
+ // Node script (runs in the container) for Claude's Notification hook: reads the
17
+ // hook payload on stdin, peeks at the transcript for the pending tool_use to
18
+ // summarize what Claude is about to do, and POSTs it to bot's /notify.
19
+ const NOTIFY_JS = `let d="";
20
+ process.stdin.on("data",c=>d+=c);
21
+ process.stdin.on("end",()=>{
22
+ let j={};try{j=JSON.parse(d)}catch{}
23
+ const url=process.env.PAIRPOD_NOTIFY_URL;if(!url)return;
24
+ let detail="";
25
+ try{
26
+ const fs=require("fs");
27
+ const lines=fs.readFileSync(j.transcript_path,"utf8").trim().split("\\n");
28
+ for(let i=lines.length-1;i>=0&&i>lines.length-80;i--){
29
+ let m;try{m=JSON.parse(lines[i])}catch(e){continue}
30
+ const c=m&&m.message&&m.message.content;
31
+ if(!Array.isArray(c))continue;
32
+ const tu=c.find(b=>b&&b.type==="tool_use");
33
+ if(tu){
34
+ const ti=tu.input||{};
35
+ detail=tu.name||"tool";
36
+ if(ti.file_path)detail+=": "+ti.file_path;
37
+ else if(ti.command)detail+=": "+String(ti.command).slice(0,200);
38
+ else if(ti.path)detail+=": "+ti.path;
39
+ else if(ti.url)detail+=": "+ti.url;
40
+ else if(ti.pattern)detail+=": "+ti.pattern;
41
+ break;
42
+ }
43
+ }
44
+ }catch(e){}
45
+ const body=JSON.stringify({
46
+ pod:process.env.PAIRPOD_POD||"",
47
+ session:process.env.PAIRPOD_SESSION||"",
48
+ token:process.env.PAIRPOD_TOKEN||"",
49
+ message:j.message||"",
50
+ detail
51
+ });
52
+ const u=new URL(url);const https=u.protocol==="https:";const lib=require(https?"https":"http");
53
+ const req=lib.request({hostname:u.hostname,port:u.port||(https?443:80),path:u.pathname,method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(body)},timeout:5000});
54
+ req.on("error",()=>{});req.on("timeout",()=>req.destroy());
55
+ req.write(body);req.end();
56
+ });`;
57
+ const MERGE_SETTINGS_JS = `const fs=require("fs"),os=require("os"),path=require("path");
58
+ const dir=path.join(os.homedir(),".claude");
59
+ fs.mkdirSync(dir,{recursive:true});
60
+ const f=path.join(dir,"settings.json");
61
+ let s={};try{s=JSON.parse(fs.readFileSync(f,"utf8"))}catch(e){}
62
+ s.hooks=s.hooks||{};
63
+ s.hooks.Notification=[{hooks:[{type:"command",command:"node "+path.join(dir,"pairpod-notify.js")}]}];
64
+ fs.writeFileSync(f,JSON.stringify(s,null,2));`;
65
+ async function setupNotifyHooks(target) {
66
+ await target.exec([
67
+ "sh", "-c",
68
+ `mkdir -p "$HOME/.claude" && cat > "$HOME/.claude/pairpod-notify.js" <<'PP_EOF'\n${NOTIFY_JS}\nPP_EOF`,
69
+ ]);
70
+ await target.exec(["node", "-e", MERGE_SETTINGS_JS]);
71
+ }
72
+ export function getPodRow(id) {
73
+ return getDb()
74
+ .prepare(`SELECT ${POD_COLS} FROM pods WHERE id = ?`)
75
+ .get(id);
76
+ }
77
+ export function listPods() {
78
+ const db = getDb();
79
+ const pods = db
80
+ .prepare(`SELECT ${POD_COLS} FROM pods ORDER BY created_at ASC`)
81
+ .all();
82
+ const sessions = db
83
+ .prepare("SELECT id, pod_id, label, status, created_at FROM sessions ORDER BY created_at ASC")
84
+ .all();
85
+ const byPod = new Map();
86
+ for (const s of sessions) {
87
+ const list = byPod.get(s.pod_id) ?? [];
88
+ list.push(s);
89
+ byPod.set(s.pod_id, list);
90
+ }
91
+ return pods.map((p) => ({ ...p, sessions: byPod.get(p.id) ?? [] }));
92
+ }
93
+ export function setPodLabel(id, label) {
94
+ getDb().prepare("UPDATE pods SET label = ? WHERE id = ?").run(label?.trim() || null, id);
95
+ }
96
+ export function setSessionLabel(podId, sessionId, label) {
97
+ getDb()
98
+ .prepare("UPDATE sessions SET label = ? WHERE pod_id = ? AND id = ?")
99
+ .run(label?.trim() || null, podId, sessionId);
100
+ }
101
+ export function getPod(id) {
102
+ return listPods().find((p) => p.id === id);
103
+ }
104
+ export async function createPod() {
105
+ const id = nextPodId();
106
+ const workspacePath = path.join(config.workspacesRoot, id);
107
+ await fs.mkdir(workspacePath, { recursive: true });
108
+ const containerId = await createContainer(id, workspacePath, agents.claude);
109
+ getDb()
110
+ .prepare("INSERT INTO pods (id, container_id, agent, workspace_path, status, created_at) VALUES (?, ?, ?, ?, ?, ?)")
111
+ .run(id, containerId, "claude", workspacePath, "running", new Date().toISOString());
112
+ return id;
113
+ }
114
+ export function createLocalPod(cwd) {
115
+ const id = nextPodId();
116
+ getDb()
117
+ .prepare(`INSERT INTO pods (id, container_id, agent, workspace_path, status, created_at, kind, remote_cwd)
118
+ VALUES (?, '', 'shell', '', 'running', ?, 'local', ?)`)
119
+ .run(id, new Date().toISOString(), cwd?.trim() || os.homedir());
120
+ return id;
121
+ }
122
+ // Host PTYs live only in this process; their DB rows are stale after a restart.
123
+ export function pruneLocalSessions() {
124
+ getDb()
125
+ .prepare("DELETE FROM sessions WHERE pod_id IN (SELECT id FROM pods WHERE kind = 'local')")
126
+ .run();
127
+ }
128
+ export function getSshEndpoint(id) {
129
+ const p = getPodRow(id);
130
+ if (!p || p.kind !== "ssh")
131
+ return undefined;
132
+ return {
133
+ id: p.id,
134
+ label: p.label,
135
+ host: p.ssh_host,
136
+ port: p.ssh_port,
137
+ username: p.ssh_user,
138
+ remoteCwd: p.remote_cwd,
139
+ auth: p.ssh_auth,
140
+ keyPath: p.ssh_key_path,
141
+ hasKey: Boolean(p.ssh_vault_ref),
142
+ };
143
+ }
144
+ // Connect once with the given settings to validate reachability + remote tmux, and
145
+ // capture the host-key fingerprint (TOFU). Rolls back a freshly-created vault entry on failure.
146
+ async function probeSsh(fields, vaultRef, rollbackVaultRef) {
147
+ let fingerprint;
148
+ const probe = new SshTarget({
149
+ host: fields.host,
150
+ port: fields.port,
151
+ username: fields.username,
152
+ auth: fields.auth,
153
+ keyPath: fields.keyPath,
154
+ vaultRef: vaultRef ?? undefined,
155
+ onFingerprint: (fp) => {
156
+ fingerprint = fp;
157
+ },
158
+ });
159
+ try {
160
+ const res = await probe.exec(["tmux", "-V"]);
161
+ if (res.exitCode !== 0) {
162
+ throw new Error(`tmux not available on remote: ${(res.stderr || res.stdout).trim()}`);
163
+ }
164
+ }
165
+ catch (e) {
166
+ await probe.dispose();
167
+ if (rollbackVaultRef)
168
+ vaultRemove(rollbackVaultRef);
169
+ throw e;
170
+ }
171
+ await probe.dispose();
172
+ return fingerprint;
173
+ }
174
+ // Decide what the vault entry should be for the desired auth state. The entry holds
175
+ // {privateKey?, passphrase?}: a pasted key (vault auth) and/or a passphrase for an
176
+ // encrypted key (vault or key_path). agent auth stores nothing. On edit, a blank
177
+ // secret keeps the existing entry; a new one rotates (old ref returned as `stale`).
178
+ function planVaultEntry(opts, existing) {
179
+ const prev = existing?.ssh_vault_ref ?? null;
180
+ if (opts.auth === "vault") {
181
+ if (opts.privateKey) {
182
+ if (!vaultEnabled())
183
+ throw new Error("vault disabled (set PAIRPOD_VAULT_KEY to paste keys)");
184
+ const ref = vaultPut(JSON.stringify({ privateKey: opts.privateKey, passphrase: opts.passphrase }));
185
+ return { ref, created: ref, stale: prev };
186
+ }
187
+ if (!prev)
188
+ throw new Error("paste a private key");
189
+ return { ref: prev, created: null, stale: null };
190
+ }
191
+ if (opts.auth === "key_path" && opts.passphrase) {
192
+ if (!vaultEnabled()) {
193
+ throw new Error("vault disabled (set PAIRPOD_VAULT_KEY to store a key passphrase)");
194
+ }
195
+ const ref = vaultPut(JSON.stringify({ passphrase: opts.passphrase }));
196
+ return { ref, created: ref, stale: prev };
197
+ }
198
+ if (opts.auth === "key_path")
199
+ return { ref: prev, created: null, stale: null };
200
+ // agent: no stored secret
201
+ return { ref: null, created: null, stale: prev };
202
+ }
203
+ export async function createSshPod(opts) {
204
+ const id = nextPodId();
205
+ const plan = planVaultEntry(opts, undefined);
206
+ const vaultRef = plan.ref;
207
+ const fingerprint = await probeSsh(opts, vaultRef, plan.created);
208
+ getDb()
209
+ .prepare(`INSERT INTO pods
210
+ (id, container_id, agent, workspace_path, status, created_at, kind, label,
211
+ ssh_host, ssh_port, ssh_user, ssh_auth, ssh_key_path, ssh_vault_ref, host_fingerprint, remote_cwd)
212
+ VALUES (?, '', 'shell', '', 'running', ?, 'ssh', ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
213
+ .run(id, new Date().toISOString(), opts.label?.trim() || null, opts.host, opts.port, opts.username, opts.auth, opts.keyPath ?? null, vaultRef, fingerprint ?? null, opts.remoteCwd || "~");
214
+ return id;
215
+ }
216
+ export async function updateSshPod(id, opts) {
217
+ const existing = getPodRow(id);
218
+ if (!existing || existing.kind !== "ssh")
219
+ throw new Error("SSH endpoint not found");
220
+ const plan = planVaultEntry(opts, existing);
221
+ const vaultRef = plan.ref;
222
+ // host-key TOFU re-pins to whatever the (possibly changed) host now presents.
223
+ const fingerprint = await probeSsh(opts, vaultRef, plan.created);
224
+ getDb()
225
+ .prepare(`UPDATE pods SET
226
+ label=?, ssh_host=?, ssh_port=?, ssh_user=?, ssh_auth=?,
227
+ ssh_key_path=?, ssh_vault_ref=?, host_fingerprint=?, remote_cwd=?
228
+ WHERE id=?`)
229
+ .run(opts.label?.trim() || null, opts.host, opts.port, opts.username, opts.auth, opts.auth === "key_path" ? opts.keyPath ?? null : null, vaultRef, fingerprint ?? null, opts.remoteCwd || "~", id);
230
+ if (plan.stale && plan.stale !== vaultRef)
231
+ vaultRemove(plan.stale);
232
+ await disposeTarget(id);
233
+ }
234
+ export async function testPod(id) {
235
+ const pod = getPodRow(id);
236
+ if (!pod)
237
+ throw new Error("pod not found");
238
+ const res = await targetForPod(pod).exec(["tmux", "-V"]);
239
+ if (res.exitCode !== 0)
240
+ throw new Error((res.stderr || res.stdout).trim() || `exit ${res.exitCode}`);
241
+ return res.stdout.trim() || "ok";
242
+ }
243
+ export async function deletePod(id) {
244
+ const db = getDb();
245
+ const pod = getPodRow(id);
246
+ if (!pod)
247
+ return;
248
+ if (pod.kind === "ssh") {
249
+ await disposeTarget(id);
250
+ if (pod.ssh_vault_ref)
251
+ vaultRemove(pod.ssh_vault_ref);
252
+ }
253
+ else if (pod.kind === "local") {
254
+ const rows = db.prepare("SELECT id FROM sessions WHERE pod_id = ?").all(id);
255
+ for (const r of rows)
256
+ killLocalSession(r.id);
257
+ }
258
+ else {
259
+ await removeContainer(pod.container_id);
260
+ }
261
+ db.prepare("DELETE FROM tg_session_state WHERE pod_id = ?").run(id);
262
+ db.prepare("DELETE FROM pods WHERE id = ?").run(id);
263
+ }
264
+ export async function createSession(podId, mode) {
265
+ const db = getDb();
266
+ const pod = getPodRow(podId);
267
+ if (!pod)
268
+ throw new Error(`Pod ${podId} not found`);
269
+ if (pod.kind === "local") {
270
+ if (mode !== "terminal")
271
+ throw new Error("Host pods support terminal sessions only");
272
+ const sid = nextTerminalId(podId);
273
+ db.prepare("INSERT INTO sessions (id, pod_id, status, created_at) VALUES (?, ?, ?, ?)").run(sid, podId, "running", new Date().toISOString());
274
+ createLocalSession(sid, pod.remote_cwd || os.homedir());
275
+ return sid;
276
+ }
277
+ const target = targetForPod(pod);
278
+ const cwd = pod.kind === "ssh" ? pod.remote_cwd || "~" : "/workspace";
279
+ // Claude on a remote needs a publicly reachable bot for the permission-notify hook
280
+ // and claude itself installed on that host. Resolve its absolute path from a login shell
281
+ // so the launch doesn't depend on tmux's (non-login) PATH.
282
+ let claudeBin = "claude";
283
+ if (mode !== "terminal" && pod.kind === "ssh") {
284
+ if (!botConfig.publicUrl) {
285
+ throw new Error("set MINIAPP_URL (or PAIRPOD_PUBLIC_URL) to run Claude on SSH — needed for permission notifications");
286
+ }
287
+ const probe = await target.exec(["sh", "-lc", "command -v claude"]);
288
+ if (probe.exitCode !== 0) {
289
+ throw new Error("claude not found on the remote host (install it and run `claude` once to log in)");
290
+ }
291
+ claudeBin = probe.stdout.trim() || "claude";
292
+ }
293
+ const sid = mode === "terminal" ? nextTerminalId(podId) : nextSessionId(podId);
294
+ db.prepare("INSERT INTO sessions (id, pod_id, status, created_at) VALUES (?, ?, ?, ?)").run(sid, podId, "running", new Date().toISOString());
295
+ // mouse on → wheel/swipe scrolls tmux's own scrollback (copy-mode) for shell
296
+ // panes and forwards to mouse-aware apps like Claude. Applied to fresh servers
297
+ // via the config and to an already-running server via the explicit set below.
298
+ await target.exec([
299
+ "sh", "-c",
300
+ "printf 'set -g mouse on\\nset -g history-limit 50000\\n' > \"$HOME/.tmux.conf\"",
301
+ ]);
302
+ const args = ["tmux", "new-session", "-d", "-s", sid, "-c", cwd];
303
+ if (mode !== "terminal") {
304
+ await setupNotifyHooks(target);
305
+ const notifyUrl = pod.kind === "ssh"
306
+ ? `${botConfig.publicUrl}/notify`
307
+ : `http://host.docker.internal:${botConfig.port}/notify`;
308
+ const env = `PAIRPOD_POD=${podId} PAIRPOD_SESSION=${sid} ` +
309
+ `PAIRPOD_NOTIFY_URL=${notifyUrl} PAIRPOD_TOKEN=${botConfig.hookToken}`;
310
+ const claude = mode === "danger" ? `${claudeBin} --dangerously-skip-permissions` : claudeBin;
311
+ // On SSH keep an interactive shell after claude exits so a launch failure or login prompt
312
+ // stays visible in the pane instead of the tmux session vanishing (which shows as 4004).
313
+ args.push(pod.kind === "ssh" ? `${env} ${claude}; exec sh -i` : `${env} ${claude}`);
314
+ }
315
+ // terminal: no command → tmux launches the host's default shell
316
+ await target.exec(args);
317
+ await target.exec(["tmux", "set", "-g", "mouse", "on"]);
318
+ return sid;
319
+ }
320
+ export async function deleteSession(podId, sessionId) {
321
+ const db = getDb();
322
+ const pod = getPodRow(podId);
323
+ if (pod) {
324
+ if (pod.kind === "local") {
325
+ killLocalSession(sessionId);
326
+ }
327
+ else {
328
+ try {
329
+ await targetForPod(pod).exec(["tmux", "kill-session", "-t", sessionId]);
330
+ }
331
+ catch { }
332
+ }
333
+ }
334
+ db.prepare("DELETE FROM sessions WHERE pod_id = ? AND id = ?").run(podId, sessionId);
335
+ db.prepare("DELETE FROM tg_session_state WHERE pod_id = ? AND session_id = ?").run(podId, sessionId);
336
+ }
337
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE/D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,SAAS,EAAgB,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAI3E,MAAM,QAAQ,GACZ,6FAA6F;IAC7F,2DAA2D,CAAC;AAE9D,gFAAgF;AAChF,6EAA6E;AAC7E,uEAAuE;AACvE,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAqCd,CAAC;AAEL,MAAM,iBAAiB,GAAG;;;;;;;8CAOoB,CAAC;AAE/C,KAAK,UAAU,gBAAgB,CAAC,MAAiB;IAC/C,MAAM,MAAM,CAAC,IAAI,CAAC;QAChB,IAAI,EAAE,IAAI;QACV,mFAAmF,SAAS,UAAU;KACvG,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC;AACvD,CAAC;AA+BD,MAAM,UAAU,SAAS,CAAC,EAAU;IAClC,OAAO,KAAK,EAAE;SACX,OAAO,CAAC,UAAU,QAAQ,yBAAyB,CAAC;SACpD,GAAG,CAAC,EAAE,CAAuB,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CAAC,UAAU,QAAQ,oCAAoC,CAAC;SAC/D,GAAG,EAAc,CAAC;IACrB,MAAM,QAAQ,GAAG,EAAE;SAChB,OAAO,CAAC,oFAAoF,CAAC;SAC7F,GAAG,EAAkB,CAAC;IAEzB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC9C,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACb,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAU,EAAE,KAAoB;IAC1D,KAAK,EAAE,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;AAC3F,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,SAAiB,EAAE,KAAoB;IACpF,KAAK,EAAE;SACJ,OAAO,CAAC,2DAA2D,CAAC;SACpE,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,EAAU;IAC/B,OAAO,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IACvB,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IAC3D,MAAM,EAAE,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEnD,MAAM,WAAW,GAAG,MAAM,eAAe,CAAC,EAAE,EAAE,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5E,KAAK,EAAE;SACJ,OAAO,CACN,0GAA0G,CAC3G;SACA,GAAG,CAAC,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IACtF,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IACvB,KAAK,EAAE;SACJ,OAAO,CACN;6DACuD,CACxD;SACA,GAAG,CAAC,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IAClE,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,kBAAkB;IAChC,KAAK,EAAE;SACJ,OAAO,CAAC,iFAAiF,CAAC;SAC1F,GAAG,EAAE,CAAC;AACX,CAAC;AA0BD,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,MAAM,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IACxB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7C,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,IAAI,EAAE,CAAC,CAAC,QAAQ;QAChB,IAAI,EAAE,CAAC,CAAC,QAAQ;QAChB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,SAAS,EAAE,CAAC,CAAC,UAAU;QACvB,IAAI,EAAE,CAAC,CAAC,QAAQ;QAChB,OAAO,EAAE,CAAC,CAAC,YAAY;QACvB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC;KACjC,CAAC;AACJ,CAAC;AAED,mFAAmF;AACnF,gGAAgG;AAChG,KAAK,UAAU,QAAQ,CACrB,MAAiB,EACjB,QAAuB,EACvB,gBAA+B;IAE/B,IAAI,WAA+B,CAAC;IACpC,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC;QAC1B,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,QAAQ,EAAE,QAAQ,IAAI,SAAS;QAC/B,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE;YACpB,WAAW,GAAG,EAAE,CAAC;QACnB,CAAC;KACF,CAAC,CAAC;IACH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;QAC7C,IAAI,GAAG,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACtB,IAAI,gBAAgB;YAAE,WAAW,CAAC,gBAAgB,CAAC,CAAC;QACpD,MAAM,CAAC,CAAC;IACV,CAAC;IACD,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;IACtB,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,oFAAoF;AACpF,mFAAmF;AACnF,iFAAiF;AACjF,oFAAoF;AACpF,SAAS,cAAc,CACrB,IAAe,EACf,QAA4B;IAE5B,MAAM,IAAI,GAAG,QAAQ,EAAE,aAAa,IAAI,IAAI,CAAC;IAE7C,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,CAAC,YAAY,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;YAC7F,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;YACnG,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAC5C,CAAC;QACD,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAClD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACnD,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QAChD,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;QACtF,CAAC;QACD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACtE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC5C,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAE/E,0BAA0B;IAC1B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACnD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAe;IAChD,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IAEvB,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC;IAC1B,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAEjE,KAAK,EAAE;SACJ,OAAO,CACN;;;mFAG6E,CAC9E;SACA,GAAG,CACF,EAAE,EACF,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EACxB,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,IAAI,EAC1B,IAAI,CAAC,IAAI,EACT,IAAI,CAAC,IAAI,EACT,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,IAAI,EACT,IAAI,CAAC,OAAO,IAAI,IAAI,EACpB,QAAQ,EACR,WAAW,IAAI,IAAI,EACnB,IAAI,CAAC,SAAS,IAAI,GAAG,CACtB,CAAC;IACJ,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EAAU,EAAE,IAAe;IAC5D,MAAM,QAAQ,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IAC/B,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAEpF,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC;IAE1B,8EAA8E;IAC9E,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAEjE,KAAK,EAAE;SACJ,OAAO,CACN;;;kBAGY,CACb;SACA,GAAG,CACF,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,IAAI,EAC1B,IAAI,CAAC,IAAI,EACT,IAAI,CAAC,IAAI,EACT,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,IAAI,EACT,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,EACtD,QAAQ,EACR,WAAW,IAAI,IAAI,EACnB,IAAI,CAAC,SAAS,IAAI,GAAG,EACrB,EAAE,CACH,CAAC;IAEJ,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ;QAAE,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnE,MAAM,aAAa,CAAC,EAAE,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,EAAU;IACtC,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IAC1B,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;IACzD,IAAI,GAAG,CAAC,QAAQ,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,QAAQ,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrG,OAAO,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,EAAU;IACxC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IAC1B,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACvB,MAAM,aAAa,CAAC,EAAE,CAAC,CAAC;QACxB,IAAI,GAAG,CAAC,aAAa;YAAE,WAAW,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACxD,CAAC;SAAM,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,0CAA0C,CAAC,CAAC,GAAG,CAAC,EAAE,CAAqB,CAAC;QAChG,KAAK,MAAM,CAAC,IAAI,IAAI;YAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,MAAM,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC1C,CAAC;IACD,EAAE,CAAC,OAAO,CAAC,+CAA+C,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpE,EAAE,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAa,EAAE,IAAiB;IAClE,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,OAAO,KAAK,YAAY,CAAC,CAAC;IAEpD,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QACrF,MAAM,GAAG,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QAClC,EAAE,CAAC,OAAO,CAAC,2EAA2E,CAAC,CAAC,GAAG,CACzF,GAAG,EACH,KAAK,EACL,SAAS,EACT,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CACzB,CAAC;QACF,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QACxD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC;IAEtE,mFAAmF;IACnF,yFAAyF;IACzF,2DAA2D;IAC3D,IAAI,SAAS,GAAG,QAAQ,CAAC;IACzB,IAAI,IAAI,KAAK,UAAU,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC9C,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,oGAAoG,CACrG,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC;QACpE,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,kFAAkF,CAAC,CAAC;QACtG,CAAC;QACD,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,QAAQ,CAAC;IAC9C,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAC/E,EAAE,CAAC,OAAO,CAAC,2EAA2E,CAAC,CAAC,GAAG,CACzF,GAAG,EACH,KAAK,EACL,SAAS,EACT,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CACzB,CAAC;IAEF,6EAA6E;IAC7E,+EAA+E;IAC/E,8EAA8E;IAC9E,MAAM,MAAM,CAAC,IAAI,CAAC;QAChB,IAAI,EAAE,IAAI;QACV,iFAAiF;KAClF,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IACjE,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAC/B,MAAM,SAAS,GACb,GAAG,CAAC,IAAI,KAAK,KAAK;YAChB,CAAC,CAAC,GAAG,SAAS,CAAC,SAAS,SAAS;YACjC,CAAC,CAAC,+BAA+B,SAAS,CAAC,IAAI,SAAS,CAAC;QAC7D,MAAM,GAAG,GACP,eAAe,KAAK,oBAAoB,GAAG,GAAG;YAC9C,sBAAsB,SAAS,kBAAkB,SAAS,CAAC,SAAS,EAAE,CAAC;QACzE,MAAM,MAAM,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,iCAAiC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7F,0FAA0F;QAC1F,yFAAyF;QACzF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,MAAM,cAAc,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,MAAM,EAAE,CAAC,CAAC;IACtF,CAAC;IACD,gEAAgE;IAChE,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;IACxD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAa,EAAE,SAAiB;IAClE,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,GAAG,EAAE,CAAC;QACR,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACzB,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC;gBACH,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;YAC1E,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACZ,CAAC;IACH,CAAC;IACD,EAAE,CAAC,OAAO,CAAC,kDAAkD,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IACrF,EAAE,CAAC,OAAO,CAAC,kEAAkE,CAAC,CAAC,GAAG,CAChF,KAAK,EACL,SAAS,CACV,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { PodTarget, ExecResult, PtySession } from "./types.js";
2
+ export declare class DockerTarget implements PodTarget {
3
+ private readonly ref;
4
+ constructor(ref: string);
5
+ exec(cmd: string[]): Promise<ExecResult>;
6
+ openPty(cmd: string[], cols: number, rows: number): Promise<PtySession>;
7
+ }