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.
@@ -0,0 +1,356 @@
1
+ "use strict";
2
+ // `codiedev traces overview | runs | run | score | scores | score-kinds |
3
+ // queue | judge | import | add-example | versions | save-version`: what the
4
+ // Traces portal shows and does (agents, runs, failures, scores, review
5
+ // queues, automatic scoring, datasets), so a coding agent can do it from
6
+ // plain words ("put this week's failed runs in the Week 44 queue"). Each
7
+ // calls a /api/cli/traces/* route that runs the portal's own code, with the
8
+ // same permissions.
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.parseArgs = parseArgs;
44
+ exports.scoreValue = scoreValue;
45
+ exports.runScore = runScore;
46
+ exports.runScores = runScores;
47
+ exports.runQueue = runQueue;
48
+ exports.runJudge = runJudge;
49
+ exports.runImport = runImport;
50
+ exports.runAddExample = runAddExample;
51
+ exports.runVersions = runVersions;
52
+ exports.runSaveVersion = runSaveVersion;
53
+ exports.runOverview = runOverview;
54
+ exports.runRuns = runRuns;
55
+ exports.runRun = runRun;
56
+ exports.runScoreKinds = runScoreKinds;
57
+ exports.runQueueCreate = runQueueCreate;
58
+ const fs = __importStar(require("fs"));
59
+ const path = __importStar(require("path"));
60
+ const shared_1 = require("./shared");
61
+ const BOOL_FLAGS = new Set(["--failed", "--on", "--off", "--json"]);
62
+ function parseArgs(args) {
63
+ const out = { positional: [], flags: {}, bools: new Set(), runs: [] };
64
+ for (let i = 0; i < args.length; i++) {
65
+ const a = args[i];
66
+ if (BOOL_FLAGS.has(a)) {
67
+ out.bools.add(a);
68
+ continue;
69
+ }
70
+ if (a.startsWith("--")) {
71
+ const eq = a.indexOf("=");
72
+ const name = eq > 0 ? a.slice(0, eq) : a;
73
+ const value = eq > 0 ? a.slice(eq + 1) : args[++i];
74
+ if (value === undefined)
75
+ throw new Error(`${name} needs a value`);
76
+ if (name === "--run")
77
+ out.runs.push(value);
78
+ else
79
+ out.flags[name] = value;
80
+ continue;
81
+ }
82
+ out.positional.push(a);
83
+ }
84
+ return out;
85
+ }
86
+ /** "4" → 4, "true" → true, anything else as text (a category). */
87
+ function scoreValue(raw) {
88
+ const t = raw.trim();
89
+ if (/^(true|false)$/i.test(t))
90
+ return t.toLowerCase() === "true";
91
+ if (t !== "" && Number.isFinite(Number(t)))
92
+ return Number(t);
93
+ return t;
94
+ }
95
+ async function call(config, tool, method, route, opts) {
96
+ const start = Date.now();
97
+ try {
98
+ const res = await (0, shared_1.apiRequest)(method, `/api/cli/traces/${route}`, { config, ...opts });
99
+ (0, shared_1.recordAgentEvent)(config, { tool, ok: true, latencyMs: Date.now() - start });
100
+ return res;
101
+ }
102
+ catch (err) {
103
+ const e = err;
104
+ (0, shared_1.recordAgentEvent)(config, { tool, ok: false, latencyMs: Date.now() - start, error: e.message });
105
+ console.error(e.message);
106
+ process.exit(1);
107
+ }
108
+ }
109
+ function usage(text) {
110
+ console.error(`Usage: ${text}`);
111
+ process.exit(1);
112
+ }
113
+ async function runScore(args, config) {
114
+ const p = parseArgs(args);
115
+ const [run, name, value] = p.positional;
116
+ if (!run || !name || value === undefined)
117
+ usage("codiedev traces score <run> <score> <value> [--agent name] [--comment text] [--step n]");
118
+ const res = await call(config, "codiedev_traces_score", "POST", "score", {
119
+ body: { run, name, value: scoreValue(value), agent: p.flags["--agent"], comment: p.flags["--comment"], ...(p.flags["--step"] ? { step: Number(p.flags["--step"]) } : {}) },
120
+ });
121
+ console.log(`✓ Scored ${res.runId}: ${name} = ${value}`);
122
+ }
123
+ async function runScores(args, config) {
124
+ const p = parseArgs(args);
125
+ const agent = p.positional[0];
126
+ if (!agent)
127
+ usage("codiedev traces scores <agent> [--days n] [--json]");
128
+ const res = await call(config, "codiedev_traces_scores", "GET", "scores", { query: { agent, days: p.flags["--days"] ?? "7" } });
129
+ if (p.bools.has("--json")) {
130
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
131
+ return;
132
+ }
133
+ if (!res.summary.length)
134
+ console.log(`No scores on ${res.agent} in the last ${p.flags["--days"] ?? 7} days.`);
135
+ for (const s of res.summary) {
136
+ const head = s.dataType === "boolean" && s.average !== null ? `${Math.round(s.average * 100)}% true`
137
+ : s.dataType === "numeric" && s.average !== null ? `avg ${Math.round(s.average * 100) / 100}`
138
+ : s.categories.map((c) => `${c.label} ${c.count}`).join(", ");
139
+ const from = [s.bySource.annotation ? `${s.bySource.annotation} people` : "", s.bySource.api ? `${s.bySource.api} your app` : "", s.bySource.judge ? `${s.bySource.judge} judge` : ""].filter(Boolean).join(", ");
140
+ console.log(`${s.name.padEnd(24)} ${head.padEnd(28)} ${s.count} scores (${from})`);
141
+ }
142
+ if (res.kinds.length)
143
+ console.log(`\nScore kinds: ${res.kinds.map((k) => `${k.name} (${k.dataType === "numeric" ? `${k.min ?? ""}–${k.max ?? ""}` : k.dataType === "boolean" ? "true/false" : k.categories.join("/")})`).join(", ")}`);
144
+ }
145
+ async function runQueue(args, config) {
146
+ const [verb, ...rest] = args;
147
+ if (verb === "list" || verb === undefined) {
148
+ const res = await call(config, "codiedev_traces_queue_list", "GET", "queues", {});
149
+ const live = res.queues.filter((q) => !q.archived);
150
+ if (!live.length)
151
+ console.log("No review queues yet. Make one in Codiedev → Traces → Review.");
152
+ for (const q of live)
153
+ console.log(`${q.name.padEnd(28)} ${String(q.pending).padStart(4)} to review ${String(q.done).padStart(4)} done ${q.scoreNames.join(", ")}`);
154
+ return;
155
+ }
156
+ if (verb === "create")
157
+ return runQueueCreate(rest, config);
158
+ if (verb !== "add")
159
+ usage("codiedev traces queue list | queue create <name> [--scores a,b] | queue add <queue> --agent <name> [--failed] [--days n] [--run id ...]");
160
+ const p = parseArgs(rest);
161
+ const queue = p.positional[0];
162
+ if (!queue || (!p.flags["--agent"] && !p.runs.length))
163
+ usage("codiedev traces queue add <queue> --agent <name> [--failed] [--days n] [--run id ...]");
164
+ const res = await call(config, "codiedev_traces_queue_add", "POST", "queue", {
165
+ body: { queue, agent: p.flags["--agent"], failed: p.bools.has("--failed"), ...(p.flags["--days"] ? { days: Number(p.flags["--days"]) } : {}), ...(p.runs.length ? { runs: p.runs } : {}) },
166
+ });
167
+ console.log(`✓ Added ${res.added} ${res.added === 1 ? "run" : "runs"} to ${res.queue}${res.skipped ? ` (${res.skipped} already there or not yours)` : ""}. Review them in Codiedev → Traces → Review.`);
168
+ }
169
+ function printJudge(agent, judge) {
170
+ if (!judge || judge === "none") {
171
+ console.log(`Automatic scoring isn't set up for ${agent}.`);
172
+ return;
173
+ }
174
+ console.log(`${agent}: automatic scoring ${judge.enabled ? "on" : "off"}`);
175
+ console.log(` scores ${judge.scoreNames.join(", ")} · ${Math.round(judge.sampleRate * 100)}% of runs, up to ${judge.dailyMaxRuns}/day · ${judge.model}`);
176
+ if (judge.judgeKey)
177
+ console.log(` billed to ${judge.judgeKey.name}${judge.judgeKey.monthlyBudgetUsd !== null ? ` ($${judge.judgeKey.monthlyBudgetUsd}/mo)` : ""} · ${judge.judgedToday} judged in 24h${judge.errorsToday ? `, ${judge.errorsToday} failed` : ""}`);
178
+ if (judge.lastError)
179
+ console.log(` last problem: ${judge.lastError}`);
180
+ }
181
+ async function runJudge(args, config) {
182
+ const p = parseArgs(args);
183
+ const agent = p.positional[0];
184
+ const help = "codiedev traces judge <agent> [--on | --off] [--scores a,b] [--instructions text] [--model id] [--sample percent] [--daily n] [--budget usd | --key cdvm_…]";
185
+ if (!agent)
186
+ usage(help);
187
+ const changing = p.bools.has("--on") || p.bools.has("--off") || Object.keys(p.flags).length > 0;
188
+ if (!changing) {
189
+ const res = await call(config, "codiedev_traces_judge", "GET", "judge", { query: { agent } });
190
+ printJudge(res.agent, res.judge);
191
+ return;
192
+ }
193
+ const res = await call(config, "codiedev_traces_judge_set", "POST", "judge", {
194
+ body: {
195
+ agent,
196
+ ...(p.bools.has("--on") ? { enabled: true } : p.bools.has("--off") ? { enabled: false } : {}),
197
+ ...(p.flags["--scores"] ? { scores: p.flags["--scores"].split(",").map((s) => s.trim()).filter(Boolean) } : {}),
198
+ ...(p.flags["--instructions"] ? { instructions: p.flags["--instructions"] } : {}),
199
+ ...(p.flags["--model"] ? { model: p.flags["--model"] } : {}),
200
+ ...(p.flags["--sample"] ? { sampleRate: Number(p.flags["--sample"]) / 100 } : {}),
201
+ ...(p.flags["--daily"] ? { dailyMaxRuns: Number(p.flags["--daily"]) } : {}),
202
+ ...(p.flags["--budget"] ? { newKeyBudgetUsd: Number(p.flags["--budget"]) } : {}),
203
+ ...(p.flags["--key"] ? { pastedKey: p.flags["--key"] } : {}),
204
+ },
205
+ });
206
+ printJudge(res.agent, res.judge);
207
+ }
208
+ async function runImport(args, config) {
209
+ const p = parseArgs(args);
210
+ const [agent, file] = p.positional;
211
+ const dataset = p.flags["--dataset"];
212
+ if (!agent || !file || !dataset)
213
+ usage("codiedev traces import <agent> <file.csv|file.jsonl> --dataset <name>");
214
+ let text;
215
+ try {
216
+ text = fs.readFileSync(path.resolve(process.cwd(), file), "utf8");
217
+ }
218
+ catch (err) {
219
+ console.error(`Couldn't read ${file}: ${err.message}`);
220
+ process.exit(1);
221
+ }
222
+ const res = await call(config, "codiedev_traces_import", "POST", "examples", { body: { agent, dataset, text } });
223
+ console.log(`✓ Imported ${res.added} ${res.added === 1 ? "example" : "examples"} into ${dataset} for ${agent}.`);
224
+ for (const e of res.errors.slice(0, 10))
225
+ console.log(` skipped${e.line ? ` line ${e.line}` : ""}: ${e.error}`);
226
+ }
227
+ async function runAddExample(args, config) {
228
+ const p = parseArgs(args);
229
+ const agent = p.positional[0];
230
+ const dataset = p.flags["--dataset"];
231
+ const input = p.flags["--input"];
232
+ if (!agent || !dataset || !input)
233
+ usage("codiedev traces add-example <agent> --dataset <name> --input <text> [--expected <text>] [--name <text>]");
234
+ await call(config, "codiedev_traces_add_example", "POST", "examples", { body: { agent, dataset, input, expected: p.flags["--expected"], name: p.flags["--name"] } });
235
+ console.log(`✓ Added an example to ${dataset} for ${agent}.`);
236
+ }
237
+ async function runVersions(args, config) {
238
+ const dataset = parseArgs(args).positional[0];
239
+ if (!dataset)
240
+ usage("codiedev traces versions <dataset>");
241
+ const res = await call(config, "codiedev_traces_versions", "GET", "versions", { query: { dataset } });
242
+ if (!res.versions.length)
243
+ console.log(`${dataset} has no saved versions. Save one with: codiedev traces save-version "${dataset}"`);
244
+ for (const v of res.versions)
245
+ console.log(`v${v.version} ${String(v.cases).padStart(4)} cases ${new Date(v.createdAt).toISOString().slice(0, 10)} +${v.added} −${v.removed} ${v.changed} edited${v.note ? ` ${v.note}` : ""}`);
246
+ if (res.unsaved)
247
+ console.log(`Unsaved since v${res.versions[0].version}: +${res.unsaved.added} −${res.unsaved.removed} ${res.unsaved.changed} edited`);
248
+ }
249
+ async function runSaveVersion(args, config) {
250
+ const p = parseArgs(args);
251
+ const dataset = p.positional[0];
252
+ if (!dataset)
253
+ usage("codiedev traces save-version <dataset> [--note text]");
254
+ const res = await call(config, "codiedev_traces_save_version", "POST", "versions", { body: { dataset, note: p.flags["--note"] } });
255
+ console.log(`✓ Saved ${dataset} as v${res.version} (${res.cases} cases). Run it later with --dataset "${dataset}" --version ${res.version}.`);
256
+ }
257
+ const usd = (n) => (n === 0 ? "$0" : n < 0.01 ? "<$0.01" : `$${n.toFixed(2)}`);
258
+ const json = (p, value) => { if (p.bools.has("--json")) {
259
+ process.stdout.write(JSON.stringify(value, null, 2) + "\n");
260
+ return true;
261
+ } return false; };
262
+ async function runOverview(args, config) {
263
+ const p = parseArgs(args);
264
+ const res = await call(config, "codiedev_traces_overview", "GET", "overview", { query: { days: p.flags["--days"] ?? "7" } });
265
+ if (json(p, res))
266
+ return;
267
+ if (!res.agents.length) {
268
+ console.log("No agents you can see yet. Connect one in Codiedev → Traces.");
269
+ return;
270
+ }
271
+ console.log(`Agents, last ${res.range}:`);
272
+ for (const a of res.agents)
273
+ console.log(` ${a.name.padEnd(28)} ${String(a.runs).padStart(6)} runs ${String(a.failedRuns).padStart(5)} failed ${usd(a.spend7dUsd).padStart(9)} 7d spend ${a.keepsPromptsDays ? `prompts ${a.keepsPromptsDays}d` : "metadata only"}${a.spike ? " spend spike" : ""}${a.budgetBlock ? " budget block" : ""}`);
274
+ if (res.issues.length) {
275
+ console.log("\nWhat's going wrong:");
276
+ for (const i of res.issues)
277
+ console.log(` ${i.label.padEnd(18)} ${String(i.runs).padStart(5)} runs ${usd(i.wastedUsd).padStart(9)} wasted ${i.agents.join(", ")}${i.exampleRun ? ` e.g. ${i.exampleRun}` : ""}`);
278
+ }
279
+ }
280
+ async function runRuns(args, config) {
281
+ const p = parseArgs(args);
282
+ const agent = p.positional[0];
283
+ if (!agent)
284
+ usage("codiedev traces runs <agent> [--failed] [--flag errors|truncated|tool_loop|context_overflow|budget_block] [--model id] [--verdict pass|fail] [--days n] [--limit n] [--json]");
285
+ const res = await call(config, "codiedev_traces_runs", "GET", "runs", {
286
+ query: { agent, days: p.flags["--days"] ?? "7", ...(p.bools.has("--failed") ? { failed: "1" } : {}), ...(p.flags["--flag"] ? { flag: p.flags["--flag"] } : {}), ...(p.flags["--model"] ? { model: p.flags["--model"] } : {}), ...(p.flags["--verdict"] ? { verdict: p.flags["--verdict"] } : {}), limit: p.flags["--limit"] ?? "50" },
287
+ });
288
+ if (json(p, res))
289
+ return;
290
+ console.log(`${res.total} ${res.total === 1 ? "run" : "runs"} for ${res.agent}, last ${res.range}${res.total > res.runs.length ? ` (newest ${res.runs.length})` : ""}:`);
291
+ for (const r of res.runs)
292
+ console.log(` ${r.startedAt.slice(0, 16).replace("T", " ")} ${r.status === "failed" ? "FAIL" : " ok "} ${r.runId.padEnd(24)} ${String(r.steps).padStart(3)} calls ${usd(r.costUsd).padStart(8)} ${r.model ?? ""}${r.flags.length ? ` [${r.flags.join(", ")}]` : ""}${r.verdict ? ` marked ${r.verdict}` : ""}`);
293
+ if (res.runs.length)
294
+ console.log(`\nOpen one: codiedev traces run ${res.runs[0].runId} --agent "${res.agent}"`);
295
+ }
296
+ async function runRun(args, config) {
297
+ const p = parseArgs(args);
298
+ const run = p.positional[0];
299
+ if (!run)
300
+ usage("codiedev traces run <run> [--agent name] [--json]");
301
+ const res = await call(config, "codiedev_traces_run", "GET", "run", { query: { run, ...(p.flags["--agent"] ? { agent: p.flags["--agent"] } : {}) } });
302
+ if (json(p, res))
303
+ return;
304
+ const r = res.run;
305
+ console.log(`${r.title} · ${r.id}`);
306
+ console.log(`${new Date(r.startedAt).toISOString().slice(0, 16).replace("T", " ")} · ${r.status === "failed" ? "FAILED" : "ok"} · ${Math.round(r.durationMs / 100) / 10}s · ${usd(r.costUsd)}${r.flags.length ? ` · ${r.flags.join(", ")}` : ""}`);
307
+ if (r.diagnosis)
308
+ console.log(`\nWhat went wrong: ${r.diagnosis.title}\n ${r.diagnosis.why}\n\nFix (for your coding agent):\n${r.diagnosis.fix.split("\n").map((l) => ` ${l}`).join("\n")}`);
309
+ if (r.annotation)
310
+ console.log(`\nMarked ${r.annotation.verdict} by ${r.annotation.by}${r.annotation.reason ? `: ${r.annotation.reason}` : ""}`);
311
+ if (res.scores.length)
312
+ console.log(`\nScores: ${res.scores.map((s) => `${s.name} ${s.text}${s.step ? ` (step ${s.step})` : ""} by ${s.by ?? s.source}${s.comment ? ` "${s.comment}"` : ""}`).join("; ")}`);
313
+ console.log("\nSteps:");
314
+ for (const s of r.spans.filter((x) => x.kind !== "run")) {
315
+ console.log(` ${s.kind === "tool" ? "tool" : "call"} ${s.name} ${Math.round(s.durationMs)}ms${s.costUsd !== undefined ? ` ${usd(s.costUsd)}` : ""}${s.finishReason ? ` finish: ${s.finishReason}` : ""}${s.statusCode && s.statusCode >= 400 ? ` HTTP ${s.statusCode}` : ""}${s.flag ? ` [${s.flag}]` : ""}${s.note ? ` ${s.note}` : ""}`);
316
+ if (s.input)
317
+ console.log(` prompt: ${s.input.replace(/\s+/g, " ").slice(0, 300)}`);
318
+ if (s.output)
319
+ console.log(` reply: ${s.output.replace(/\s+/g, " ").slice(0, 300)}`);
320
+ }
321
+ if (!r.bodiesVisible)
322
+ console.log("\n(Prompts and replies are shown only to the agent's maintainers.)");
323
+ }
324
+ async function runScoreKinds(args, config) {
325
+ const [verb, ...rest] = args;
326
+ if (verb === "add") {
327
+ const p = parseArgs(rest);
328
+ const name = p.positional[0];
329
+ const type = p.flags["--type"];
330
+ if (!name || !type)
331
+ usage("codiedev traces score-kinds add <name> --type number|categories|boolean [--min n --max n] [--categories a,b,c] [--description text]");
332
+ const dataType = type === "number" || type === "numeric" ? "numeric" : type === "categories" || type === "categorical" ? "categorical" : type === "boolean" || type === "bool" ? "boolean" : type;
333
+ await call(config, "codiedev_traces_score_kind_add", "POST", "score-kinds", {
334
+ body: { name, type: dataType, ...(p.flags["--min"] ? { min: Number(p.flags["--min"]) } : {}), ...(p.flags["--max"] ? { max: Number(p.flags["--max"]) } : {}), ...(p.flags["--categories"] ? { categories: p.flags["--categories"].split(",").map((c) => c.trim()) } : {}), ...(p.flags["--description"] ? { description: p.flags["--description"] } : {}) },
335
+ });
336
+ console.log(`✓ Added score kind ${name}.`);
337
+ return;
338
+ }
339
+ const p = parseArgs(verb === undefined || verb === "list" ? rest : args);
340
+ const res = await call(config, "codiedev_traces_score_kinds", "GET", "score-kinds", {});
341
+ if (json(p, res))
342
+ return;
343
+ const live = res.kinds.filter((k) => !k.archived);
344
+ if (!live.length)
345
+ console.log("No score kinds yet. Add one: codiedev traces score-kinds add helpfulness --type number --min 1 --max 5");
346
+ for (const k of live)
347
+ console.log(` ${k.name.padEnd(24)} ${k.dataType === "numeric" ? `number ${k.min ?? ""}–${k.max ?? ""}` : k.dataType === "boolean" ? "true / false" : k.categories.join(" / ")}${k.description ? ` ${k.description}` : ""}`);
348
+ }
349
+ async function runQueueCreate(args, config) {
350
+ const p = parseArgs(args);
351
+ const name = p.positional[0];
352
+ if (!name)
353
+ usage("codiedev traces queue create <name> [--scores a,b] [--description text]");
354
+ await call(config, "codiedev_traces_queue_create", "POST", "queues", { body: { name, scores: (p.flags["--scores"] ?? "").split(",").map((s) => s.trim()).filter(Boolean), ...(p.flags["--description"] ? { description: p.flags["--description"] } : {}) } });
355
+ console.log(`✓ Created review queue ${name}. Add runs: codiedev traces queue add "${name}" --agent <name> --failed`);
356
+ }
@@ -1 +1,10 @@
1
+ /**
2
+ * The user's home, always absolute. $HOME can arrive relative or unexpanded ("~", ".", "") from a
3
+ * sandboxed shell or a wrapper; trusting it once wrote hook commands like ".codiedev/bin/…" into
4
+ * ~/.claude/settings.json, which only resolve when Claude Code starts in the home folder, so
5
+ * capture silently failed in every project. Fall back to the account's home from the OS.
6
+ */
7
+ export declare function realHome(): string;
8
+ /** $HOME when it's a real absolute path, else the OS account's home, else resolved against cwd. */
9
+ export declare function pickHome(fromEnv: string | undefined, fromOs: string | undefined): string;
1
10
  export declare function codiedevStateDir(): string;
