copilot-reverse 0.0.1 → 0.0.2

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,37 +1,166 @@
1
1
  # copilot-reverse
2
2
 
3
- Interactive terminal app that turns your GitHub Copilot subscription into local
4
- OpenAI- and Anthropic-compatible endpoints, with a self-healing daemon and a
5
- built-in assistant.
3
+ **Use the GitHub Copilot subscription you already pay for as a local Claude Code / Codex backend.**
4
+ No new API keys. No per-token bills. One terminal app.
6
5
 
7
- > **New here? Read the [User Guide](GUIDE.md) — a 60-second start, no jargon.**
6
+ ```
7
+ ┌─────────────┐ ┌───────────────────┐ ┌─────────────┐
8
+ │ Claude Code │ ─────▶ │ copilot-reverse │ ─────▶ │ Copilot │
9
+ │ / Codex │ local │ (your machine) │ proxy │ (your sub) │
10
+ └─────────────┘ └───────────────────┘ └─────────────┘
11
+ ```
8
12
 
9
13
  > **Disclaimer:** The GitHub Copilot integration uses community-documented,
10
14
  > unofficial endpoints, for use with your own Copilot subscription only. It may
11
15
  > break if GitHub changes these endpoints.
12
16
 
17
+ ---
18
+
19
+ ## 60-second start
20
+
21
+ ```bash
22
+ npx copilot-reverse
23
+ ```
24
+
25
+ 1. It asks you to log in to GitHub (device code — paste a code in your browser). One time only.
26
+ 2. The terminal app launches. You'll see a prompt and a status bar.
27
+ 3. In the app, type:
28
+ ```
29
+ /setup-claude
30
+ ```
31
+ Pick a model (e.g. **claude-opus-4.8 (1M)**), choose **global**, done.
32
+ 4. Open a **new** terminal and run `claude`. It's now talking to Copilot through copilot-reverse. 🎉
33
+
34
+ That's it. Codex users: run `/setup-codex` instead.
35
+
36
+ Here's the app itself — a prompt, a live status bar, and slash-command autocomplete:
37
+
38
+ ```text
39
+ ✳ copilot-reverse worker: ready
40
+
41
+ Type a message to chat with the assistant, or /help for commands.
42
+ ╭─────────────────────────────────────────────────────────────────────────────────────╮
43
+ │ › /setup │
44
+ ╰─────────────────────────────────────────────────────────────────────────────────────╯
45
+ ❯ /setup-claude print Claude Code config
46
+ /setup-codex print Codex/OpenAI config
47
+ /setup-status show configured endpoints
48
+ ↑↓ navigate · tab complete · enter run
49
+ model claude-opus-4.8 · daemon ready · claude u:✓ p:○ codex u:✓ p:○ · /help
50
+ ```
51
+
52
+ ---
53
+
54
+ ## What can I do in the app?
55
+
56
+ Just **talk to it** — it understands plain English and will do the work for you:
57
+
58
+ > *"list models"* → shows every model + its context window
59
+ > *"set up claude"* → configures Claude Code
60
+ > *"is the worker healthy?"* → runs a health check
61
+ > *"why did my last request fail?"* → shows the error
62
+
63
+ Prefer commands? Type `/` to see them all. The essentials:
64
+
65
+ | Command | What it does |
66
+ |---|---|
67
+ | `/setup-claude` · `/setup-codex` | Point Claude Code / Codex at copilot-reverse |
68
+ | `/model` | Switch the chat model (1M-context models marked) |
69
+ | `/status` · `/doctor` | Is everything healthy? |
70
+ | `/logs` · `/metrics` | What ran, what failed, and why |
71
+ | `/dashboard` | Open a live web dashboard in your browser |
72
+ | `/report` | File a pre-filled bug report (diagnostics only — no prompts) |
73
+ | `/reset-claude` · `/reset-codex` | Undo setup, restore original config |
74
+ | `/login` · `/logout` | Sign in to GitHub (device-code) · sign out (remove token) |
75
+ | `/help` · `/quit` | List commands · exit |
76
+
77
+ ### The live dashboard
78
+
79
+ `/dashboard` opens a self-refreshing web view of everything happening through the proxy — worker
80
+ health, request volume, and (most useful) recent **errors with their real messages**:
81
+
13
82
  ![copilot-reverse dashboard](images/dashboard.png)
14
83
 
