@termlink/cli 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.
- package/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/auth/login.d.ts +55 -0
- package/dist/auth/login.d.ts.map +1 -0
- package/dist/auth/login.js +238 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/oauth-defaults.d.ts +9 -0
- package/dist/auth/oauth-defaults.d.ts.map +1 -0
- package/dist/auth/oauth-defaults.js +9 -0
- package/dist/auth/oauth-defaults.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +319 -0
- package/dist/cli.js.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/claude/history.d.ts +15 -0
- package/dist/providers/claude/history.d.ts.map +1 -0
- package/dist/providers/claude/history.js +71 -0
- package/dist/providers/claude/history.js.map +1 -0
- package/dist/providers/claude/input-queue.d.ts +8 -0
- package/dist/providers/claude/input-queue.d.ts.map +1 -0
- package/dist/providers/claude/input-queue.js +34 -0
- package/dist/providers/claude/input-queue.js.map +1 -0
- package/dist/providers/claude/mapper.d.ts +21 -0
- package/dist/providers/claude/mapper.d.ts.map +1 -0
- package/dist/providers/claude/mapper.js +136 -0
- package/dist/providers/claude/mapper.js.map +1 -0
- package/dist/providers/claude/provider.d.ts +46 -0
- package/dist/providers/claude/provider.d.ts.map +1 -0
- package/dist/providers/claude/provider.js +148 -0
- package/dist/providers/claude/provider.js.map +1 -0
- package/dist/providers/claude/session.d.ts +27 -0
- package/dist/providers/claude/session.d.ts.map +1 -0
- package/dist/providers/claude/session.js +353 -0
- package/dist/providers/claude/session.js.map +1 -0
- package/dist/providers/codex/app-server.d.ts +24 -0
- package/dist/providers/codex/app-server.d.ts.map +1 -0
- package/dist/providers/codex/app-server.js +79 -0
- package/dist/providers/codex/app-server.js.map +1 -0
- package/dist/providers/codex/command.d.ts +12 -0
- package/dist/providers/codex/command.d.ts.map +1 -0
- package/dist/providers/codex/command.js +60 -0
- package/dist/providers/codex/command.js.map +1 -0
- package/dist/providers/codex/history.d.ts +5 -0
- package/dist/providers/codex/history.d.ts.map +1 -0
- package/dist/providers/codex/history.js +33 -0
- package/dist/providers/codex/history.js.map +1 -0
- package/dist/providers/codex/mapper.d.ts +13 -0
- package/dist/providers/codex/mapper.d.ts.map +1 -0
- package/dist/providers/codex/mapper.js +83 -0
- package/dist/providers/codex/mapper.js.map +1 -0
- package/dist/providers/codex/protocol.d.ts +223 -0
- package/dist/providers/codex/protocol.d.ts.map +1 -0
- package/dist/providers/codex/protocol.js +11 -0
- package/dist/providers/codex/protocol.js.map +1 -0
- package/dist/providers/codex/provider.d.ts +28 -0
- package/dist/providers/codex/provider.d.ts.map +1 -0
- package/dist/providers/codex/provider.js +116 -0
- package/dist/providers/codex/provider.js.map +1 -0
- package/dist/providers/codex/rpc.d.ts +23 -0
- package/dist/providers/codex/rpc.d.ts.map +1 -0
- package/dist/providers/codex/rpc.js +104 -0
- package/dist/providers/codex/rpc.js.map +1 -0
- package/dist/providers/codex/session.d.ts +16 -0
- package/dist/providers/codex/session.d.ts.map +1 -0
- package/dist/providers/codex/session.js +262 -0
- package/dist/providers/codex/session.js.map +1 -0
- package/dist/providers/display.d.ts +9 -0
- package/dist/providers/display.d.ts.map +1 -0
- package/dist/providers/display.js +21 -0
- package/dist/providers/display.js.map +1 -0
- package/dist/providers/terminal.d.ts +24 -0
- package/dist/providers/terminal.d.ts.map +1 -0
- package/dist/providers/terminal.js +33 -0
- package/dist/providers/terminal.js.map +1 -0
- package/dist/providers/types.d.ts +72 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/relay/api.d.ts +13 -0
- package/dist/relay/api.d.ts.map +1 -0
- package/dist/relay/api.js +75 -0
- package/dist/relay/api.js.map +1 -0
- package/dist/relay/device.d.ts +26 -0
- package/dist/relay/device.d.ts.map +1 -0
- package/dist/relay/device.js +83 -0
- package/dist/relay/device.js.map +1 -0
- package/dist/relay/relay-host.d.ts +41 -0
- package/dist/relay/relay-host.d.ts.map +1 -0
- package/dist/relay/relay-host.js +238 -0
- package/dist/relay/relay-host.js.map +1 -0
- package/dist/server/coalesce.d.ts +15 -0
- package/dist/server/coalesce.d.ts.map +1 -0
- package/dist/server/coalesce.js +50 -0
- package/dist/server/coalesce.js.map +1 -0
- package/dist/server/connection.d.ts +30 -0
- package/dist/server/connection.d.ts.map +1 -0
- package/dist/server/connection.js +182 -0
- package/dist/server/connection.js.map +1 -0
- package/dist/server/local-server.d.ts +27 -0
- package/dist/server/local-server.d.ts.map +1 -0
- package/dist/server/local-server.js +108 -0
- package/dist/server/local-server.js.map +1 -0
- package/dist/session/event-log.d.ts +17 -0
- package/dist/session/event-log.d.ts.map +1 -0
- package/dist/session/event-log.js +21 -0
- package/dist/session/event-log.js.map +1 -0
- package/dist/session/manager.d.ts +62 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +282 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/session.d.ts +43 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/session/session.js +310 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/store.d.ts +22 -0
- package/dist/session/store.d.ts.map +1 -0
- package/dist/session/store.js +44 -0
- package/dist/session/store.js.map +1 -0
- package/dist/session/terminal-session.d.ts +52 -0
- package/dist/session/terminal-session.d.ts.map +1 -0
- package/dist/session/terminal-session.js +210 -0
- package/dist/session/terminal-session.js.map +1 -0
- package/dist/util/id.d.ts +2 -0
- package/dist/util/id.d.ts.map +1 -0
- package/dist/util/id.js +5 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/lock.d.ts +12 -0
- package/dist/util/lock.d.ts.map +1 -0
- package/dist/util/lock.js +52 -0
- package/dist/util/lock.js.map +1 -0
- package/dist/util/which.d.ts +3 -0
- package/dist/util/which.d.ts.map +1 -0
- package/dist/util/which.js +12 -0
- package/dist/util/which.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bert Shim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# @termlink/cli
|
|
2
|
+
|
|
3
|
+
The TermLink host: terminals, Claude Code and Codex sessions on your machine, reachable
|
|
4
|
+
from the TermLink web app on any device. One host per machine; sessions open when you ask.
|
|
5
|
+
|
|
6
|
+
- **Terminal**: your shell in a PTY, like an ssh login. vi, top, npm install, passwords,
|
|
7
|
+
Ctrl+C all work. Reconnect from a phone and the screen is still there.
|
|
8
|
+
- **Claude** and **Codex**: coding agents driven through their SDKs, with turns, tool calls
|
|
9
|
+
and approval requests as JSON events. They come back after the host restarts.
|
|
10
|
+
|
|
11
|
+
Design: [docs/DESIGN.md](docs/DESIGN.md). Why it is built this way: [docs/DESIGN-V2.md](docs/DESIGN-V2.md).
|
|
12
|
+
|
|
13
|
+
## Install and run
|
|
14
|
+
|
|
15
|
+
Requires Node.js 22 or later. Nothing is compiled on your machine: the PTY binary comes
|
|
16
|
+
prebuilt for Windows, macOS and Linux (x64 and arm64).
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install -g @termlink/cli
|
|
20
|
+
termlink login # Google sign-in in the browser, once per machine
|
|
21
|
+
cd ~/my-project
|
|
22
|
+
termlink start # or just: termlink
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The host joins the TermLink relay and the web app lists it under this machine's name.
|
|
26
|
+
Sessions open only inside the folder the host was started in, unless you pass `--allow-root`.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
termlink start [options] run the host (default)
|
|
30
|
+
termlink login sign this machine in
|
|
31
|
+
termlink logout sign this machine out
|
|
32
|
+
termlink whoami show the account
|
|
33
|
+
termlink devices list the machines on this account; `devices revoke <id>` signs one out
|
|
34
|
+
termlink --help every option
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Useful options for `start`:
|
|
38
|
+
|
|
39
|
+
- `--allow-root <dir>` — another folder sessions may open in (repeatable)
|
|
40
|
+
- `--providers terminal,claude,codex` — which session kinds to offer (this is the default)
|
|
41
|
+
- `--shell <path>` — shell for terminal sessions (default: your shell; PowerShell on Windows)
|
|
42
|
+
- `--auto-approve edits|all` — let new agent sessions answer file or command approvals themselves
|
|
43
|
+
|
|
44
|
+
The host does not handle Claude or Codex credentials. Log in with those tools directly
|
|
45
|
+
(`claude`, `codex login`); the host only checks that you did.
|
|
46
|
+
|
|
47
|
+
## Layout
|
|
48
|
+
|
|
49
|
+
- `packages/protocol` — command, response and event schemas (zod) and the terminal frame codec, shared with clients
|
|
50
|
+
- `packages/terminal` — a shell in a PTY with a headless screen for reconnects; knows nothing about hosts or clients
|
|
51
|
+
- `packages/host` — the `termlink` package: sessions, providers, local server, relay link, login, CLI
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
npm install
|
|
57
|
+
npm test
|
|
58
|
+
npm run typecheck
|
|
59
|
+
npm run build
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Run the host from source, then drive it with the demo client:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
npm run dev
|
|
66
|
+
# prints ws://127.0.0.1:7420/ws?token=...
|
|
67
|
+
|
|
68
|
+
npm run demo -- "ws://127.0.0.1:7420/ws?token=..." --provider claude
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
One machine runs one host per relay session name (`<hostname>-agent` unless `--relay-session`
|
|
72
|
+
says otherwise); a second one refuses to start. Tests and scripts that start a host of their
|
|
73
|
+
own should pass their own `--relay-session` and a temporary `--state`, so they neither take the
|
|
74
|
+
place of the host you use nor share `~/.termlink/agent-sessions.json` with it.
|
|
75
|
+
|
|
76
|
+
`npm run bundle -w @termlink/cli` builds `dist/bundle/termlink.mjs`, one file to run from a checkout.
|
|
77
|
+
The PTY binary and the headless terminal stay in `node_modules` next to it.
|
|
78
|
+
|
|
79
|
+
### Sign-in configuration
|
|
80
|
+
|
|
81
|
+
`termlink login` uses a Google OAuth desktop client. The published package carries its own
|
|
82
|
+
(`packages/host/src/auth/oauth-defaults.ts`). For development, point
|
|
83
|
+
`TERMLINK_GOOGLE_CLIENT_SECRET_FILE` at a `client_secret_*.json` download, or set
|
|
84
|
+
`TERMLINK_GOOGLE_CLIENT_ID` and `TERMLINK_GOOGLE_CLIENT_SECRET`. `TERMLINK_SERVER` picks the master relay.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type Fetch } from "../relay/api.js";
|
|
2
|
+
import { type DeviceCredential } from "../relay/device.js";
|
|
3
|
+
export interface OAuthClient {
|
|
4
|
+
clientId: string;
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
/** Fixed redirect for web-type clients. Empty picks a random loopback port. */
|
|
7
|
+
redirectUri: string;
|
|
8
|
+
}
|
|
9
|
+
export interface Identity {
|
|
10
|
+
sub: string;
|
|
11
|
+
email?: string | undefined;
|
|
12
|
+
name?: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Which Google OAuth client to use, in order: a client_secret_*.json named by
|
|
16
|
+
* TERMLINK_GOOGLE_CLIENT_SECRET_FILE, then TERMLINK_GOOGLE_CLIENT_ID/_SECRET, then the
|
|
17
|
+
* values built into this package.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveOAuthClient(env?: NodeJS.ProcessEnv): Promise<OAuthClient>;
|
|
20
|
+
export interface LoginOptions {
|
|
21
|
+
client: OAuthClient;
|
|
22
|
+
fetch?: Fetch;
|
|
23
|
+
/** Called with the URL to open. Defaults to opening the system browser. */
|
|
24
|
+
openBrowser?: (url: string) => void;
|
|
25
|
+
log?: (line: string) => void;
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Runs the browser sign-in and returns Google's ID token for the account. */
|
|
29
|
+
export declare function googleLogin(options: LoginOptions): Promise<{
|
|
30
|
+
idToken: string;
|
|
31
|
+
identity: Identity;
|
|
32
|
+
}>;
|
|
33
|
+
/** Trades a Google ID token for a device credential on the master relay. */
|
|
34
|
+
export declare function enrollDevice(server: string, idToken: string, label: string, fetchFn?: Fetch): Promise<DeviceCredential>;
|
|
35
|
+
/** Signs in and enrolls this machine. The credential is written to `file`. */
|
|
36
|
+
export declare function loginAndEnroll(server: string, file: string, options?: Omit<LoginOptions, "client"> & {
|
|
37
|
+
client?: OAuthClient;
|
|
38
|
+
}): Promise<DeviceCredential>;
|
|
39
|
+
export interface DeviceInfo {
|
|
40
|
+
id: string;
|
|
41
|
+
label: string;
|
|
42
|
+
createdAt: string;
|
|
43
|
+
lastSeen: string;
|
|
44
|
+
}
|
|
45
|
+
export declare function listDevices(server: string, credential: string, fetchFn?: Fetch): Promise<DeviceInfo[]>;
|
|
46
|
+
/** Revokes one device (or every device of the account with `all`). Returns how many. */
|
|
47
|
+
export declare function revokeDevices(server: string, credential: string, target: {
|
|
48
|
+
id: string;
|
|
49
|
+
} | {
|
|
50
|
+
all: true;
|
|
51
|
+
}, fetchFn?: Fetch): Promise<number>;
|
|
52
|
+
/** How this machine appears in the account's device list, like the Go CLI named it. */
|
|
53
|
+
export declare function deviceLabel(): string;
|
|
54
|
+
export declare function decodeIdentity(idToken: string): Identity;
|
|
55
|
+
//# sourceMappingURL=login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":"AAYA,OAAO,EAA4B,KAAK,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAQpF,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,WAAW,CAAC,CAWnG;AAsBD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,2EAA2E;IAC3E,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,8EAA8E;AAC9E,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,CA+FzG;AAED,4EAA4E;AAC5E,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,KAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAoBpI;AAED,8EAA8E;AAC9E,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACpE,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,KAAa,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQnH;AAED,wFAAwF;AACxF,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,IAAI,CAAA;CAAE,EACtC,OAAO,GAAE,KAAa,GACrB,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED,uFAAuF;AACvF,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAcxD"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// `termlink login`: a Google sign-in in the browser, traded at the master relay for a
|
|
2
|
+
// device credential that the host uses from then on. Google is never asked again until
|
|
3
|
+
// the person signs out; the device token renews itself (relay/device.ts).
|
|
4
|
+
//
|
|
5
|
+
// Mirrors termlink-app/internal/auth (the Go CLI this replaces): PKCE, a loopback
|
|
6
|
+
// redirect, the scopes openid email profile, and the same ~/.termlink/device.json.
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
9
|
+
import { readFile } from "node:fs/promises";
|
|
10
|
+
import { createServer } from "node:http";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import { httpBase, RelayHttpError } from "../relay/api.js";
|
|
13
|
+
import { DeviceError, saveDevice } from "../relay/device.js";
|
|
14
|
+
import { DEFAULT_GOOGLE_CLIENT_ID, DEFAULT_GOOGLE_CLIENT_SECRET } from "./oauth-defaults.js";
|
|
15
|
+
const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
16
|
+
const TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
17
|
+
const SCOPES = "openid email profile";
|
|
18
|
+
const LOGIN_TIMEOUT_MS = 5 * 60_000;
|
|
19
|
+
/**
|
|
20
|
+
* Which Google OAuth client to use, in order: a client_secret_*.json named by
|
|
21
|
+
* TERMLINK_GOOGLE_CLIENT_SECRET_FILE, then TERMLINK_GOOGLE_CLIENT_ID/_SECRET, then the
|
|
22
|
+
* values built into this package.
|
|
23
|
+
*/
|
|
24
|
+
export async function resolveOAuthClient(env = process.env) {
|
|
25
|
+
const file = env.TERMLINK_GOOGLE_CLIENT_SECRET_FILE;
|
|
26
|
+
if (file)
|
|
27
|
+
return loadClientSecretFile(file, env.TERMLINK_GOOGLE_REDIRECT_URI);
|
|
28
|
+
const clientId = env.TERMLINK_GOOGLE_CLIENT_ID || DEFAULT_GOOGLE_CLIENT_ID;
|
|
29
|
+
const clientSecret = env.TERMLINK_GOOGLE_CLIENT_SECRET || DEFAULT_GOOGLE_CLIENT_SECRET;
|
|
30
|
+
if (!clientId) {
|
|
31
|
+
throw new DeviceError("no Google OAuth client is configured; set TERMLINK_GOOGLE_CLIENT_SECRET_FILE or TERMLINK_GOOGLE_CLIENT_ID");
|
|
32
|
+
}
|
|
33
|
+
return { clientId, clientSecret, redirectUri: env.TERMLINK_GOOGLE_REDIRECT_URI ?? "" };
|
|
34
|
+
}
|
|
35
|
+
async function loadClientSecretFile(file, redirectOverride) {
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = JSON.parse(await readFile(file, "utf8"));
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
throw new DeviceError(`could not read ${file}: ${err instanceof Error ? err.message : err}`);
|
|
42
|
+
}
|
|
43
|
+
const section = parsed.web ?? parsed.installed;
|
|
44
|
+
if (!section?.client_id)
|
|
45
|
+
throw new DeviceError(`${file} has no web or installed client`);
|
|
46
|
+
// Desktop clients use a random loopback port; only web clients need their registered URI.
|
|
47
|
+
const redirect = parsed.web ? (section.redirect_uris?.[0] ?? "") : "";
|
|
48
|
+
return { clientId: section.client_id, clientSecret: section.client_secret ?? "", redirectUri: redirectOverride ?? redirect };
|
|
49
|
+
}
|
|
50
|
+
/** Runs the browser sign-in and returns Google's ID token for the account. */
|
|
51
|
+
export async function googleLogin(options) {
|
|
52
|
+
const fetchFn = options.fetch ?? fetch;
|
|
53
|
+
const { client } = options;
|
|
54
|
+
const verifier = randomBytes(48).toString("base64url");
|
|
55
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
56
|
+
const state = randomBytes(24).toString("base64url");
|
|
57
|
+
const server = createServer();
|
|
58
|
+
let callbackPath = "/callback";
|
|
59
|
+
let listenHost = "127.0.0.1";
|
|
60
|
+
let listenPort = 0;
|
|
61
|
+
if (client.redirectUri) {
|
|
62
|
+
const url = new URL(client.redirectUri);
|
|
63
|
+
listenHost = url.hostname;
|
|
64
|
+
listenPort = Number(url.port || (url.protocol === "https:" ? 443 : 80));
|
|
65
|
+
callbackPath = url.pathname || "/";
|
|
66
|
+
}
|
|
67
|
+
await new Promise((resolve, reject) => {
|
|
68
|
+
server.once("error", reject);
|
|
69
|
+
server.listen(listenPort, listenHost, () => resolve());
|
|
70
|
+
});
|
|
71
|
+
const { port } = server.address();
|
|
72
|
+
const redirectUri = client.redirectUri || `http://127.0.0.1:${port}${callbackPath}`;
|
|
73
|
+
const code = new Promise((resolve, reject) => {
|
|
74
|
+
const timer = setTimeout(() => reject(new DeviceError("login timed out")), options.timeoutMs ?? LOGIN_TIMEOUT_MS);
|
|
75
|
+
server.on("request", (req, res) => {
|
|
76
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
77
|
+
if (url.pathname !== callbackPath) {
|
|
78
|
+
res.writeHead(404).end();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const error = url.searchParams.get("error");
|
|
82
|
+
if (error) {
|
|
83
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(page(`Login failed: ${error}. You can close this tab.`));
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
reject(new DeviceError(`authorization denied: ${error}`));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (url.searchParams.get("state") !== state) {
|
|
89
|
+
res.writeHead(400).end("state mismatch");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const value = url.searchParams.get("code");
|
|
93
|
+
if (!value) {
|
|
94
|
+
res.writeHead(400).end("missing code");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(page("Login complete. You can close this tab and return to the terminal."));
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
resolve(value);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
const authUrl = new URL(AUTH_URL);
|
|
103
|
+
authUrl.search = new URLSearchParams({
|
|
104
|
+
client_id: client.clientId,
|
|
105
|
+
redirect_uri: redirectUri,
|
|
106
|
+
response_type: "code",
|
|
107
|
+
scope: SCOPES,
|
|
108
|
+
code_challenge: challenge,
|
|
109
|
+
code_challenge_method: "S256",
|
|
110
|
+
state,
|
|
111
|
+
access_type: "offline",
|
|
112
|
+
prompt: "consent",
|
|
113
|
+
}).toString();
|
|
114
|
+
const log = options.log ?? ((line) => console.error(line));
|
|
115
|
+
log("Complete the Google login in your browser. If it does not open, use this link:");
|
|
116
|
+
log(` ${authUrl}`);
|
|
117
|
+
(options.openBrowser ?? openBrowser)(authUrl.toString());
|
|
118
|
+
try {
|
|
119
|
+
const authCode = await code;
|
|
120
|
+
const form = new URLSearchParams({
|
|
121
|
+
code: authCode,
|
|
122
|
+
client_id: client.clientId,
|
|
123
|
+
redirect_uri: redirectUri,
|
|
124
|
+
grant_type: "authorization_code",
|
|
125
|
+
code_verifier: verifier,
|
|
126
|
+
});
|
|
127
|
+
if (client.clientSecret)
|
|
128
|
+
form.set("client_secret", client.clientSecret);
|
|
129
|
+
const res = await fetchFn(TOKEN_URL, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
132
|
+
body: form.toString(),
|
|
133
|
+
signal: AbortSignal.timeout(30_000),
|
|
134
|
+
});
|
|
135
|
+
if (!res.ok)
|
|
136
|
+
throw new DeviceError(`token exchange failed: ${(await res.text()).trim() || res.statusText}`);
|
|
137
|
+
const body = (await res.json());
|
|
138
|
+
if (typeof body.id_token !== "string")
|
|
139
|
+
throw new DeviceError("Google returned no id_token; check that the openid scope is allowed");
|
|
140
|
+
return { idToken: body.id_token, identity: decodeIdentity(body.id_token) };
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
server.close();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Trades a Google ID token for a device credential on the master relay. */
|
|
147
|
+
export async function enrollDevice(server, idToken, label, fetchFn = fetch) {
|
|
148
|
+
const res = await fetchFn(`${httpBase(server)}/devices`, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: { authorization: `Bearer ${idToken}`, "content-type": "application/json" },
|
|
151
|
+
body: JSON.stringify({ label }),
|
|
152
|
+
signal: AbortSignal.timeout(15_000),
|
|
153
|
+
});
|
|
154
|
+
if (!res.ok)
|
|
155
|
+
throw new RelayHttpError(res.status, (await res.text()).trim() || res.statusText);
|
|
156
|
+
const body = (await res.json());
|
|
157
|
+
if (!body.device_token || !body.device_id || !body.expires_at)
|
|
158
|
+
throw new DeviceError("the service did not issue a device credential");
|
|
159
|
+
const { sub } = decodeIdentity(idToken);
|
|
160
|
+
return {
|
|
161
|
+
token: body.device_token,
|
|
162
|
+
id: body.device_id,
|
|
163
|
+
sub,
|
|
164
|
+
...(body.email ? { email: body.email } : {}),
|
|
165
|
+
...(body.name ? { name: body.name } : {}),
|
|
166
|
+
expires_at: body.expires_at,
|
|
167
|
+
master: server,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** Signs in and enrolls this machine. The credential is written to `file`. */
|
|
171
|
+
export async function loginAndEnroll(server, file, options = {}) {
|
|
172
|
+
const client = options.client ?? (await resolveOAuthClient());
|
|
173
|
+
const { idToken } = await googleLogin({ ...options, client });
|
|
174
|
+
const credential = await enrollDevice(server, idToken, deviceLabel(), options.fetch);
|
|
175
|
+
await saveDevice(file, credential);
|
|
176
|
+
return credential;
|
|
177
|
+
}
|
|
178
|
+
export async function listDevices(server, credential, fetchFn = fetch) {
|
|
179
|
+
const res = await fetchFn(`${httpBase(server)}/devices`, {
|
|
180
|
+
headers: { authorization: `Bearer ${credential}` },
|
|
181
|
+
signal: AbortSignal.timeout(15_000),
|
|
182
|
+
});
|
|
183
|
+
if (!res.ok)
|
|
184
|
+
throw new RelayHttpError(res.status, (await res.text()).trim() || res.statusText);
|
|
185
|
+
const body = (await res.json());
|
|
186
|
+
return Array.isArray(body) ? body : [];
|
|
187
|
+
}
|
|
188
|
+
/** Revokes one device (or every device of the account with `all`). Returns how many. */
|
|
189
|
+
export async function revokeDevices(server, credential, target, fetchFn = fetch) {
|
|
190
|
+
const query = "all" in target ? "all=1" : `id=${encodeURIComponent(target.id)}`;
|
|
191
|
+
const res = await fetchFn(`${httpBase(server)}/devices?${query}`, {
|
|
192
|
+
method: "DELETE",
|
|
193
|
+
headers: { authorization: `Bearer ${credential}` },
|
|
194
|
+
signal: AbortSignal.timeout(15_000),
|
|
195
|
+
});
|
|
196
|
+
if (res.status === 404 && "id" in target)
|
|
197
|
+
return 0; // Already gone: what logout wanted anyway.
|
|
198
|
+
if (!res.ok)
|
|
199
|
+
throw new RelayHttpError(res.status, (await res.text()).trim() || res.statusText);
|
|
200
|
+
if (res.status === 204)
|
|
201
|
+
return 1;
|
|
202
|
+
const body = (await res.json().catch(() => ({})));
|
|
203
|
+
return body.revoked ?? 1;
|
|
204
|
+
}
|
|
205
|
+
/** How this machine appears in the account's device list, like the Go CLI named it. */
|
|
206
|
+
export function deviceLabel() {
|
|
207
|
+
return `${os.hostname() || "unknown host"} (${process.platform})`;
|
|
208
|
+
}
|
|
209
|
+
export function decodeIdentity(idToken) {
|
|
210
|
+
const payload = idToken.split(".")[1];
|
|
211
|
+
if (!payload)
|
|
212
|
+
throw new DeviceError("malformed id_token");
|
|
213
|
+
const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
214
|
+
if (typeof claims.sub !== "string")
|
|
215
|
+
throw new DeviceError("id_token has no subject");
|
|
216
|
+
return {
|
|
217
|
+
sub: claims.sub,
|
|
218
|
+
email: typeof claims.email === "string" ? claims.email : undefined,
|
|
219
|
+
name: typeof claims.name === "string" ? claims.name : undefined,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function openBrowser(url) {
|
|
223
|
+
const command = process.platform === "win32"
|
|
224
|
+
? { file: "rundll32", args: ["url.dll,FileProtocolHandler", url] }
|
|
225
|
+
: process.platform === "darwin"
|
|
226
|
+
? { file: "open", args: [url] }
|
|
227
|
+
: { file: "xdg-open", args: [url] };
|
|
228
|
+
try {
|
|
229
|
+
spawn(command.file, command.args, { stdio: "ignore", detached: true }).on("error", () => { }).unref();
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// The link is printed as well.
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function page(text) {
|
|
236
|
+
return `<!doctype html><html><body style="font-family:system-ui;padding:2rem"><h2>TermLink</h2><p>${text}</p></body></html>`;
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Google OAuth client the published package signs in with. A desktop ("installed")
|
|
3
|
+
* client's secret is not confidential by Google's own definition; PKCE protects the
|
|
4
|
+
* flow. Fill these in before publishing, or point TERMLINK_GOOGLE_CLIENT_SECRET_FILE at
|
|
5
|
+
* a client_secret_*.json download (see auth/login.ts).
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_GOOGLE_CLIENT_ID = "";
|
|
8
|
+
export declare const DEFAULT_GOOGLE_CLIENT_SECRET = "";
|
|
9
|
+
//# sourceMappingURL=oauth-defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-defaults.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-defaults.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAC3C,eAAO,MAAM,4BAA4B,KAAK,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Google OAuth client the published package signs in with. A desktop ("installed")
|
|
3
|
+
* client's secret is not confidential by Google's own definition; PKCE protects the
|
|
4
|
+
* flow. Fill these in before publishing, or point TERMLINK_GOOGLE_CLIENT_SECRET_FILE at
|
|
5
|
+
* a client_secret_*.json download (see auth/login.ts).
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_GOOGLE_CLIENT_ID = "87682614646-ikkb4vo01h71dc8knp4ld5gv1q7kjqic.apps.googleusercontent.com";
|
|
8
|
+
export const DEFAULT_GOOGLE_CLIENT_SECRET = "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";
|
|
9
|
+
//# sourceMappingURL=oauth-defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-defaults.js","sourceRoot":"","sources":["../../src/auth/oauth-defaults.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAC3C,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|