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
package/dist/db.js ADDED
@@ -0,0 +1,87 @@
1
+ import Database from "better-sqlite3";
2
+ import { config } from "./config.js";
3
+ let _db = null;
4
+ export function getDb() {
5
+ if (!_db) {
6
+ _db = new Database(config.dbPath);
7
+ _db.pragma("journal_mode = WAL");
8
+ _db.pragma("foreign_keys = ON");
9
+ migrate(_db);
10
+ }
11
+ return _db;
12
+ }
13
+ function migrate(db) {
14
+ // Legacy rename (v1 "projects" -> "pods"). No-ops on already-migrated / fresh DBs.
15
+ try {
16
+ db.exec(`ALTER TABLE projects RENAME TO pods`);
17
+ }
18
+ catch { }
19
+ try {
20
+ db.exec(`ALTER TABLE sessions RENAME COLUMN project_id TO pod_id`);
21
+ }
22
+ catch { }
23
+ try {
24
+ db.exec(`ALTER TABLE tg_session_state RENAME COLUMN project_id TO pod_id`);
25
+ }
26
+ catch { }
27
+ try {
28
+ db.exec(`UPDATE counters SET name = 'pods' WHERE name = 'projects'`);
29
+ }
30
+ catch { }
31
+ db.exec(`
32
+ CREATE TABLE IF NOT EXISTS pods (
33
+ id TEXT PRIMARY KEY,
34
+ container_id TEXT NOT NULL,
35
+ agent TEXT NOT NULL,
36
+ workspace_path TEXT NOT NULL,
37
+ status TEXT NOT NULL,
38
+ created_at TEXT NOT NULL
39
+ );
40
+
41
+ CREATE TABLE IF NOT EXISTS sessions (
42
+ id TEXT NOT NULL,
43
+ pod_id TEXT NOT NULL REFERENCES pods(id) ON DELETE CASCADE,
44
+ status TEXT NOT NULL,
45
+ created_at TEXT NOT NULL,
46
+ PRIMARY KEY (pod_id, id)
47
+ );
48
+ `);
49
+ try {
50
+ db.exec(`ALTER TABLE sessions ADD COLUMN label TEXT`);
51
+ }
52
+ catch { }
53
+ for (const col of [
54
+ `ALTER TABLE pods ADD COLUMN kind TEXT NOT NULL DEFAULT 'docker'`,
55
+ `ALTER TABLE pods ADD COLUMN label TEXT`,
56
+ `ALTER TABLE pods ADD COLUMN ssh_host TEXT`,
57
+ `ALTER TABLE pods ADD COLUMN ssh_port INTEGER`,
58
+ `ALTER TABLE pods ADD COLUMN ssh_user TEXT`,
59
+ `ALTER TABLE pods ADD COLUMN ssh_auth TEXT`,
60
+ `ALTER TABLE pods ADD COLUMN ssh_key_path TEXT`,
61
+ `ALTER TABLE pods ADD COLUMN ssh_vault_ref TEXT`,
62
+ `ALTER TABLE pods ADD COLUMN host_fingerprint TEXT`,
63
+ `ALTER TABLE pods ADD COLUMN remote_cwd TEXT`,
64
+ ]) {
65
+ try {
66
+ db.exec(col);
67
+ }
68
+ catch { }
69
+ }
70
+ db.exec(`
71
+ CREATE TABLE IF NOT EXISTS tg_session_state (
72
+ user_id INTEGER NOT NULL,
73
+ pod_id TEXT NOT NULL,
74
+ session_id TEXT NOT NULL,
75
+ last_seen_uuid TEXT,
76
+ updated_at TEXT NOT NULL,
77
+ PRIMARY KEY (user_id, pod_id, session_id)
78
+ );
79
+ `);
80
+ db.exec(`
81
+ CREATE TABLE IF NOT EXISTS counters (
82
+ name TEXT PRIMARY KEY,
83
+ value INTEGER NOT NULL
84
+ );
85
+ `);
86
+ }
87
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,IAAI,GAAG,GAA6B,IAAI,CAAC;AAEzC,MAAM,UAAU,KAAK;IACnB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClC,GAAG,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACjC,GAAG,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,OAAO,CAAC,EAAqB;IACpC,mFAAmF;IACnF,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAChE,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IACpF,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAC5F,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAEtF,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;GAiBP,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,EAAE,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAEV,KAAK,MAAM,GAAG,IAAI;QAChB,iEAAiE;QACjE,wCAAwC;QACxC,2CAA2C;QAC3C,8CAA8C;QAC9C,2CAA2C;QAC3C,2CAA2C;QAC3C,+CAA+C;QAC/C,gDAAgD;QAChD,mDAAmD;QACnD,6CAA6C;KAC9C,EAAE,CAAC;QACF,IAAI,CAAC;YACH,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IAED,EAAE,CAAC,IAAI,CAAC;;;;;;;;;GASP,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;;;;;GAKP,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,15 @@
1
+ import Dockerode from "dockerode";
2
+ import { Duplex } from "node:stream";
3
+ import type { AgentDef } from "./agents.js";
4
+ export declare function getDocker(): Dockerode;
5
+ export declare function createContainer(podId: string, workspacePath: string, agent: AgentDef): Promise<string>;
6
+ export declare function removeContainer(containerId: string): Promise<void>;
7
+ export declare function attachExec(containerName: string, cmd: string[], cols: number, rows: number): Promise<{
8
+ stream: Duplex;
9
+ resize: (c: number, r: number) => Promise<void>;
10
+ }>;
11
+ export declare function execInContainer(containerId: string, cmd: string[]): Promise<{
12
+ stdout: string;
13
+ stderr: string;
14
+ exitCode: number;
15
+ }>;
package/dist/docker.js ADDED
@@ -0,0 +1,113 @@
1
+ import Dockerode from "dockerode";
2
+ import { Writable, Duplex } from "node:stream";
3
+ import { config } from "./config.js";
4
+ let _docker = null;
5
+ export function getDocker() {
6
+ if (!_docker) {
7
+ _docker = new Dockerode({ socketPath: config.dockerSocket });
8
+ }
9
+ return _docker;
10
+ }
11
+ export async function createContainer(podId, workspacePath, agent) {
12
+ const docker = getDocker();
13
+ const container = await docker.createContainer({
14
+ Image: agent.image,
15
+ name: `pairpod-${podId}`,
16
+ Entrypoint: agent.entrypoint,
17
+ WorkingDir: "/workspace",
18
+ Tty: true,
19
+ OpenStdin: true,
20
+ HostConfig: {
21
+ Binds: [`${workspacePath}:/workspace:rw`],
22
+ ExtraHosts: ["host.docker.internal:host-gateway"],
23
+ },
24
+ NetworkingConfig: {
25
+ EndpointsConfig: {
26
+ [config.pairpodNetwork]: {},
27
+ },
28
+ },
29
+ });
30
+ await container.start();
31
+ return container.id;
32
+ }
33
+ export async function removeContainer(containerId) {
34
+ const docker = getDocker();
35
+ const container = docker.getContainer(containerId);
36
+ await container.remove({ force: true });
37
+ }
38
+ export async function attachExec(containerName, cmd, cols, rows) {
39
+ const docker = getDocker();
40
+ const container = docker.getContainer(containerName);
41
+ const exec = await container.exec({
42
+ Cmd: cmd,
43
+ AttachStdin: true,
44
+ AttachStdout: true,
45
+ AttachStderr: true,
46
+ Tty: true,
47
+ Env: ["TERM=xterm-256color"],
48
+ });
49
+ const raw = await new Promise((resolve, reject) => {
50
+ exec.start({ hijack: true, stdin: true }, (err, s) => {
51
+ if (err)
52
+ return reject(err);
53
+ if (!s)
54
+ return reject(new Error("no stream"));
55
+ resolve(s);
56
+ });
57
+ });
58
+ await exec.resize({ w: cols, h: rows });
59
+ const duplex = new Duplex({
60
+ read() { },
61
+ write(chunk, _enc, cb) { raw.write(chunk); cb(); },
62
+ });
63
+ let buf = Buffer.alloc(0);
64
+ raw.on("data", (chunk) => {
65
+ buf = Buffer.concat([buf, chunk]);
66
+ while (buf.length >= 8) {
67
+ const size = buf.readUInt32BE(4);
68
+ if (buf.length < 8 + size)
69
+ break;
70
+ duplex.push(buf.subarray(8, 8 + size));
71
+ buf = buf.subarray(8 + size);
72
+ }
73
+ });
74
+ raw.on("end", () => duplex.push(null));
75
+ raw.on("error", (e) => duplex.destroy(e));
76
+ return {
77
+ stream: duplex,
78
+ resize: (c, r) => exec.resize({ w: c, h: r }),
79
+ };
80
+ }
81
+ export async function execInContainer(containerId, cmd) {
82
+ const docker = getDocker();
83
+ const container = docker.getContainer(containerId);
84
+ const exec = await container.exec({
85
+ Cmd: cmd,
86
+ AttachStdout: true,
87
+ AttachStderr: true,
88
+ });
89
+ return new Promise((resolve, reject) => {
90
+ exec.start({ hijack: true, stdin: false }, (err, stream) => {
91
+ if (err)
92
+ return reject(err);
93
+ if (!stream)
94
+ return reject(new Error("No stream from exec"));
95
+ let stdout = "";
96
+ let stderr = "";
97
+ const stdoutSink = new Writable({ write(chunk, _enc, cb) { stdout += chunk.toString(); cb(); } });
98
+ const stderrSink = new Writable({ write(chunk, _enc, cb) { stderr += chunk.toString(); cb(); } });
99
+ docker.modem.demuxStream(stream, stdoutSink, stderrSink);
100
+ stream.on("end", async () => {
101
+ try {
102
+ const inspect = await exec.inspect();
103
+ resolve({ stdout, stderr, exitCode: inspect.ExitCode ?? 0 });
104
+ }
105
+ catch (e) {
106
+ reject(e);
107
+ }
108
+ });
109
+ stream.on("error", reject);
110
+ });
111
+ });
112
+ }
113
+ //# sourceMappingURL=docker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker.js","sourceRoot":"","sources":["../src/docker.ts"],"names":[],"mappings":"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"}
package/dist/env.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function parseEnv(text: string): Record<string, string>;
2
+ export declare function loadEnv(): void;
package/dist/env.js ADDED
@@ -0,0 +1,36 @@
1
+ import fs from "node:fs";
2
+ import { paths } from "./paths.js";
3
+ export function parseEnv(text) {
4
+ const out = {};
5
+ for (const line of text.split("\n")) {
6
+ const t = line.trim();
7
+ if (!t || t.startsWith("#"))
8
+ continue;
9
+ const eq = t.indexOf("=");
10
+ if (eq === -1)
11
+ continue;
12
+ const key = t.slice(0, eq).trim();
13
+ let val = t.slice(eq + 1).trim();
14
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
15
+ val = val.slice(1, -1);
16
+ }
17
+ out[key] = val;
18
+ }
19
+ return out;
20
+ }
21
+ // Load ~/.pairpod/.env into process.env without overriding already-set vars,
22
+ // so a value injected by the launcher (e.g. a fresh MINIAPP_URL) wins.
23
+ export function loadEnv() {
24
+ let text = "";
25
+ try {
26
+ text = fs.readFileSync(paths.env, "utf8");
27
+ }
28
+ catch {
29
+ return;
30
+ }
31
+ for (const [k, v] of Object.entries(parseEnv(text))) {
32
+ if (process.env[k] === undefined)
33
+ process.env[k] = v;
34
+ }
35
+ }
36
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACtC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,SAAS;QACxB,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAClC,IAAI,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC7F,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACzB,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,6EAA6E;AAC7E,uEAAuE;AACvE,MAAM,UAAU,OAAO;IACrB,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,CAAC;QACH,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACpD,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACvD,CAAC;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { FastifyError, FastifyReply, FastifyRequest } from "fastify";
2
+ export declare class AppError extends Error {
3
+ readonly statusCode: number;
4
+ readonly code: string;
5
+ constructor(statusCode: number, code: string, message: string);
6
+ }
7
+ export declare function errorHandler(error: FastifyError | AppError | Error, _req: FastifyRequest, reply: FastifyReply): void;
package/dist/errors.js ADDED
@@ -0,0 +1,25 @@
1
+ export class AppError extends Error {
2
+ statusCode;
3
+ code;
4
+ constructor(statusCode, code, message) {
5
+ super(message);
6
+ this.statusCode = statusCode;
7
+ this.code = code;
8
+ this.name = "AppError";
9
+ }
10
+ }
11
+ export function errorHandler(error, _req, reply) {
12
+ if (error instanceof AppError) {
13
+ reply.status(error.statusCode).send({ error: { code: error.code, message: error.message } });
14
+ return;
15
+ }
16
+ const fastifyError = error;
17
+ if (fastifyError.statusCode && fastifyError.statusCode < 500) {
18
+ reply.status(fastifyError.statusCode).send({
19
+ error: { code: "VALIDATION_ERROR", message: fastifyError.message },
20
+ });
21
+ return;
22
+ }
23
+ reply.status(500).send({ error: { code: "INTERNAL_ERROR", message: "Internal server error" } });
24
+ }
25
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,QAAS,SAAQ,KAAK;IAEf;IACA;IAFlB,YACkB,UAAkB,EAClB,IAAY,EAC5B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,eAAU,GAAV,UAAU,CAAQ;QAClB,SAAI,GAAJ,IAAI,CAAQ;QAI5B,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF;AAED,MAAM,UAAU,YAAY,CAC1B,KAAsC,EACtC,IAAoB,EACpB,KAAmB;IAEnB,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC7F,OAAO;IACT,CAAC;IAED,MAAM,YAAY,GAAG,KAAqB,CAAC;IAC3C,IAAI,YAAY,CAAC,UAAU,IAAI,YAAY,CAAC,UAAU,GAAG,GAAG,EAAE,CAAC;QAC7D,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;YACzC,KAAK,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,YAAY,CAAC,OAAO,EAAE;SACnE,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,uBAAuB,EAAE,EAAE,CAAC,CAAC;AAClG,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { AttachSocket } from "../routes/attach.js";
2
+ export declare function hasLocalSession(id: string): boolean;
3
+ export declare function createLocalSession(id: string, cwd: string): void;
4
+ export declare function attachLocal(socket: AttachSocket, id: string, cols: number, rows: number): void;
5
+ export declare function killLocalSession(id: string): void;
@@ -0,0 +1,83 @@
1
+ import os from "node:os";
2
+ import pty from "node-pty";
3
+ // Host terminals run as node-pty processes held in this process's memory (Route B: no tmux).
4
+ // They do not survive a bot restart — pruned from the DB on boot (see pruneLocalSessions).
5
+ const MAX_REPLAY = 256 * 1024;
6
+ const sessions = new Map();
7
+ export function hasLocalSession(id) {
8
+ return sessions.has(id);
9
+ }
10
+ export function createLocalSession(id, cwd) {
11
+ if (sessions.has(id))
12
+ return;
13
+ const shell = process.env.SHELL || "/bin/bash";
14
+ const term = pty.spawn(shell, [], {
15
+ name: "xterm-256color",
16
+ cols: 80,
17
+ rows: 24,
18
+ cwd: cwd || os.homedir(),
19
+ env: process.env,
20
+ });
21
+ const session = { pty: term, buffer: Buffer.alloc(0), socket: null };
22
+ term.onData((d) => {
23
+ const chunk = Buffer.from(d, "utf8");
24
+ session.buffer = Buffer.concat([session.buffer, chunk]);
25
+ if (session.buffer.length > MAX_REPLAY) {
26
+ session.buffer = session.buffer.subarray(session.buffer.length - MAX_REPLAY);
27
+ }
28
+ const s = session.socket;
29
+ if (s && s.readyState === s.OPEN)
30
+ s.send(chunk);
31
+ });
32
+ term.onExit(() => {
33
+ const s = session.socket;
34
+ if (s && s.readyState === s.OPEN)
35
+ s.close(1000, "exited");
36
+ sessions.delete(id);
37
+ });
38
+ sessions.set(id, session);
39
+ }
40
+ export function attachLocal(socket, id, cols, rows) {
41
+ const session = sessions.get(id);
42
+ if (!session) {
43
+ socket.close(4004, "session not found");
44
+ return;
45
+ }
46
+ // single viewer: a new attach replaces any existing one
47
+ const prev = session.socket;
48
+ if (prev && prev !== socket && prev.readyState === prev.OPEN)
49
+ prev.close(1000, "replaced");
50
+ session.socket = socket;
51
+ session.pty.resize(cols, rows);
52
+ if (session.buffer.length && socket.readyState === socket.OPEN)
53
+ socket.send(session.buffer);
54
+ socket.on("message", (data) => {
55
+ const raw = typeof data === "string" ? data : data.toString("utf8");
56
+ if (raw.charCodeAt(0) === 0x7b) {
57
+ try {
58
+ const msg = JSON.parse(raw);
59
+ if (msg.type === "resize" && typeof msg.cols === "number" && typeof msg.rows === "number") {
60
+ session.pty.resize(msg.cols, msg.rows);
61
+ return;
62
+ }
63
+ }
64
+ catch { }
65
+ }
66
+ session.pty.write(raw);
67
+ });
68
+ socket.on("close", () => {
69
+ if (session.socket === socket)
70
+ session.socket = null; // detach; keep the pty alive
71
+ });
72
+ }
73
+ export function killLocalSession(id) {
74
+ const session = sessions.get(id);
75
+ if (!session)
76
+ return;
77
+ try {
78
+ session.pty.kill();
79
+ }
80
+ catch { }
81
+ sessions.delete(id);
82
+ }
83
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/local/sessions.ts"],"names":[],"mappings":"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"}
package/dist/main.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/main.js ADDED
@@ -0,0 +1,8 @@
1
+ import { ensureHome } from "./paths.js";
2
+ import { loadEnv } from "./env.js";
3
+ ensureHome();
4
+ loadEnv();
5
+ // Import the server only after env is loaded, so config reads the final values.
6
+ const { startServer } = await import("./server.js");
7
+ await startServer();
8
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,UAAU,EAAE,CAAC;AACb,OAAO,EAAE,CAAC;AAEV,gFAAgF;AAChF,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;AACpD,MAAM,WAAW,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function nextPodId(): string;
2
+ export declare function nextSessionId(podId: string): string;
3
+ export declare function nextTerminalId(podId: string): string;
package/dist/naming.js ADDED
@@ -0,0 +1,19 @@
1
+ import { getDb } from "./db.js";
2
+ function nextCounter(name) {
3
+ const db = getDb();
4
+ db.prepare("INSERT OR IGNORE INTO counters (name, value) VALUES (?, 0)").run(name);
5
+ const row = db
6
+ .prepare("UPDATE counters SET value = value + 1 WHERE name = ? RETURNING value")
7
+ .get(name);
8
+ return row.value;
9
+ }
10
+ export function nextPodId() {
11
+ return `pod-${nextCounter("pods")}`;
12
+ }
13
+ export function nextSessionId(podId) {
14
+ return `claude-${nextCounter(`sessions:${podId}`)}`;
15
+ }
16
+ export function nextTerminalId(podId) {
17
+ return `terminal-${nextCounter(`terminals:${podId}`)}`;
18
+ }
19
+ //# sourceMappingURL=naming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"naming.js","sourceRoot":"","sources":["../src/naming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,EAAE,CAAC,OAAO,CAAC,4DAA4D,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnF,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CAAC,sEAAsE,CAAC;SAC/E,GAAG,CAAC,IAAI,CAAsB,CAAC;IAClC,OAAO,GAAG,CAAC,KAAK,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,UAAU,WAAW,CAAC,YAAY,KAAK,EAAE,CAAC,EAAE,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,YAAY,WAAW,CAAC,aAAa,KAAK,EAAE,CAAC,EAAE,CAAC;AACzD,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function ensurePairpodNetwork(): Promise<void>;
@@ -0,0 +1,11 @@
1
+ import { getDocker } from "./docker.js";
2
+ import { config } from "./config.js";
3
+ export async function ensurePairpodNetwork() {
4
+ const docker = getDocker();
5
+ const networks = await docker.listNetworks();
6
+ const exists = networks.some((n) => n.Name === config.pairpodNetwork);
7
+ if (exists)
8
+ return;
9
+ await docker.createNetwork({ Name: config.pairpodNetwork, Driver: "bridge" });
10
+ }
11
+ //# sourceMappingURL=network.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network.js","sourceRoot":"","sources":["../src/network.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,YAAY,EAAE,CAAC;IAC7C,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,cAAc,CAAC,CAAC;IACtE,IAAI,MAAM;QAAE,OAAO;IACnB,MAAM,MAAM,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;AAChF,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type Bot } from "grammy";
2
+ export declare function setNotifierBot(b: Bot): void;
3
+ export declare function recordChat(id: number): void;
4
+ export declare function pushNotification(text: string, pod?: string, session?: string): Promise<void>;
@@ -0,0 +1,47 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { InlineKeyboard } from "grammy";
4
+ import { config as shared } from "./config.js";
5
+ import { botConfig } from "./config.js";
6
+ const STORE = path.join(path.dirname(shared.dbPath), "notify-chats.json");
7
+ let bot = null;
8
+ const chatIds = new Set(load());
9
+ function load() {
10
+ try {
11
+ return JSON.parse(fs.readFileSync(STORE, "utf8"));
12
+ }
13
+ catch {
14
+ return [];
15
+ }
16
+ }
17
+ function save() {
18
+ try {
19
+ fs.writeFileSync(STORE, JSON.stringify([...chatIds]));
20
+ }
21
+ catch { }
22
+ }
23
+ export function setNotifierBot(b) {
24
+ bot = b;
25
+ }
26
+ export function recordChat(id) {
27
+ if (!chatIds.has(id)) {
28
+ chatIds.add(id);
29
+ save();
30
+ }
31
+ }
32
+ export async function pushNotification(text, pod, session) {
33
+ if (!bot)
34
+ return;
35
+ let reply_markup;
36
+ if (botConfig.miniappUrl && pod && session) {
37
+ const url = `${botConfig.miniappUrl}/?pod=${encodeURIComponent(pod)}&session=${encodeURIComponent(session)}`;
38
+ reply_markup = new InlineKeyboard().webApp(`▶ Open ${pod}:${session}`, url);
39
+ }
40
+ for (const id of chatIds) {
41
+ try {
42
+ await bot.api.sendMessage(id, text, reply_markup ? { reply_markup } : undefined);
43
+ }
44
+ catch { }
45
+ }
46
+ }
47
+ //# sourceMappingURL=notifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifier.js","sourceRoot":"","sources":["../src/notifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAY,cAAc,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC,CAAC;AAE1E,IAAI,GAAG,GAAe,IAAI,CAAC;AAC3B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,IAAI,EAAE,CAAC,CAAC;AAExC,SAAS,IAAI;IACX,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAa,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,IAAI;IACX,IAAI,CAAC;QACH,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,CAAM;IACnC,GAAG,GAAG,CAAC,CAAC;AACV,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EAAU;IACnC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,IAAI,EAAE,CAAC;IACT,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAY,EACZ,GAAY,EACZ,OAAgB;IAEhB,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,IAAI,YAAwC,CAAC;IAC7C,IAAI,SAAS,CAAC,UAAU,IAAI,GAAG,IAAI,OAAO,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC,UAAU,SAAS,kBAAkB,CAAC,GAAG,CAAC,YAAY,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7G,YAAY,GAAG,IAAI,cAAc,EAAE,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC;IAC9E,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACnF,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ export declare function notifyRoutes(app: FastifyInstance): Promise<void>;
package/dist/notify.js ADDED
@@ -0,0 +1,19 @@
1
+ import { botConfig } from "./config.js";
2
+ import { pushNotification } from "./notifier.js";
3
+ export async function notifyRoutes(app) {
4
+ app.post("/notify", async (req, reply) => {
5
+ const b = (req.body || {});
6
+ if (!botConfig.hookToken || b.token !== botConfig.hookToken) {
7
+ return reply.status(403).send({ ok: false });
8
+ }
9
+ const label = `${b.pod || "?"}:${b.session || "?"}`;
10
+ let text = `🔔 ${label} needs you`;
11
+ if (b.message)
12
+ text += `\n${b.message}`;
13
+ if (b.detail)
14
+ text += `\n↳ ${b.detail}`;
15
+ await pushNotification(text, b.pod, b.session);
16
+ return reply.send({ ok: true });
17
+ });
18
+ }
19
+ //# sourceMappingURL=notify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.js","sourceRoot":"","sources":["../src/notify.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAUjD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAoB;IACrD,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QACvC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAe,CAAC;QACzC,IAAI,CAAC,SAAS,CAAC,SAAS,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,SAAS,EAAE,CAAC;YAC5D,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;QACpD,IAAI,IAAI,GAAG,MAAM,KAAK,YAAY,CAAC;QACnC,IAAI,CAAC,CAAC,OAAO;YAAE,IAAI,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QACxC,IAAI,CAAC,CAAC,MAAM;YAAE,IAAI,IAAI,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;QACxC,MAAM,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;QAC/C,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,9 @@
1
+ export declare const PAIRPOD_HOME: string;
2
+ export declare const paths: {
3
+ home: string;
4
+ env: string;
5
+ db: string;
6
+ workspaces: string;
7
+ vault: string;
8
+ };
9
+ export declare function ensureHome(): void;
package/dist/paths.js ADDED
@@ -0,0 +1,18 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ // All host-specific state lives under one dir. Override with PAIRPOD_HOME.
5
+ export const PAIRPOD_HOME = process.env.PAIRPOD_HOME
6
+ ? path.resolve(process.env.PAIRPOD_HOME)
7
+ : path.join(os.homedir(), ".pairpod");
8
+ export const paths = {
9
+ home: PAIRPOD_HOME,
10
+ env: path.join(PAIRPOD_HOME, ".env"),
11
+ db: path.join(PAIRPOD_HOME, "pairpod.db"),
12
+ workspaces: path.join(PAIRPOD_HOME, "workspaces"),
13
+ vault: path.join(PAIRPOD_HOME, "vault"),
14
+ };
15
+ export function ensureHome() {
16
+ fs.mkdirSync(PAIRPOD_HOME, { recursive: true });
17
+ }
18
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,2EAA2E;AAC3E,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY;IAClD,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IACxC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AAExC,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC;IACpC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC;IACzC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC;IACjD,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC;CACxC,CAAC;AAEF,MAAM,UAAU,UAAU;IACxB,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { PodTarget } from "../targets/types.js";
2
+ export interface AttachSocket {
3
+ readyState: number;
4
+ OPEN: number;
5
+ send(data: Buffer): void;
6
+ close(code?: number, reason?: string): void;
7
+ on(event: "message", listener: (data: Buffer | string) => void): unknown;
8
+ on(event: "close", listener: () => void): unknown;
9
+ }
10
+ export declare function wireAttach(socket: AttachSocket, query: {
11
+ cols?: string;
12
+ rows?: string;
13
+ }, target: PodTarget, sessionId: string, log: (msg: string, extra?: unknown) => void): Promise<void>;