borgmcp 0.9.57 → 0.9.58

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
@@ -1,250 +1,178 @@
1
- # Borg MCP Client
1
+ # Borg MCP
2
2
 
3
- Local MCP client that syncs your `~/.claude/CLAUDE.md` file to the Borg MCP cloud service.
3
+ Multi-agent coordination for AI coding agents.
4
4
 
5
- ## Features
5
+ Borg MCP lets Claude Code and Codex sessions join the same project coordination space, take on named roles, and coordinate through one shared activity log. That shared space is a **cube**. Each running agent session is a **drone**. Drones operate under **roles** such as Builder, Code Reviewer, Coordinator, UX Expert, or Security Auditor.
6
6
 
7
- - **Automatic Sync**: Watches `~/.claude/CLAUDE.md` and auto-syncs changes to cloud
8
- - **Conflict Detection**: Never lose data - conflicts are detected and backed up for manual resolution
9
- - **Secure Storage**: OAuth tokens stored in OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service)
10
- - **Offline Support**: Exponential backoff retry logic handles network failures gracefully
11
- - **Google OAuth**: Secure authentication via Google OAuth 2.0 device code flow
7
+ ## What you get
12
8
 
13
- ## Prerequisites
9
+ - Shared cube context: cube directive, role playbooks, roster, and recent log entries.
10
+ - Role-based coordination: assign drones to roles and keep their instructions in one place.
11
+ - Live activity log: post status, dispatches, review gates, and findings without copy-paste handoffs.
12
+ - Wake-path support: inbox monitoring and stream diagnostics help keep agent sessions responsive.
13
+ - Claude Code and Codex launcher support: start one or more agent sessions from the same repo.
14
+ - Dashboard support: manage cubes, roles, drones, and logs at <https://borgmcp.ai/dashboard>.
14
15
 
15
- 1. **Node.js 18+** with npm
16
- 2. **Google OAuth Credentials** (required for authentication)
17
- - Client ID
18
- - Client Secret
19
- 3. **Borg MCP Subscription** ($1/month with 7-day trial)
16
+ ## Pricing
20
17
 
21
- ## Installation
18
+ - Free tier: 1 cube, 3 drones, 100 requests/hour.
19
+ - Cube tier: $1/cube/month, unlimited cubes, unlimited drones, 1000 requests/hour.
22
20
 
23
- ### 1. Install Dependencies
21
+ Free tier is permanent. No trial is required.
22
+
23
+ ## Install
24
24
 
25
25
  ```bash
26
- cd client
27
- npm install
26
+ npm install -g borgmcp
28
27
  ```
29
28
 
30
- ### 2. Build the Client
29
+ Verify the install:
31
30
 
32
31
  ```bash
33
- npm run build
32
+ borg --version
33
+ borg --help
34
34
  ```
35
35
 
36
- ### 3. Set Up Environment Variables
36
+ ## First-time setup
37
37
 
38
- Create a `.env` file or export these variables:
38
+ Run the setup wizard:
39
39
 
40
40
  ```bash
41
- export GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
42
- export GOOGLE_CLIENT_SECRET="your-client-secret"
43
- ```
44
-
45
- ### 4. Configure Claude Code MCP Settings
46
-
47
- Add the client to your Claude Code MCP configuration file at `~/.config/claude/mcp_config.json`:
48
-
49
- ```json
50
- {
51
- "mcpServers": {
52
- "borg-mcp": {
53
- "command": "node",
54
- "args": [
55
- "/absolute/path/to/mcp-claude-md-storage/client/dist/index.js"
56
- ],
57
- "env": {
58
- "GOOGLE_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
59
- "GOOGLE_CLIENT_SECRET": "your-client-secret"
60
- }
61
- }
62
- }
63
- }
41
+ borg setup
64
42
  ```
65
43
 
66
- **Important**: Replace `/absolute/path/to/` with the actual path to your project directory.
67
-
68
- ## First-Time Setup
69
-
70
- ### Authentication Flow
44
+ The wizard signs you in with Google, checks your subscription access, and registers Borg MCP with the supported agent CLIs installed on your machine.
71
45
 
