@obtoai/agent-bridge 0.1.0-beta.6 → 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 CHANGED
@@ -42,15 +42,15 @@ npx @obtoai/agent-bridge <command>
42
42
  obto-bridge init
43
43
  ```
44
44
 
45
- `init` prompts for an email, a username (3-40 chars, lowercase + digits + `_`/`-`), and a password, then creates the account inline via `POST /api/bridge/register` and saves the returned token to `~/.obto-bridge/config.json` (mode 0600). It then 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. Done — you can sign in to the web UI as `@username` with your password to start a thread.
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
46
 
47
- Already have a token from an earlier invite or another machine? Skip the registration step:
47
+ Overrides:
48
48
 
49
- ```bash
50
- obto-bridge init --token obto_xxxxxxxx --account acc_xxxxxxxx
51
- ```
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).
52
52
 
53
- The 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.)
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.)
54
54
 
55
55
  ### Agents (claude / codex / opencode)
56
56
 
@@ -25,6 +25,8 @@ const usage = () => {
25
25
  console.error('Flags:');
26
26
  console.error(' --version, -v Print the installed package version.');
27
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.');
28
30
  console.error(' --token <obto_…> (init only) Skip self-serve register, use this token.');
29
31
  console.error(' --account <acc_…> (init only) Pair with --token for paste-in mode.');
30
32
  };
package/cli/init.js CHANGED
@@ -2,20 +2,22 @@
2
2
 
3
3
  // `obto-bridge init` — interactive setup wizard.
4
4
  //
5
- // Default (>=0.1.0-beta.6): self-serve registration. Prompts for email,
6
- // username, password and POSTs to /api/bridge/register, which returns a
7
- // freshly-minted free-tier account + API token. No invite, no waiting.
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).
8
10
  //
9
- // Legacy paste-in (for users with an existing token, e.g. from before
10
- // self-serve, or migrating across machines): `obto-bridge init --token <obto_…>
11
- // --account <acc_…>` skips the register step and uses the supplied creds.
12
- //
13
- // Either way the wizard writes ~/.obto-bridge/config.json with mode 0600 and
14
- // then validates the token against the server via GET /api/bridge/whoami.
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.
15
16
 
16
17
  const fs = require('fs');
17
18
  const path = require('path');
18
19
  const os = require('os');
20
+ const crypto = require('crypto');
19
21
  const readline = require('readline');
20
22
 
21
23
  const CONFIG_DIR = path.join(os.homedir(), '.obto-bridge');
@@ -27,7 +29,7 @@ const DEFAULTS = {
27
29
  relayPermissions: true,
28
30
  };
29
31
 
30
- // argv after `obto-bridge init` — for --token / --account paste-in mode.
32
+ // argv after `obto-bridge init`.
31
33
  const argv = process.argv.slice(3);
32
34
  const flagValue = (name) => {
33
35
  const i = argv.indexOf(name);
@@ -35,6 +37,8 @@ const flagValue = (name) => {
35
37
  };
36
38
  const cliToken = flagValue('--token');
37
39
  const cliAccount = flagValue('--account');
40
+ const cliUsername = flagValue('--username');
41
+ const cliPassword = flagValue('--password');
38
42
 
39
43
  const loadExisting = () => {
40
44
  try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); }
@@ -52,12 +56,25 @@ const ask = (rl, prompt, def) =>
52
56
  });
53
57
  });
54
58
 
55
- // Password echoes (readline + raw mode is gnarly to do portably). Users doing
56
- // scripted/headless deploys should set BRIDGE_API_TOKEN env directly instead.
57
- const askPassword = (rl, prompt) =>
58
- new Promise((resolve) => {
59
- rl.question(prompt + ': ', (answer) => resolve(answer));
60
- });
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
+ };
61
78
 
62
79
  const registerSelfServe = async ({ baseUrl, originHost, email, username, password }) => {
63
80
  const url = baseUrl.replace(/\/$/, '') + '/api/bridge/register';
@@ -110,13 +127,16 @@ const main = async () => {
110
127
  let accountId = existing.accountId || '';
111
128
  let apiToken = existing.apiToken || '';
112
129
 
113
- // Pick the credential path. Order of precedence:
114
- // 1. --token + --account flags (machine-paste, scripted).
115
- // 2. Existing config from a prior init (carry over).
116
- // 3. Self-serve registration via /api/bridge/register (default for new users).
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).
117
134
  const haveCliPaste = !!(cliToken && cliAccount);
118
135
  const haveExisting = !!(existing.accountId && existing.apiToken);
119
136
 
137
+ let registeredUser = '';
138
+ let registeredPassword = '';
139
+
120
140
  if (haveCliPaste) {
121
141
  apiToken = cliToken;
122
142
  accountId = cliAccount;
@@ -131,8 +151,21 @@ const main = async () => {
131
151
  } else {
132
152
  console.log('Create a free account (no card needed):');
133
153
  const email = await ask(rl, ' Email', '');
134
- const username = await ask(rl, ' Username (3-40 chars: a-z, 0-9, _ or -)', '');
135
- const password = await askPassword(rl, ' Password (min 8 chars)');
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
+ }
136
169
  console.log('');
137
170
  console.log('Creating account at ' + baseUrl + ' ...');
138
171
  let r;
@@ -148,16 +181,18 @@ const main = async () => {
148
181
  if (!r.ok || !r.data || !r.data.ok) {
149
182
  const msg = (r.data && (r.data.error || r.data._rawBody)) || ('HTTP ' + r.status);
150
183
  console.error('error: registration rejected: ' + msg);
184
+ console.error(' (username taken? rerun with `obto-bridge init --username <different>`)');
151
185
  rl.close();
152
186
  process.exit(1);
153
187
  }
154
188
  accountId = r.data.accountId;
155
189
  apiToken = r.data.apiToken;
190
+ registeredUser = r.data.basicAuthUser || username;
191
+ registeredPassword = password;
156
192
  console.log(' ✓ Free account created.');
157
193
  console.log(' Account: ' + accountId);
158
- console.log(' User: @' + (r.data.basicAuthUser || username));
194
+ console.log(' User: @' + registeredUser);
159
195
  console.log(' Plan: ' + (r.data.plan || 'free'));
160
- console.log(' Sign in at ' + baseUrl + ' as @' + (r.data.basicAuthUser || username) + ' with the password you just set.');
161
196
  console.log('');
162
197
  }
163
198
 
@@ -225,7 +260,12 @@ const main = async () => {
225
260
  const a = result.parsed.account;
226
261
  console.log(' ✓ Authenticated as @' + a.basicAuthUser + ' (' + a.accountId + ', status: ' + a.status + ')');
227
262
  console.log('');
228
- console.log('Next: obto-bridge start');
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');
229
269
  return;
230
270
  }
231
271
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obtoai/agent-bridge",
3
- "version": "0.1.0-beta.6",
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.",