@everywheredev/cli 0.0.5
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 +135 -0
- package/bin/pocketdev.js +19 -0
- package/dist/agent/agent-loop.d.ts +40 -0
- package/dist/agent/agent-loop.d.ts.map +1 -0
- package/dist/agent/agent-loop.js +162 -0
- package/dist/agent/agent-loop.js.map +1 -0
- package/dist/agent/approval.d.ts +38 -0
- package/dist/agent/approval.d.ts.map +1 -0
- package/dist/agent/approval.js +55 -0
- package/dist/agent/approval.js.map +1 -0
- package/dist/agent/run-tracker.d.ts +26 -0
- package/dist/agent/run-tracker.d.ts.map +1 -0
- package/dist/agent/run-tracker.js +43 -0
- package/dist/agent/run-tracker.js.map +1 -0
- package/dist/auth/device-flow.d.ts +61 -0
- package/dist/auth/device-flow.d.ts.map +1 -0
- package/dist/auth/device-flow.js +141 -0
- package/dist/auth/device-flow.js.map +1 -0
- package/dist/auth/hydra-oauth-auth-provider.d.ts +35 -0
- package/dist/auth/hydra-oauth-auth-provider.d.ts.map +1 -0
- package/dist/auth/hydra-oauth-auth-provider.js +153 -0
- package/dist/auth/hydra-oauth-auth-provider.js.map +1 -0
- package/dist/auth/keychain.d.ts +42 -0
- package/dist/auth/keychain.d.ts.map +1 -0
- package/dist/auth/keychain.js +138 -0
- package/dist/auth/keychain.js.map +1 -0
- package/dist/auth/pkce.d.ts +20 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/pkce.js +44 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/ports.d.ts +25 -0
- package/dist/auth/ports.d.ts.map +1 -0
- package/dist/auth/ports.js +8 -0
- package/dist/auth/ports.js.map +1 -0
- package/dist/commands/activity.d.ts +17 -0
- package/dist/commands/activity.d.ts.map +1 -0
- package/dist/commands/activity.js +54 -0
- package/dist/commands/activity.js.map +1 -0
- package/dist/commands/agent.d.ts +25 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +80 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/agents.d.ts +9 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +28 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/commits.d.ts +16 -0
- package/dist/commands/commits.d.ts.map +1 -0
- package/dist/commands/commits.js +39 -0
- package/dist/commands/commits.js.map +1 -0
- package/dist/commands/diff.d.ts +15 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +27 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/expose.d.ts +15 -0
- package/dist/commands/expose.d.ts.map +1 -0
- package/dist/commands/expose.js +39 -0
- package/dist/commands/expose.js.map +1 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +12 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/login.d.ts +9 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +27 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +9 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +24 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/me.d.ts +10 -0
- package/dist/commands/me.d.ts.map +1 -0
- package/dist/commands/me.js +25 -0
- package/dist/commands/me.js.map +1 -0
- package/dist/commands/open.d.ts +16 -0
- package/dist/commands/open.d.ts.map +1 -0
- package/dist/commands/open.js +34 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/ports.d.ts +75 -0
- package/dist/commands/ports.d.ts.map +1 -0
- package/dist/commands/ports.js +78 -0
- package/dist/commands/ports.js.map +1 -0
- package/dist/commands/profile.d.ts +21 -0
- package/dist/commands/profile.d.ts.map +1 -0
- package/dist/commands/profile.js +93 -0
- package/dist/commands/profile.js.map +1 -0
- package/dist/commands/push.d.ts +18 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +116 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/registry.d.ts +15 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +45 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/request.d.ts +25 -0
- package/dist/commands/request.d.ts.map +1 -0
- package/dist/commands/request.js +58 -0
- package/dist/commands/request.js.map +1 -0
- package/dist/commands/resume.d.ts +22 -0
- package/dist/commands/resume.d.ts.map +1 -0
- package/dist/commands/resume.js +69 -0
- package/dist/commands/resume.js.map +1 -0
- package/dist/commands/save.d.ts +34 -0
- package/dist/commands/save.d.ts.map +1 -0
- package/dist/commands/save.js +141 -0
- package/dist/commands/save.js.map +1 -0
- package/dist/commands/session.d.ts +9 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +282 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/skills/markdown-loader.d.ts +19 -0
- package/dist/commands/skills/markdown-loader.d.ts.map +1 -0
- package/dist/commands/skills/markdown-loader.js +108 -0
- package/dist/commands/skills/markdown-loader.js.map +1 -0
- package/dist/commands/skills/registry.d.ts +23 -0
- package/dist/commands/skills/registry.d.ts.map +1 -0
- package/dist/commands/skills/registry.js +45 -0
- package/dist/commands/skills/registry.js.map +1 -0
- package/dist/commands/terminal.d.ts +19 -0
- package/dist/commands/terminal.d.ts.map +1 -0
- package/dist/commands/terminal.js +39 -0
- package/dist/commands/terminal.js.map +1 -0
- package/dist/commands/tree.d.ts +16 -0
- package/dist/commands/tree.d.ts.map +1 -0
- package/dist/commands/tree.js +44 -0
- package/dist/commands/tree.js.map +1 -0
- package/dist/commands/undo.d.ts +18 -0
- package/dist/commands/undo.d.ts.map +1 -0
- package/dist/commands/undo.js +46 -0
- package/dist/commands/undo.js.map +1 -0
- package/dist/config/config-store.d.ts +19 -0
- package/dist/config/config-store.d.ts.map +1 -0
- package/dist/config/config-store.js +199 -0
- package/dist/config/config-store.js.map +1 -0
- package/dist/config/defaults.d.ts +22 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +38 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/ports.d.ts +60 -0
- package/dist/config/ports.d.ts.map +1 -0
- package/dist/config/ports.js +8 -0
- package/dist/config/ports.js.map +1 -0
- package/dist/config/profile-constants.d.ts +4 -0
- package/dist/config/profile-constants.d.ts.map +1 -0
- package/dist/config/profile-constants.js +10 -0
- package/dist/config/profile-constants.js.map +1 -0
- package/dist/config/profile.d.ts +84 -0
- package/dist/config/profile.d.ts.map +1 -0
- package/dist/config/profile.js +66 -0
- package/dist/config/profile.js.map +1 -0
- package/dist/contract/index.d.ts +193 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +14 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/core/app.d.ts +35 -0
- package/dist/core/app.d.ts.map +1 -0
- package/dist/core/app.js +86 -0
- package/dist/core/app.js.map +1 -0
- package/dist/core/command-router.d.ts +29 -0
- package/dist/core/command-router.d.ts.map +1 -0
- package/dist/core/command-router.js +61 -0
- package/dist/core/command-router.js.map +1 -0
- package/dist/core/errors.d.ts +39 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +106 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/preflight.d.ts +16 -0
- package/dist/core/preflight.d.ts.map +1 -0
- package/dist/core/preflight.js +38 -0
- package/dist/core/preflight.js.map +1 -0
- package/dist/core/repl.d.ts +18 -0
- package/dist/core/repl.d.ts.map +1 -0
- package/dist/core/repl.js +128 -0
- package/dist/core/repl.js.map +1 -0
- package/dist/di/container.d.ts +13 -0
- package/dist/di/container.d.ts.map +1 -0
- package/dist/di/container.js +160 -0
- package/dist/di/container.js.map +1 -0
- package/dist/di/tokens.d.ts +20 -0
- package/dist/di/tokens.d.ts.map +1 -0
- package/dist/di/tokens.js +22 -0
- package/dist/di/tokens.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/render/ink-renderer.d.ts +28 -0
- package/dist/render/ink-renderer.d.ts.map +1 -0
- package/dist/render/ink-renderer.js +174 -0
- package/dist/render/ink-renderer.js.map +1 -0
- package/dist/render/plain-renderer.d.ts +16 -0
- package/dist/render/plain-renderer.d.ts.map +1 -0
- package/dist/render/plain-renderer.js +38 -0
- package/dist/render/plain-renderer.js.map +1 -0
- package/dist/render/ports.d.ts +12 -0
- package/dist/render/ports.d.ts.map +1 -0
- package/dist/render/ports.js +10 -0
- package/dist/render/ports.js.map +1 -0
- package/dist/render/raw-renderer.d.ts +11 -0
- package/dist/render/raw-renderer.d.ts.map +1 -0
- package/dist/render/raw-renderer.js +68 -0
- package/dist/render/raw-renderer.js.map +1 -0
- package/dist/render/repl-input.d.ts +15 -0
- package/dist/render/repl-input.d.ts.map +1 -0
- package/dist/render/repl-input.js +102 -0
- package/dist/render/repl-input.js.map +1 -0
- package/dist/session/file-session-store.d.ts +26 -0
- package/dist/session/file-session-store.d.ts.map +1 -0
- package/dist/session/file-session-store.js +174 -0
- package/dist/session/file-session-store.js.map +1 -0
- package/dist/session/ports.d.ts +50 -0
- package/dist/session/ports.d.ts.map +1 -0
- package/dist/session/ports.js +6 -0
- package/dist/session/ports.js.map +1 -0
- package/dist/sync/git-sync.d.ts +16 -0
- package/dist/sync/git-sync.d.ts.map +1 -0
- package/dist/sync/git-sync.js +28 -0
- package/dist/sync/git-sync.js.map +1 -0
- package/dist/sync/git.d.ts +12 -0
- package/dist/sync/git.d.ts.map +1 -0
- package/dist/sync/git.js +100 -0
- package/dist/sync/git.js.map +1 -0
- package/dist/sync/reconciler.d.ts +36 -0
- package/dist/sync/reconciler.d.ts.map +1 -0
- package/dist/sync/reconciler.js +143 -0
- package/dist/sync/reconciler.js.map +1 -0
- package/dist/sync/walk.d.ts +23 -0
- package/dist/sync/walk.d.ts.map +1 -0
- package/dist/sync/walk.js +81 -0
- package/dist/sync/walk.js.map +1 -0
- package/dist/sync/watch.d.ts +13 -0
- package/dist/sync/watch.d.ts.map +1 -0
- package/dist/sync/watch.js +75 -0
- package/dist/sync/watch.js.map +1 -0
- package/dist/tools/git-diff.d.ts +16 -0
- package/dist/tools/git-diff.d.ts.map +1 -0
- package/dist/tools/git-diff.js +25 -0
- package/dist/tools/git-diff.js.map +1 -0
- package/dist/tools/http-request.d.ts +25 -0
- package/dist/tools/http-request.d.ts.map +1 -0
- package/dist/tools/http-request.js +41 -0
- package/dist/tools/http-request.js.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +22 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/ports.d.ts +58 -0
- package/dist/tools/ports.d.ts.map +1 -0
- package/dist/tools/ports.js +17 -0
- package/dist/tools/ports.js.map +1 -0
- package/dist/tools/read-file.d.ts +16 -0
- package/dist/tools/read-file.d.ts.map +1 -0
- package/dist/tools/read-file.js +24 -0
- package/dist/tools/read-file.js.map +1 -0
- package/dist/tools/registry.d.ts +10 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +28 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/run-command.d.ts +20 -0
- package/dist/tools/run-command.d.ts.map +1 -0
- package/dist/tools/run-command.js +65 -0
- package/dist/tools/run-command.js.map +1 -0
- package/dist/tools/write-file.d.ts +24 -0
- package/dist/tools/write-file.d.ts.map +1 -0
- package/dist/tools/write-file.js +60 -0
- package/dist/tools/write-file.js.map +1 -0
- package/dist/transport/composite-transport.d.ts +30 -0
- package/dist/transport/composite-transport.d.ts.map +1 -0
- package/dist/transport/composite-transport.js +71 -0
- package/dist/transport/composite-transport.js.map +1 -0
- package/dist/transport/device-browser.d.ts +19 -0
- package/dist/transport/device-browser.d.ts.map +1 -0
- package/dist/transport/device-browser.js +243 -0
- package/dist/transport/device-browser.js.map +1 -0
- package/dist/transport/device-git.d.ts +14 -0
- package/dist/transport/device-git.d.ts.map +1 -0
- package/dist/transport/device-git.js +107 -0
- package/dist/transport/device-git.js.map +1 -0
- package/dist/transport/device-http.d.ts +10 -0
- package/dist/transport/device-http.d.ts.map +1 -0
- package/dist/transport/device-http.js +96 -0
- package/dist/transport/device-http.js.map +1 -0
- package/dist/transport/device-terminal.d.ts +44 -0
- package/dist/transport/device-terminal.d.ts.map +1 -0
- package/dist/transport/device-terminal.js +140 -0
- package/dist/transport/device-terminal.js.map +1 -0
- package/dist/transport/http-transport.d.ts +26 -0
- package/dist/transport/http-transport.d.ts.map +1 -0
- package/dist/transport/http-transport.js +125 -0
- package/dist/transport/http-transport.js.map +1 -0
- package/dist/transport/ports.d.ts +67 -0
- package/dist/transport/ports.d.ts.map +1 -0
- package/dist/transport/ports.js +11 -0
- package/dist/transport/ports.js.map +1 -0
- package/dist/transport/reconnect.d.ts +44 -0
- package/dist/transport/reconnect.d.ts.map +1 -0
- package/dist/transport/reconnect.js +69 -0
- package/dist/transport/reconnect.js.map +1 -0
- package/dist/transport/terminal-transport.d.ts +28 -0
- package/dist/transport/terminal-transport.d.ts.map +1 -0
- package/dist/transport/terminal-transport.js +63 -0
- package/dist/transport/terminal-transport.js.map +1 -0
- package/dist/transport/ws-stream-transport.d.ts +66 -0
- package/dist/transport/ws-stream-transport.d.ts.map +1 -0
- package/dist/transport/ws-stream-transport.js +246 -0
- package/dist/transport/ws-stream-transport.js.map +1 -0
- package/package.json +65 -0
- package/scripts/ensure-node-pty.mjs +52 -0
- package/scripts/smoke.mjs +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# @everywheredev/cli
|
|
2
|
+
|
|
3
|
+
A **Claude-Code-style TypeScript terminal client** for the PocketDev host. It is a *thin client to the
|
|
4
|
+
Go host* — it never owns the filesystem, git, PTY, or AI agent. It browses/edits the project, streams
|
|
5
|
+
the terminal and activity feed, drives the HTTP proxy, and runs AI prompts, working **identically**
|
|
6
|
+
against a **LOCAL** host (loopback + Personal Access Token over Tailscale) or a **FULL-REMOTE** host
|
|
7
|
+
(cloud + Ory Hydra OAuth2), selected by a per-host **connection profile**.
|
|
8
|
+
|
|
9
|
+
> Full design: [`docs/architecture/cli.md`](../docs/architecture/cli.md).
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm i -g @everywheredev/cli # provides the `pocketdev` binary
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Standalone single-file builds (Bun / Node SEA / pkg) for macOS/Linux are planned so it runs without a
|
|
18
|
+
Node toolchain, including over SSH.
|
|
19
|
+
|
|
20
|
+
### Local development install
|
|
21
|
+
|
|
22
|
+
To put the `pocketdev` command on your PATH from this working copy (no registry, no publish) — needs
|
|
23
|
+
**Node ≥ 20**:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
make cli-link # from repo root: npm install + npm link (auto-builds via `prepare`)
|
|
27
|
+
# or, from cli/: npm link
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`npm link` symlinks this package globally, so `pocketdev` points at your source. After editing,
|
|
31
|
+
rebuild and the linked command picks it up — keep a watcher running for a tight loop:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npm run build:watch # rebuild dist/ on every save (run in a spare terminal)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Validate the **published** shape (the eventual `npm i -g @everywheredev/cli` experience) without touching
|
|
38
|
+
the registry — `npm pack` builds the exact tarball `files` would ship (`bin/`, `dist/`, `README.md`):
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
npm pack # → everywheredev-cli-0.1.0.tgz
|
|
42
|
+
npm i -g ./everywheredev-cli-0.1.0.tgz # install it like a real user would
|
|
43
|
+
npm rm -g @everywheredev/cli # ...then remove and re-link for dev
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Remove the dev link with `make cli-unlink` (or `npm unlink -g @everywheredev/cli`).
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
# LOCAL-ONLY (free): point at the host on 127.0.0.1:4317 with a Personal Access Token.
|
|
52
|
+
export POCKETDEV_MODE=local
|
|
53
|
+
export POCKETDEV_TOKEN=<pat>
|
|
54
|
+
pocketdev tree
|
|
55
|
+
pocketdev open src/index.ts
|
|
56
|
+
pocketdev agent "refactor the parser and run tests"
|
|
57
|
+
|
|
58
|
+
# FULL-REMOTE (premium): device-grant login against Ory Hydra, then the same commands.
|
|
59
|
+
pocketdev --profile cloud login
|
|
60
|
+
pocketdev --profile cloud agents
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Architecture (foundation)
|
|
64
|
+
|
|
65
|
+
The pipeline mirrors Claude Code: **entry binary → DI composition root → command/router →
|
|
66
|
+
agent + tool loop (approval gate) → transport → renderer**, with a session/transcript model and
|
|
67
|
+
layered settings underneath.
|
|
68
|
+
|
|
69
|
+
Every external dependency is an injected **interface (port)**; concretes are wired **once** at the DI
|
|
70
|
+
composition root (`src/di/container.ts`, [tsyringe](https://github.com/microsoft/tsyringe)). That root
|
|
71
|
+
is the *only* place that branches on `POCKETDEV_MODE` (`local` | `remote`) — nothing downstream
|
|
72
|
+
branches on mode, so the command/tool/agent layer is byte-identical across both.
|
|
73
|
+
|
|
74
|
+
| Seam (port) | LOCAL concrete | REMOTE concrete |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| `AuthProvider` | `SharedTokenAuthProvider` (PAT / `POCKETDEV_TOKEN`) | `HydraOAuthAuthProvider` (device grant + PKCE) |
|
|
77
|
+
| `HttpPort` | `HttpTransport` → `http://127.0.0.1:4317` | `HttpTransport` → `https://…` |
|
|
78
|
+
| `StreamPort` | `WsStreamTransport` → `ws://…/ws` | `WsStreamTransport` → `wss://…/ws` |
|
|
79
|
+
| `TerminalPort` | `TerminalTransport` over the stream | same |
|
|
80
|
+
| `SessionStore` | `FileSessionStore` (JSONL + `/api/sessions`) | same |
|
|
81
|
+
|
|
82
|
+
### Layout
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
cli/
|
|
86
|
+
├─ bin/pocketdev.js # shebang shim → dist/index.js
|
|
87
|
+
└─ src/
|
|
88
|
+
├─ index.ts # bootstrap: reflect-metadata, buildContainer, route
|
|
89
|
+
├─ di/{container,tokens}.ts # composition root (ONLY local/remote wiring) + injection tokens
|
|
90
|
+
├─ core/{app,command-router,errors}.ts
|
|
91
|
+
├─ config/{ports,config-store,profile,defaults}.ts # precedence flags>env>project>user>defaults
|
|
92
|
+
├─ transport/{ports,http-transport,ws-stream-transport,terminal-transport,composite-transport,reconnect}.ts
|
|
93
|
+
├─ auth/{ports,shared-token-auth-provider,hydra-oauth-auth-provider,device-flow,pkce,keychain}.ts
|
|
94
|
+
├─ session/{ports,file-session-store}.ts
|
|
95
|
+
└─ contract/index.ts # thin re-export of @pocketdev/contract types (no drift)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Configuration & precedence
|
|
99
|
+
|
|
100
|
+
Highest wins:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
1. CLI flags --host, --mode, --profile, --token, --print/-p, --json, --no-tty
|
|
104
|
+
2. Environment POCKETDEV_MODE, POCKETDEV_HOST, POCKETDEV_PORT (4317), POCKETDEV_TOKEN,
|
|
105
|
+
POCKETDEV_PROJECT_ROOT, POCKETDEV_MAX_FILE_BYTES,
|
|
106
|
+
KRATOS_PUBLIC_URL, HYDRA_PUBLIC_URL, OIDC_ISSUER
|
|
107
|
+
3. Project config ./.pocketdev/config.json
|
|
108
|
+
4. User config ~/.pocketdev/config.json
|
|
109
|
+
5. Built-in defaults
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Server-only variables (`DATABASE_URL`, `REDIS_URL`, `CASBIN_WATCHER_CHANNEL`, `STRIPE_*`,
|
|
113
|
+
`*_ADMIN_URL`) are **not** consumed by the CLI — they belong to the host.
|
|
114
|
+
|
|
115
|
+
## Authentication
|
|
116
|
+
|
|
117
|
+
- **LOCAL** — `Authorization: Bearer $POCKETDEV_TOKEN`; the host validates with a constant-time
|
|
118
|
+
compare. `pocketdev login` is a verify round-trip. No keychain, no browser.
|
|
119
|
+
- **REMOTE** — OAuth2 **Device Authorization Grant + PKCE** (RFC 8628 + 7636) against Ory Hydra;
|
|
120
|
+
access JWT used as the same Bearer header for REST and the WS handshake; tokens stored in the OS
|
|
121
|
+
keychain with a chmod-0600 file fallback for headless/CI.
|
|
122
|
+
|
|
123
|
+
## Scripts
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
npm run build # tsc → dist/
|
|
127
|
+
npm run build:watch # tsc --watch → rebuild dist/ on save (pairs with `npm link`)
|
|
128
|
+
npm run dev # run from source via tsx
|
|
129
|
+
npm test # vitest
|
|
130
|
+
npm run typecheck # tsc --noEmit
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
Proprietary — internal PocketDev component.
|
package/bin/pocketdev.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PocketDev CLI entry shim.
|
|
3
|
+
//
|
|
4
|
+
// This is the published `pocketdev` binary (see package.json "bin"). It is a thin loader whose
|
|
5
|
+
// only job is to hand control to the compiled bootstrap in dist/index.js. Keeping the shim tiny
|
|
6
|
+
// means the real wiring (DI composition root, mode selection, routing) lives in TypeScript and is
|
|
7
|
+
// fully testable — the shim itself has nothing to test.
|
|
8
|
+
//
|
|
9
|
+
// `reflect-metadata` is imported first (before any decorated class loads) because tsyringe relies
|
|
10
|
+
// on emitted design-time type metadata for constructor injection.
|
|
11
|
+
import "reflect-metadata";
|
|
12
|
+
import { main } from "../dist/index.js";
|
|
13
|
+
|
|
14
|
+
main(process.argv.slice(2)).catch((err) => {
|
|
15
|
+
// Last-resort guard: the bootstrap maps known failures to CliError with friendly hints; anything
|
|
16
|
+
// reaching here is unexpected. Print and exit non-zero so CI/pipes observe the failure.
|
|
17
|
+
process.stderr.write(`${err?.stack ?? err}\n`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Renderer } from "../render/ports.js";
|
|
2
|
+
import type { SessionStore } from "../session/ports.js";
|
|
3
|
+
import type { HttpPort, StreamPort } from "../transport/ports.js";
|
|
4
|
+
import { ApprovalGate } from "./approval.js";
|
|
5
|
+
import { RunTracker } from "./run-tracker.js";
|
|
6
|
+
/** Arguments for one agent prompt. */
|
|
7
|
+
export interface PromptRequest {
|
|
8
|
+
prompt: string;
|
|
9
|
+
agentId?: string;
|
|
10
|
+
context?: string;
|
|
11
|
+
attachments?: string[];
|
|
12
|
+
/** Session to attribute the turns to (REPL); when absent, transcript is skipped. */
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Outcome of a completed run. */
|
|
16
|
+
export interface RunOutcome {
|
|
17
|
+
runId: string;
|
|
18
|
+
agent: string;
|
|
19
|
+
code?: number;
|
|
20
|
+
error?: string;
|
|
21
|
+
/** Files the run touched, in first-seen order (for `/undo`). */
|
|
22
|
+
touchedPaths: string[];
|
|
23
|
+
}
|
|
24
|
+
export declare class AgentLoop {
|
|
25
|
+
private readonly http;
|
|
26
|
+
private readonly stream;
|
|
27
|
+
private readonly renderer;
|
|
28
|
+
private readonly approval;
|
|
29
|
+
private readonly tracker;
|
|
30
|
+
private readonly sessions;
|
|
31
|
+
constructor(http: HttpPort, stream: StreamPort, renderer: Renderer, approval: ApprovalGate, tracker: RunTracker, sessions: SessionStore);
|
|
32
|
+
/**
|
|
33
|
+
* Run one prompt to completion. Resolves when the host emits `agent:end` for this run.
|
|
34
|
+
* `signal` aborts streaming (the host run itself is cancelled by the host on disconnect).
|
|
35
|
+
*/
|
|
36
|
+
run(req: PromptRequest, signal: AbortSignal): Promise<RunOutcome>;
|
|
37
|
+
/** Run the approval gate for a tool event and record it for the transcript + undo. */
|
|
38
|
+
private handleTool;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=agent-loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../../src/agent/agent-loop.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,qBAAqB,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAe,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAmB,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,sCAAsC;AACtC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,kCAAkC;AAClC,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AASD,qBAAa,SAAS;IAElB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBALR,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,YAAY;IAGzC;;;OAGG;IACG,GAAG,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAoFvE,sFAAsF;YACxE,UAAU;CAiBzB"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// src/agent/agent-loop.ts — the agent + tool loop (cli.md §7).
|
|
2
|
+
//
|
|
3
|
+
// Flow: prompt → POST /api/agent/prompt {runId} → stream agent:output / agent:tool /
|
|
4
|
+
// agent:end over the WS → approval gate on write/exec tools → render → transcript.
|
|
5
|
+
//
|
|
6
|
+
// The host runs the model and APPLIES edits server-side (enforcing Casbin); the CLI renders
|
|
7
|
+
// the stream, runs the LOCAL approval gate (UX only), tracks runId for /undo and resume, and
|
|
8
|
+
// appends turns to the SessionStore. DIP: depends only on ports + the gate/tracker.
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
import { CliError } from "../core/errors.js";
|
|
11
|
+
export class AgentLoop {
|
|
12
|
+
http;
|
|
13
|
+
stream;
|
|
14
|
+
renderer;
|
|
15
|
+
approval;
|
|
16
|
+
tracker;
|
|
17
|
+
sessions;
|
|
18
|
+
constructor(http, stream, renderer, approval, tracker, sessions) {
|
|
19
|
+
this.http = http;
|
|
20
|
+
this.stream = stream;
|
|
21
|
+
this.renderer = renderer;
|
|
22
|
+
this.approval = approval;
|
|
23
|
+
this.tracker = tracker;
|
|
24
|
+
this.sessions = sessions;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Run one prompt to completion. Resolves when the host emits `agent:end` for this run.
|
|
28
|
+
* `signal` aborts streaming (the host run itself is cancelled by the host on disconnect).
|
|
29
|
+
*/
|
|
30
|
+
async run(req, signal) {
|
|
31
|
+
await this.stream.connect();
|
|
32
|
+
// Subscribe BEFORE issuing the prompt so we never miss the first chunk. Until the POST
|
|
33
|
+
// returns the runId, `activeRunId` is undefined and handlers accept any run; once set we
|
|
34
|
+
// filter strictly on it.
|
|
35
|
+
let activeRunId;
|
|
36
|
+
const subs = [];
|
|
37
|
+
// The user's turn is appended immediately (crash-safe) for resume.
|
|
38
|
+
if (req.sessionId) {
|
|
39
|
+
await this.sessions.append(req.sessionId, makeTurn("user", req.prompt));
|
|
40
|
+
}
|
|
41
|
+
let outputBuffer = "";
|
|
42
|
+
const toolCalls = [];
|
|
43
|
+
const result = new Promise((resolve, reject) => {
|
|
44
|
+
const onOutput = (p) => {
|
|
45
|
+
if (activeRunId && p.runId !== activeRunId)
|
|
46
|
+
return;
|
|
47
|
+
outputBuffer += p.chunk;
|
|
48
|
+
this.renderer.stream(p.chunk);
|
|
49
|
+
};
|
|
50
|
+
const onTool = (p) => {
|
|
51
|
+
if (activeRunId && p.runId !== activeRunId)
|
|
52
|
+
return;
|
|
53
|
+
// Defer the async approval so the synchronous event handler stays cheap.
|
|
54
|
+
void this.handleTool(p, toolCalls);
|
|
55
|
+
};
|
|
56
|
+
const onEnd = (p) => {
|
|
57
|
+
if (activeRunId && p.runId !== activeRunId)
|
|
58
|
+
return;
|
|
59
|
+
cleanup();
|
|
60
|
+
this.tracker.end(p.runId, { code: p.code, error: p.error });
|
|
61
|
+
const run = this.tracker.get(p.runId);
|
|
62
|
+
const touchedPaths = run?.touchedPaths ?? [];
|
|
63
|
+
// Append the agent's turn (collected output + tool calls) to the transcript.
|
|
64
|
+
if (req.sessionId) {
|
|
65
|
+
const turn = makeTurn("agent", outputBuffer, p.runId);
|
|
66
|
+
if (toolCalls.length)
|
|
67
|
+
turn.toolCalls = toolCalls;
|
|
68
|
+
void this.sessions.append(req.sessionId, turn);
|
|
69
|
+
}
|
|
70
|
+
if (p.error) {
|
|
71
|
+
this.renderer.error(`agent run ${shortId(p.runId)} failed: ${p.error}`);
|
|
72
|
+
}
|
|
73
|
+
else if (touchedPaths.length) {
|
|
74
|
+
// Offer undo for the first touched path (the REPL surfaces the full list).
|
|
75
|
+
this.renderer.stream(`\n[run ${shortId(p.runId)} done — undo with: pocketdev undo ${p.runId} <path>]\n`);
|
|
76
|
+
}
|
|
77
|
+
resolve({ runId: p.runId, agent: run?.agent ?? "", code: p.code, error: p.error, touchedPaths });
|
|
78
|
+
};
|
|
79
|
+
const onAbort = () => {
|
|
80
|
+
cleanup();
|
|
81
|
+
reject(new CliError("network", "agent run aborted before completion."));
|
|
82
|
+
};
|
|
83
|
+
const cleanup = () => {
|
|
84
|
+
for (const off of subs)
|
|
85
|
+
off();
|
|
86
|
+
signal.removeEventListener("abort", onAbort);
|
|
87
|
+
};
|
|
88
|
+
subs.push(this.stream.on("agent:output", onOutput));
|
|
89
|
+
subs.push(this.stream.on("agent:tool", onTool));
|
|
90
|
+
subs.push(this.stream.on("agent:end", onEnd));
|
|
91
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
92
|
+
});
|
|
93
|
+
// Now issue the prompt; map 422 (agent unavailable) to a friendly CliError upstream.
|
|
94
|
+
const res = await this.http.post("/api/agent/prompt", {
|
|
95
|
+
...(req.agentId ? { agentId: req.agentId } : {}),
|
|
96
|
+
prompt: req.prompt,
|
|
97
|
+
...(req.context ? { context: req.context } : {}),
|
|
98
|
+
...(req.attachments ? { attachments: req.attachments } : {}),
|
|
99
|
+
});
|
|
100
|
+
activeRunId = res.runId;
|
|
101
|
+
this.tracker.begin(res.runId, res.agent);
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
/** Run the approval gate for a tool event and record it for the transcript + undo. */
|
|
105
|
+
async handleTool(p, toolCalls) {
|
|
106
|
+
const path = typeof p.path === "string" ? p.path : undefined;
|
|
107
|
+
const action = normalizeAction(p.action);
|
|
108
|
+
const sideEffect = sideEffectFor(p.kind);
|
|
109
|
+
if (path)
|
|
110
|
+
this.tracker.touch(p.runId, path);
|
|
111
|
+
toolCalls.push({ kind: kindFor(p.kind), action, path });
|
|
112
|
+
// Reads are auto-allowed by the default policy; writes/execs prompt the user.
|
|
113
|
+
const allowed = await this.approval.guard({ name: toolName(p.kind, action), sideEffect, path });
|
|
114
|
+
if (!allowed) {
|
|
115
|
+
this.renderer.error(`Denied ${toolName(p.kind, action)}${path ? ` on ${path}` : ""}.`, "The host may have already applied this server-side; review with `pocketdev diff`.");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
120
|
+
function makeTurn(role, text, runId) {
|
|
121
|
+
return { id: randomUUID(), role, text, at: Date.now(), ...(runId ? { runId } : {}) };
|
|
122
|
+
}
|
|
123
|
+
function sideEffectFor(kind) {
|
|
124
|
+
switch (kind) {
|
|
125
|
+
case "read":
|
|
126
|
+
return "read";
|
|
127
|
+
case "file":
|
|
128
|
+
return "write";
|
|
129
|
+
case "cmd":
|
|
130
|
+
return "exec";
|
|
131
|
+
default:
|
|
132
|
+
return "exec";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function kindFor(kind) {
|
|
136
|
+
switch (kind) {
|
|
137
|
+
case "read":
|
|
138
|
+
return "read";
|
|
139
|
+
case "file":
|
|
140
|
+
return "file";
|
|
141
|
+
case "cmd":
|
|
142
|
+
return "cmd";
|
|
143
|
+
default:
|
|
144
|
+
return "cmd";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function normalizeAction(action) {
|
|
148
|
+
if (action === "create" || action === "modify" || action === "delete")
|
|
149
|
+
return action;
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
function toolName(kind, action) {
|
|
153
|
+
if (kind === "file")
|
|
154
|
+
return action ? `file:${action}` : "file";
|
|
155
|
+
if (kind === "cmd")
|
|
156
|
+
return "command";
|
|
157
|
+
return "read";
|
|
158
|
+
}
|
|
159
|
+
function shortId(id) {
|
|
160
|
+
return id.slice(0, 8);
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=agent-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-loop.js","sourceRoot":"","sources":["../../src/agent/agent-loop.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,EAAE;AACF,sFAAsF;AACtF,0FAA0F;AAC1F,EAAE;AACF,4FAA4F;AAC5F,6FAA6F;AAC7F,oFAAoF;AAEpF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAkC7C,MAAM,OAAO,SAAS;IAED;IACA;IACA;IACA;IACA;IACA;IANnB,YACmB,IAAc,EACd,MAAkB,EAClB,QAAkB,EAClB,QAAsB,EACtB,OAAmB,EACnB,QAAsB;QALtB,SAAI,GAAJ,IAAI,CAAU;QACd,WAAM,GAAN,MAAM,CAAY;QAClB,aAAQ,GAAR,QAAQ,CAAU;QAClB,aAAQ,GAAR,QAAQ,CAAc;QACtB,YAAO,GAAP,OAAO,CAAY;QACnB,aAAQ,GAAR,QAAQ,CAAc;IACtC,CAAC;IAEJ;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAC,GAAkB,EAAE,MAAmB;QAC/C,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAE5B,uFAAuF;QACvF,yFAAyF;QACzF,yBAAyB;QACzB,IAAI,WAA+B,CAAC;QACpC,MAAM,IAAI,GAAkB,EAAE,CAAC;QAE/B,mEAAmE;QACnE,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1E,CAAC;QAED,IAAI,YAAY,GAAG,EAAE,CAAC;QACtB,MAAM,SAAS,GAAe,EAAE,CAAC;QAEjC,MAAM,MAAM,GAAG,IAAI,OAAO,CAAa,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACzD,MAAM,QAAQ,GAAG,CAAC,CAAiC,EAAE,EAAE;gBACrD,IAAI,WAAW,IAAI,CAAC,CAAC,KAAK,KAAK,WAAW;oBAAE,OAAO;gBACnD,YAAY,IAAI,CAAC,CAAC,KAAK,CAAC;gBACxB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAChC,CAAC,CAAC;YAEF,MAAM,MAAM,GAAG,CAAC,CAAY,EAAE,EAAE;gBAC9B,IAAI,WAAW,IAAI,CAAC,CAAC,KAAK,KAAK,WAAW;oBAAE,OAAO;gBACnD,yEAAyE;gBACzE,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;YACrC,CAAC,CAAC;YAEF,MAAM,KAAK,GAAG,CAAC,CAA8B,EAAE,EAAE;gBAC/C,IAAI,WAAW,IAAI,CAAC,CAAC,KAAK,KAAK,WAAW;oBAAE,OAAO;gBACnD,OAAO,EAAE,CAAC;gBACV,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;gBACtC,MAAM,YAAY,GAAG,GAAG,EAAE,YAAY,IAAI,EAAE,CAAC;gBAE7C,6EAA6E;gBAC7E,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;oBAClB,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;oBACtD,IAAI,SAAS,CAAC,MAAM;wBAAE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;oBACjD,KAAK,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;gBACjD,CAAC;gBAED,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;oBACZ,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,aAAa,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC1E,CAAC;qBAAM,IAAI,YAAY,CAAC,MAAM,EAAE,CAAC;oBAC/B,2EAA2E;oBAC3E,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC,KAAK,YAAY,CAAC,CAAC;gBAC3G,CAAC;gBAED,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;YACnG,CAAC,CAAC;YAEF,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,OAAO,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,QAAQ,CAAC,SAAS,EAAE,sCAAsC,CAAC,CAAC,CAAC;YAC1E,CAAC,CAAC;YAEF,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,KAAK,MAAM,GAAG,IAAI,IAAI;oBAAE,GAAG,EAAE,CAAC;gBAC9B,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC/C,CAAC,CAAC;YAEF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC;YACpD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;YAChD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,qFAAqF;QACrF,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAiB,mBAAmB,EAAE;YACpE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChD,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChD,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7D,CAAC,CAAC;QAEH,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QAEzC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,sFAAsF;IAC9E,KAAK,CAAC,UAAU,CAAC,CAAY,EAAE,SAAqB;QAC1D,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7D,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACzC,MAAM,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAEzC,IAAI,IAAI;YAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC5C,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAExD,8EAA8E;QAC9E,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QAChG,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,IAAI,CAAC,QAAQ,CAAC,KAAK,CACjB,UAAU,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EACjE,mFAAmF,CACpF,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAED,iFAAiF;AAEjF,SAAS,QAAQ,CAAC,IAAkB,EAAE,IAAY,EAAE,KAAc;IAChE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACvF,CAAC;AAED,SAAS,aAAa,CAAC,IAAuB;IAC5C,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,MAAM;YACT,OAAO,OAAO,CAAC;QACjB,KAAK,KAAK;YACR,OAAO,MAAM,CAAC;QAChB;YACE,OAAO,MAAM,CAAC;IAClB,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,IAAuB;IACtC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,KAAK;YACR,OAAO,KAAK,CAAC;QACf;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,MAAe;IACtC,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IACrF,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,QAAQ,CAAC,IAAuB,EAAE,MAA2B;IACpE,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/D,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,SAAS,CAAC;IACrC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,OAAO,CAAC,EAAU;IACzB,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Renderer } from "../render/ports.js";
|
|
2
|
+
/** Side effect class drives the gate: reads pass freely, writes/execs are gated. */
|
|
3
|
+
export type SideEffect = "read" | "write" | "exec";
|
|
4
|
+
/** A normalized request the gate reasons about (from a host `agent:tool` event or a Tool). */
|
|
5
|
+
export interface ToolRequest {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly sideEffect: SideEffect;
|
|
8
|
+
readonly path?: string;
|
|
9
|
+
}
|
|
10
|
+
export type Decision = "ask" | "allow" | "deny";
|
|
11
|
+
/**
|
|
12
|
+
* PermissionPolicy decides ask/allow/deny per tool. Implementations are pluggable (OCP):
|
|
13
|
+
* a default policy, a "yolo" auto-allow policy for `--dangerously-allow-all`, or a config
|
|
14
|
+
* driven rule set. The gate depends only on this interface (DIP).
|
|
15
|
+
*/
|
|
16
|
+
export interface PermissionPolicy {
|
|
17
|
+
decide(tool: ToolRequest): Decision;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Default policy: reads are always allowed, writes/execs ask, unless a rule has been
|
|
21
|
+
* remembered via `remember()` (the "always" answer) for that tool name.
|
|
22
|
+
*/
|
|
23
|
+
export declare class DefaultPermissionPolicy implements PermissionPolicy {
|
|
24
|
+
private readonly autoAllowAll;
|
|
25
|
+
private readonly allowed;
|
|
26
|
+
constructor(autoAllowAll?: boolean);
|
|
27
|
+
decide(tool: ToolRequest): Decision;
|
|
28
|
+
/** Remember an "always" answer for the rest of the session. */
|
|
29
|
+
remember(toolName: string): void;
|
|
30
|
+
}
|
|
31
|
+
export declare class ApprovalGate {
|
|
32
|
+
private readonly policy;
|
|
33
|
+
private readonly renderer;
|
|
34
|
+
constructor(policy: PermissionPolicy, renderer: Renderer);
|
|
35
|
+
/** Returns true if the tool may proceed (the host still re-checks via Casbin). */
|
|
36
|
+
guard(tool: ToolRequest): Promise<boolean>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=approval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../src/agent/approval.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,oFAAoF;AACpF,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEnD,8FAA8F;AAC9F,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;AAEhD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,IAAI,EAAE,WAAW,GAAG,QAAQ,CAAC;CACrC;AAED;;;GAGG;AACH,qBAAa,uBAAwB,YAAW,gBAAgB;IAGlD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAFzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;gBAEhB,YAAY,UAAQ;IAEjD,MAAM,CAAC,IAAI,EAAE,WAAW,GAAG,QAAQ;IAOnC,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;CAGjC;AAED,qBAAa,YAAY;IAErB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBADR,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,QAAQ;IAGrC,kFAAkF;IAC5E,KAAK,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;CAkBjD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/agent/approval.ts — ApprovalGate + PermissionPolicy (cli.md §7).
|
|
2
|
+
//
|
|
3
|
+
// The approval gate is a LOCAL UX convenience. It is NEVER the sole guard: the host
|
|
4
|
+
// independently authorizes every action via Casbin server-side. The gate prompts the user
|
|
5
|
+
// before a write/exec tool is applied, and supports "always" to remember a decision for the
|
|
6
|
+
// rest of the session.
|
|
7
|
+
/**
|
|
8
|
+
* Default policy: reads are always allowed, writes/execs ask, unless a rule has been
|
|
9
|
+
* remembered via `remember()` (the "always" answer) for that tool name.
|
|
10
|
+
*/
|
|
11
|
+
export class DefaultPermissionPolicy {
|
|
12
|
+
autoAllowAll;
|
|
13
|
+
allowed = new Set();
|
|
14
|
+
constructor(autoAllowAll = false) {
|
|
15
|
+
this.autoAllowAll = autoAllowAll;
|
|
16
|
+
}
|
|
17
|
+
decide(tool) {
|
|
18
|
+
if (this.autoAllowAll)
|
|
19
|
+
return "allow";
|
|
20
|
+
if (tool.sideEffect === "read")
|
|
21
|
+
return "allow";
|
|
22
|
+
if (this.allowed.has(tool.name))
|
|
23
|
+
return "allow";
|
|
24
|
+
return "ask";
|
|
25
|
+
}
|
|
26
|
+
/** Remember an "always" answer for the rest of the session. */
|
|
27
|
+
remember(toolName) {
|
|
28
|
+
this.allowed.add(toolName);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class ApprovalGate {
|
|
32
|
+
policy;
|
|
33
|
+
renderer;
|
|
34
|
+
constructor(policy, renderer) {
|
|
35
|
+
this.policy = policy;
|
|
36
|
+
this.renderer = renderer;
|
|
37
|
+
}
|
|
38
|
+
/** Returns true if the tool may proceed (the host still re-checks via Casbin). */
|
|
39
|
+
async guard(tool) {
|
|
40
|
+
switch (this.policy.decide(tool)) {
|
|
41
|
+
case "allow":
|
|
42
|
+
return true;
|
|
43
|
+
case "deny":
|
|
44
|
+
return false;
|
|
45
|
+
case "ask": {
|
|
46
|
+
const answer = await this.renderer.prompt(`Allow ${tool.name}${tool.path ? ` on ${tool.path}` : ""}?`, ["yes", "no", "always"]);
|
|
47
|
+
if (answer === "always" && this.policy instanceof DefaultPermissionPolicy) {
|
|
48
|
+
this.policy.remember(tool.name);
|
|
49
|
+
}
|
|
50
|
+
return answer !== "no";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=approval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../../src/agent/approval.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,EAAE;AACF,oFAAoF;AACpF,0FAA0F;AAC1F,4FAA4F;AAC5F,uBAAuB;AAyBvB;;;GAGG;AACH,MAAM,OAAO,uBAAuB;IAGL;IAFZ,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAE7C,YAA6B,eAAe,KAAK;QAApB,iBAAY,GAAZ,YAAY,CAAQ;IAAG,CAAC;IAErD,MAAM,CAAC,IAAiB;QACtB,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO,OAAO,CAAC;QACtC,IAAI,IAAI,CAAC,UAAU,KAAK,MAAM;YAAE,OAAO,OAAO,CAAC;QAC/C,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,OAAO,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,+DAA+D;IAC/D,QAAQ,CAAC,QAAgB;QACvB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,OAAO,YAAY;IAEJ;IACA;IAFnB,YACmB,MAAwB,EACxB,QAAkB;QADlB,WAAM,GAAN,MAAM,CAAkB;QACxB,aAAQ,GAAR,QAAQ,CAAU;IAClC,CAAC;IAEJ,kFAAkF;IAClF,KAAK,CAAC,KAAK,CAAC,IAAiB;QAC3B,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,KAAK,OAAO;gBACV,OAAO,IAAI,CAAC;YACd,KAAK,MAAM;gBACT,OAAO,KAAK,CAAC;YACf,KAAK,KAAK,CAAC,CAAC,CAAC;gBACX,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CACvC,SAAS,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAC3D,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CACxB,CAAC;gBACF,IAAI,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,YAAY,uBAAuB,EAAE,CAAC;oBAC1E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAClC,CAAC;gBACD,OAAO,MAAM,KAAK,IAAI,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface TrackedRun {
|
|
2
|
+
readonly runId: string;
|
|
3
|
+
readonly agent: string;
|
|
4
|
+
readonly startedAt: number;
|
|
5
|
+
/** Paths the run created/modified, in first-seen order (for undo offers). */
|
|
6
|
+
readonly touchedPaths: string[];
|
|
7
|
+
endedAt?: number;
|
|
8
|
+
code?: number;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class RunTracker {
|
|
12
|
+
private readonly runs;
|
|
13
|
+
begin(runId: string, agent: string): TrackedRun;
|
|
14
|
+
/** Record a file path a tool touched during a run (deduplicated, order-preserving). */
|
|
15
|
+
touch(runId: string, path: string): void;
|
|
16
|
+
end(runId: string, result: {
|
|
17
|
+
code?: number;
|
|
18
|
+
error?: string;
|
|
19
|
+
}): void;
|
|
20
|
+
get(runId: string): TrackedRun | undefined;
|
|
21
|
+
/** runIds still streaming — replayed in ResumeState after a reconnect. */
|
|
22
|
+
inFlight(): string[];
|
|
23
|
+
/** Most recent run (for `/undo` with no explicit runId). */
|
|
24
|
+
latest(): TrackedRun | undefined;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=run-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-tracker.d.ts","sourceRoot":"","sources":["../../src/agent/run-tracker.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiC;IAEtD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU;IAM/C,uFAAuF;IACvF,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IASnE,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAI1C,0EAA0E;IAC1E,QAAQ,IAAI,MAAM,EAAE;IAIpB,4DAA4D;IAC5D,MAAM,IAAI,UAAU,GAAG,SAAS;CAOjC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/agent/run-tracker.ts — tracks agent runs for /undo and reconnect/resume.
|
|
2
|
+
//
|
|
3
|
+
// SRP: remember which runIds are in-flight (so the StreamPort can resume them after a drop)
|
|
4
|
+
// and which file paths a run touched (so `/undo runId path` can offer the right targets).
|
|
5
|
+
export class RunTracker {
|
|
6
|
+
runs = new Map();
|
|
7
|
+
begin(runId, agent) {
|
|
8
|
+
const run = { runId, agent, startedAt: Date.now(), touchedPaths: [] };
|
|
9
|
+
this.runs.set(runId, run);
|
|
10
|
+
return run;
|
|
11
|
+
}
|
|
12
|
+
/** Record a file path a tool touched during a run (deduplicated, order-preserving). */
|
|
13
|
+
touch(runId, path) {
|
|
14
|
+
const run = this.runs.get(runId);
|
|
15
|
+
if (run && !run.touchedPaths.includes(path))
|
|
16
|
+
run.touchedPaths.push(path);
|
|
17
|
+
}
|
|
18
|
+
end(runId, result) {
|
|
19
|
+
const run = this.runs.get(runId);
|
|
20
|
+
if (run) {
|
|
21
|
+
run.endedAt = Date.now();
|
|
22
|
+
run.code = result.code;
|
|
23
|
+
run.error = result.error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
get(runId) {
|
|
27
|
+
return this.runs.get(runId);
|
|
28
|
+
}
|
|
29
|
+
/** runIds still streaming — replayed in ResumeState after a reconnect. */
|
|
30
|
+
inFlight() {
|
|
31
|
+
return [...this.runs.values()].filter((r) => r.endedAt === undefined).map((r) => r.runId);
|
|
32
|
+
}
|
|
33
|
+
/** Most recent run (for `/undo` with no explicit runId). */
|
|
34
|
+
latest() {
|
|
35
|
+
let newest;
|
|
36
|
+
for (const r of this.runs.values()) {
|
|
37
|
+
if (!newest || r.startedAt > newest.startedAt)
|
|
38
|
+
newest = r;
|
|
39
|
+
}
|
|
40
|
+
return newest;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=run-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-tracker.js","sourceRoot":"","sources":["../../src/agent/run-tracker.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,4FAA4F;AAC5F,0FAA0F;AAa1F,MAAM,OAAO,UAAU;IACJ,IAAI,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEtD,KAAK,CAAC,KAAa,EAAE,KAAa;QAChC,MAAM,GAAG,GAAe,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;QAClF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC1B,OAAO,GAAG,CAAC;IACb,CAAC;IAED,uFAAuF;IACvF,KAAK,CAAC,KAAa,EAAE,IAAY;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3E,CAAC;IAED,GAAG,CAAC,KAAa,EAAE,MAAyC;QAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,GAAG,EAAE,CAAC;YACR,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACzB,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;YACvB,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,GAAG,CAAC,KAAa;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,0EAA0E;IAC1E,QAAQ;QACN,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC5F,CAAC;IAED,4DAA4D;IAC5D,MAAM;QACJ,IAAI,MAA8B,CAAC;QACnC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YACnC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS;gBAAE,MAAM,GAAG,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** OIDC discovery document fields we use. */
|
|
2
|
+
export interface OidcDiscovery {
|
|
3
|
+
issuer: string;
|
|
4
|
+
device_authorization_endpoint: string;
|
|
5
|
+
token_endpoint: string;
|
|
6
|
+
revocation_endpoint?: string;
|
|
7
|
+
jwks_uri: string;
|
|
8
|
+
}
|
|
9
|
+
/** RFC 8628 device-authorization response. */
|
|
10
|
+
export interface DeviceAuthResponse {
|
|
11
|
+
device_code: string;
|
|
12
|
+
user_code: string;
|
|
13
|
+
verification_uri: string;
|
|
14
|
+
verification_uri_complete?: string;
|
|
15
|
+
expires_in: number;
|
|
16
|
+
interval?: number;
|
|
17
|
+
}
|
|
18
|
+
/** Token endpoint success response. */
|
|
19
|
+
export interface TokenSet {
|
|
20
|
+
access_token: string;
|
|
21
|
+
token_type: string;
|
|
22
|
+
expires_in?: number;
|
|
23
|
+
refresh_token?: string;
|
|
24
|
+
id_token?: string;
|
|
25
|
+
scope?: string;
|
|
26
|
+
/** Absolute epoch-ms expiry computed from `expires_in` at receipt. */
|
|
27
|
+
expiresAt?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface DeviceFlowConfig {
|
|
30
|
+
oidcIssuer: string;
|
|
31
|
+
clientId: string;
|
|
32
|
+
scopes: readonly string[];
|
|
33
|
+
}
|
|
34
|
+
/** Injected so the device flow stays testable and dependency-free (no global fetch coupling). */
|
|
35
|
+
export type Fetch = typeof fetch;
|
|
36
|
+
/** Called once with the user_code + verification URL so the renderer can prompt the user. */
|
|
37
|
+
export type DevicePrompt = (info: {
|
|
38
|
+
userCode: string;
|
|
39
|
+
verificationUri: string;
|
|
40
|
+
verificationUriComplete?: string;
|
|
41
|
+
expiresIn: number;
|
|
42
|
+
}) => void;
|
|
43
|
+
export declare class DeviceFlow {
|
|
44
|
+
private readonly cfg;
|
|
45
|
+
private readonly fetchImpl;
|
|
46
|
+
private readonly sleep;
|
|
47
|
+
constructor(cfg: DeviceFlowConfig, fetchImpl?: Fetch, sleep?: (ms: number) => Promise<void>);
|
|
48
|
+
/** Discover the OIDC endpoints via /.well-known/openid-configuration. */
|
|
49
|
+
discover(): Promise<OidcDiscovery>;
|
|
50
|
+
/**
|
|
51
|
+
* Run the full grant: discover → request device code (with PKCE challenge) → prompt → poll.
|
|
52
|
+
* Resolves with a TokenSet (access + optional refresh/id tokens) on user approval.
|
|
53
|
+
*/
|
|
54
|
+
authorize(prompt: DevicePrompt): Promise<TokenSet>;
|
|
55
|
+
/** Refresh an access token using a refresh_token grant. */
|
|
56
|
+
refresh(refreshToken: string): Promise<TokenSet>;
|
|
57
|
+
/** Revoke a token (refresh or access) at logout, if the issuer exposes a revocation endpoint. */
|
|
58
|
+
revoke(token: string): Promise<void>;
|
|
59
|
+
private poll;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=device-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-flow.d.ts","sourceRoot":"","sources":["../../src/auth/device-flow.ts"],"names":[],"mappings":"AAUA,6CAA6C;AAC7C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B,EAAE,MAAM,CAAC;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,8CAA8C;AAC9C,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,uCAAuC;AACvC,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AAED,iGAAiG;AACjG,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC;AAEjC,6FAA6F;AAC7F,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB,KAAK,IAAI,CAAC;AAEX,qBAAa,UAAU;IAEnB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAFL,GAAG,EAAE,gBAAgB,EACrB,SAAS,GAAE,KAAa,EACxB,KAAK,GAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAgB;IAGtE,yEAAyE;IACnE,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC;IAYxC;;;OAGG;IACG,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC;IA+BxD,2DAA2D;IACrD,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAkBtD,iGAAiG;IAC3F,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAY5B,IAAI;CAyCnB"}
|