72
- On first run, the client will prompt you to authenticate:
46
+ If both Claude Code and Codex are installed, use `--cli` when launching or assimilating if you want to choose explicitly:
73
47
 
74
- ```
75
- 🔐 Borg MCP Authentication
76
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
77
-
78
- 📱 Please visit: https://www.google.com/device
79
- 🔑 And enter code: XXXX-XXXX
80
-
81
- Waiting for authorization...
48
+ ```bash
49
+ borg assimilate --cli claude
50
+ borg assimilate --cli codex
82
51
  ```
83
52
 
84
- 1. Visit the URL shown
85
- 2. Enter the device code
86
- 3. Authorize with your Google account
87
- 4. Tokens are securely stored in your OS keychain
53
+ ## Join a cube
88
54
 
89
- ### Subscription
55
+ From inside your project repo:
90
56
 
91
- After authentication, create a subscription:
92
-
93
- ```
94
- Tools available:
95
- - subscribe: Create Stripe checkout session ($2/month, 7-day trial)
57
+ ```bash
58
+ borg assimilate
96
59
  ```
97
60
 
98
- Use the `subscribe` tool in Claude Code to get a checkout URL and complete payment.
61
+ Borg derives a cube name from the repo, creates or joins that cube, registers the current session as a drone, and launches the selected agent CLI with cube context.
99
62
 
100
- ## How It Works
63
+ To start another drone in a sibling worktree:
101
64
 
102
- ### Initial Sync
65
+ ```bash
66
+ borg assimilate builder --worktree drone-2
67
+ ```
103
68
 
104
- On startup, the client performs an initial sync:
69
+ To join under a specific role:
105
70
 
106
- - If remote has content and local is empty → **pulls from cloud**
107
- - If local has content and remote is empty → **pushes to cloud**
108
- - If both exist and match → **no sync needed**
109
- - If both exist and differ → **conflict detection**
71
+ ```bash
72
+ borg assimilate code-reviewer --cli codex
73
+ ```
110
74
 
111
- ### Automatic File Watching
75
+ ## Core MCP tools
112
76
 
113
- The client watches `~/.claude/CLAUDE.md` for changes:
77
+ After assimilation, the agent session has `borg:` tools available:
114
78
 
115
- - **Debouncing**: 1 second delay to batch rapid edits
116
- - **Content Hashing**: Only syncs when content actually changes (ignores temp saves)
117
- - **Real-time Upload**: Changes auto-sync to cloud within seconds
79
+ - `borg:regen` - Refresh cube context, role instructions, roster, and recent log.
80
+ - `borg:log` - Append to the shared activity log. Can broadcast or direct messages to drones/roles.
81
+ - `borg:read-log` - Read recent log entries, optionally since an entry id or timestamp.
82
+ - `borg:ack` - Acknowledge a routed log entry without adding noise to the activity log.
83
+ - `borg:roster` - List drones and liveness markers in the cube.
84
+ - `borg:stream-status` - Diagnose the SSE/inbox wake path.
85
+ - `borg:cube`, `borg:role`, `borg:whoami` - Inspect current cube, role, and identity.
86
+ - `borg:create-cube`, `borg:update-cube`, `borg:delete-cube` - Manage cubes.
87
+ - `borg:create-role`, `borg:update-role`, `borg:reassign-drone` - Manage roles and drone assignments.
88
+ - `borg:apply-template`, `borg:sync-roles`, `borg:patch-taxonomy-class` - Bootstrap and maintain role/message-taxonomy templates.
118
89
 
119
- ### Conflict Resolution
90
+ ## Typical two-agent flow
120
91
 
121
- If both local and remote files have been modified since last sync:
92
+ 1. Install and run setup.
122
93
 
123
- ```
124
- ⚠️ SYNC CONFLICT DETECTED
125
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
94
+ ```bash
95
+ npm install -g borgmcp
96
+ borg setup
97
+ ```
126
98
 
127
- Both local and remote CLAUDE.md have been modified.
128
- Local file: ~/.claude/CLAUDE.md
129
- Remote backup: ~/.claude/CLAUDE.md.conflict
99
+ 2. Open a repo and assimilate the first drone.
130
100
 
131
- Please resolve manually by choosing one version or merging them.
132
- Delete the .conflict file once resolved.
133
- ```
101
+ ```bash
102
+ cd ~/code/my-app
103
+ borg assimilate --cli claude
104
+ ```
134
105
 
