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.
- package/LICENSE +21 -0
- package/README.md +6 -0
- package/dist/access.d.ts +2 -0
- package/dist/access.js +14 -0
- package/dist/access.js.map +1 -0
- package/dist/agents.d.ts +7 -0
- package/dist/agents.js +10 -0
- package/dist/agents.js.map +1 -0
- package/dist/attach.d.ts +2 -0
- package/dist/attach.js +65 -0
- package/dist/attach.js.map +1 -0
- package/dist/bot.d.ts +1 -0
- package/dist/bot.js +357 -0
- package/dist/bot.js.map +1 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.js +39 -0
- package/dist/config.js.map +1 -0
- package/dist/db.d.ts +2 -0
- package/dist/db.js +87 -0
- package/dist/db.js.map +1 -0
- package/dist/docker.d.ts +15 -0
- package/dist/docker.js +113 -0
- package/dist/docker.js.map +1 -0
- package/dist/env.d.ts +2 -0
- package/dist/env.js +36 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +25 -0
- package/dist/errors.js.map +1 -0
- package/dist/local/sessions.d.ts +5 -0
- package/dist/local/sessions.js +83 -0
- package/dist/local/sessions.js.map +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +8 -0
- package/dist/main.js.map +1 -0
- package/dist/naming.d.ts +3 -0
- package/dist/naming.js +19 -0
- package/dist/naming.js.map +1 -0
- package/dist/network.d.ts +1 -0
- package/dist/network.js +11 -0
- package/dist/network.js.map +1 -0
- package/dist/notifier.d.ts +4 -0
- package/dist/notifier.js +47 -0
- package/dist/notifier.js.map +1 -0
- package/dist/notify.d.ts +2 -0
- package/dist/notify.js +19 -0
- package/dist/notify.js.map +1 -0
- package/dist/paths.d.ts +9 -0
- package/dist/paths.js +18 -0
- package/dist/paths.js.map +1 -0
- package/dist/routes/attach.d.ts +13 -0
- package/dist/routes/attach.js +49 -0
- package/dist/routes/attach.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +51 -0
- package/dist/server.js.map +1 -0
- package/dist/ssh.d.ts +2 -0
- package/dist/ssh.js +84 -0
- package/dist/ssh.js.map +1 -0
- package/dist/store.d.ts +65 -0
- package/dist/store.js +337 -0
- package/dist/store.js.map +1 -0
- package/dist/targets/docker.d.ts +7 -0
- package/dist/targets/docker.js +14 -0
- package/dist/targets/docker.js.map +1 -0
- package/dist/targets/index.d.ts +4 -0
- package/dist/targets/index.js +33 -0
- package/dist/targets/index.js.map +1 -0
- package/dist/targets/ssh.d.ts +25 -0
- package/dist/targets/ssh.js +121 -0
- package/dist/targets/ssh.js.map +1 -0
- package/dist/targets/types.d.ts +15 -0
- package/dist/targets/types.js +2 -0
- package/dist/targets/types.js.map +1 -0
- package/dist/telegram-auth.d.ts +7 -0
- package/dist/telegram-auth.js +41 -0
- package/dist/telegram-auth.js.map +1 -0
- package/dist/vault.d.ts +4 -0
- package/dist/vault.js +57 -0
- package/dist/vault.js.map +1 -0
- package/miniapp/index.html +597 -0
- package/miniapp/ssh.html +251 -0
- package/package.json +44 -0
- 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"}
|
package/dist/docker.d.ts
ADDED
|
@@ -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
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
|
package/dist/env.js.map
ADDED
|
@@ -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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -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
|
package/dist/main.js.map
ADDED
|
@@ -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"}
|
package/dist/naming.d.ts
ADDED
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>;
|
package/dist/network.js
ADDED
|
@@ -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"}
|
package/dist/notifier.js
ADDED
|
@@ -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"}
|
package/dist/notify.d.ts
ADDED
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"}
|
package/dist/paths.d.ts
ADDED
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>;
|