15
- ## Quick start
84
+ ---
85
+
86
+ ## Connect your own tools
87
+
88
+ Already have something that speaks OpenAI or Anthropic? Point it here:
89
+
90
+ - **OpenAI-compatible:** `http://127.0.0.1:7891/v1`
91
+ - **Anthropic-compatible:** `http://127.0.0.1:7891`
92
+
93
+ Any API key value works locally (it's your machine). Example:
16
94
 
17
95
  ```bash
18
- npx copilot-reverse # device-code login, then the TUI launches
96
+ export ANTHROPIC_BASE_URL=http://127.0.0.1:7891
97
+ export ANTHROPIC_API_KEY=local
98
+ claude
19
99
  ```
20
100
 
21
- In the TUI: `/help`, `/doctor`, `/setup-claude`, `/setup-codex`, `/metrics`, or
22
- just talk to the assistant in natural language.
101
+ ---
23
102
 
24
- Point clients at:
25
- - OpenAI: `http://127.0.0.1:7891/v1`
26
- - Anthropic: `http://127.0.0.1:7891`
103
+ ## The status bar, decoded
27
104
 
28
- ## Architecture (M1)
105
+ The bottom line of the app tells you everything at a glance:
29
106
 
30
- - **TUI** (Ink) — the `copilot-reverse` process: REPL + slash commands + claude-agent-sdk
107
+ ```text
108
+ model claude-opus-4.8 · daemon ready · claude u:✓ p:○ codex u:○ p:○ · /help
109
+ ```
110
+
111
+ - **worker / daemon** — green `ready` means the proxy is up and self-healing.
112
+ - **claude u:✓ p:○** — Claude Code is configured at the **u**ser (global) level, not in this **p**roject. Read live from your real config files.
113
+
114
+ ---
115
+
116
+ ## Troubleshooting
117
+
118
+ **"context 100%" or `/compact` fails in Claude Code**
119
+ Re-run `/setup-claude` and pick a **1M** model (e.g. `claude-opus-4.8 (1M)`). copilot-reverse writes
120
+ the right context-window hint so the client stops assuming a small window. Then restart Claude Code.
121
+
122
+ **"GitHub login expired"**
123
+ Your Copilot session lapsed. You don't need to restart anything — when a chat fails, copilot-reverse
124
+ detects it and tells you right there:
125
+
126
+ ```text
127
+ assistant error: 401 authentication_error: GitHub login expired
128
+ ↳ your GitHub login looks expired — run /login to sign in again
129
+ ```
130
+
131
+ Just type **`/login`**, complete the device-code prompt, and you're back — the worker reloads the new
132
+ token automatically. (Switching accounts? `/logout` first, then `/login`.)
133
+
134
+ **A request failed and I don't know why**
135
+ Type `/logs` (or ask *"why did that fail?"*). Every failure is captured with its real upstream
136
+ message. Still stuck? `/report` opens a pre-filled GitHub issue with diagnostics — **never** your
137
+ prompt content.
138
+
139
+ **Want to undo everything**
140
+ `/reset-claude` and `/reset-codex` remove exactly the keys copilot-reverse added and leave the rest
141
+ of your config untouched.
142
+
143
+ ---
144
+
145
+ ## Good to know
146
+
147
+ - **Your data stays local.** The app proxies between your editor and Copilot on `127.0.0.1`. Your
148
+ GitHub token lives only in `~/.copilot-reverse/creds.json` on your own disk.
149
+ - **It heals itself.** If the proxy crashes, the supervisor restarts it with backoff and records why.
150
+ - **Unofficial endpoints.** This uses community-documented Copilot endpoints with *your own*
151
+ subscription. It may break if GitHub changes them — that's the trade-off for not needing extra keys.
152
+
153
+ ---
154
+
155
+ ## Architecture
156
+
157
+ Three processes, one terminal app:
158
+
159
+ - **TUI** (Ink) — the `copilot-reverse` process: REPL + slash commands + a claude-agent-sdk
31
160
  assistant (which dogfoods copilot-reverse's own Anthropic endpoint).
32
161
  - **Supervisor** (:7890) — control API + SQLite + self-healing worker supervision.
33
- - **Worker** (:7891) — OpenAI `/v1/chat/completions` + Anthropic `/v1/messages`
34
- → Copilot, with tool-use translation both ways.
162
+ - **Worker** (:7891) — OpenAI `/v1/chat/completions` + Anthropic `/v1/messages` → Copilot,
163
+ with tool-use translation both ways.
35
164
 
36
165
  ## Development
37
166
 
@@ -58,3 +187,8 @@ re-run and update `e2e/RESULTS.md`.
58
187
  `useInput` subscribes to stdin asynchronously after mount, so writes issued in
59
188
  the same tick as `render()` are dropped. The delay lets the subscription
60
189
  attach; assertions are otherwise unchanged.
190
+
191
+ ---
192
+
193
+ Questions or bugs? Use `/report` from inside the app, or open an issue on
194
+ [GitHub](https://github.com/wangcansunking/copilot-reverse). Happy hacking. 🚀
package/dist/cli/index.js CHANGED
@@ -10,7 +10,7 @@ import { probeSupervisor } from "../daemon/lifecycle.js";
10
10
  import { startSupervisor } from "../supervisor/index.js";
11
11
  import { runAssistantTurn } from "../tui/assistant/runtime.js";
12
12
  import { makeOnChat } from "../tui/assistant/on-chat.js";
13
- import { readGhToken } from "../shared/creds.js";
13
+ import { readGhToken, clearGhToken } from "../shared/creds.js";
14
14
  import { readClientSetup, writeClientSetup } from "../shared/client-setup.js";
15
15
  import { readChatModel, writeChatModel } from "../shared/prefs.js";
16
16
  import { CopilotTokenStore, isCopilotTokenValid } from "../providers/copilot/token.js";
@@ -66,9 +66,23 @@ async function launchTui() {
66
66
  const registry = buildRegistry({ client, quit }, endpoint, {
67
67
  dashboardUrl: `http://${cfg.bindHost}:${cfg.supervisorPort}/`,
68
68
  reportRepo: cfg.reportRepo,
69
- appVersion: "0.0.1",
69
+ appVersion: "0.0.2",
70
70
  platform: `${process.platform} node-${process.version}`,
71
71
  resetClient,
72
+ // Re-run device-code login, then restart the worker so it picks up the new token.
73
+ login: async () => {
74
+ const lines = [];
75
+ await runDeviceLogin(dataDir(), fetch, (m) => lines.push(m));
76
+ await client.restart().catch(() => { });
77
+ lines.push("worker restarting with the new token");
78
+ return lines;
79
+ },
80
+ // Clear the stored token and restart the worker (it will report unauthenticated until re-login).
81
+ logout: async () => {
82
+ clearGhToken(dataDir());
83
+ await client.restart().catch(() => { });
84
+ return ["signed out — GitHub token removed", "run /login to sign in again"];
85
+ },
72
86
  });
73
87
  // Filled in below once we have a token; the assistant prefers a model's real window over the default.
74
88
  const modelLimits = {};
@@ -127,7 +141,7 @@ async function launchTui() {
127
141
  }));
128
142
  }
129
143
  const program = new Command();
130
- program.name("copilot-reverse").description("copilot-reverse: interactive Copilot proxy").version("0.0.1");
144
+ program.name("copilot-reverse").description("copilot-reverse: interactive Copilot proxy").version("0.0.2");
131
145
  program.command("login").description("GitHub device-code login").action(() => runDeviceLogin(dataDir()));
132
146
  program.action(() => { void launchTui(); });
133
147
  program.parseAsync(process.argv);
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  // M1: plaintext token in the data dir (0600). Encryption-at-rest is M2.
4
4
  const file = (dir) => join(dir, "creds.json");
@@ -12,3 +12,7 @@ export function readGhToken(dir) {
12
12
  return null;
13
13
  return JSON.parse(readFileSync(file(dir), "utf8")).ghToken ?? null;
14
14
  }
15
+ // Remove the stored token (logout). No-op if there's nothing to remove.
16
+ export function clearGhToken(dir) {
17
+ rmSync(file(dir), { force: true });
18
+ }
@@ -18,8 +18,15 @@ export function makeOnChat(cfg, runner, timeoutMs = DEFAULT_TURN_TIMEOUT_MS) {
18
18
  print("⎿ interrupted");
19
19
  return;
20
20
  }
21
- print(`assistant error: ${err instanceof Error ? err.message : String(err)}`);
22
- print("\n ↳ next: retry · /model to switch model · /doctor to check health · /report to file it");
21
+ const message = err instanceof Error ? err.message : String(err);
22
+ print(`assistant error: ${message}`);
23
+ // Detect an expired/revoked GitHub login anywhere in the error and steer the user to re-auth.
24
+ if (/login expired|authentication_error|unauthorized|\b401\b|\b403\b|token exchange failed/i.test(message)) {
25
+ print("\n ↳ your GitHub login looks expired — run /login to sign in again");
26
+ }
27
+ else {
28
+ print("\n ↳ next: retry · /model to switch model · /doctor to check health · /report to file it");
29
+ }
23
30
  }