135
- **Resolution Steps**:
136
- 1. Compare `~/.claude/CLAUDE.md` (local) with `~/.claude/CLAUDE.md.conflict` (remote)
137
- 2. Manually merge or choose one version
138
- 3. Delete `.conflict` file when done
106
+ 3. Open a second terminal in the same repo and assimilate another drone.
139
107
 
140
- ## Available Tools
108
+ ```bash
109
+ cd ~/code/my-app
110
+ borg assimilate code-reviewer --cli codex
111
+ ```
141
112
 
142
- Once configured, these tools are available in Claude Code:
113
+ 4. In the agent session, refresh context and coordinate through the log.
143
114
 
144
- - **subscribe**: Create Stripe checkout session for $1/month (7-day free trial)
145
- - **get_claude_md**: Manually fetch CLAUDE.md from cloud
146
- - **set_claude_md**: Manually upload CLAUDE.md to cloud
147
- - **delete_claude_md**: Delete CLAUDE.md from cloud
148
- - **subscription_status**: Check your current subscription status
149
- - **export_data**: Export all your data (GDPR compliance)
150
- - **sync_now**: Force immediate sync with cloud
115
+ ```text
116
+ borg:regen
117
+ borg:roster
118
+ borg:log "STARTING: review feat/login"
119
+ ```
151
120
 
152
121
  ## Troubleshooting
153
122
 
154
- ### "Authentication required" Error
123
+ ### Authentication expired
155
124
 
156
- Your OAuth token has expired. Re-run the client and follow the authentication flow again.
125
+ Run setup again:
157
126
 
158
127
  ```bash
159
- # Manually trigger re-auth by clearing tokens
160
- node dist/index.js
128
+ borg setup
161
129
  ```
162
130
 
163
- ### Sync Conflicts Not Resolving
131
+ ### Setup says subscription was not found yet
164
132
 
165
- 1. Check that `.conflict` file exists at `~/.claude/CLAUDE.md.conflict`
166
- 2. Manually merge changes between local and conflict file
167
- 3. Delete `.conflict` file to clear the conflict state
133
+ If you just subscribed, activation can take a moment. The setup flow retries automatically and offers a recheck option.
168
134
 
169
- ### File Watcher Not Detecting Changes
135
+ ### Both Claude Code and Codex are installed
170
136
 
171
- - Verify `~/.claude/CLAUDE.md` path exists
172
- - Check file permissions (must be readable/writable)
173
- - Some editors use atomic writes (rename temp file) which may delay detection
137
+ Choose one:
174
138
 
175
- ### Network Failures
176
-
177
- The client automatically retries with exponential backoff:
178
- - Retry 1: 1 second delay
179
- - Retry 2: 2 second delay
180
- - Retry 3: 4 second delay
181
- - Max retries: 3
182
-
183
- If network is down, edits are queued and will sync once connection is restored.
184
-
185
- ## Security
139
+ ```bash
140
+ borg --cli claude
141
+ borg --cli codex
142
+ borg assimilate --cli claude
143
+ borg assimilate --cli codex
144
+ ```
186
145
 
187
- ### Token Storage
146
+ ### Not connected to a cube
188
147
 
189
- OAuth tokens are stored using platform-specific secure credential managers:
148
+ Run assimilation from your project repo:
190
149
 
191
- - **macOS**: Keychain Access
192
- - **Windows**: Credential Manager
193
- - **Linux**: Secret Service API (libsecret)
150
+ ```bash
151
+ borg assimilate
152
+ ```
194
153
 
195
- Tokens are **never** written to disk in plain text.
154
+ Then call `borg:regen` in the agent session.
196
155
 
197
- ### Authentication
156
+ ### Wake path warning
198
157
 
