@sentropic/remote-cli 0.0.3

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.
@@ -0,0 +1,294 @@
1
+ import {
2
+ getTunnel
3
+ } from "./chunk-E3GXAKJ3.js";
4
+
5
+ // src/h2a-bridge.ts
6
+ import { spawnSync } from "child_process";
7
+ import {
8
+ existsSync,
9
+ mkdirSync,
10
+ readdirSync,
11
+ readFileSync,
12
+ writeFileSync
13
+ } from "fs";
14
+ import { homedir } from "os";
15
+ import { join } from "path";
16
+ var H2A_POD_ROOT = "h2a-workspace/.h2a";
17
+ function defaultLocalH2aRoot() {
18
+ return join(homedir(), "h2a-workspace", ".h2a");
19
+ }
20
+ function instanceInboxDir(instance) {
21
+ return instance.replace(/:/g, "__");
22
+ }
23
+ function profileTool(profile) {
24
+ switch (profile) {
25
+ case "claude":
26
+ case "claude-code":
27
+ return "claude";
28
+ case "codex":
29
+ return "codex";
30
+ case "agy":
31
+ case "antigravity":
32
+ return "agy";
33
+ case "gemini":
34
+ case "mistral":
35
+ return profile;
36
+ default:
37
+ return profile && profile.length > 0 ? profile : "claude";
38
+ }
39
+ }
40
+ function defaultPodInstance(sessionId, profile) {
41
+ return `${profileTool(profile)}:remote:${sessionId}`;
42
+ }
43
+ var SAFE_ENTRY = /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*\.json$/;
44
+ function planBridge(args) {
45
+ const { podInstanceDirs } = args;
46
+ const split = (entry) => {
47
+ const slash = entry.indexOf("/");
48
+ return { dir: entry.slice(0, slash), file: entry.slice(slash + 1) };
49
+ };
50
+ let skipped = 0;
51
+ let ignored = 0;
52
+ const podSet = /* @__PURE__ */ new Set();
53
+ const pull = [];
54
+ const push = [];
55
+ for (const entry of args.podFiles) {
56
+ if (!SAFE_ENTRY.test(entry)) {
57
+ ignored += 1;
58
+ continue;
59
+ }
60
+ podSet.add(entry);
61
+ }
62
+ const localSet = /* @__PURE__ */ new Set();
63
+ for (const entry of args.localFiles) {
64
+ if (!SAFE_ENTRY.test(entry)) {
65
+ ignored += 1;
66
+ continue;
67
+ }
68
+ localSet.add(entry);
69
+ }
70
+ for (const entry of podSet) {
71
+ const f = split(entry);
72
+ if (podInstanceDirs.has(f.dir)) continue;
73
+ if (localSet.has(entry)) skipped += 1;
74
+ else pull.push(f);
75
+ }
76
+ for (const entry of localSet) {
77
+ const f = split(entry);
78
+ if (!podInstanceDirs.has(f.dir)) continue;
79
+ if (podSet.has(entry)) skipped += 1;
80
+ else push.push(f);
81
+ }
82
+ return { pull, push, skipped, ignored };
83
+ }
84
+ function parsePodListing(out) {
85
+ const instances = [];
86
+ const files = [];
87
+ let section = "none";
88
+ for (const raw of out.split("\n")) {
89
+ const line = raw.trim();
90
+ if (line === "==INSTANCES==") {
91
+ section = "instances";
92
+ continue;
93
+ }
94
+ if (line === "==FILES==") {
95
+ section = "files";
96
+ continue;
97
+ }
98
+ if (line.length === 0) continue;
99
+ if (section === "instances") instances.push(line);
100
+ else if (section === "files") files.push(line);
101
+ }
102
+ return { instances, files };
103
+ }
104
+ function expandHome(p) {
105
+ return p.startsWith("~") ? join(homedir(), p.slice(1)) : p;
106
+ }
107
+ function execPod(tunnel, pod, script, input) {
108
+ const args = ["-n", tunnel.namespace, "exec"];
109
+ if (input !== void 0) args.push("-i");
110
+ args.push(pod, "-c", "session-agent", "--", "bash", "-lc", script);
111
+ const env = { ...process.env };
112
+ if (tunnel.kubeconfig) env.KUBECONFIG = expandHome(tunnel.kubeconfig);
113
+ const r = spawnSync("kubectl", args, {
114
+ encoding: "utf8",
115
+ env,
116
+ maxBuffer: 64 * 1024 * 1024,
117
+ ...input !== void 0 ? { input } : {}
118
+ });
119
+ if (r.status !== 0) {
120
+ throw new Error(
121
+ `kubectl exec failed: ${(r.stderr || "").trim().slice(0, 200)}`
122
+ );
123
+ }
124
+ return r.stdout;
125
+ }
126
+ function podReadme(podInboxDir) {
127
+ return [
128
+ "# h2a store (bridged)",
129
+ "",
130
+ "This directory is an h2a file store kept in sync with the operator's",
131
+ "local ~/h2a-workspace/.h2a by `remote h2a bridge` (no h2a binary needed",
132
+ "in this Pod).",
133
+ "",
134
+ "To MESSAGE another agent, write a JSON envelope",
135
+ ' { "protocol": "sentropic.h2a", "version": "0.1", "id": "env:<ts>:<slug>",',
136
+ ' "type": "event", "actor": { "instance", "role", "scope" }, "body", "createdAt" }',
137
+ "to inbox/<instance-dir>/env__<ts>__<slug>.json, where <instance-dir> is",
138
+ 'the target instance id with ":" replaced by "__"',
139
+ "(claude:track:abc -> claude__track__abc).",
140
+ "",
141
+ `Your own inbox is inbox/${podInboxDir}/ \u2014 check it for new envelopes.`,
142
+ "The bridge never deletes files; acks/cleanup belong to h2a on the local",
143
+ "side. Existing file names are never overwritten.",
144
+ ""
145
+ ].join("\n");
146
+ }
147
+ function listLocalInbox(localRoot) {
148
+ const inbox = join(localRoot, "inbox");
149
+ if (!existsSync(inbox)) return [];
150
+ const entries = [];
151
+ for (const dir of readdirSync(inbox, { withFileTypes: true })) {
152
+ if (!dir.isDirectory()) continue;
153
+ for (const f of readdirSync(join(inbox, dir.name), {
154
+ withFileTypes: true
155
+ })) {
156
+ if (f.isFile() && f.name.endsWith(".json"))
157
+ entries.push(`${dir.name}/${f.name}`);
158
+ }
159
+ }
160
+ return entries;
161
+ }
162
+ async function bridgeSession(sessionId, options = {}) {
163
+ const stderr = options.stderr ?? process.stderr;
164
+ const tunnel = getTunnel();
165
+ if (!tunnel) {
166
+ throw new Error(
167
+ "h2a bridge needs a tunnel configured (remote config tunnel \u2026)"
168
+ );
169
+ }
170
+ const pod = `session-${sessionId}`;
171
+ const localRoot = options.localRoot ?? defaultLocalH2aRoot();
172
+ const podInbox = `$HOME/${H2A_POD_ROOT}/inbox`;
173
+ const defaultDir = instanceInboxDir(
174
+ defaultPodInstance(sessionId, options.profile)
175
+ );
176
+ const scaffold = execPod(
177
+ tunnel,
178
+ pod,
179
+ [
180
+ `root="$HOME/${H2A_POD_ROOT}"`,
181
+ `if [ -d "$root/inbox" ]; then echo h2a-store-exists; else`,
182
+ `mkdir -p "$root/inbox/${defaultDir}"`,
183
+ `cat > "$root/README.md" <<'H2ADOC'`,
184
+ podReadme(defaultDir),
185
+ `H2ADOC`,
186
+ `echo h2a-store-created`,
187
+ `fi`
188
+ ].join("\n")
189
+ );
190
+ const scaffolded = scaffold.includes("h2a-store-created");
191
+ const listing = parsePodListing(
192
+ execPod(
193
+ tunnel,
194
+ pod,
195
+ [
196
+ `cd "$HOME/${H2A_POD_ROOT}" 2>/dev/null || exit 0`,
197
+ `echo ==INSTANCES==`,
198
+ `sed -n 's/.*"instance"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' registry/instances.jsonl 2>/dev/null`,
199
+ `echo ==FILES==`,
200
+ `shopt -s nullglob`,
201
+ `for f in inbox/*/*.json; do printf '%s\\n' "\${f#inbox/}"; done`
202
+ ].join("\n")
203
+ )
204
+ );
205
+ const podInstanceDirs = /* @__PURE__ */ new Set([defaultDir]);
206
+ for (const instance of listing.instances) {
207
+ podInstanceDirs.add(instanceInboxDir(instance));
208
+ }
209
+ const plan = planBridge({
210
+ podFiles: listing.files,
211
+ localFiles: listLocalInbox(localRoot),
212
+ podInstanceDirs
213
+ });
214
+ if (plan.ignored > 0) {
215
+ stderr.write(
216
+ `[remote] h2a bridge ${sessionId}: ignored ${plan.ignored} unsafe inbox entry name(s)
217
+ `
218
+ );
219
+ }
220
+ let pulled = 0;
221
+ let pushed = 0;
222
+ let skipped = plan.skipped;
223
+ let failed = 0;
224
+ for (const f of plan.pull) {
225
+ const dst = join(localRoot, "inbox", f.dir, f.file);
226
+ if (existsSync(dst)) {
227
+ skipped += 1;
228
+ continue;
229
+ }
230
+ try {
231
+ const b64 = execPod(
232
+ tunnel,
233
+ pod,
234
+ `base64 < "${podInbox}/${f.dir}/${f.file}" | tr -d '\\n'`
235
+ );
236
+ mkdirSync(join(localRoot, "inbox", f.dir), { recursive: true });
237
+ writeFileSync(dst, Buffer.from(b64.trim(), "base64"));
238
+ pulled += 1;
239
+ } catch (error) {
240
+ failed += 1;
241
+ stderr.write(
242
+ `[remote] h2a bridge ${sessionId}: pull ${f.dir}/${f.file} failed: ${String(
243
+ error instanceof Error ? error.message : error
244
+ ).slice(0, 160)}
245
+ `
246
+ );
247
+ }
248
+ }
249
+ for (const f of plan.push) {
250
+ const src = join(localRoot, "inbox", f.dir, f.file);
251
+ try {
252
+ const payload = readFileSync(src).toString("base64");
253
+ const out = execPod(
254
+ tunnel,
255
+ pod,
256
+ [
257
+ `d="${podInbox}/${f.dir}"; f="$d/${f.file}"`,
258
+ `mkdir -p "$d"`,
259
+ `if [ -e "$f" ]; then echo h2a-exists; else base64 -d > "$f" && echo h2a-written; fi`
260
+ ].join("\n"),
261
+ payload
262
+ );
263
+ if (out.includes("h2a-written")) pushed += 1;
264
+ else skipped += 1;
265
+ } catch (error) {
266
+ failed += 1;
267
+ stderr.write(
268
+ `[remote] h2a bridge ${sessionId}: push ${f.dir}/${f.file} failed: ${String(
269
+ error instanceof Error ? error.message : error
270
+ ).slice(0, 160)}
271
+ `
272
+ );
273
+ }
274
+ }
275
+ return {
276
+ sessionId,
277
+ pulled,
278
+ pushed,
279
+ skipped,
280
+ failed,
281
+ scaffolded,
282
+ podInstanceDirs: [...podInstanceDirs].sort()
283
+ };
284
+ }
285
+
286
+ export {
287
+ H2A_POD_ROOT,
288
+ defaultLocalH2aRoot,
289
+ instanceInboxDir,
290
+ defaultPodInstance,
291
+ planBridge,
292
+ parsePodListing,
293
+ bridgeSession
294
+ };
@@ -0,0 +1,20 @@
1
+ import {
2
+ H2A_POD_ROOT,
3
+ bridgeSession,
4
+ defaultLocalH2aRoot,
5
+ defaultPodInstance,
6
+ instanceInboxDir,
7
+ parsePodListing,
8
+ planBridge
9
+ } from "./chunk-Z277FK2S.js";
10
+ import "./chunk-E3GXAKJ3.js";
11
+ import "./chunk-3RG5ZIWI.js";
12
+ export {
13
+ H2A_POD_ROOT,
14
+ bridgeSession,
15
+ defaultLocalH2aRoot,
16
+ defaultPodInstance,
17
+ instanceInboxDir,
18
+ parsePodListing,
19
+ planBridge
20
+ };