copilot-reverse 0.4.0 → 0.5.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/dist/core/responses-inbound.js +26 -8
- package/dist/providers/copilot/token.js +14 -4
- package/dist/supervisor/api.js +1 -1
- package/dist/supervisor/github-heartbeat.js +81 -0
- package/dist/supervisor/index.js +15 -13
- package/dist/tui/app.js +13 -6
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -50,13 +50,21 @@ export function responsesRequestToCanonical(req) {
|
|
|
50
50
|
}
|
|
51
51
|
return {
|
|
52
52
|
model: req.model, stream: Boolean(req.stream), temperature: req.temperature, maxTokens: req.max_output_tokens,
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
|
|
53
|
+
// Function tools and `custom` tools (e.g. Codex's apply_patch) both carry a name — keep them as
|
|
54
|
+
// named tools so Copilot doesn't reject a nameless tool. Only the KNOWN nameless server-side tools
|
|
55
|
+
// pass through as hostedTools; an unrecognized nameless tool is dropped rather than forwarded as a
|
|
56
|
+
// bare {type} (which makes Copilot 400 "Missing required parameter: tools[N].name" and kills the
|
|
57
|
+
// whole stream — surfaced to the Codex CLI as "stream closed before response.completed").
|
|
58
|
+
tools: req.tools?.filter((t) => (t.type === "function" || t.type === "custom") && t.name).map((t) => ({ name: t.name, description: t.description, parameters: t.parameters ?? {} })),
|
|
59
|
+
hostedTools: req.tools?.filter((t) => HOSTED_TOOL_TYPES.has(t.type ?? "")).map((t) => t.type),
|
|
57
60
|
messages,
|
|
58
61
|
};
|
|
59
62
|
}
|
|
63
|
+
// Copilot's /responses accepts these as standalone nameless hosted tools. NOTE: `tool_search` is
|
|
64
|
+
// deliberately excluded — Copilot rejects it unless the request also defines "deferred" tools
|
|
65
|
+
// ("tools.tool_search requires at least one deferred tool"), which we can't satisfy, so forwarding it
|
|
66
|
+
// 400s the whole request. web_search is the one Codex hosted tool we can pass straight through.
|
|
67
|
+
const HOSTED_TOOL_TYPES = new Set(["web_search", "web_search_preview"]);
|
|
60
68
|
// Build the non-stream Responses object: text -> an output_text message item, tool_use -> function_call items.
|
|
61
69
|
export function canonicalToResponsesResponse(r) {
|
|
62
70
|
const output = [];
|
|
@@ -84,6 +92,7 @@ export class ResponsesSSE {
|
|
|
84
92
|
nextIndex = 0;
|
|
85
93
|
textIndex;
|
|
86
94
|
textItemId;
|
|
95
|
+
accumulatedText = ""; // the full assistant text, replayed in the terminal done events
|
|
87
96
|
toolIndex = new Map();
|
|
88
97
|
constructor(responseId, model) {
|
|
89
98
|
this.responseId = responseId;
|
|
@@ -107,6 +116,7 @@ export class ResponsesSSE {
|
|
|
107
116
|
out.push(this.ev("response.content_part.added", { item_id: this.textItemId, output_index: this.textIndex, content_index: 0, part: { type: "output_text", text: "", annotations: [] } }));
|
|
108
117
|
}
|
|
109
118
|
out.push(this.ev("response.output_text.delta", { item_id: this.textItemId, output_index: this.textIndex, content_index: 0, delta }));
|
|
119
|
+
this.accumulatedText += delta;
|
|
110
120
|
return out;
|
|
111
121
|
}
|
|
112
122
|
toolStart(copilotIdx, callId, name) {
|
|
@@ -127,9 +137,10 @@ export class ResponsesSSE {
|
|
|
127
137
|
finish(usage, _finishReason, argsByIdx) {
|
|
128
138
|
const out = [];
|
|
129
139
|
if (this.textIndex !== undefined) {
|
|
130
|
-
|
|
131
|
-
out.push(this.ev("response.
|
|
132
|
-
out.push(this.ev("response.
|
|
140
|
+
const text = this.accumulatedText;
|
|
141
|
+
out.push(this.ev("response.output_text.done", { item_id: this.textItemId, output_index: this.textIndex, content_index: 0, text }));
|
|
142
|
+
out.push(this.ev("response.content_part.done", { item_id: this.textItemId, output_index: this.textIndex, content_index: 0, part: { type: "output_text", text, annotations: [] } }));
|
|
143
|
+
out.push(this.ev("response.output_item.done", { output_index: this.textIndex, item: { type: "message", id: this.textItemId, role: "assistant", status: "completed", content: [{ type: "output_text", text, annotations: [] }] } }));
|
|
133
144
|
}
|
|
134
145
|
for (const [copilotIdx, t] of this.toolIndex) {
|
|
135
146
|
const args = argsByIdx?.get(copilotIdx) ?? "";
|
|
@@ -137,7 +148,14 @@ export class ResponsesSSE {
|
|
|
137
148
|
out.push(this.ev("response.output_item.done", { output_index: t.outputIndex, item: { type: "function_call", id: t.itemId, status: "completed" } }));
|
|
138
149
|
}
|
|
139
150
|
const u = usage ? { input_tokens: usage.promptTokens, output_tokens: usage.completionTokens, total_tokens: usage.promptTokens + usage.completionTokens } : undefined;
|
|
140
|
-
|
|
151
|
+
// Spec-correct clients reconstruct the final response from response.completed.response.output, so
|
|
152
|
+
// include the finished items (the text message + any function calls), not just an empty envelope.
|
|
153
|
+
const output = [];
|
|
154
|
+
if (this.textIndex !== undefined)
|
|
155
|
+
output.push({ type: "message", id: this.textItemId, role: "assistant", status: "completed", content: [{ type: "output_text", text: this.accumulatedText, annotations: [] }] });
|
|
156
|
+
for (const [copilotIdx, t] of this.toolIndex)
|
|
157
|
+
output.push({ type: "function_call", id: t.itemId, call_id: t.itemId.replace(/^fc_/, ""), arguments: argsByIdx?.get(copilotIdx) ?? "", status: "completed" });
|
|
158
|
+
out.push(this.ev("response.completed", { response: { ...this.envelope("completed"), output, ...(u ? { usage: u } : {}) } }));
|
|
141
159
|
return out;
|
|
142
160
|
}
|
|
143
161
|
}
|
|
@@ -41,13 +41,23 @@ export class CopilotTokenStore {
|
|
|
41
41
|
return data.token;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
// True if the stored GitHub token still exchanges for a Copilot token.
|
|
44
|
+
// True if the stored GitHub token still exchanges for a Copilot token. A thin wrapper over
|
|
45
|
+
// probeGithubAuth so the token-exchange logic lives in exactly one place.
|
|
45
46
|
export async function isCopilotTokenValid(ghToken, fetchFn = fetch) {
|
|
47
|
+
return (await probeGithubAuth(ghToken, fetchFn)).ok;
|
|
48
|
+
}
|
|
49
|
+
export async function probeGithubAuth(ghToken, fetchFn = fetch) {
|
|
46
50
|
try {
|
|
47
51
|
await new CopilotTokenStore(ghToken, fetchFn).get();
|
|
48
|
-
return true;
|
|
52
|
+
return { ok: true, transient: false, detail: "token valid" };
|
|
49
53
|
}
|
|
50
|
-
catch {
|
|
51
|
-
|
|
54
|
+
catch (e) {
|
|
55
|
+
// CopilotTokenStore throws CopilotAuthError(status) for any non-ok response, and other errors
|
|
56
|
+
// (AbortError on timeout, network failures) for the rest. We treat 401/403 as definitive auth
|
|
57
|
+
// failures; everything else is transient. See the limitations noted above.
|
|
58
|
+
if (e instanceof CopilotAuthError && (e.status === 401 || e.status === 403)) {
|
|
59
|
+
return { ok: false, transient: false, detail: e.message };
|
|
60
|
+
}
|
|
61
|
+
return { ok: false, transient: true, detail: e instanceof Error ? e.message : String(e) };
|
|
52
62
|
}
|
|
53
63
|
}
|
package/dist/supervisor/api.js
CHANGED
|
@@ -5,7 +5,7 @@ export function createControlApp(deps) {
|
|
|
5
5
|
const app = express();
|
|
6
6
|
app.use(express.json());
|
|
7
7
|
app.get("/", (_req, res) => res.type("html").send(dashboardHtml()));
|
|
8
|
-
app.get("/api/status", (_req, res) => res.json({ workerState: deps.getState(), restarts: listRestarts(deps.db, 50) }));
|
|
8
|
+
app.get("/api/status", (_req, res) => res.json({ workerState: deps.getState(), restarts: listRestarts(deps.db, 50), github: deps.github() }));
|
|
9
9
|
app.post("/api/restart", (_req, res) => { deps.restart(); res.json({ ok: true }); });
|
|
10
10
|
app.post("/api/stop", (_req, res) => { deps.stop(); res.json({ ok: true }); });
|
|
11
11
|
app.post("/api/start", (_req, res) => { deps.start(); res.json({ ok: true }); });
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { probeGithubAuth } from "../providers/copilot/token.js";
|
|
2
|
+
// How often the supervisor re-checks the GitHub token. Token failure is rare (revoke / re-auth) and
|
|
3
|
+
// GitHub rate-limits, so a slow cadence is plenty; an initial short delay populates the status soon
|
|
4
|
+
// after boot without racing worker startup.
|
|
5
|
+
export const GITHUB_HEARTBEAT_INTERVAL_MS = 60_000;
|
|
6
|
+
export const GITHUB_HEARTBEAT_INITIAL_DELAY_MS = 2_000;
|
|
7
|
+
// Shared so /doctor and the heartbeat show the same remediation hint for the signed-out state.
|
|
8
|
+
export const SIGNED_OUT_DETAIL = "not logged in — run /login";
|
|
9
|
+
// Pure reducer: given the prior cached status, whether a token is on disk, and the latest probe
|
|
10
|
+
// result, decide the next cached status. Transient errors are sticky — they keep the prior status —
|
|
11
|
+
// so a brief blip doesn't flip a connected session to "expired". Caveat (see probeGithubAuth): the
|
|
12
|
+
// stickiness is unbounded, and if the FIRST probe is transient (prev still undefined) the status stays
|
|
13
|
+
// undefined / "pending", so /api/status omits `github` and the HUD shows no badge until a non-transient
|
|
14
|
+
// result lands.
|
|
15
|
+
export function nextGithubStatus(prev, hasToken, probe, now) {
|
|
16
|
+
if (!hasToken)
|
|
17
|
+
return { ok: false, hasToken: false, checkedAt: now, detail: SIGNED_OUT_DETAIL };
|
|
18
|
+
if (probe && probe.transient)
|
|
19
|
+
return prev; // keep last-known-good (or stay pending if none yet)
|
|
20
|
+
if (!probe)
|
|
21
|
+
return prev;
|
|
22
|
+
return { ok: probe.ok, hasToken: true, checkedAt: now, detail: probe.detail };
|
|
23
|
+
}
|
|
24
|
+
// Periodically probes the GitHub token in the supervisor process and caches a GithubStatus the control
|
|
25
|
+
// API exposes via /api/status. Dependencies are injected for testing (token reader, probe, clock).
|
|
26
|
+
export class GithubHeartbeat {
|
|
27
|
+
readToken;
|
|
28
|
+
probe;
|
|
29
|
+
now;
|
|
30
|
+
status;
|
|
31
|
+
timer;
|
|
32
|
+
stopped = false;
|
|
33
|
+
inFlight = false;
|
|
34
|
+
intervalMs;
|
|
35
|
+
initialDelayMs;
|
|
36
|
+
constructor(readToken, probe = probeGithubAuth, now = () => Date.now(), opts = {}) {
|
|
37
|
+
this.readToken = readToken;
|
|
38
|
+
this.probe = probe;
|
|
39
|
+
this.now = now;
|
|
40
|
+
this.intervalMs = opts.intervalMs ?? GITHUB_HEARTBEAT_INTERVAL_MS;
|
|
41
|
+
this.initialDelayMs = opts.initialDelayMs ?? GITHUB_HEARTBEAT_INITIAL_DELAY_MS;
|
|
42
|
+
}
|
|
43
|
+
current() { return this.status; }
|
|
44
|
+
// One probe cycle. Reads the token first: no token → signed-out, and the network probe is skipped.
|
|
45
|
+
// Guarded so a slow probe (up to ~8s) can't overlap the next tick.
|
|
46
|
+
async runOnce() {
|
|
47
|
+
if (this.inFlight)
|
|
48
|
+
return;
|
|
49
|
+
this.inFlight = true;
|
|
50
|
+
try {
|
|
51
|
+
const token = this.readToken();
|
|
52
|
+
const probe = token ? await this.probe(token) : null;
|
|
53
|
+
if (this.stopped)
|
|
54
|
+
return; // a late result after stop() must not resurrect the timer/state
|
|
55
|
+
this.status = nextGithubStatus(this.status, Boolean(token), probe, this.now());
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
this.inFlight = false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
start() {
|
|
62
|
+
if (this.timer)
|
|
63
|
+
return; // idempotent: don't leak a second timer if start() is called twice
|
|
64
|
+
this.stopped = false;
|
|
65
|
+
const tick = () => { void this.runOnce(); };
|
|
66
|
+
this.timer = setTimeout(() => {
|
|
67
|
+
tick();
|
|
68
|
+
this.timer = setInterval(tick, this.intervalMs);
|
|
69
|
+
}, this.initialDelayMs);
|
|
70
|
+
}
|
|
71
|
+
stop() {
|
|
72
|
+
this.stopped = true;
|
|
73
|
+
// The timer handle is either the initial setTimeout or the later setInterval; clearing both kinds
|
|
74
|
+
// is safe with either function in Node.
|
|
75
|
+
if (this.timer) {
|
|
76
|
+
clearTimeout(this.timer);
|
|
77
|
+
clearInterval(this.timer);
|
|
78
|
+
this.timer = undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
package/dist/supervisor/index.js
CHANGED
|
@@ -8,7 +8,8 @@ import { createControlApp } from "./api.js";
|
|
|
8
8
|
import { defaultConfig } from "../shared/config.js";
|
|
9
9
|
import { dataDir, dbPath } from "../shared/paths.js";
|
|
10
10
|
import { readGhToken } from "../shared/creds.js";
|
|
11
|
-
import {
|
|
11
|
+
import { probeGithubAuth } from "../providers/copilot/token.js";
|
|
12
|
+
import { GithubHeartbeat, SIGNED_OUT_DETAIL } from "./github-heartbeat.js";
|
|
12
13
|
export function startSupervisor() {
|
|
13
14
|
const config = defaultConfig();
|
|
14
15
|
mkdirSync(dataDir(), { recursive: true });
|
|
@@ -34,32 +35,33 @@ export function startSupervisor() {
|
|
|
34
35
|
const gh = readGhToken(dataDir());
|
|
35
36
|
let auth;
|
|
36
37
|
if (!gh) {
|
|
37
|
-
auth = { name: "github-auth", ok: false, detail:
|
|
38
|
+
auth = { name: "github-auth", ok: false, detail: SIGNED_OUT_DETAIL };
|
|
38
39
|
}
|
|
39
40
|
else {
|
|
40
|
-
// Validate the token actually exchanges, not just that it exists on disk.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
catch (e) {
|
|
46
|
-
auth = { name: "github-auth", ok: false, detail: e instanceof Error ? e.message : String(e) };
|
|
47
|
-
}
|
|
41
|
+
// Validate the token actually exchanges, not just that it exists on disk. Shares the heartbeat's
|
|
42
|
+
// classifier so on-demand /doctor and the periodic probe agree.
|
|
43
|
+
const probe = await probeGithubAuth(gh);
|
|
44
|
+
auth = { name: "github-auth", ok: probe.ok, detail: probe.detail };
|
|
48
45
|
}
|
|
49
46
|
return [auth, { name: "worker", ok: state === "ready", detail: `worker is ${state}` }];
|
|
50
47
|
};
|
|
48
|
+
// Periodically re-check the GitHub token so the UI reflects an expired/revoked login within ~60s,
|
|
49
|
+
// instead of only on the next failed request or a manual /status.
|
|
50
|
+
const heartbeat = new GithubHeartbeat(() => readGhToken(dataDir()));
|
|
51
51
|
const app = createControlApp({
|
|
52
52
|
db, getState: () => state,
|
|
53
53
|
restart: () => monitor.restartManually(),
|
|
54
54
|
stop: () => monitor.stop(),
|
|
55
55
|
start: () => monitor.start(),
|
|
56
56
|
doctor,
|
|
57
|
+
github: () => heartbeat.current(),
|
|
57
58
|
subscribe: (send) => bus.subscribe(send),
|
|
58
59
|
});
|
|
59
60
|
app.listen(config.supervisorPort, config.bindHost, () => monitor.start());
|
|
60
|
-
|
|
61
|
-
process.on("
|
|
62
|
-
|
|
61
|
+
heartbeat.start();
|
|
62
|
+
process.on("SIGINT", () => { heartbeat.stop(); monitor.stop(); process.exit(0); });
|
|
63
|
+
process.on("SIGTERM", () => { heartbeat.stop(); monitor.stop(); process.exit(0); });
|
|
64
|
+
return { stop: () => { heartbeat.stop(); monitor.stop(); } };
|
|
63
65
|
}
|
|
64
66
|
// Allow `node dist/supervisor/index.js` to boot the daemon directly.
|
|
65
67
|
if (process.argv[1] && process.argv[1].endsWith(join("supervisor", "index.js")))
|
package/dist/tui/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { Box, Text, useInput } from "ink";
|
|
4
4
|
import { loadingVerb } from "../shared/format.js";
|
|
@@ -7,7 +7,7 @@ import { SetupWizard } from "./setup/wizard.js";
|
|
|
7
7
|
import { ModelScreen } from "./screens/model.js";
|
|
8
8
|
import { ConfigScreen } from "./screens/config.js";
|
|
9
9
|
import { WebIqKeyScreen } from "./screens/webiq-key.js";
|
|
10
|
-
import { summarizeStatus } from "./status-summary.js";
|
|
10
|
+
import { summarizeStatus, githubLoginState } from "./status-summary.js";
|
|
11
11
|
import { theme } from "./theme.js";
|
|
12
12
|
const stateColor = {
|
|
13
13
|
ready: theme.ready, starting: theme.starting, crashed: theme.crashed, unhealthy: theme.unhealthy,
|
|
@@ -63,6 +63,8 @@ export function App({ registry, title, workerState = "starting", initialModel =
|
|
|
63
63
|
const [state, setState] = useState(workerState);
|
|
64
64
|
const [status, setStatus] = useState(() => readStatus?.() ?? EMPTY_STATUS);
|
|
65
65
|
const [webBackend, setWebBackend] = useState(() => webSearchBackend?.() ?? "unavailable");
|
|
66
|
+
// GitHub login state, kept fresh by the supervisor heartbeat surfaced through the 2s status poll.
|
|
67
|
+
const [github, setGithub] = useState(startupStatus?.github);
|
|
66
68
|
const [model, setModel] = useState(initialModel);
|
|
67
69
|
const [screen, setScreen] = useState(pickModelOnStart && loadModels ? { kind: "model" } : null);
|
|
68
70
|
const [, setNow] = useState(0); // ticks the live loading line while the assistant streams
|
|
@@ -82,8 +84,11 @@ export function App({ registry, title, workerState = "starting", initialModel =
|
|
|
82
84
|
const tick = async () => {
|
|
83
85
|
try {
|
|
84
86
|
const s = await statusSource?.();
|
|
85
|
-
if (alive && s)
|
|
87
|
+
if (alive && s) {
|
|
86
88
|
setState(s.workerState);
|
|
89
|
+
if (s.github)
|
|
90
|
+
setGithub(githubLoginState(s.github.hasToken, s.github.ok)); // live login badge
|
|
91
|
+
}
|
|
87
92
|
}
|
|
88
93
|
catch { /* daemon momentarily down */ }
|
|
89
94
|
if (alive)
|
|
@@ -127,7 +132,9 @@ export function App({ registry, title, workerState = "starting", initialModel =
|
|
|
127
132
|
}
|
|
128
133
|
if (t === "/status" && (startupStatus || githubStatus || webSearchBackend)) {
|
|
129
134
|
// Render the live status overview (same card as startup), then the worker restart history.
|
|
130
|
-
|
|
135
|
+
// /status is an explicit "is my login OK right now?" — do the live check when wired (the cached
|
|
136
|
+
// heartbeat can be up to ~60s stale), falling back to the cached/seed value only if it isn't.
|
|
137
|
+
const ghState = githubStatus ? await githubStatus() : (github ?? startupStatus?.github ?? "signed-out");
|
|
131
138
|
let worker = state, restarts = [];
|
|
132
139
|
try {
|
|
133
140
|
const s = await statusSource?.();
|
|
@@ -138,7 +145,7 @@ export function App({ registry, title, workerState = "starting", initialModel =
|
|
|
138
145
|
}
|
|
139
146
|
catch { /* daemon momentarily down — show what we have */ }
|
|
140
147
|
const summary = summarizeStatus({
|
|
141
|
-
hasToken:
|
|
148
|
+
hasToken: ghState !== "signed-out", tokenValid: ghState === "connected",
|
|
142
149
|
webSearch: webSearchBackend?.() ?? webBackend, worker,
|
|
143
150
|
clients: { claude: status.claude.user || status.claude.project, codex: status.codex.user || status.codex.project },
|
|
144
151
|
});
|
|
@@ -253,5 +260,5 @@ export function App({ registry, title, workerState = "starting", initialModel =
|
|
|
253
260
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.accent, children: ["\u273D ", _jsxs(Text, { color: theme.muted, children: [frame, " ", loadingVerb(elapsed), "\u2026 (esc to interrupt \u00B7 ", fmtElapsed(elapsed), " \u00B7 \u2193 ", fmtTokens(tokens), " tokens \u00B7 thinking)"] })] }), e.text ? _jsx(Text, { color: color, children: e.text }) : null] }, i));
|
|
254
261
|
}
|
|
255
262
|
return _jsx(Text, { color: color, children: e.text }, i);
|
|
256
|
-
}) }), body, _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "
|
|
263
|
+
}) }), body, _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { children: [github && _jsxs(_Fragment, { children: [_jsx(Text, { color: theme.muted, children: "github " }), _jsx(Text, { color: github === "connected" ? theme.ready : theme.error, children: github === "connected" ? "✓" : "✗ /login" })] }), _jsxs(Text, { color: theme.muted, children: [github ? " · " : "", "daemon "] }), _jsx(Text, { color: stateColor[state], children: state })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "web " }), _jsx(Text, { color: webBackend === "unavailable" ? theme.muted : theme.ready, children: webBackend === "webiq" ? "✓ webiq" : webBackend === "copilot" ? "✓ copilot" : "✗ /webiq" }), _jsx(Text, { color: theme.muted, children: " \u00B7 " }), _jsx(ClientBadge, { name: "claude", status: status.claude }), _jsx(Text, { color: theme.muted, children: " " }), _jsx(ClientBadge, { name: "codex", status: status.codex }), _jsx(Text, { color: theme.muted, children: " \u00B7 /help" })] })] })] }));
|
|
257
264
|
}
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// AUTO-GENERATED by scripts/gen-version.mjs from package.json — do not edit.
|
|
2
|
-
export const APP_VERSION = "0.
|
|
2
|
+
export const APP_VERSION = "0.5.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "copilot-reverse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|