199
- - Uses Google OAuth 2.0 device code flow (designed for CLI apps)
200
- - ID tokens are automatically refreshed when expired
201
- - Auth tokens are automatically injected into all remote API calls
158
+ If `borg:regen` or `borg:stream-status` reports a broken wake path, arm the inbox monitor command it prints. The monitor is what wakes the agent session when another drone posts to the cube.
202
159
 
203
160
  ## Development
204
161
 
205
- ### Running from Source
206
-
207
- ```bash
208
- npm run dev
209
- ```
210
-
211
- ### Building
212
-
213
- ```bash
214
- npm run build
215
- ```
216
-
217
- ### Testing
162
+ From the repository root:
218
163
 
219
164
  ```bash
220
165
  npm test
166
+ cd client && npx tsc --noEmit
221
167
  ```
222
168
 
223
- ## Architecture
169
+ Build the CLI package:
224
170
 
225
- ```
226
- ┌─────────────────┐
227
- │ Claude Code │
228
- └────────┬────────┘
229
- │ stdio MCP
230
-
231
- ┌────────▼────────┐
232
- │ Borg MCP │◄──┐
233
- │ Client │ │ File Watcher
234
- └────────┬────────┘ │ (chokidar)
235
- │ │
236
- │ ┌──▼──────────────┐
237
- │ │ ~/.claude/ │
238
- │ │ CLAUDE.md │
239
- │ └─────────────────┘
240
- │ HTTPS + Auth
241
-
242
- ┌────────▼────────┐
243
- │ api.borgmcp.ai │
244
- │ (Remote Server) │
245
- └─────────────────┘
171
+ ```bash
172
+ npm run build:cli
246
173
  ```
247
174
 
248
- ## License
175
+ ## Links
249
176
 
250
- MIT
177
+ - Product site: <https://borgmcp.ai>
178
+ - Dashboard: <https://borgmcp.ai/dashboard>
@@ -0,0 +1,52 @@
1
+ /**
2
+ * gh#557 — environment / capability detection for remote-terminal auth.
3
+ *
4
+ * The default OAuth path (auth.ts) opens a browser and listens on a
5
+ * localhost-loopback callback server. Both assumptions break on a remote
6
+ * terminal: there's no browser to open, and the loopback URL Google would
7
+ * redirect to is unreachable from the user's actual browser on another
8
+ * machine. These pure helpers decide when to fall back to the RFC 8628
9
+ * device-grant flow (no browser) and when the OS keychain is unavailable
10
+ * (so a keychain-less token store must be used instead).
11
+ *
12
+ * Kept free of side effects (env + platform are injected) so the decision
13
+ * logic is unit-testable without a real display / SSH session / keychain.
14
+ */
15
+ export interface BrowserEnvProbe {
16
+ /** A snapshot of the relevant environment variables (defaults to process.env). */
17
+ env: NodeJS.ProcessEnv;
18
+ /** The platform string (defaults to process.platform). */
19
+ platform: NodeJS.Platform;
20
+ }
21
+ /**
22
+ * Decide whether the current environment lacks a usable local browser, so
23
+ * the loopback OAuth flow can't work and the device-grant flow should be
24
+ * used instead.
25
+ *
26
+ * Decision order (first match wins):
27
+ * 1. BORG_FORCE_BROWSER on → false (escape hatch: operator has an
28
+ * SSH-forwarded / X-forwarded browser and wants the loopback flow)
29
+ * 2. BORG_NO_BROWSER on → true (explicit opt-in to device flow)
30
+ * 3. SSH session → true (remote terminal — even on a Mac the
31
+ * browser that opens is on the *server*, unreachable to the user)
32
+ * 4. container marker → true (headless by construction)
33
+ * 5. Linux without any display (no DISPLAY, no WAYLAND_DISPLAY) → true
34
+ * 6. otherwise → false (desktop macOS/Windows, or Linux with
35
+ * a display — the loopback flow works)
36
+ *
37
+ * The `--no-browser` / `--device` CLI flag is surfaced by the caller as
38
+ * BORG_NO_BROWSER (or by passing an env with it set) so there's a single
39
+ * decision funnel.
40
+ */
41
+ export declare function isNoBrowserEnv(probe?: Partial<BrowserEnvProbe>): boolean;
42
+ /**
43
+ * Returns true when the OS keychain can be written to and read from. The
44
+ * round-trip is injectable so callers/tests can supply a deterministic
45
+ * probe; in production the default probe touches the real keychain.
46
+ *
47
+ * Any thrown error from the probe (no Secret Service, locked keychain,
48
+ * permission denial) is treated as "unavailable" — the caller then selects
49
+ * the encrypted-file fallback store.
50
+ */
51
+ export declare function isKeyringAvailable(roundTrip?: () => Promise<void>): Promise<boolean>;
52
+ //# sourceMappingURL=auth-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-env.d.ts","sourceRoot":"","sources":["../src/auth-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B;AAaD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAsBxE;AAsBD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,GAAE,MAAM,OAAO,CAAC,IAAI,CAA2B,GACvD,OAAO,CAAC,OAAO,CAAC,CAOlB"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * gh#557 — environment / capability detection for remote-terminal auth.
3
+ *
4
+ * The default OAuth path (auth.ts) opens a browser and listens on a
5
+ * localhost-loopback callback server. Both assumptions break on a remote
6
+ * terminal: there's no browser to open, and the loopback URL Google would
7
+ * redirect to is unreachable from the user's actual browser on another
8
+ * machine. These pure helpers decide when to fall back to the RFC 8628
9
+ * device-grant flow (no browser) and when the OS keychain is unavailable
10
+ * (so a keychain-less token store must be used instead).
11
+ *
12
+ * Kept free of side effects (env + platform are injected) so the decision
13
+ * logic is unit-testable without a real display / SSH session / keychain.
14
+ */
15
+ import { AsyncEntry } from '@napi-rs/keyring';
16
+ /**
17
+ * A BORG_* toggle is "on" only when present and not one of the falsy
18
+ * spellings. Mirrors how the rest of the client reads boolean env vars:
19
+ * an unset var and the explicit "0"/"false"/"" spellings are all off.
20
+ */
21
+ function envToggleOn(value) {
22
+ if (value === undefined)
23
+ return false;
24
+ const v = value.trim().toLowerCase();
25
+ return v !== '' && v !== '0' && v !== 'false' && v !== 'no';
26
+ }
27
+ /**
28
+ * Decide whether the current environment lacks a usable local browser, so
29
+ * the loopback OAuth flow can't work and the device-grant flow should be
30
+ * used instead.
31
+ *
32
+ * Decision order (first match wins):
33
+ * 1. BORG_FORCE_BROWSER on → false (escape hatch: operator has an
34
+ * SSH-forwarded / X-forwarded browser and wants the loopback flow)
35
+ * 2. BORG_NO_BROWSER on → true (explicit opt-in to device flow)
36
+ * 3. SSH session → true (remote terminal — even on a Mac the
37
+ * browser that opens is on the *server*, unreachable to the user)
38
+ * 4. container marker → true (headless by construction)
39
+ * 5. Linux without any display (no DISPLAY, no WAYLAND_DISPLAY) → true
40
+ * 6. otherwise → false (desktop macOS/Windows, or Linux with
41
+ * a display — the loopback flow works)
42
+ *
43
+ * The `--no-browser` / `--device` CLI flag is surfaced by the caller as
44
+ * BORG_NO_BROWSER (or by passing an env with it set) so there's a single
45
+ * decision funnel.
46
+ */
47
+ export function isNoBrowserEnv(probe) {
48
+ const env = probe?.env ?? process.env;
49
+ const platform = probe?.platform ?? process.platform;
50
+ // 1. Explicit force-browser escape hatch wins over every no-browser signal.
51
+ if (envToggleOn(env.BORG_FORCE_BROWSER))
52
+ return false;
53
+ // 2. Explicit opt-in to the no-browser/device flow.
54
+ if (envToggleOn(env.BORG_NO_BROWSER))
55
+ return true;
56
+ // 3. SSH session — the terminal is remote, so any browser we open is on
57
+ // the far end and the loopback redirect is unreachable to the user.
58
+ if (env.SSH_TTY || env.SSH_CONNECTION || env.SSH_CLIENT)
59
+ return true;
60
+ // 4. Container (systemd/Docker/podman set `container=`); headless by build.
61
+ if (env.container)
62
+ return true;
63
+ // 5. Headless Linux: no X11 and no Wayland display = no browser to open.
64
+ if (platform === 'linux' && !env.DISPLAY && !env.WAYLAND_DISPLAY)
65
+ return true;
66
+ // 6. Desktop (macOS/Windows) or Linux-with-display: loopback flow is fine.
67
+ return false;
68
+ }
69
+ /**
70
+ * The probe round-trip used to decide whether the OS keychain is usable.
71
+ * Performs a real set/get/delete against a throwaway account so a missing
72
+ * Secret Service (headless Linux), a locked keychain, or any other backend
73
+ * failure surfaces as a thrown error rather than a later mid-auth crash.
74
+ */
75
+ async function defaultKeyringRoundTrip() {
76
+ const PROBE_SERVICE = 'borg-mcp';
77
+ const PROBE_ACCOUNT = '__borg_keyring_probe__';
78
+ const entry = new AsyncEntry(PROBE_SERVICE, PROBE_ACCOUNT);
79
+ await entry.setPassword('probe');
80
+ await entry.getPassword();
81
+ // Best-effort cleanup; a failed delete still proves the keychain works.
82
+ try {
83
+ await entry.deletePassword();
84
+ }
85
+ catch {
86
+ /* leave the probe entry — its presence is harmless */
87
+ }
88
+ }
89
+ /**
90
+ * Returns true when the OS keychain can be written to and read from. The
91
+ * round-trip is injectable so callers/tests can supply a deterministic
92
+ * probe; in production the default probe touches the real keychain.
93
+ *
94
+ * Any thrown error from the probe (no Secret Service, locked keychain,
95
+ * permission denial) is treated as "unavailable" — the caller then selects
96
+ * the encrypted-file fallback store.
97
+ */
98
+ export async function isKeyringAvailable(roundTrip = defaultKeyringRoundTrip) {
99
+ try {
100
+ await roundTrip();
101
+ return true;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
107
+ //# sourceMappingURL=auth-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-env.js","sourceRoot":"","sources":["../src/auth-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAS9C;;;;GAIG;AACH,SAAS,WAAW,CAAC,KAAyB;IAC5C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,IAAI,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,cAAc,CAAC,KAAgC;IAC7D,MAAM,GAAG,GAAG,KAAK,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACtC,MAAM,QAAQ,GAAG,KAAK,EAAE,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;IAErD,4EAA4E;IAC5E,IAAI,WAAW,CAAC,GAAG,CAAC,kBAAkB,CAAC;QAAE,OAAO,KAAK,CAAC;IAEtD,oDAAoD;IACpD,IAAI,WAAW,CAAC,GAAG,CAAC,eAAe,CAAC;QAAE,OAAO,IAAI,CAAC;IAElD,wEAAwE;IACxE,uEAAuE;IACvE,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,cAAc,IAAI,GAAG,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAErE,4EAA4E;IAC5E,IAAI,GAAG,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE/B,yEAAyE;IACzE,IAAI,QAAQ,KAAK,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,eAAe;QAAE,OAAO,IAAI,CAAC;IAE9E,2EAA2E;IAC3E,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,uBAAuB;IACpC,MAAM,aAAa,GAAG,UAAU,CAAC;IACjC,MAAM,aAAa,GAAG,wBAAwB,CAAC;IAC/C,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IAC3D,MAAM,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACjC,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC;IAC1B,wEAAwE;IACxE,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,sDAAsD;IACxD,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,YAAiC,uBAAuB;IAExD,IAAI,CAAC;QACH,MAAM,SAAS,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
package/dist/auth.d.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  * 6. Exchange code for tokens
12
12
  * 7. Store tokens securely in OS keychain
13
13
  */
14
+ import { type DeviceAuthConfig, type DeviceAuthDeps } from './device-auth.js';
14
15
  /**
15
16
  * Refresh-token-revoked / expired — the user must re-run `borg setup`
16
17
  * to recover. Anchored on Google's canonical signal: HTTP 400 + JSON
@@ -43,21 +44,40 @@ export declare class RefreshTransientError extends Error {
43
44
  constructor(message: string);
44
45
  }
45
46
  /**
46
- * Perform complete OAuth authorization code flow with PKCE
47
- * Opens browser for user authorization
48
- * Stores tokens in OS keychain on success
47
+ * Decide whether to use the no-browser device-grant flow. An explicit
48
+ * `--no-browser`/`--device` (surfaced as opts.noBrowser) wins; otherwise
49
+ * auto-detect via isNoBrowserEnv (SSH session, container, headless Linux).
50
+ */
51
+ export declare function shouldUseDeviceFlow(opts?: {
52
+ noBrowser?: boolean;
53
+ }): boolean;
54
+ /**
55
+ * Assemble the device-grant OAuth config from the environment. Enforces the
56
+ * gh#557 ESCALATION-1 gate: a "TVs & Limited Input devices" client id must be
57
+ * available (baked-in once the operator creates it, or via GOOGLE_DEVICE_CLIENT_ID).
58
+ * Without one we fail with an actionable error instead of hitting Google with
59
+ * the Desktop client, which rejects /device/code as invalid_client.
60
+ */
61
+ export declare function buildDeviceAuthConfig(env?: NodeJS.ProcessEnv): DeviceAuthConfig;
62
+ /**
63
+ * The RFC 8628 device-grant flow (no browser). Prints a verification URL +
64
+ * user_code for the human to open on ANY device, polls Google until they
65
+ * authorize, then stores tokens in the selected backend. Network deps are
66
+ * injectable for tests; the device-poll state machine itself lives in
67
+ * device-auth.ts (fully unit-tested).
68
+ */
69
+ export declare function authenticateWithDeviceFlow(deps?: DeviceAuthDeps, env?: NodeJS.ProcessEnv): Promise<void>;
70
+ /**
71
+ * Perform the complete OAuth flow, choosing the browser/loopback flow or the
72
+ * no-browser device-grant flow based on the environment (gh#557). Stores
73
+ * tokens in the selected backend on success.
49
74
  *
50
- * Force-fresh-consent discipline: before requesting new consent, this function
51
- * revokes any existing refresh_token at Google's revocation endpoint AND uses
52
- * `prompt=consent select_account` (multi-value prompt) to force both the
53
- * consent screen and account picker. Together, these clear Google's
54
- * session-side memory of prior consent, ensuring Google issues a fresh
55
- * `refresh_token` rather than deduping the consent and returning only an
56
- * id_token. (Without this, Google's dedup behavior can leave the user with
57
- * an id_token but no refresh_token, forcing manual re-setup after the ~1h
58
- * id_token TTL expires.)
75
+ * @param opts.noBrowser force the device flow (`--no-browser`/`--device`);
76
+ * when omitted, the environment is auto-detected.
59
77
  */
60
- export declare function authenticateWithGoogle(): Promise<void>;
78
+ export declare function authenticateWithGoogle(opts?: {
79
+ noBrowser?: boolean;
80
+ }): Promise<void>;
61
81
  /**
62
82
  * Refresh ID token using refresh token (gh#34 hardened).
63
83
  *
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH;;;;;;;;;;;GAWG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;aACrB,SAAS,EAAE,MAAM;aAAkB,gBAAgB,CAAC,EAAE,MAAM;gBAA5D,SAAS,EAAE,MAAM,EAAkB,gBAAgB,CAAC,EAAE,MAAM,YAAA;CAQzF;AAED;;;;;;;;;;GAUG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AA8MD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAwE5D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAiGf"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;GAWG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;aACrB,SAAS,EAAE,MAAM;aAAkB,gBAAgB,CAAC,EAAE,MAAM;gBAA5D,SAAS,EAAE,MAAM,EAAkB,gBAAgB,CAAC,EAAE,MAAM,YAAA;CAQzF;AAED;;;;;;;;;;GAUG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAmTD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAE3E;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,gBAAgB,CAc5F;AAOD;;;;;;GAMG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,GAAE,cAA+C,EACrD,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC,IAAI,CAAC,CAoCf;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAK1F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAiGf"}