24
31
  };
25
32
  }
@@ -42,6 +42,8 @@ export function buildRegistry(ctx, endpoint, opts = {}) {
42
42
  reg.add({ name: "/setup-status", describe: "show configured endpoints", run: async () => [`OpenAI: http://${endpoint.host}:${endpoint.port}/v1`, `Anthropic: http://${endpoint.host}:${endpoint.port}`] });
43
43
  reg.add({ name: "/reset-claude", describe: "restore Claude Code config (remove copilot-reverse's keys)", run: async () => opts.resetClient ? opts.resetClient("claude") : ["reset not available"] });
44
44
  reg.add({ name: "/reset-codex", describe: "restore Codex/OpenAI config (remove copilot-reverse's keys)", run: async () => opts.resetClient ? opts.resetClient("codex") : ["reset not available"] });
45
+ reg.add({ name: "/login", describe: "sign in to GitHub (device-code)", run: async () => opts.login ? opts.login() : ["login not available"] });
46
+ reg.add({ name: "/logout", describe: "sign out — remove the stored GitHub token", run: async () => opts.logout ? opts.logout() : ["logout not available"] });
45
47
  reg.add({ name: "/model", describe: "switch the chat model", run: async () => ["opening model picker…"] });
46
48
  reg.add({ name: "/config", describe: "view & change configuration", run: async () => ["opening config panel…"] });
47
49
  reg.add({ name: "/dashboard", describe: "open the web dashboard in your browser", run: async () => {
@@ -2,6 +2,10 @@
2
2
  // structured context-window-exceeded + model_not_supported guidance instead of a bare 400).
3
3
  export function errorHint(message) {
4
4
  const m = message.toLowerCase();
5
+ // An expired/revoked GitHub login — tell the user to re-authenticate from inside the app.
6
+ if (/login expired|authentication_error|token exchange failed|unauthorized|\b401\b|\b403\b/.test(m)) {
7
+ return "GitHub login expired — run /login to sign in again";
8
+ }
5
9
  if (/context_length_exceeded|prompt is too long|maximum context|too many tokens|context window/.test(m)) {
6
10
  return "context window exceeded — the conversation is too long; /compact or switch to a larger-context model";
7
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-reverse",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Interactive terminal app that exposes your GitHub Copilot subscription as local OpenAI- and Anthropic-compatible endpoints, with a self-healing daemon and a built-in assistant.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,7 +18,6 @@
18
18
  "files": [
19
19
  "dist",
20
20
  "README.md",
21
- "GUIDE.md",
22
21
  "images"
23
22
  ],
24
23
  "keywords": [
package/GUIDE.md DELETED
@@ -1,142 +0,0 @@
1
- # copilot-reverse — User Guide
2
-
3
- **Use the Copilot subscription you already pay for as a local Claude Code / Codex backend.**
4
- No new API keys. No per-token bills. One terminal app.
5
-
6
- ```
7
- ┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
8
- │ Claude Code │ ─────▶ │ copilot-reverse │ ─────▶ │ Copilot │
9
- │ / Codex │ local │ (your machine) │ proxy │ (your sub) │
10
- └─────────────┘ └──────────────────┘ └─────────────┘
11
- ```
12
-
13
- ---
14
-
15
- ## 60-second start
16
-
17
- ```bash
18
- npx copilot-reverse
19
- ```
20
-
21
- 1. It asks you to log in to GitHub (device code — paste a code in your browser). One time only.
22
- 2. The terminal app launches. You'll see a prompt and a status bar.
23
- 3. In the app, type:
24
- ```
25
- /setup-claude
26
- ```
27
- Pick a model (e.g. **claude-opus-4.8 (1M)**), choose **global**, done.
28
- 4. Open a **new** terminal and run `claude`. It's now talking to Copilot through copilot-reverse. 🎉
29
-
30
- That's it. Codex users: run `/setup-codex` instead.
31
-
32
- Here's the app itself — a prompt, a live status bar, and slash-command autocomplete:
33
-
34
- ```text
35
- ✳ copilot-reverse worker: ready
36
-
37
- Type a message to chat with the assistant, or /help for commands.
38
- ╭─────────────────────────────────────────────────────────────────────────────────────╮
39
- │ › /setup │
40
- ╰─────────────────────────────────────────────────────────────────────────────────────╯
41
- ❯ /setup-claude print Claude Code config
42
- /setup-codex print Codex/OpenAI config
43
- /setup-status show configured endpoints
44
- ↑↓ navigate · tab complete · enter run
45
- model claude-opus-4.8 · daemon ready · claude u:✓ p:○ codex u:✓ p:○ · /help
46
- ```
47
-
48
- ---
49
-
50
- ## What can I do in the app?
51
-
52
- Just **talk to it** — it understands plain English and will do the work for you:
53
-
54
- > *"list models"* → shows every model + its context window
55
- > *"set up claude"* → configures Claude Code
56
- > *"is the worker healthy?"* → runs a health check
57
- > *"why did my last request fail?"* → shows the error
58
-
59
- Prefer commands? Type `/` to see them all. The essentials:
60
-
61
- | Command | What it does |
62
- |---|---|
63
- | `/setup-claude` · `/setup-codex` | Point Claude Code / Codex at copilot-reverse |
64
- | `/model` | Switch the chat model (1M-context models marked) |
65
- | `/status` · `/doctor` | Is everything healthy? |
66
- | `/logs` · `/metrics` | What ran, what failed, and why |
67
- | `/dashboard` | Open a live web dashboard in your browser |
68
- | `/report` | File a pre-filled bug report (diagnostics only — no prompts) |
69
- | `/reset-claude` · `/reset-codex` | Undo setup, restore original config |
70
- | `/help` · `/quit` | List commands · exit |
71
-
72
- ### The live dashboard
73
-
74
- `/dashboard` opens a self-refreshing web view of everything happening through the proxy — worker
75
- health, request volume, and (most useful) recent **errors with their real messages**:
76
-
77
- ![copilot-reverse dashboard](images/dashboard.png)
78
-
79
- ---
80
-
81
- ## Connect your own tools
82
-
83
- Already have something that speaks OpenAI or Anthropic? Point it here:
84
-
85
- - **OpenAI-compatible:** `http://127.0.0.1:7891/v1`
86
- - **Anthropic-compatible:** `http://127.0.0.1:7891`
87
-
88
- Any API key value works locally (it's your machine). Example:
89
-
90
- ```bash
91
- export ANTHROPIC_BASE_URL=http://127.0.0.1:7891
92
- export ANTHROPIC_API_KEY=local
93
- claude
94
- ```
95
-
96
- ---
97
-
98
- ## The status bar, decoded
99
-
100
- The bottom line of the app (see the screenshot above) tells you everything at a glance:
101
-
102
- ```text
103
- model claude-opus-4.8 · daemon ready · claude u:✓ p:○ codex u:○ p:○ · /help
104
- ```
105
-
106
- - **worker / daemon** — green `ready` means the proxy is up and self-healing.
107
- - **claude u:✓ p:○** — Claude Code is configured at the **u**ser (global) level, not in this **p**roject. Read live from your real config files.
108
-
109
- ---
110
-
111
- ## Troubleshooting
112
-
113
- **"context 100%" or `/compact` fails in Claude Code**
114
- Re-run `/setup-claude` and pick a **1M** model (e.g. `claude-opus-4.8 (1M)`). copilot-reverse writes
115
- the right context-window hint so the client stops assuming a small window. Then restart Claude Code.
116
-
117
- **"GitHub login expired"**
118
- Your Copilot session lapsed. Restart copilot-reverse — it'll prompt you to log in again.
119
-
120
- **A request failed and I don't know why**
121
- Type `/logs` (or ask *"why did that fail?"*). Every failure is captured with its real upstream
122
- message. Still stuck? `/report` opens a pre-filled GitHub issue with diagnostics — **never** your
123
- prompt content.
124
-
125
- **Want to undo everything**
126
- `/reset-claude` and `/reset-codex` remove exactly the keys copilot-reverse added and leave the rest
127
- of your config untouched.
128
-
129
- ---
130
-
131
- ## Good to know
132
-
133
- - **Your data stays local.** The app proxies between your editor and Copilot on `127.0.0.1`. Your
134
- GitHub token lives only in `~/.copilot-reverse/creds.json` on your own disk.
135
- - **It heals itself.** If the proxy crashes, the supervisor restarts it with backoff and records why.
136
- - **Unofficial endpoints.** This uses community-documented Copilot endpoints with *your own*
137
- subscription. It may break if GitHub changes them — that's the trade-off for not needing extra keys.
138
-
139
- ---
140
-
141
- Questions or bugs? Use `/report` from inside the app, or open an issue on
142
- [GitHub](https://github.com/wangcansunking/copilot-reverse). Happy hacking. 🚀