codiedev 0.9.78 → 0.9.80
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/assets/opencode-plugin.mjs +6 -0
- package/assets/session-key.mjs +48 -0
- package/dist/cli.js +70 -10
- package/dist/cliSession.d.ts +108 -0
- package/dist/cliSession.js +513 -0
- package/dist/codie.js +21 -1
- package/dist/commands/traces.js +57 -1
- package/dist/commands/tracesLabel.d.ts +24 -0
- package/dist/commands/tracesLabel.js +356 -0
- package/dist/configDir.d.ts +9 -0
- package/dist/configDir.js +25 -1
- package/dist/connect.js +45 -2
- package/dist/connectFlow.d.ts +21 -0
- package/dist/connectFlow.js +41 -0
- package/dist/hook.js +7 -0
- package/dist/launcher.d.ts +7 -2
- package/dist/launcher.js +36 -2
- package/dist/managedPi.js +18 -2
- package/dist/mcp.js +3 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +16 -8
- package/package.json +1 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
trackWorkPath,
|
|
11
11
|
workDirBySession,
|
|
12
12
|
} from "./git-refs.mjs";
|
|
13
|
+
import { freshGatewayKey } from "./session-key.mjs";
|
|
13
14
|
|
|
14
15
|
const skillUseBySession = new Map();
|
|
15
16
|
|
|
@@ -60,6 +61,11 @@ export const CodiedevMetadata = async () => ({
|
|
|
60
61
|
});
|
|
61
62
|
},
|
|
62
63
|
"chat.headers": async (input, output) => {
|
|
64
|
+
// Browser sign-in: the current 24h token, refreshed as it nears expiry,
|
|
65
|
+
// so a session open for days keeps working (see session-key.mjs). Exact
|
|
66
|
+
// "Authorization" key: per-call headers win over the apiKey-derived one.
|
|
67
|
+
const key = await freshGatewayKey();
|
|
68
|
+
if (key) output.headers.Authorization = `Bearer ${key}`;
|
|
63
69
|
Object.assign(
|
|
64
70
|
output.headers,
|
|
65
71
|
buildAttributionHeaders({
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// The gateway key for one model request, kept fresh for the whole session.
|
|
2
|
+
//
|
|
3
|
+
// With browser sign-in (`codiedev connect`), the key is a 24h access token.
|
|
4
|
+
// The engine (OpenCode, pi) would otherwise hold the one it was launched
|
|
5
|
+
// with until the session ends, so a session open for more than a day would
|
|
6
|
+
// start failing. `codie` sets these variables only for a browser sign-in:
|
|
7
|
+
// CODIEDEV_SESSION_CONFIG path to ~/.codiedev/config.json
|
|
8
|
+
// CODIEDEV_CLI_JS the CLI entry (dist/cli.js)
|
|
9
|
+
// CODIEDEV_NODE the node binary that runs it
|
|
10
|
+
// Each request reads the current token from config (other codiedev
|
|
11
|
+
// processes may have refreshed it). When it has under 30 minutes left, this
|
|
12
|
+
// runs `codiedev session-refresh`, which refreshes under the shared lock
|
|
13
|
+
// (cliSession.ts, specs/tla/CliSession.tla), then reads again.
|
|
14
|
+
// No variables (pasted API token, CI): returns null and the launch key stands.
|
|
15
|
+
|
|
16
|
+
import { execFile } from "node:child_process";
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
|
|
19
|
+
export const REFRESH_WHEN_LEFT_MS = 30 * 60_000;
|
|
20
|
+
|
|
21
|
+
function readSession(path) {
|
|
22
|
+
try {
|
|
23
|
+
const c = JSON.parse(readFileSync(path, "utf8"));
|
|
24
|
+
return c && c.auth && typeof c.token === "string" && c.token ? c : null;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function runRefresh(env) {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
execFile(env.CODIEDEV_NODE, [env.CODIEDEV_CLI_JS, "session-refresh"], { timeout: 30_000, env }, () => resolve());
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Fresh gateway key, or null when the session isn't a browser sign-in. */
|
|
37
|
+
export async function freshGatewayKey(env = process.env, now = Date.now, refresh = runRefresh) {
|
|
38
|
+
const path = env.CODIEDEV_SESSION_CONFIG;
|
|
39
|
+
if (!path || !env.CODIEDEV_CLI_JS || !env.CODIEDEV_NODE) return null;
|
|
40
|
+
let session = readSession(path);
|
|
41
|
+
if (!session) return null;
|
|
42
|
+
const left = Number(session.auth.accessTokenExpiresAt) - now();
|
|
43
|
+
if (!(left > REFRESH_WHEN_LEFT_MS)) {
|
|
44
|
+
await refresh(env);
|
|
45
|
+
session = readSession(path) ?? session;
|
|
46
|
+
}
|
|
47
|
+
return session.token;
|
|
48
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
const connect_1 = require("./connect");
|
|
18
|
+
const cliSession_1 = require("./cliSession");
|
|
19
|
+
const utils_1 = require("./utils");
|
|
18
20
|
const push_1 = require("./commands/push");
|
|
19
21
|
const delete_1 = require("./commands/delete");
|
|
20
22
|
const pull_1 = require("./commands/pull");
|
|
@@ -45,8 +47,10 @@ Codiedev CLI
|
|
|
45
47
|
someone left off across sessions and providers.
|
|
46
48
|
|
|
47
49
|
Connect:
|
|
48
|
-
codiedev connect
|
|
50
|
+
codiedev connect Sign in with your browser and link
|
|
51
|
+
Claude Code / Codex / Cursor /
|
|
49
52
|
GitHub Copilot / pi to your org
|
|
53
|
+
codiedev logout Sign this machine out
|
|
50
54
|
codiedev doctor Verify your setup after connecting
|
|
51
55
|
|
|
52
56
|
Harness:
|
|
@@ -95,11 +99,14 @@ Starter packs:
|
|
|
95
99
|
|
|
96
100
|
Traces (agents on the gateway):
|
|
97
101
|
codiedev traces agents Agents you maintain, with saved test cases
|
|
98
|
-
codiedev traces cases <agent> [--dataset name] [--model id]
|
|
102
|
+
codiedev traces cases <agent> [--dataset name [--version n]] [--model id]
|
|
99
103
|
Pull an agent's test cases (or one dataset)
|
|
100
104
|
into this repo for your coding agent to
|
|
101
105
|
run, optionally on another model
|
|
102
106
|
codiedev traces report <results.json> Send the results back to Traces
|
|
107
|
+
codiedev traces overview | runs | run | score | scores | score-kinds | queue | judge | import | add-example | versions | save-version
|
|
108
|
+
Agents, runs, failures, scores, review queues,
|
|
109
|
+
automatic scoring and datasets (codiedev traces help)
|
|
103
110
|
codiedev evals report <run.json> File one variant's eval scores under an experiment
|
|
104
111
|
codiedev evals last <subject> Latest experiment on a subject + its champion
|
|
105
112
|
|
|
@@ -169,8 +176,9 @@ const COMMAND_HELP = {
|
|
|
169
176
|
connect: `
|
|
170
177
|
codiedev connect — link your agent CLI to Codiedev
|
|
171
178
|
|
|
172
|
-
|
|
173
|
-
|
|
179
|
+
Opens your browser to sign in (approve once, no API key to copy), then
|
|
180
|
+
installs:
|
|
181
|
+
- ~/.codiedev/config.json (sign-in + org + tracked repos)
|
|
174
182
|
- ~/.claude/settings.json hook (captures session transcripts)
|
|
175
183
|
- ~/.claude/CLAUDE.md instructions (teaches Claude the commands)
|
|
176
184
|
- ~/.codex/hooks.json hook (same, for Codex)
|
|
@@ -183,7 +191,27 @@ codiedev connect — link your agent CLI to Codiedev
|
|
|
183
191
|
|
|
184
192
|
Re-run anytime to swap accounts or refresh the instruction block.
|
|
185
193
|
|
|
186
|
-
|
|
194
|
+
Over SSH or on a machine with no display it shows a link and a short code
|
|
195
|
+
instead: open the link on your laptop or phone, check the code, approve.
|
|
196
|
+
Detected automatically.
|
|
197
|
+
|
|
198
|
+
Options:
|
|
199
|
+
--token Paste an API token instead of signing in with a browser
|
|
200
|
+
(CI, or a machine with no browser anywhere). Create one at
|
|
201
|
+
https://codiedev.com/portal/integrations/claude-code
|
|
202
|
+
--force, -f Sign in again even if this machine is already connected.
|
|
203
|
+
|
|
204
|
+
Browser sign-in stores a 24-hour access token and a refresh token that
|
|
205
|
+
rotates on every use; nothing long-lived is saved. Revoke a machine from
|
|
206
|
+
Integrations in the portal, or run 'codiedev logout' on it.
|
|
207
|
+
`.trim(),
|
|
208
|
+
logout: `
|
|
209
|
+
codiedev logout — sign this machine out
|
|
210
|
+
|
|
211
|
+
Ends this machine's browser sign-in on the server and removes it from
|
|
212
|
+
~/.codiedev/config.json. Device settings (ignored folders) are kept.
|
|
213
|
+
For a pasted API token, it's only forgotten locally: revoke it in the
|
|
214
|
+
portal under Integrations.
|
|
187
215
|
`.trim(),
|
|
188
216
|
push: `
|
|
189
217
|
codiedev push — author or update an artifact
|
|
@@ -402,13 +430,15 @@ Run 'codiedev help <command>' for each, or see the quick list:
|
|
|
402
430
|
|
|
403
431
|
codiedev help
|
|
404
432
|
|
|
405
|
-
##
|
|
433
|
+
## Signing in
|
|
406
434
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
3. Copy the cdv_... string and paste it into 'codiedev connect'
|
|
435
|
+
'codiedev connect' opens your browser; approve, and you're done. Nothing
|
|
436
|
+
long-lived is stored: a 24-hour access token plus a rotating refresh token.
|
|
410
437
|
|
|
411
|
-
|
|
438
|
+
Over SSH it shows a link and code to approve from your laptop or phone,
|
|
439
|
+
automatically. For CI, create a token at
|
|
440
|
+
https://codiedev.com/portal/integrations/claude-code and run
|
|
441
|
+
'codiedev connect --token'.
|
|
412
442
|
`.trim();
|
|
413
443
|
// Retired commands: say so instead of "Unknown command", since agent
|
|
414
444
|
// instructions written by older installs may still reference them.
|
|
@@ -471,11 +501,41 @@ async function main() {
|
|
|
471
501
|
console.log(`codiedev v${version_1.CLI_VERSION}`);
|
|
472
502
|
return;
|
|
473
503
|
}
|
|
504
|
+
// Browser sign-in: refresh the access token (under the shared lock) before
|
|
505
|
+
// the command reads config. No-op for pasted API tokens.
|
|
506
|
+
if (command !== "connect" && command !== "logout") {
|
|
507
|
+
const fresh = await (0, cliSession_1.ensureFreshToken)({ configPath: utils_1.CONFIG_PATH }).catch(() => null);
|
|
508
|
+
if (fresh?.status === "signed_out") {
|
|
509
|
+
console.error("\nYour Codiedev sign-in has ended (revoked or expired). Run `codiedev connect` to sign in again.\n");
|
|
510
|
+
}
|
|
511
|
+
}
|
|
474
512
|
try {
|
|
475
513
|
switch (command) {
|
|
476
514
|
case "connect":
|
|
477
515
|
await (0, connect_1.runConnect)(rest);
|
|
478
516
|
return;
|
|
517
|
+
case "session-refresh": {
|
|
518
|
+
// Internal: the Codie engine's per-request hook asks for a fresh
|
|
519
|
+
// token when the current one has under 30 minutes left. Refreshes
|
|
520
|
+
// under the shared lock; prints nothing.
|
|
521
|
+
await (0, cliSession_1.ensureFreshToken)({ configPath: utils_1.CONFIG_PATH }, 60 * 60_000).catch(() => null);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
case "logout": {
|
|
525
|
+
const out = await (0, cliSession_1.logout)({ configPath: utils_1.CONFIG_PATH });
|
|
526
|
+
if (!out.wasConnected) {
|
|
527
|
+
console.log("This machine isn't signed in.");
|
|
528
|
+
}
|
|
529
|
+
else if (out.hadBrowserAuth) {
|
|
530
|
+
console.log(out.serverRevoked
|
|
531
|
+
? "Signed out. This machine's sign-in is revoked."
|
|
532
|
+
: "Signed out on this machine. Couldn't reach Codiedev to revoke the sign-in; revoke it under Integrations in the portal.");
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
console.log("Removed the API token from this machine. It still works elsewhere until you revoke it under Integrations in the portal.");
|
|
536
|
+
}
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
479
539
|
case "doctor":
|
|
480
540
|
case "verify":
|
|
481
541
|
await (0, doctor_1.runDoctor)(rest);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { CodiedevConfig } from "./utils";
|
|
2
|
+
export interface BrowserAuth {
|
|
3
|
+
kind: "browser";
|
|
4
|
+
clientId: string;
|
|
5
|
+
refreshToken: string;
|
|
6
|
+
/** epoch ms when `token` (the access token) stops working */
|
|
7
|
+
accessTokenExpiresAt: number;
|
|
8
|
+
}
|
|
9
|
+
export interface TokenSet {
|
|
10
|
+
accessToken: string;
|
|
11
|
+
refreshToken: string;
|
|
12
|
+
expiresAt: number;
|
|
13
|
+
clientId: string;
|
|
14
|
+
}
|
|
15
|
+
/** Default: refresh when less than 10 minutes remain. */
|
|
16
|
+
export declare const DEFAULT_MIN_VALIDITY_MS: number;
|
|
17
|
+
/** A lock older than this was left by a crashed process. Refresh calls time
|
|
18
|
+
* out well before it (REFRESH_TIMEOUT_MS), so a live holder never looks stale. */
|
|
19
|
+
export declare const LOCK_STALE_MS = 30000;
|
|
20
|
+
export interface SessionDeps {
|
|
21
|
+
configPath: string;
|
|
22
|
+
fetchImpl?: typeof fetch;
|
|
23
|
+
now?: () => number;
|
|
24
|
+
sleep?: (ms: number) => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare function readConfigAt(configPath: string): CodiedevConfig | null;
|
|
27
|
+
/** rename(), retrying the transient Windows sharing errors. Exported for tests. */
|
|
28
|
+
export declare function renameWithRetry(from: string, to: string, rename?: (a: string, b: string) => void, sleep?: (ms: number) => void): void;
|
|
29
|
+
/**
|
|
30
|
+
* Write via temp file + rename so a concurrent reader never sees a torn file.
|
|
31
|
+
* If the rename still fails after ~1.6s of retries (a handle held that long),
|
|
32
|
+
* fall back to writing in place: a rare torn read beats failing `connect`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function writeConfigAtomic(configPath: string, config: CodiedevConfig): void;
|
|
35
|
+
export type FreshResult = {
|
|
36
|
+
status: "fresh";
|
|
37
|
+
config: CodiedevConfig;
|
|
38
|
+
} | {
|
|
39
|
+
status: "refreshed";
|
|
40
|
+
config: CodiedevConfig;
|
|
41
|
+
} | {
|
|
42
|
+
status: "adopted";
|
|
43
|
+
config: CodiedevConfig;
|
|
44
|
+
} | {
|
|
45
|
+
status: "unavailable";
|
|
46
|
+
config: CodiedevConfig;
|
|
47
|
+
} | {
|
|
48
|
+
status: "signed_out";
|
|
49
|
+
config: CodiedevConfig;
|
|
50
|
+
} | {
|
|
51
|
+
status: "none";
|
|
52
|
+
config: CodiedevConfig | null;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Make sure config.token is a usable access token for at least
|
|
56
|
+
* `minValidityMs`. Never throws: auth problems surface as the next request's
|
|
57
|
+
* 401, which every command already explains.
|
|
58
|
+
*/
|
|
59
|
+
export declare function ensureFreshToken(deps: SessionDeps, minValidityMs?: number): Promise<FreshResult>;
|
|
60
|
+
export declare function pkcePair(): {
|
|
61
|
+
verifier: string;
|
|
62
|
+
challenge: string;
|
|
63
|
+
};
|
|
64
|
+
export declare function openBrowser(url: string): boolean;
|
|
65
|
+
export interface LoginOptions {
|
|
66
|
+
backendUrl: string;
|
|
67
|
+
clientName?: string;
|
|
68
|
+
fetchImpl?: typeof fetch;
|
|
69
|
+
/** Test hook: called with the authorize URL instead of opening a browser. */
|
|
70
|
+
onAuthorizeUrl?: (url: string) => void;
|
|
71
|
+
log?: (line: string) => void;
|
|
72
|
+
now?: () => number;
|
|
73
|
+
}
|
|
74
|
+
/** Parse `code`/`state` (or `error`) out of a loopback callback URL. */
|
|
75
|
+
export declare function parseCallback(input: string): {
|
|
76
|
+
code?: string;
|
|
77
|
+
state?: string;
|
|
78
|
+
error?: string;
|
|
79
|
+
};
|
|
80
|
+
export declare function browserLogin(opts: LoginOptions): Promise<TokenSet>;
|
|
81
|
+
export declare const DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
82
|
+
/**
|
|
83
|
+
* True when a browser opened here can't reach the user: an SSH session, or a
|
|
84
|
+
* Linux box with no display. Then `connect` uses the device flow (a code the
|
|
85
|
+
* user approves on any device) instead of a loopback redirect, with no flag.
|
|
86
|
+
*/
|
|
87
|
+
export declare function isHeadless(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
|
|
88
|
+
export interface DeviceLoginOptions {
|
|
89
|
+
backendUrl: string;
|
|
90
|
+
clientName?: string;
|
|
91
|
+
fetchImpl?: typeof fetch;
|
|
92
|
+
log?: (line: string) => void;
|
|
93
|
+
now?: () => number;
|
|
94
|
+
sleep?: (ms: number) => Promise<void>;
|
|
95
|
+
/** Test hook: receives the code + link instead of only printing them. */
|
|
96
|
+
onCode?: (info: {
|
|
97
|
+
userCode: string;
|
|
98
|
+
verificationUri: string;
|
|
99
|
+
verificationUriComplete: string;
|
|
100
|
+
}) => void;
|
|
101
|
+
}
|
|
102
|
+
export declare function deviceLogin(opts: DeviceLoginOptions): Promise<TokenSet>;
|
|
103
|
+
/** `codiedev logout`: end a browser sign-in server-side, forget it locally. */
|
|
104
|
+
export declare function logout(deps: SessionDeps): Promise<{
|
|
105
|
+
wasConnected: boolean;
|
|
106
|
+
hadBrowserAuth: boolean;
|
|
107
|
+
serverRevoked: boolean;
|
|
108
|
+
}>;
|