package/dist/configDir.js CHANGED
@@ -33,6 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.realHome = realHome;
37
+ exports.pickHome = pickHome;
36
38
  exports.codiedevStateDir = codiedevStateDir;
37
39
  // Single source of truth for where the CLI's config/state lives on disk.
38
40
  //
@@ -46,9 +48,31 @@ exports.codiedevStateDir = codiedevStateDir;
46
48
  // without creating a circular import.
47
49
  const path = __importStar(require("path"));
48
50
  const os = __importStar(require("os"));
51
+ /**
52
+ * The user's home, always absolute. $HOME can arrive relative or unexpanded ("~", ".", "") from a
53
+ * sandboxed shell or a wrapper; trusting it once wrote hook commands like ".codiedev/bin/…" into
54
+ * ~/.claude/settings.json, which only resolve when Claude Code starts in the home folder, so
55
+ * capture silently failed in every project. Fall back to the account's home from the OS.
56
+ */
57
+ function realHome() {
58
+ let fromOs;
59
+ try {
60
+ fromOs = os.userInfo().homedir;
61
+ }
62
+ catch { /* no passwd entry */ }
63
+ return pickHome(os.homedir(), fromOs);
64
+ }
65
+ /** $HOME when it's a real absolute path, else the OS account's home, else resolved against cwd. */
66
+ function pickHome(fromEnv, fromOs) {
67
+ if (fromEnv && path.isAbsolute(fromEnv) && !fromEnv.split(path.sep).includes("~"))
68
+ return fromEnv;
69
+ if (fromOs && path.isAbsolute(fromOs))
70
+ return fromOs;
71
+ return path.resolve(fromEnv || ".");
72
+ }
49
73
  const CONFIG_DIR = process.env.CODIEDEV_CONFIG_DIR?.trim()
