cc-flight 0.4.1

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,113 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import { AgentLogSource, AgentProvider, ParsedAgentEvent, TimelineEvent, TokenUsage } from "../../core/types";
3
+ import { redactValue } from "../../core/redactor";
4
+ import { sha256 } from "../../core/hash";
5
+
6
+ export async function fileSource(provider: AgentProvider, filePath: string): Promise<AgentLogSource> {
7
+ const stats = await stat(filePath);
8
+ return {
9
+ provider,
10
+ id: `${provider}:${filePath}`,
11
+ path: filePath,
12
+ sizeBytes: stats.size,
13
+ mtimeMs: stats.mtimeMs
14
+ };
15
+ }
16
+
17
+ export async function readJsonFile(sourcePath: string): Promise<unknown> {
18
+ return JSON.parse(await readFile(sourcePath, "utf8"));
19
+ }
20
+
21
+ export function parsedEvent(input: {
22
+ provider: AgentProvider;
23
+ sourcePath: string;
24
+ lineNo: number;
25
+ timestamp: string;
26
+ type: string;
27
+ payload: unknown;
28
+ raw?: string;
29
+ }): ParsedAgentEvent {
30
+ const raw = input.raw ?? JSON.stringify({ timestamp: input.timestamp, type: input.type, payload: input.payload });
31
+ return {
32
+ provider: input.provider,
33
+ sourcePath: input.sourcePath,
34
+ lineNo: input.lineNo,
35
+ timestamp: input.timestamp,
36
+ type: input.type,
37
+ payload: input.payload,
38
+ redactedPayload: redactValue(input.payload),
39
+ sha256: sha256(raw)
40
+ };
41
+ }
42
+
43
+ export function makeTokenUsage(value: unknown): TokenUsage | null {
44
+ const rawInput = firstNumber(value, ["input_tokens", "prompt_tokens", "input", "prompt"]);
45
+ const output = firstNumber(value, ["output_tokens", "completion_tokens", "output", "completion"]);
46
+ const reasoning = firstNumber(value, ["reasoning_tokens", "reasoning", "reasoning_output_tokens"]);
47
+ // OpenAI/Codex style: cached_tokens / cached_input_tokens are ALREADY included in prompt_tokens/input_tokens.
48
+ // Anthropic style: cache_read_input_tokens and cache_creation_input_tokens are reported SEPARATELY from input_tokens.
49
+ const cachedRead = firstNumber(value, ["cached_input_tokens", "cached_tokens", "cache_read_input_tokens", "cached_input", "cache_read", "read"]);
50
+ const anthropicCacheRead = firstNumber(value, ["cache_read_input_tokens"]);
51
+ const anthropicCacheCreation = firstNumber(value, ["cache_creation_input_tokens"]);
52
+ const explicitTotal = firstNumber(value, ["total_tokens", "total"]);
53
+ const isAnthropicStyle = anthropicCacheRead !== null || anthropicCacheCreation !== null;
54
+ const input = isAnthropicStyle
55
+ ? (rawInput ?? 0) + (anthropicCacheRead ?? 0) + (anthropicCacheCreation ?? 0)
56
+ : rawInput;
57
+ const cachedInput = isAnthropicStyle ? (anthropicCacheRead ?? 0) : cachedRead;
58
+ const total = explicitTotal ?? sumKnown(input, output);
59
+ if (rawInput === null && output === null && reasoning === null && cachedRead === null && total === null && !isAnthropicStyle) return null;
60
+ return {
61
+ input: input ?? 0,
62
+ output: output ?? 0,
63
+ reasoning: reasoning ?? 0,
64
+ cachedInput: cachedInput ?? 0,
65
+ total: total ?? 0
66
+ };
67
+ }
68
+
69
+ export function attachTokenUsage(event: TimelineEvent, usage: TokenUsage | null): TimelineEvent {
70
+ return usage ? { ...event, tokenUsage: usage } : event;
71
+ }
72
+
73
+ export function asRecord(value: unknown): Record<string, unknown> {
74
+ return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
75
+ }
76
+
77
+ export function stringValue(value: unknown): string | null {
78
+ return typeof value === "string" ? value : null;
79
+ }
80
+
81
+ export function numberTimestamp(value: unknown): string | null {
82
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
83
+ return new Date(value).toISOString();
84
+ }
85
+
86
+ function firstNumber(value: unknown, keys: string[]): number | null {
87
+ const matches = collectNumbers(value, new Set(keys.map(normalizeKey)));
88
+ return matches[0] ?? null;
89
+ }
90
+
91
+ function collectNumbers(value: unknown, keys: Set<string>): number[] {
92
+ if (!value || typeof value !== "object") return [];
93
+ if (Array.isArray(value)) return value.flatMap((item) => collectNumbers(item, keys));
94
+ const matches: number[] = [];
95
+ for (const [key, child] of Object.entries(value)) {
96
+ if (keys.has(normalizeKey(key)) && typeof child === "number" && Number.isFinite(child) && child >= 0) {
97
+ matches.push(Math.trunc(child));
98
+ }
99
+ if (child && typeof child === "object") {
100
+ matches.push(...collectNumbers(child, keys));
101
+ }
102
+ }
103
+ return matches;
104
+ }
105
+
106
+ function normalizeKey(value: string) {
107
+ return value.replace(/[^a-z0-9]/gi, "").toLowerCase();
108
+ }
109
+
110
+ function sumKnown(...values: Array<number | null>) {
111
+ const known = values.filter((value): value is number => value !== null);
112
+ return known.length > 0 ? known.reduce((sum, value) => sum + value, 0) : null;
113
+ }
@@ -0,0 +1,22 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+
4
+ export const CHROME_DEVTOOLS_CONFIG_PATH = "/.well-known/appspecific/com.chrome.devtools.json";
5
+
6
+ export function buildChromeDevToolsConfig(root = process.cwd()) {
7
+ const workspaceRoot = path.resolve(root);
8
+ return {
9
+ workspace: {
10
+ root: workspaceRoot,
11
+ uuid: uuidFromRoot(workspaceRoot)
12
+ }
13
+ };
14
+ }
15
+
16
+ function uuidFromRoot(root: string) {
17
+ const chars = createHash("sha256").update(root).digest("hex").slice(0, 32).split("");
18
+ chars[12] = "4";
19
+ chars[16] = ((Number.parseInt(chars[16], 16) & 0x3) | 0x8).toString(16);
20
+ const hex = chars.join("");
21
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
22
+ }
@@ -0,0 +1,7 @@
1
+ import { SuperViewDatabase } from "../storage/database";
2
+ import { IngestService } from "./ingest";
3
+
4
+ const db = new SuperViewDatabase();
5
+ const service = new IngestService(db);
6
+ const result = service.start(process.argv[2] ? { codexHome: process.argv[2] } : {});
7
+ console.log(JSON.stringify({ jobId: result.job.id, alreadyRunning: result.alreadyRunning }, null, 2));
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { createRequire } from "node:module";
4
+ import { fileURLToPath } from "node:url";
5
+ import path from "node:path";
6
+
7
+ const require = createRequire(import.meta.url);
8
+ const tsxDir = path.dirname(require.resolve("tsx/package.json"));
9
+ const tsxPath = path.join(tsxDir, "dist", "cli.mjs");
10
+ const cliPath = fileURLToPath(new URL("./cli-start.ts", import.meta.url));
11
+
12
+ const child = spawn(process.execPath, [tsxPath, cliPath, ...process.argv.slice(2)], {
13
+ stdio: "inherit"
14
+ });
15
+ child.on("exit", (code) => process.exit(code ?? 0));
@@ -0,0 +1,21 @@
1
+ import path from "node:path";
2
+ import { startProdServer } from "./prod-server.js";
3
+
4
+ const portArg = process.argv.find((arg) => arg.startsWith("--port="));
5
+ const dataDirArg = process.argv.find((arg) => arg.startsWith("--data-dir="));
6
+ const projectDirArg = process.argv.find((arg) => arg.startsWith("--project-dir="));
7
+
8
+ if (portArg) process.env.SUPERVIEW_PORT = portArg.split("=")[1];
9
+ if (dataDirArg) process.env.SUPERVIEW_DATA_DIR = dataDirArg.split("=")[1];
10
+
11
+ const rawProjectDir = projectDirArg
12
+ ? projectDirArg.split("=")[1]
13
+ : process.argv[2] && !process.argv[2].startsWith("--")
14
+ ? process.argv[2]
15
+ : undefined;
16
+
17
+ const projectDir = rawProjectDir
18
+ ? path.resolve(process.cwd(), rawProjectDir)
19
+ : undefined;
20
+
21
+ startProdServer({ projectDir });
@@ -0,0 +1,13 @@
1
+ import { createServer } from "./server.js";
2
+
3
+ const port = Number(process.env.SUPERVIEW_API_PORT ?? 5174);
4
+ const app = createServer();
5
+
6
+ app.get("/", (_req, res) => {
7
+ const clientUrl = process.env.SUPERVIEW_UI_URL ?? `http://127.0.0.1:${process.env.SUPERVIEW_UI_PORT ?? 5173}/`;
8
+ res.redirect(302, clientUrl);
9
+ });
10
+
11
+ app.listen(port, "127.0.0.1", () => {
12
+ console.log(`SuperView API listening on http://127.0.0.1:${port}`);
13
+ });
@@ -0,0 +1,102 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import type { GitCommitRecord } from "../core/types";
4
+
5
+ const execFileAsync = promisify(execFile);
6
+ const GIT_TIMEOUT_MS = 3000;
7
+
8
+ export async function getRepoRoot(cwd: string): Promise<string | null> {
9
+ try {
10
+ const { stdout } = await execFileAsync("git", ["-C", cwd, "rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS });
11
+ return stdout.trim() || null;
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+
17
+ export async function getCommits(repoRoot: string, from?: string | null, to?: string | null): Promise<GitCommitRecord[]> {
18
+ const args = [
19
+ "-C",
20
+ repoRoot,
21
+ "log",
22
+ "--date=iso-strict",
23
+ "--pretty=format:%x1e%H%x1f%h%x1f%an%x1f%ae%x1f%ad%x1f%s",
24
+ "--numstat"
25
+ ];
26
+
27
+ if (from) {
28
+ args.push(`--since=${formatGitDateArg(from)}`);
29
+ }
30
+ if (to) {
31
+ args.push(`--until=${formatGitDateArg(to)}`);
32
+ }
33
+
34
+ try {
35
+ const { stdout } = await execFileAsync("git", args, {
36
+ timeout: GIT_TIMEOUT_MS,
37
+ maxBuffer: 10 * 1024 * 1024
38
+ });
39
+ return parseGitLogNumstat(stdout, repoRoot);
40
+ } catch {
41
+ return [];
42
+ }
43
+ }
44
+
45
+ function parseGitLogNumstat(stdout: string, repoRoot: string): GitCommitRecord[] {
46
+ return stdout
47
+ .split("\x1e")
48
+ .map((entry) => entry.trim())
49
+ .filter(Boolean)
50
+ .map((entry) => parseGitCommitEntry(entry, repoRoot))
51
+ .filter((commit): commit is GitCommitRecord => commit !== null);
52
+ }
53
+
54
+ function parseGitCommitEntry(entry: string, repoRoot: string): GitCommitRecord | null {
55
+ const lines = entry.split(/\r?\n/).filter(Boolean);
56
+ const header = lines.shift();
57
+ if (!header) return null;
58
+
59
+ const [hash, shortHash, authorName, authorEmail, timestamp, subject] = header.split("\x1f");
60
+ if (!hash || !shortHash || !timestamp) return null;
61
+
62
+ let filesChanged = 0;
63
+ let insertions = 0;
64
+ let deletions = 0;
65
+
66
+ for (const line of lines) {
67
+ const [inserted, deleted] = line.split(/\t/);
68
+ if (inserted === undefined || deleted === undefined) continue;
69
+
70
+ filesChanged += 1;
71
+ insertions += parseNumstatCount(inserted);
72
+ deletions += parseNumstatCount(deleted);
73
+ }
74
+
75
+ return {
76
+ id: hash,
77
+ projectId: repoRoot,
78
+ repoRoot,
79
+ hash,
80
+ shortHash,
81
+ authorName: authorName || null,
82
+ authorEmail: authorEmail || null,
83
+ timestamp,
84
+ subject: subject ?? "",
85
+ filesChanged,
86
+ insertions,
87
+ deletions
88
+ };
89
+ }
90
+
91
+ function parseNumstatCount(value: string): number {
92
+ const parsed = Number.parseInt(value, 10);
93
+ return Number.isFinite(parsed) ? parsed : 0;
94
+ }
95
+
96
+ function formatGitDateArg(value: string): string {
97
+ const parsed = new Date(value);
98
+ if (Number.isNaN(parsed.getTime())) {
99
+ return value;
100
+ }
101
+ return `@${Math.floor(parsed.getTime() / 1000)}`;
102
+ }
@@ -0,0 +1,9 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { parseCodexHistoryJsonlContent, type CodexHistoryParseResult } from "../core/history";
5
+
6
+ export async function parseCodexHistoryJsonlFile(sourcePath = join(homedir(), ".codex", "history.jsonl")): Promise<CodexHistoryParseResult> {
7
+ const content = await readFile(sourcePath, "utf8");
8
+ return parseCodexHistoryJsonlContent(content, sourcePath);
9
+ }
@@ -0,0 +1,32 @@
1
+ import { SuperViewDatabase } from "../storage/database";
2
+ import { parseIngestOptions, runIngestJob } from "./ingest";
3
+
4
+ const [jobId, encodedOptions] = process.argv.slice(2);
5
+
6
+ if (!jobId) {
7
+ throw new Error("Missing ingest job id");
8
+ }
9
+
10
+ const db = new SuperViewDatabase();
11
+
12
+ try {
13
+ if (process.env.SUPERVIEW_TEST_INGEST_WORKER_FAIL === "1") {
14
+ markFailed(db, jobId, "Forced ingest worker failure");
15
+ process.exitCode = 1;
16
+ } else {
17
+ await runIngestJob(db, jobId, parseIngestOptions(encodedOptions), { workerPid: process.pid });
18
+ }
19
+ } finally {
20
+ db.close();
21
+ }
22
+
23
+ function markFailed(database: SuperViewDatabase, id: string, message: string) {
24
+ const job = database.getJob(id);
25
+ if (!job) return;
26
+ job.status = "failed";
27
+ job.phase = "failed";
28
+ job.finishedAt = new Date().toISOString();
29
+ job.currentFile = null;
30
+ job.errors.push(message);
31
+ database.upsertJob(job);
32
+ }