codex-copilot-dx 0.6.6 → 0.6.8
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 +38 -14
- package/bin/cli.mjs +16 -3
- package/package.json +1 -1
- package/src/cli-auth.mjs +99 -3
- package/src/cli-models.mjs +60 -5
- package/src/cli-options.mjs +43 -16
- package/src/cli-table.mjs +114 -0
- package/src/config.mjs +18 -0
- package/src/pm-studio-asar.mjs +100 -1
- package/src/pm-studio-setup.mjs +302 -25
- package/src/pm-studio-status.mjs +81 -23
- package/src/usage.mjs +104 -9
package/src/pm-studio-status.mjs
CHANGED
|
@@ -18,6 +18,12 @@ import {
|
|
|
18
18
|
import { readAdapterStatus } from "./cli-status.mjs";
|
|
19
19
|
import { checkRunningAdapter } from "./running-adapter.mjs";
|
|
20
20
|
import { status } from "./status.mjs";
|
|
21
|
+
import {
|
|
22
|
+
cliOutputFormat,
|
|
23
|
+
cliOutputWidth,
|
|
24
|
+
formatResponsiveCliTable,
|
|
25
|
+
terminalCell,
|
|
26
|
+
} from "./cli-table.mjs";
|
|
21
27
|
|
|
22
28
|
const PM_RELAY_HOST = "127.0.0.1";
|
|
23
29
|
const PM_RELAY_PORT = 2026;
|
|
@@ -166,54 +172,106 @@ export async function inspectPmStudioStatus({
|
|
|
166
172
|
return { ok: operational, appPath, app, claude, adapter, runtime };
|
|
167
173
|
}
|
|
168
174
|
|
|
169
|
-
function
|
|
175
|
+
function appStatusItem(result, commandName) {
|
|
170
176
|
const { app } = result;
|
|
171
177
|
const version = app.metadata ? `${app.metadata.version} build ${app.metadata.build}` : "";
|
|
172
178
|
if (app.state === "patched" && app.patchRecord?.valid === true) {
|
|
173
|
-
return
|
|
179
|
+
return { kind: "ok", component: "App patch", detail: `${version}; patched and verified`, message: `PM Studio ${version} is patched and verified against its installed patch record` };
|
|
174
180
|
}
|
|
175
181
|
if (app.state === "patched") {
|
|
176
182
|
const reason = app.patchRecord?.reason || "installed patch record is missing";
|
|
177
|
-
return
|
|
183
|
+
return { kind: "err", component: "App patch", detail: `${version}; patch record not verified`, message: `PM Studio ${version} matches the patch recipe, but its installed patch record is not verified: ${reason}` };
|
|
178
184
|
}
|
|
179
|
-
if (app.state === "clean") return
|
|
180
|
-
if (app.state === "unsupported") return
|
|
185
|
+
if (app.state === "clean") return { kind: "warn", component: "App patch", detail: `${version}; supported, not patched`, message: `PM Studio ${version} is supported but not patched; run ${commandName} pms setup` };
|
|
186
|
+
if (app.state === "unsupported") return { kind: "err", component: "App patch", detail: `${version}; unsupported version`, message: `PM Studio ${version} has no exact patch recipe; no files will be changed` };
|
|
181
187
|
if (app.state === "drift" && app.patchRecipeMatched) {
|
|
182
|
-
return
|
|
188
|
+
return { kind: "err", component: "App patch", detail: `${version}; patch record drift`, message: `PM Studio ${version} matches the patch recipe, but its installed patch record is not verified: ${app.issues.join("; ")}` };
|
|
183
189
|
}
|
|
184
|
-
if (app.state === "drift") return
|
|
185
|
-
if (app.state === "not_installed") return
|
|
186
|
-
return
|
|
190
|
+
if (app.state === "drift") return { kind: "err", component: "App patch", detail: `${version}; integrity drift`, message: `PM Studio ${version} does not match the clean or patched recipe: ${app.issues.join("; ")}` };
|
|
191
|
+
if (app.state === "not_installed") return { kind: "warn", component: "App patch", detail: "not installed", message: `PM Studio is not installed at ${result.appPath}` };
|
|
192
|
+
return { kind: "err", component: "App patch", detail: "inspection failed", message: `PM Studio could not be inspected: ${app.issues.join("; ")}` };
|
|
187
193
|
}
|
|
188
194
|
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
`${commandName} pms status`,
|
|
192
|
-
appStatusLine(result, commandName),
|
|
193
|
-
];
|
|
195
|
+
function pmStudioStatusItems(result, commandName) {
|
|
196
|
+
const items = [appStatusItem(result, commandName)];
|
|
194
197
|
if (result.claude.valid) {
|
|
195
198
|
const account = result.claude.login ? ` for ${result.claude.login}` : "";
|
|
196
|
-
|
|
199
|
+
const detail = result.claude.login ? `${result.claude.login}; isolated profile valid` : "isolated profile valid";
|
|
200
|
+
items.push({ kind: "ok", component: "Claude profile", detail, message: `Isolated Claude profile is valid${account}` });
|
|
197
201
|
} else {
|
|
198
202
|
const reason = result.claude.configured && result.claude.reason ? ` (${result.claude.reason})` : "";
|
|
199
|
-
|
|
203
|
+
items.push({ kind: "err", component: "Claude profile", detail: "not ready", message: `Isolated Claude profile is not ready${reason}; run ${PM_STUDIO_CLAUDE_AUTH_COMMAND}` });
|
|
200
204
|
}
|
|
201
205
|
if (result.adapter?.ok && result.runtime?.ok) {
|
|
202
|
-
|
|
206
|
+
items.push({ kind: "ok", component: "PM relay", detail: `verified at ${result.adapter.baseUrl}`, message: `PM relay and isolated Claude routing are verified at ${result.adapter.baseUrl}` });
|
|
203
207
|
} else if (result.adapter?.ok) {
|
|
204
208
|
const reason = result.runtime?.issues?.join("; ") || "runtime status is unavailable";
|
|
205
|
-
|
|
209
|
+
items.push({ kind: "err", component: "PM relay", detail: "running but routing not ready", message: `The adapter is running at ${result.adapter.baseUrl}, but PM relay routing is not ready: ${reason}; stop it and run ${commandName} start` });
|
|
206
210
|
} else if (result.adapter?.incompatible) {
|
|
207
|
-
|
|
211
|
+
items.push({ kind: "err", component: "PM relay", detail: `incompatible at ${result.adapter.baseUrl}`, message: `The adapter at ${result.adapter.baseUrl} is incompatible; stop it and run ${commandName} start` });
|
|
208
212
|
} else {
|
|
209
|
-
|
|
213
|
+
items.push({ kind: "warn", component: "PM relay", detail: `not running at ${PM_RELAY_HOST}:${PM_RELAY_PORT}`, message: `PM relay is not running at http://${PM_RELAY_HOST}:${PM_RELAY_PORT}; run ${commandName} start` });
|
|
210
214
|
}
|
|
211
215
|
if (result.runtime?.ok) {
|
|
212
|
-
|
|
216
|
+
items.push({ kind: "info", component: "Routing", detail: "GPT -> PM bearer; Claude -> isolated profile", message: "Routing: PM GPT uses the PM Studio bearer; eligible Claude chat uses the isolated Claude profile" });
|
|
213
217
|
} else {
|
|
214
|
-
|
|
218
|
+
items.push({ kind: "info", component: "Routing", detail: "expected: GPT -> PM bearer; Claude -> isolated profile", message: "Expected routing: PM GPT uses the PM Studio bearer; eligible Claude chat uses the isolated Claude profile" });
|
|
219
|
+
}
|
|
220
|
+
return items;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function formatPmStudioStatusPlain(result, commandName, { sanitize = false } = {}) {
|
|
224
|
+
const lines = [
|
|
225
|
+
`${commandName} pms status`,
|
|
226
|
+
...pmStudioStatusItems(result, commandName).map((item) => status(item.kind, item.message)),
|
|
227
|
+
];
|
|
228
|
+
return (sanitize ? lines.map((line) => terminalCell(line, { fallback: "" })) : lines).join("\n");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function tableState(kind) {
|
|
232
|
+
return `[${String(kind).toUpperCase()}]`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function formatPmStudioStatusTable(result, { commandName, output }) {
|
|
236
|
+
const items = pmStudioStatusItems(result, commandName);
|
|
237
|
+
const rows = items.map((item) => ({
|
|
238
|
+
component: item.component,
|
|
239
|
+
state: tableState(item.kind),
|
|
240
|
+
detail: item.detail,
|
|
241
|
+
}));
|
|
242
|
+
const table = formatResponsiveCliTable({
|
|
243
|
+
columns: [
|
|
244
|
+
{ key: "component", label: "COMPONENT" },
|
|
245
|
+
{ key: "state", label: "STATE" },
|
|
246
|
+
{ key: "detail", label: "DETAIL" },
|
|
247
|
+
],
|
|
248
|
+
compactColumns: [
|
|
249
|
+
{ key: "component", label: "COMPONENT" },
|
|
250
|
+
{ key: "state", label: "STATE" },
|
|
251
|
+
],
|
|
252
|
+
rows,
|
|
253
|
+
width: cliOutputWidth(output),
|
|
254
|
+
});
|
|
255
|
+
const lines = [`${commandName} pms status`, table.output];
|
|
256
|
+
const details = table.compact ? items : items.filter((item) => item.kind === "warn" || item.kind === "err");
|
|
257
|
+
if (details.length) {
|
|
258
|
+
lines.push("", "Details:", ...details.map((item) => terminalCell(status(item.kind, item.message), { fallback: "" })));
|
|
259
|
+
}
|
|
260
|
+
return { output: lines.join("\n"), overflow: table.overflow };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function formatPmStudioStatus(result, {
|
|
264
|
+
commandName = "ccdx",
|
|
265
|
+
format = "plain",
|
|
266
|
+
output = process.stdout,
|
|
267
|
+
} = {}) {
|
|
268
|
+
if (cliOutputFormat(format, output) === "plain") {
|
|
269
|
+
return formatPmStudioStatusPlain(result, commandName);
|
|
215
270
|
}
|
|
216
|
-
|
|
271
|
+
const table = formatPmStudioStatusTable(result, { commandName, output });
|
|
272
|
+
return format === "auto" && table.overflow
|
|
273
|
+
? formatPmStudioStatusPlain(result, commandName, { sanitize: true })
|
|
274
|
+
: table.output;
|
|
217
275
|
}
|
|
218
276
|
|
|
219
277
|
export async function runPmStudioStatus(options = {}) {
|
package/src/usage.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import readline from "node:readline";
|
|
5
|
+
import { cliOutputFormat, cliOutputWidth, formatResponsiveCliTable, terminalCell } from "./cli-table.mjs";
|
|
5
6
|
import { parseByteLimit, rotateFileIfNeededSync, rotatedFilePath } from "./file-rotation.mjs";
|
|
6
7
|
|
|
7
8
|
const DEFAULT_USAGE_PATH = path.join(os.homedir(), ".local", "share", "codex-copilot-dx", "usage.jsonl");
|
|
@@ -219,17 +220,111 @@ function fmt(n) {
|
|
|
219
220
|
return Number.isFinite(n) ? n.toLocaleString("en-US") : "0";
|
|
220
221
|
}
|
|
221
222
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
function tableNumber(n) {
|
|
224
|
+
return Number.isFinite(n) ? n.toLocaleString("en-US") : undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function cacheReadTokens(usage = {}) {
|
|
228
|
+
const values = [usage.cache_read_input_tokens, usage.cached_input_tokens]
|
|
229
|
+
.filter((value) => Number.isFinite(value));
|
|
230
|
+
const value = values.length > 0 ? values.reduce((total, current) => total + current, 0) : undefined;
|
|
231
|
+
return Number.isFinite(value) ? value : undefined;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function formatPlainUsageSummary(summary, filePath, { sanitize = false } = {}) {
|
|
235
|
+
const lines = [`Usage log: ${filePath}`];
|
|
225
236
|
if (summary.requests === 0) {
|
|
226
|
-
|
|
227
|
-
return;
|
|
237
|
+
lines.push("No usage records yet.");
|
|
238
|
+
return (sanitize ? lines.map((line) => terminalCell(line, { fallback: "" })) : lines).join("\n");
|
|
228
239
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
240
|
+
lines.push(
|
|
241
|
+
`Requests: ${fmt(summary.requests)}`,
|
|
242
|
+
`Tokens: input=${fmt(summary.totals.input_tokens)} cache_read=${fmt(cacheReadTokens(summary.totals))} output=${fmt(summary.totals.output_tokens)} total=${fmt(summary.totals.total_tokens)}`,
|
|
243
|
+
"",
|
|
244
|
+
"By model:",
|
|
245
|
+
);
|
|
232
246
|
for (const [model, row] of Object.entries(summary.byModel)) {
|
|
233
|
-
|
|
247
|
+
lines.push(` ${model}: requests=${fmt(row.requests)} input=${fmt(row.input_tokens)} cache_read=${fmt(cacheReadTokens(row))} output=${fmt(row.output_tokens)} total=${fmt(row.total_tokens)}`);
|
|
248
|
+
}
|
|
249
|
+
return (sanitize ? lines.map((line) => terminalCell(line, { fallback: "" })) : lines).join("\n");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function compareModelUsage(left, right) {
|
|
253
|
+
const leftTotal = Number.isFinite(left.row.total_tokens) ? left.row.total_tokens : -Infinity;
|
|
254
|
+
const rightTotal = Number.isFinite(right.row.total_tokens) ? right.row.total_tokens : -Infinity;
|
|
255
|
+
if (leftTotal !== rightTotal) return rightTotal - leftTotal;
|
|
256
|
+
return left.model < right.model ? -1 : left.model > right.model ? 1 : 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function usageTableRow(model, usage) {
|
|
260
|
+
return {
|
|
261
|
+
model,
|
|
262
|
+
records: tableNumber(usage.requests),
|
|
263
|
+
input: tableNumber(usage.input_tokens),
|
|
264
|
+
cacheRead: tableNumber(cacheReadTokens(usage)),
|
|
265
|
+
output: tableNumber(usage.output_tokens),
|
|
266
|
+
total: tableNumber(usage.total_tokens),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function usageDetailLine(model, usage) {
|
|
271
|
+
return `${terminalCell(model)}: input=${fmt(usage.input_tokens)} cache_read=${fmt(cacheReadTokens(usage))} output=${fmt(usage.output_tokens)}`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function formatTableUsageSummary(summary, filePath, output) {
|
|
275
|
+
const safePath = terminalCell(filePath, { fallback: "" });
|
|
276
|
+
if (summary.requests === 0) return { output: `Usage log: ${safePath}\nNo usage records yet.`, overflow: false };
|
|
277
|
+
const columns = [
|
|
278
|
+
{ key: "model", label: "MODEL" },
|
|
279
|
+
{ key: "records", label: "RECORDS", align: "right" },
|
|
280
|
+
{ key: "input", label: "INPUT", align: "right" },
|
|
281
|
+
{ key: "cacheRead", label: "CACHE READ", align: "right" },
|
|
282
|
+
{ key: "output", label: "OUTPUT", align: "right" },
|
|
283
|
+
{ key: "total", label: "TOTAL", align: "right" },
|
|
284
|
+
];
|
|
285
|
+
const compactColumns = [columns[0], columns[1], columns[5]];
|
|
286
|
+
const modelUsage = Object.entries(summary.byModel)
|
|
287
|
+
.map(([model, row]) => ({ model, row }))
|
|
288
|
+
.sort(compareModelUsage);
|
|
289
|
+
const totalUsage = { requests: summary.requests, ...summary.totals };
|
|
290
|
+
const rows = [usageTableRow("TOTAL", totalUsage), ...modelUsage.map(({ model, row }) => usageTableRow(model, row))];
|
|
291
|
+
const table = formatResponsiveCliTable({
|
|
292
|
+
columns,
|
|
293
|
+
compactColumns,
|
|
294
|
+
rows,
|
|
295
|
+
width: cliOutputWidth(output),
|
|
296
|
+
gap: 1,
|
|
297
|
+
});
|
|
298
|
+
const lines = [`Usage log: ${safePath}`, "", table.output];
|
|
299
|
+
if (table.compact) {
|
|
300
|
+
lines.push(
|
|
301
|
+
"",
|
|
302
|
+
"Details:",
|
|
303
|
+
usageDetailLine("TOTAL", totalUsage),
|
|
304
|
+
...modelUsage.map(({ model, row }) => usageDetailLine(model, row)),
|
|
305
|
+
);
|
|
234
306
|
}
|
|
307
|
+
return { output: lines.join("\n"), overflow: table.overflow };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function formatUsageSummary(summary, {
|
|
311
|
+
filePath = usageLogPath(),
|
|
312
|
+
format = "auto",
|
|
313
|
+
output = process.stdout,
|
|
314
|
+
} = {}) {
|
|
315
|
+
if (cliOutputFormat(format, output) === "plain") return formatPlainUsageSummary(summary, filePath);
|
|
316
|
+
const table = formatTableUsageSummary(summary, filePath, output);
|
|
317
|
+
return format === "auto" && table.overflow
|
|
318
|
+
? formatPlainUsageSummary(summary, filePath, { sanitize: true })
|
|
319
|
+
: table.output;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export async function printUsageSummary({
|
|
323
|
+
filePath = usageLogPath(),
|
|
324
|
+
format = "auto",
|
|
325
|
+
output = process.stdout,
|
|
326
|
+
log = console.log,
|
|
327
|
+
} = {}) {
|
|
328
|
+
const summary = await summarizeUsageLogs(filePath);
|
|
329
|
+
log(formatUsageSummary(summary, { filePath, format, output }));
|
|
235
330
|
}
|