@obtoai/agent-bridge 0.1.0-beta.5 → 0.1.0-beta.7
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/README.md +17 -4
- package/bin/obto-bridge.js +8 -5
- package/cli/init.js +148 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,9 +4,17 @@ A local daemon that lets coding agents — [Claude Code](https://claude.ai/code)
|
|
|
4
4
|
|
|
5
5
|
You post a message on a thread from your phone or laptop. The daemon (running on your machine, no port forwarding required) receives it over a long-lived HTTPS stream, spawns or resumes a session for the agent that thread is bound to, and the response posts back to the bridge thread within seconds.
|
|
6
6
|
|
|
7
|
+
**Three commands, you're driving Claude/Codex/opencode on your phone:**
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @obtoai/agent-bridge
|
|
11
|
+
obto-bridge init # creates a free account inline — no card, no invite
|
|
12
|
+
obto-bridge start # daemon connects, you're live
|
|
13
|
+
```
|
|
14
|
+
|
|
7
15
|
## Status
|
|
8
16
|
|
|
9
|
-
**
|
|
17
|
+
**Public beta.** Self-serve, no card. `obto-bridge init` creates a free account inline — no waiting on an invite, no support email loop. A Pro tier with longer Hindsight memory retention is coming; until then everyone gets the same daemon features on the free tier.
|
|
10
18
|
|
|
11
19
|
## What you'll need
|
|
12
20
|
|
|
@@ -15,7 +23,6 @@ You post a message on a thread from your phone or laptop. The daemon (running on
|
|
|
15
23
|
- **Claude** — Claude Code / the Claude Agent SDK, billed to your Anthropic account.
|
|
16
24
|
- **Codex** — the `codex` CLI (`npm i -g @openai/codex`), signed in to your OpenAI/ChatGPT account.
|
|
17
25
|
- **opencode** — `npm i -g opencode-ai` (the `opencode` CLI; the daemon bundles the `@opencode-ai/sdk`). Auth is your own provider key (Anthropic by default; override with env vars below).
|
|
18
|
-
- An invite from `support@obto.co` (gives you an `accountId`, browser username/password, and an API token).
|
|
19
26
|
|
|
20
27
|
## Install
|
|
21
28
|
|
|
@@ -35,9 +42,15 @@ npx @obtoai/agent-bridge <command>
|
|
|
35
42
|
obto-bridge init
|
|
36
43
|
```
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
`init` asks for one thing — your email. Username is derived from the email's local part (`divyansh.verma@gmail.com` → `divyansh-verma`); password is auto-generated as a strong 12-char string and **shown once** in stdout for you to save. It then creates the account inline via `POST /api/bridge/register`, saves the returned API token to `~/.obto-bridge/config.json` (mode 0600), and asks for an agent name (so multiple machines on the same account don't collide), the project directory the daemon should work in, a *fallback* agent (`claude` / `codex` / `opencode` — used only for legacy events without an explicit agent), and whether to relay tool-permission requests via the bridge. Sign in at `https://agent-bridge.obto.co/api/view` as `@username` with the generated password to start a thread.
|
|
46
|
+
|
|
47
|
+
Overrides:
|
|
48
|
+
|
|
49
|
+
- `obto-bridge init --username <name>` — pick your own username instead of the derived one.
|
|
50
|
+
- `obto-bridge init --password <pwd>` — set your own password instead of auto-generating.
|
|
51
|
+
- `obto-bridge init --token obto_xxxxxxxx --account acc_xxxxxxxx` — skip registration entirely (paste-in for existing users or scripted/headless setups).
|
|
39
52
|
|
|
40
|
-
|
|
53
|
+
The API token is shown to you exactly once at registration time. **Save it.** If you lose it, rotate it from your account settings — it is not stored in readable form server-side. Safe to commit your `accountId`; **never commit the `apiToken`**. (Server URL is a built-in default; advanced / self-hosted users can override with the `BRIDGE_BASE_URL` env var.)
|
|
41
54
|
|
|
42
55
|
### Agents (claude / codex / opencode)
|
|
43
56
|
|
package/bin/obto-bridge.js
CHANGED
|
@@ -14,7 +14,8 @@ const usage = () => {
|
|
|
14
14
|
console.error('Usage: obto-bridge <command>');
|
|
15
15
|
console.error('');
|
|
16
16
|
console.error('Commands:');
|
|
17
|
-
console.error(' init
|
|
17
|
+
console.error(' init Create a free account (or paste an existing token via --token/--account)');
|
|
18
|
+
console.error(' and write ~/.obto-bridge/config.json.');
|
|
18
19
|
console.error(' start Run the daemon (foreground).');
|
|
19
20
|
console.error(' status Print active thread/session bindings.');
|
|
20
21
|
console.error(' whoami Verify config and show your account info from the server.');
|
|
@@ -22,10 +23,12 @@ const usage = () => {
|
|
|
22
23
|
console.error(' logout Wipe local credentials at ~/.obto-bridge/config.json.');
|
|
23
24
|
console.error('');
|
|
24
25
|
console.error('Flags:');
|
|
25
|
-
console.error(' --version, -v
|
|
26
|
-
console.error(' --help, -h
|
|
27
|
-
console.error('');
|
|
28
|
-
console.error('
|
|
26
|
+
console.error(' --version, -v Print the installed package version.');
|
|
27
|
+
console.error(' --help, -h Show this help.');
|
|
28
|
+
console.error(' --username <name> (init only) Override the username derived from email.');
|
|
29
|
+
console.error(' --password <pwd> (init only) Set your password instead of auto-generating one.');
|
|
30
|
+
console.error(' --token <obto_…> (init only) Skip self-serve register, use this token.');
|
|
31
|
+
console.error(' --account <acc_…> (init only) Pair with --token for paste-in mode.');
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
const cmd = process.argv[2];
|
package/cli/init.js
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// `obto-bridge init` — interactive setup wizard.
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// `obto-bridge init` — interactive setup wizard.
|
|
4
|
+
//
|
|
5
|
+
// Default (>=0.1.0-beta.7): self-serve registration. Email is the only
|
|
6
|
+
// required input; username is derived from the email's local part, and a
|
|
7
|
+
// strong password is auto-generated and shown once. Posts to
|
|
8
|
+
// /api/bridge/register, saves the returned API token to
|
|
9
|
+
// ~/.obto-bridge/config.json (mode 0600).
|
|
10
|
+
//
|
|
11
|
+
// Overrides:
|
|
12
|
+
// --username <name> Use this instead of the derived username.
|
|
13
|
+
// --password <pwd> Use this instead of an auto-generated password.
|
|
14
|
+
// --token <obto_…> Skip registration entirely; paste an existing token.
|
|
15
|
+
// --account <acc_…> Pair with --token for paste-in mode.
|
|
7
16
|
|
|
8
17
|
const fs = require('fs');
|
|
9
18
|
const path = require('path');
|
|
10
19
|
const os = require('os');
|
|
20
|
+
const crypto = require('crypto');
|
|
11
21
|
const readline = require('readline');
|
|
12
22
|
|
|
13
23
|
const CONFIG_DIR = path.join(os.homedir(), '.obto-bridge');
|
|
@@ -19,6 +29,17 @@ const DEFAULTS = {
|
|
|
19
29
|
relayPermissions: true,
|
|
20
30
|
};
|
|
21
31
|
|
|
32
|
+
// argv after `obto-bridge init`.
|
|
33
|
+
const argv = process.argv.slice(3);
|
|
34
|
+
const flagValue = (name) => {
|
|
35
|
+
const i = argv.indexOf(name);
|
|
36
|
+
return i !== -1 && argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[i + 1] : null;
|
|
37
|
+
};
|
|
38
|
+
const cliToken = flagValue('--token');
|
|
39
|
+
const cliAccount = flagValue('--account');
|
|
40
|
+
const cliUsername = flagValue('--username');
|
|
41
|
+
const cliPassword = flagValue('--password');
|
|
42
|
+
|
|
22
43
|
const loadExisting = () => {
|
|
23
44
|
try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); }
|
|
24
45
|
catch (_) { return {}; }
|
|
@@ -35,6 +56,45 @@ const ask = (rl, prompt, def) =>
|
|
|
35
56
|
});
|
|
36
57
|
});
|
|
37
58
|
|
|
59
|
+
// Derive a basicAuthUser-shaped username from email's local part. Mirrors
|
|
60
|
+
// the server-side derivation in registerSubmit so the username we sign in
|
|
61
|
+
// with matches what we display to the user inline.
|
|
62
|
+
const deriveUsername = (email) => {
|
|
63
|
+
const local = String(email || '').split('@')[0].toLowerCase();
|
|
64
|
+
let u = local.replace(/[^a-z0-9_-]+/g, '-').replace(/-+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40);
|
|
65
|
+
if (u.length < 3) u = 'user-' + crypto.randomBytes(3).toString('hex');
|
|
66
|
+
return u;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// 12-char password, no ambiguous chars (0/O, 1/l/I), grouped as 4-4-4
|
|
70
|
+
// for readability and easy copy-paste. Backed by crypto.randomBytes.
|
|
71
|
+
const generatePassword = () => {
|
|
72
|
+
const chars = 'abcdefghjkmnpqrstuvwxyz23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
|
|
73
|
+
const bytes = crypto.randomBytes(12);
|
|
74
|
+
let out = '';
|
|
75
|
+
for (let i = 0; i < 12; i++) out += chars[bytes[i] % chars.length];
|
|
76
|
+
return out.slice(0, 4) + '-' + out.slice(4, 8) + '-' + out.slice(8, 12);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const registerSelfServe = async ({ baseUrl, originHost, email, username, password }) => {
|
|
80
|
+
const url = baseUrl.replace(/\/$/, '') + '/api/bridge/register';
|
|
81
|
+
const res = await fetch(url, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: {
|
|
84
|
+
Accept: 'application/json',
|
|
85
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
86
|
+
'OBTO-ORIGIN-HOST': originHost,
|
|
87
|
+
},
|
|
88
|
+
body: new URLSearchParams({ email, username, password }).toString(),
|
|
89
|
+
cache: 'no-store',
|
|
90
|
+
redirect: 'manual',
|
|
91
|
+
});
|
|
92
|
+
const text = await res.text();
|
|
93
|
+
let data;
|
|
94
|
+
try { data = JSON.parse(text); } catch (_) { data = { _rawBody: text }; }
|
|
95
|
+
return { status: res.status, ok: res.ok, data };
|
|
96
|
+
};
|
|
97
|
+
|
|
38
98
|
const validateAgainstServer = async (cfg) => {
|
|
39
99
|
const url = cfg.baseUrl.replace(/\/$/, '') + '/api/bridge/whoami';
|
|
40
100
|
const res = await fetch(url, {
|
|
@@ -55,25 +115,92 @@ const validateAgainstServer = async (cfg) => {
|
|
|
55
115
|
const main = async () => {
|
|
56
116
|
console.log('OBTO Agent Bridge — setup');
|
|
57
117
|
console.log('-------------------------');
|
|
58
|
-
console.log('Need credentials? Email support@obto.co for an invite.');
|
|
59
118
|
console.log('Config will be written to: ' + CONFIG_PATH);
|
|
60
119
|
console.log('');
|
|
61
120
|
|
|
62
121
|
const existing = loadExisting();
|
|
63
122
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
64
123
|
|
|
65
|
-
// Server base URL and OBTO-ORIGIN-HOST are constants of the platform, not
|
|
66
|
-
// per-user config — every daemon talks to the same bridge app. They are no
|
|
67
|
-
// longer prompted. Advanced / self-hosted users can still override them via
|
|
68
|
-
// the BRIDGE_BASE_URL / BRIDGE_ORIGIN_HOST env vars or by editing config.json.
|
|
69
124
|
const baseUrl = process.env.BRIDGE_BASE_URL || existing.baseUrl || DEFAULTS.baseUrl;
|
|
70
125
|
const originHost = process.env.BRIDGE_ORIGIN_HOST || existing.originHost || DEFAULTS.originHost;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
126
|
+
|
|
127
|
+
let accountId = existing.accountId || '';
|
|
128
|
+
let apiToken = existing.apiToken || '';
|
|
129
|
+
|
|
130
|
+
// Pick the credential path. Precedence:
|
|
131
|
+
// 1. --token + --account flags (machine paste-in, scripted).
|
|
132
|
+
// 2. Existing config from a prior init.
|
|
133
|
+
// 3. Self-serve registration via /api/bridge/register (default).
|
|
134
|
+
const haveCliPaste = !!(cliToken && cliAccount);
|
|
135
|
+
const haveExisting = !!(existing.accountId && existing.apiToken);
|
|
136
|
+
|
|
137
|
+
let registeredUser = '';
|
|
138
|
+
let registeredPassword = '';
|
|
139
|
+
|
|
140
|
+
if (haveCliPaste) {
|
|
141
|
+
apiToken = cliToken;
|
|
142
|
+
accountId = cliAccount;
|
|
143
|
+
console.log('Using credentials from --token / --account flags.');
|
|
144
|
+
console.log('');
|
|
145
|
+
} else if (haveExisting) {
|
|
146
|
+
console.log('Existing config found:');
|
|
147
|
+
console.log(' Account: ' + existing.accountId);
|
|
148
|
+
console.log(' Token: ' + (existing.apiToken || '').slice(0, 10) + '…');
|
|
149
|
+
console.log('Reusing those credentials. To re-register from scratch, remove ' + CONFIG_PATH + ' first.');
|
|
150
|
+
console.log('');
|
|
151
|
+
} else {
|
|
152
|
+
console.log('Create a free account (no card needed):');
|
|
153
|
+
const email = await ask(rl, ' Email', '');
|
|
154
|
+
if (!email || email.indexOf('@') === -1) {
|
|
155
|
+
console.error('error: a valid email is required.');
|
|
156
|
+
rl.close();
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
const username = cliUsername || deriveUsername(email);
|
|
160
|
+
const password = cliPassword || generatePassword();
|
|
161
|
+
console.log('');
|
|
162
|
+
console.log(' Username: @' + username + (cliUsername ? '' : ' (derived from email)'));
|
|
163
|
+
if (!cliPassword) {
|
|
164
|
+
console.log(' Password: ' + password + ' (auto-generated)');
|
|
165
|
+
console.log('');
|
|
166
|
+
console.log(' ⚠ SAVE THIS PASSWORD — you will need it to sign in to the web UI.');
|
|
167
|
+
console.log(' It is shown once here and never again. Reset later from your account page.');
|
|
168
|
+
}
|
|
169
|
+
console.log('');
|
|
170
|
+
console.log('Creating account at ' + baseUrl + ' ...');
|
|
171
|
+
let r;
|
|
172
|
+
try {
|
|
173
|
+
r = await registerSelfServe({ baseUrl, originHost, email, username, password });
|
|
174
|
+
} catch (e) {
|
|
175
|
+
console.error('error: registration request failed: ' + (e && e.message ? e.message : e));
|
|
176
|
+
console.error(' (network problem? you can also paste an existing token via:');
|
|
177
|
+
console.error(' `obto-bridge init --token <obto_…> --account <acc_…>`)');
|
|
178
|
+
rl.close();
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
if (!r.ok || !r.data || !r.data.ok) {
|
|
182
|
+
const msg = (r.data && (r.data.error || r.data._rawBody)) || ('HTTP ' + r.status);
|
|
183
|
+
console.error('error: registration rejected: ' + msg);
|
|
184
|
+
console.error(' (username taken? rerun with `obto-bridge init --username <different>`)');
|
|
185
|
+
rl.close();
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
accountId = r.data.accountId;
|
|
189
|
+
apiToken = r.data.apiToken;
|
|
190
|
+
registeredUser = r.data.basicAuthUser || username;
|
|
191
|
+
registeredPassword = password;
|
|
192
|
+
console.log(' ✓ Free account created.');
|
|
193
|
+
console.log(' Account: ' + accountId);
|
|
194
|
+
console.log(' User: @' + registeredUser);
|
|
195
|
+
console.log(' Plan: ' + (r.data.plan || 'free'));
|
|
196
|
+
console.log('');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const agentId = await ask(rl, 'Agent name (e.g. my-mac)', existing.agentId || os.hostname().split('.')[0] || 'unnamed-agent');
|
|
74
200
|
const projectDir = await ask(rl, 'Project working dir', existing.projectDir || process.cwd());
|
|
75
|
-
const agentAns = await ask(rl, 'Coding agent — claude
|
|
76
|
-
const
|
|
201
|
+
const agentAns = await ask(rl, 'Coding agent fallback — claude / codex / opencode', existing.agent || 'claude');
|
|
202
|
+
const agentLow = String(agentAns).trim().toLowerCase();
|
|
203
|
+
const agent = ['claude', 'codex', 'opencode'].indexOf(agentLow) !== -1 ? agentLow : 'claude';
|
|
77
204
|
const relayAns = await ask(rl, 'Relay permission requests via bridge? (y/n)', existing.relayPermissions !== false ? 'y' : 'n');
|
|
78
205
|
const relayPermissions = String(relayAns).toLowerCase().startsWith('y');
|
|
79
206
|
|
|
@@ -133,12 +260,17 @@ const main = async () => {
|
|
|
133
260
|
const a = result.parsed.account;
|
|
134
261
|
console.log(' ✓ Authenticated as @' + a.basicAuthUser + ' (' + a.accountId + ', status: ' + a.status + ')');
|
|
135
262
|
console.log('');
|
|
136
|
-
|
|
263
|
+
// The OBTO platform's root URL bounces unauthenticated users to /login.bto;
|
|
264
|
+
// /api/view is the canonical bridge entry point that serves either the
|
|
265
|
+
// sign-in form (unauthenticated) or the threads UI (authenticated).
|
|
266
|
+
const signInUrl = baseUrl.replace(/\/$/, '') + '/api/view';
|
|
267
|
+
console.log('Sign in at ' + signInUrl + ' as @' + a.basicAuthUser + (registeredPassword ? ' (password above)' : '') + '.');
|
|
268
|
+
console.log('Run: obto-bridge start');
|
|
137
269
|
return;
|
|
138
270
|
}
|
|
139
271
|
|
|
140
272
|
if (result.status === 401) {
|
|
141
|
-
console.error(' ✗ Server rejected the API token (HTTP 401).
|
|
273
|
+
console.error(' ✗ Server rejected the API token (HTTP 401). Re-run `obto-bridge init` to reset.');
|
|
142
274
|
process.exit(2);
|
|
143
275
|
}
|
|
144
276
|
if (result.status === 403) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obtoai/agent-bridge",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.7",
|
|
4
4
|
"description": "Local consumer for the OBTO Agent Bridge. Receives bridge events over SSE and drives a coding agent (Claude Code or OpenAI Codex) on your machine.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "OBTO Inc.",
|