50
74
  ? path.resolve(process.env.CODIEDEV_CONFIG_DIR.trim())
51
- : path.join(os.homedir(), ".codiedev");
75
+ : path.join(realHome(), ".codiedev");
52
76
  function codiedevStateDir() {
53
77
  return CONFIG_DIR;
54
78
  }
package/dist/connect.js CHANGED
@@ -41,6 +41,7 @@ const crypto = __importStar(require("crypto"));
41
41
  const os = __importStar(require("os"));
42
42
  const path = __importStar(require("path"));
43
43
  const utils_1 = require("./utils");
44
+ const cliSession_1 = require("./cliSession");
44
45
  const connectFlow_1 = require("./connectFlow");
45
46
  const version_1 = require("./version");
46
47
  const upgradeNudge_1 = require("./upgradeNudge");
@@ -60,6 +61,16 @@ const BACKEND_URL = process.env.CODIEDEV_URL || "https://cli.codiedev.com";
60
61
  * input in CI). Without a TTY there's no echo to suppress anyway, and
61
62
  * raw mode would error.
62
63
  */
64
+ /** One visible line from the terminal (for yes/no questions). */
65
+ function prompt(question) {
66
+ return new Promise((resolve) => {
67
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
68
+ rl.question(question, (answer) => {
69
+ rl.close();
70
+ resolve(answer);
71
+ });
72
+ });
73
+ }
63
74
  function promptHidden(question) {
64
75
  return new Promise((resolve) => {
65
76
  const stdin = process.stdin;
@@ -168,16 +179,47 @@ async function runConnect(args = []) {
168
179
  // mint a fresh token from the portal — which is what bit Sonifi's first
169
180
  // engineer when he interpreted "no visible token" as "the old one is gone".
170
181
  const force = args.includes("--force") || args.includes("-f");
182
+ // `--token`: paste an API token instead of signing in (CI). Otherwise sign
183
+ // in with the browser: a loopback redirect on a desktop, a device code
184
+ // (approve on any device) over SSH or with no display, detected
185
+ // automatically. `--no-browser` / `--browser` force one or the other.
186
+ const pasteToken = args.includes("--token");
187
+ const useDevice = args.includes("--no-browser") || (!args.includes("--browser") && (0, cliSession_1.isHeadless)());
171
188
  console.log("\nWelcome to Codiedev CLI\n");
172
189
  await (0, upgradeNudge_1.maybePrintUpgradeNudge)();
173
190
  console.log("This will connect your coding agent to your Codiedev workspace and install");
174
191
  console.log("the session capture hook for org-wide session sharing.\n");
192
+ // A saved browser sign-in is reused only if it still works: refresh it
193
+ // first (a revoked one comes back signed out, with an empty token).
194
+ await (0, cliSession_1.ensureFreshToken)({ configPath: utils_1.CONFIG_PATH }).catch(() => undefined);
175
195
  const saved = (0, utils_1.readConfig)();
176
- const decision = (0, connectFlow_1.resolveConnectToken)({ savedConfig: saved, force });
196
+ const decision = (0, connectFlow_1.resolveConnectToken)({ savedConfig: saved, force: force || pasteToken });
177
197
  let token;
198
+ let auth;
178
199
  if (decision.source === "reused") {
179
200
  token = decision.token;
180
- console.log(`Reusing API token from ~/.codiedev/config.json (run with --force to use a new one).\n`);
201
+ auth = saved?.auth;
202
+ console.log(auth
203
+ ? `Reusing your browser sign-in from ~/.codiedev/config.json (run with --force to sign in again).\n`
204
+ : `Reusing API token from ~/.codiedev/config.json (run with --force to sign in again).\n`);
205
+ }
206
+ else if (!pasteToken) {
207
+ // Replacing a browser sign-in: end the old one so the portal doesn't
208
+ // keep listing it.
209
+ if (saved?.auth)
210
+ await (0, cliSession_1.logout)({ configPath: utils_1.CONFIG_PATH }).catch(() => undefined);
211
+ const result = await (0, connectFlow_1.signInWithTokenFallback)({
212
+ signIn: () => useDevice ? (0, cliSession_1.deviceLogin)({ backendUrl: BACKEND_URL }) : (0, cliSession_1.browserLogin)({ backendUrl: BACKEND_URL }),
213
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
214
+ askYesNo: async (q) => /^y(es)?$/i.test((await prompt(q)).trim()),
215
+ askToken: promptHidden,
216
+ log: (line) => console.log(line),
217
+ error: (line) => console.error(line),
218
+ });
219
+ if (!result)
220
+ process.exit(1);
221
+ token = result.token;
222
+ auth = result.auth;
181
223
  }
182
224
  else {
183
225
  // Hidden prompt — masks the token with `*` as it's typed/pasted so it
@@ -217,6 +259,7 @@ async function runConnect(args = []) {
217
259
  deviceId: saved?.deviceId || crypto.randomUUID(),
218
260
  deviceName: saved?.deviceName || os.hostname(),
219
261
  captureIgnoredPaths: saved?.captureIgnoredPaths ?? [],
262
+ ...(auth ? { auth } : {}),
220
263
  };
221
264
  try {
222
265
  (0, utils_1.writeConfig)(config);
@@ -11,3 +11,24 @@ export type ConnectTokenResult = {
11
11
  source: "prompt-required";
12
12
  };
13
13
  export declare function resolveConnectToken(input: ConnectTokenInput): ConnectTokenResult;
14
+ export interface SignInResult {
15
+ token: string;
16
+ auth?: import("./cliSession").BrowserAuth;
17
+ }
18
+ export interface SignInDeps {
19
+ /** Browser (loopback) or device-code sign-in; throws on failure. */
20
+ signIn: () => Promise<import("./cliSession").TokenSet>;
21
+ /** A person at a terminal who can answer prompts. */
22
+ interactive: boolean;
23
+ askYesNo: (question: string) => Promise<boolean>;
24
+ askToken: (question: string) => Promise<string>;
25
+ log: (line: string) => void;
26
+ error: (line: string) => void;
27
+ }
28
+ /**
29
+ * Browser sign-in, with the API-token paste as the way out: if the browser
30
+ * flow fails (blocked page, expired code, proxy) an interactive user is asked
31
+ * whether to paste a token instead, rather than being sent to rerun connect.
32
+ * Returns null when there's nothing to continue with (caller exits 1).
33
+ */
34
+ export declare function signInWithTokenFallback(deps: SignInDeps): Promise<SignInResult | null>;
@@ -10,6 +10,7 @@
10
10
  // generate a new one — leaving him thinking the old one had been lost.
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.resolveConnectToken = resolveConnectToken;
13
+ exports.signInWithTokenFallback = signInWithTokenFallback;
13
14
  function resolveConnectToken(input) {
14
15
  if (input.force)
15
16
  return { source: "prompt-required" };
@@ -19,3 +20,43 @@ function resolveConnectToken(input) {
19
20
  return { source: "prompt-required" };
20
21
  return { source: "reused", token: input.savedConfig.token };
21
22
  }
23
+ /**
24
+ * Browser sign-in, with the API-token paste as the way out: if the browser
25
+ * flow fails (blocked page, expired code, proxy) an interactive user is asked
26
+ * whether to paste a token instead, rather than being sent to rerun connect.
27
+ * Returns null when there's nothing to continue with (caller exits 1).
28
+ */
29
+ async function signInWithTokenFallback(deps) {
30
+ deps.log("Prefer an API token? Press Ctrl+C and run `codiedev connect --token`.\n");
31
+ try {
32
+ const s = await deps.signIn();
33
+ deps.log("Signed in. No API key is stored on this machine.\n");
34
+ return {
35
+ token: s.accessToken,
36
+ auth: {
37
+ kind: "browser",
38
+ clientId: s.clientId,
39
+ refreshToken: s.refreshToken,
40
+ accessTokenExpiresAt: s.expiresAt,
41
+ },
42
+ };
43
+ }
44
+ catch (err) {
45
+ deps.error(`\nBrowser sign-in didn't finish: ${err.message}`);
46
+ if (!deps.interactive) {
47
+ deps.error("To paste an API token instead, run `codiedev connect --token`.");
48
+ return null;
49
+ }
50
+ if (!(await deps.askYesNo("Paste an API token instead? [y/N] "))) {
51
+ deps.error("Run `codiedev connect` to try again, or `codiedev connect --token` to paste a token.");
52
+ return null;
53
+ }
54
+ deps.log("Create one at https://codiedev.com/portal/integrations/claude-code\n");
55
+ const token = (await deps.askToken("Enter your Codiedev API token: ")).trim();
56
+ if (!token) {
57
+ deps.error("Error: API token is required.");
58
+ return null;
59
+ }
60
+ return { token };
61
+ }
62
+ }
package/dist/hook.js CHANGED
@@ -60,6 +60,8 @@ const captureExclusions_1 = require("./captureExclusions");
60
60
  const cursorHooks_1 = require("./cursorHooks");
61
61
  const copilotHooks_1 = require("./copilotHooks");
62
62
  const piHooks_1 = require("./piHooks");
63
+ const cliSession_1 = require("./cliSession");
64
+ const utils_2 = require("./utils");
63
65
  // Pure builder for the discovery additionalContext. Returns null when there are
64
66
  // no matches. The suggested `codiedev pull` command carries the EXACT top key
65
67
  // (e.g. "pr-push.md") rather than a "<key>" placeholder — otherwise agents
@@ -440,6 +442,11 @@ function resolveMode(arg) {
440
442
  return "claude-code";
441
443
  }
442
444
  async function main() {
445
+ // Browser sign-in: refresh the access token under the shared lock before
446
+ // anything reads config (cliSession.ts). A no-op for pasted API tokens and
447
+ // for tokens with time left; never throws, never prints (hook stdout is
448
+ // the agent's).
449
+ await (0, cliSession_1.ensureFreshToken)({ configPath: utils_2.CONFIG_PATH }).catch(() => undefined);
443
450
  // Shared upload deps used by both "upload" and "upload-deferred" branches.
444
451
  // Extracted to keep both branches in lockstep — any new dep added here
445
452
  // automatically applies to the deferred path too.
@@ -8,13 +8,18 @@ export declare function launcherPath(): string;
8
8
  /** True iff `cmd` invokes the stable launcher (from any state dir). */
9
9
  export declare function usesLauncher(cmd: string | undefined): boolean;
10
10
  export declare function readInstallRecord(): InstallRecord | null;
11
+ /**
12
+ * The hook script worth recording: absolute and present, and a global install over an npx cache
13
+ * copy (npm prunes ~/.npm/_npx, and hooks pointed there go quietly dead). Null = don't record.
14
+ */
15
+ export declare function stableHookScript(hookScript: string, globalCandidates?: string[]): string | null;
11
16
  /** Point the launcher at `hookScript`. Atomic, so a hook mid-launch never reads half a file. */
12
- export declare function recordCurrentInstall(hookScript: string): boolean;
17
+ export declare function recordCurrentInstall(requested: string): boolean;
13
18
  /**
14
19
  * Make sure the launcher exists and forwards to `hookScript`, returning the
15
20
  * launcher path to bake into hook configs — or null if the state dir is not
16
21
  * writable, in which case callers keep baking the direct path. Memoized:
17
22
  * one connect writes ~20 hook entries.
18
23
  */
19
- export declare function ensureLauncher(hookScript: string): string | null;
24
+ export declare function ensureLauncher(requested: string): string | null;
20
25
  export declare function resetLauncherCacheForTests(): void;