mini-opti-bridge 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 (4) hide show
  1. package/README.md +16 -0
  2. package/cli.js +2 -0
  3. package/dist/cli.js +232 -0
  4. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # mini-opti-bridge
2
+
3
+ One-command bridge bootstrap for the Mini Opti Codex dashboard.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx mini-opti-bridge@latest https://mini-opti.vercel.app <registrationKey>
9
+ pnpm dlx mini-opti-bridge@latest https://mini-opti.vercel.app <registrationKey>
10
+ ```
11
+
12
+ Optional named node:
13
+
14
+ ```bash
15
+ npx mini-opti-bridge@latest https://mini-opti.vercel.app <registrationKey> my-node local
16
+ ```
package/cli.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "./dist/cli.js";
package/dist/cli.js ADDED
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/run.ts
4
+ import os from "os";
5
+ import process2 from "process";
6
+ import { readFile, writeFile } from "fs/promises";
7
+
8
+ // ../connectors/src/bridge/bootstrap.ts
9
+ var normalizePlatform = (platform) => {
10
+ if (platform === "win32") return "windows";
11
+ if (platform === "darwin") return "macos";
12
+ return platform;
13
+ };
14
+ var defaultNodeLabel = (input) => `${input.hostname}-${normalizePlatform(input.platform)}`;
15
+ var readPollMs = (value) => {
16
+ const parsed = Number(value ?? "4000");
17
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 4e3;
18
+ };
19
+ var parseBridgeBootstrap = (input) => {
20
+ const env = input.env ?? {};
21
+ return {
22
+ serverUrl: input.argv[3] ?? env.MINI_OPTI_SERVER_URL ?? "",
23
+ key: input.argv[4] ?? env.MINI_OPTI_REGISTRATION_KEY ?? "",
24
+ label: input.argv[5] ?? env.MINI_OPTI_NODE_LABEL ?? defaultNodeLabel({ hostname: input.hostname, platform: input.platform }),
25
+ kind: input.argv[6] ?? env.MINI_OPTI_NODE_KIND ?? "local",
26
+ pollMs: readPollMs(env.MINI_OPTI_POLL_MS)
27
+ };
28
+ };
29
+
30
+ // ../connectors/src/codex/app-server-client.ts
31
+ import { spawn } from "child_process";
32
+ import readline from "readline";
33
+ var CodexAppServerClient = class {
34
+ proc = null;
35
+ counter = 0;
36
+ pending = /* @__PURE__ */ new Map();
37
+ async start() {
38
+ if (this.proc) return;
39
+ this.proc = spawn("codex", ["app-server"], { stdio: ["pipe", "pipe", "inherit"] });
40
+ const rl = readline.createInterface({ input: this.proc.stdout });
41
+ rl.on("line", (line) => this.onMessage(JSON.parse(line)));
42
+ await this.request("initialize", {
43
+ clientInfo: { name: "mini-opti-bridge", title: "Mini Opti Bridge", version: "0.1.0" }
44
+ });
45
+ this.notify("initialized", {});
46
+ }
47
+ async listThreads() {
48
+ const data = [];
49
+ let cursor = null;
50
+ do {
51
+ const result = await this.request("thread/list", {
52
+ cursor,
53
+ limit: 100,
54
+ sortKey: "updated_at"
55
+ });
56
+ data.push(...result.data ?? []);
57
+ cursor = result.nextCursor ?? null;
58
+ } while (cursor);
59
+ return data;
60
+ }
61
+ async readThread(threadId) {
62
+ const result = await this.request("thread/read", { threadId, includeTurns: true });
63
+ return result.thread;
64
+ }
65
+ async interrupt(threadId, turnId) {
66
+ return this.request("turn/interrupt", { threadId, turnId });
67
+ }
68
+ onMessage(message) {
69
+ if (typeof message.id === "number") {
70
+ this.pending.get(message.id)?.(message);
71
+ this.pending.delete(message.id);
72
+ }
73
+ }
74
+ notify(method, params) {
75
+ this.proc?.stdin.write(`${JSON.stringify({ method, params })}
76
+ `);
77
+ }
78
+ request(method, params) {
79
+ return new Promise((resolve, reject) => {
80
+ const id = ++this.counter;
81
+ this.pending.set(id, (message) => {
82
+ if (message.error) reject(new Error(message.error.message));
83
+ else resolve(message.result);
84
+ });
85
+ this.proc?.stdin.write(`${JSON.stringify({ method, id, params })}
86
+ `);
87
+ });
88
+ }
89
+ };
90
+
91
+ // ../connectors/src/codex/normalize.ts
92
+ var mapContent = (content = []) => content.map((item) => item.text ?? "").join("\n").trim();
93
+ var normalizeLog = (thread) => (thread.turns ?? []).flatMap(
94
+ (turn) => (turn.items ?? []).flatMap((item) => {
95
+ if (item.type === "userMessage") {
96
+ return [{ role: "user", text: mapContent(item.content), turnId: turn.id, timestamp: Date.now() }];
97
+ }
98
+ if (item.type === "agentMessage") {
99
+ return [{ role: "assistant", text: item.text ?? "", turnId: turn.id, timestamp: Date.now() }];
100
+ }
101
+ return [];
102
+ })
103
+ );
104
+ var normalizeSession = (thread, log = []) => ({
105
+ id: thread.id,
106
+ name: thread.name ?? "Untitled session",
107
+ preview: thread.preview ?? "",
108
+ status: thread.status?.type ?? "notLoaded",
109
+ createdAt: thread.createdAt ?? 0,
110
+ updatedAt: thread.updatedAt ?? thread.createdAt ?? 0,
111
+ modelProvider: thread.modelProvider ?? "openai",
112
+ log
113
+ });
114
+
115
+ // ../connectors/src/codex/sdk-client.ts
116
+ var sendMessageToThread = async (threadId, prompt) => {
117
+ const { Codex } = await import("@openai/codex-sdk");
118
+ const codex = new Codex();
119
+ const thread = codex.resumeThread(threadId);
120
+ return thread.run(prompt);
121
+ };
122
+
123
+ // src/args.ts
124
+ var normalizeBridgeArgv = (argv) => {
125
+ const command = argv[2];
126
+ if (command === "register" || command === "serve" || command === "up") return argv;
127
+ return [argv[0] ?? "node", argv[1] ?? "mini-opti-bridge", "up", ...argv.slice(2)];
128
+ };
129
+
130
+ // src/run.ts
131
+ var configPath = ".codex-bridge.json";
132
+ var readConfig = async () => JSON.parse(await readFile(configPath, "utf8"));
133
+ var saveConfig = async (next) => {
134
+ await writeFile(configPath, JSON.stringify(next, null, 2), "utf8");
135
+ };
136
+ var postJson = async (serverUrl, pathname, body) => {
137
+ const response = await fetch(new URL(pathname, serverUrl), {
138
+ method: "POST",
139
+ headers: { "content-type": "application/json" },
140
+ body: JSON.stringify(body)
141
+ });
142
+ const text = await response.text();
143
+ if (!response.ok) throw new Error(text || `${response.status} ${response.statusText}`);
144
+ return text ? JSON.parse(text) : {};
145
+ };
146
+ var readBootstrap = (argv) => parseBridgeBootstrap({
147
+ argv: normalizeBridgeArgv(argv),
148
+ env: process2.env,
149
+ hostname: os.hostname(),
150
+ platform: process2.platform
151
+ });
152
+ var register = async (bootstrap) => {
153
+ const node = await postJson(bootstrap.serverUrl, "/api/bridges/register", {
154
+ key: bootstrap.key,
155
+ label: bootstrap.label,
156
+ kind: bootstrap.kind,
157
+ cwd: process2.cwd()
158
+ });
159
+ const next = { ...node, serverUrl: bootstrap.serverUrl, pollMs: bootstrap.pollMs };
160
+ await saveConfig(next);
161
+ console.log(`registered ${next.nodeId}`);
162
+ return next;
163
+ };
164
+ var loadOrRegister = async (argv) => {
165
+ try {
166
+ return await readConfig();
167
+ } catch {
168
+ const bootstrap = readBootstrap(argv);
169
+ if (!bootstrap.serverUrl || !bootstrap.key) {
170
+ throw new Error("Usage: mini-opti-bridge [up] <serverUrl> <registrationKey> [label] [local|remote]");
171
+ }
172
+ return register(bootstrap);
173
+ }
174
+ };
175
+ var completeCommand = async (config, client, command) => {
176
+ if (!command.sessionId) return;
177
+ if (command.type === "send-message") {
178
+ await sendMessageToThread(command.sessionId, command.payload?.text ?? "");
179
+ }
180
+ if (command.type === "send-message" || command.type === "refresh-log") {
181
+ const fullThread = await client.readThread(command.sessionId);
182
+ const log = normalizeLog(fullThread).map((item) => `${item.role}: ${item.text}`).join("\n\n");
183
+ await postJson(config.serverUrl, "/api/bridges/commands/complete", {
184
+ commandId: command.id,
185
+ status: "completed",
186
+ result: command.type === "send-message" ? "message delivered" : "log refreshed",
187
+ log
188
+ });
189
+ }
190
+ };
191
+ var syncOnce = async (config, client) => {
192
+ const threads = await client.listThreads();
193
+ const sessions = threads.map((thread) => normalizeSession(thread));
194
+ const beat = await postJson(config.serverUrl, "/api/bridges/heartbeat", {
195
+ nodeId: config.nodeId,
196
+ bridgeToken: config.bridgeToken,
197
+ sessions
198
+ });
199
+ for (const command of beat.commands ?? []) await completeCommand(config, client, command);
200
+ };
201
+ var serve = async (argv) => {
202
+ const config = await loadOrRegister(argv);
203
+ const client = new CodexAppServerClient();
204
+ await client.start();
205
+ await syncOnce(config, client);
206
+ setInterval(() => {
207
+ syncOnce(config, client).catch((error) => console.error(error));
208
+ }, config.pollMs);
209
+ };
210
+ var runBridgeCli = async (argv = process2.argv) => {
211
+ const normalizedArgv = normalizeBridgeArgv(argv);
212
+ const command = normalizedArgv[2];
213
+ if (command === "register") {
214
+ const bootstrap = readBootstrap(normalizedArgv);
215
+ if (!bootstrap.serverUrl || !bootstrap.key) {
216
+ throw new Error("Usage: mini-opti-bridge register <serverUrl> <registrationKey> [label] [local|remote]");
217
+ }
218
+ await register(bootstrap);
219
+ return;
220
+ }
221
+ if (command === "serve" || command === "up") {
222
+ await serve(normalizedArgv);
223
+ return;
224
+ }
225
+ throw new Error("Usage: mini-opti-bridge [up] <serverUrl> <registrationKey> [label] [local|remote]");
226
+ };
227
+
228
+ // src/cli.ts
229
+ runBridgeCli().catch((error) => {
230
+ console.error(error instanceof Error ? error.message : error);
231
+ process.exitCode = 1;
232
+ });
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "mini-opti-bridge",
3
+ "version": "0.1.0",
4
+ "description": "One-command Codex bridge bootstrap for the Mini Opti dashboard",
5
+ "type": "module",
6
+ "bin": "cli.js",
7
+ "files": [
8
+ "cli.js",
9
+ "dist"
10
+ ],
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/dimkk/mini-opti.git",
17
+ "directory": "packages/bridge-cli"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/dimkk/mini-opti/issues"
21
+ },
22
+ "homepage": "https://github.com/dimkk/mini-opti/tree/main/packages/bridge-cli",
23
+ "scripts": {
24
+ "build": "tsup --config tsup.config.ts",
25
+ "lint": "tsc -p tsconfig.json --noEmit",
26
+ "test": "vitest run --config ../../vitest.config.ts --passWithNoTests",
27
+ "typecheck": "tsc -p tsconfig.json --noEmit"
28
+ },
29
+ "dependencies": {
30
+ "@openai/codex-sdk": "latest"
31
+ },
32
+ "devDependencies": {
33
+ "@mini-opti/core-schemas": "workspace:*"
34
+ },
35
+ "keywords": [
36
+ "codex",
37
+ "openai",
38
+ "telegram",
39
+ "vercel",
40
+ "bridge"
41
+ ],
42
+ "license": "MIT"
43
+ }