codex-lens 0.1.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/cli.js ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "child_process";
3
+ import { dirname, join } from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { existsSync } from "fs";
6
+ import { createAggregator } from "./aggregator.js";
7
+ import { createLogger } from "./lib/logger.js";
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const PROXY_PORT = 8080;
10
+ const logger = createLogger("CLI");
11
+ async function findCodexBinary() {
12
+ const { execSync } = await import("child_process");
13
+ logger.debug("Searching for Codex binary...");
14
+ const candidates = [
15
+ "D:\\Software\\npm\\codex.cmd",
16
+ "C:\\Program Files\\nodejs\\codex.cmd",
17
+ join(process.env.APPDATA || "", "npm", "codex.cmd")
18
+ ];
19
+ for (const candidate of candidates) {
20
+ if (existsSync(candidate)) {
21
+ logger.info(`Found Codex at: ${candidate}`);
22
+ return candidate;
23
+ }
24
+ }
25
+ try {
26
+ const result = execSync("where codex", { encoding: "utf-8" });
27
+ const lines = result.trim().split("\n");
28
+ if (lines.length > 0) {
29
+ logger.info(`Found Codex in PATH: ${lines[0].trim()}`);
30
+ return lines[0].trim();
31
+ }
32
+ } catch {
33
+ }
34
+ logger.warn('Codex not found, using "codex" command');
35
+ return "codex";
36
+ }
37
+ function findProjectRoot() {
38
+ let cwd = process.cwd();
39
+ while (cwd !== dirname(cwd)) {
40
+ if (existsSync(join(cwd, "package.json")) || existsSync(join(cwd, ".git")) || existsSync(join(cwd, "src"))) {
41
+ return cwd;
42
+ }
43
+ cwd = dirname(cwd);
44
+ }
45
+ return process.cwd();
46
+ }
47
+ async function main() {
48
+ logger.info("========================================");
49
+ logger.info(" Codex Viewer Starting");
50
+ logger.info("========================================");
51
+ const projectRoot = findProjectRoot();
52
+ logger.info(`Project root: ${projectRoot}`);
53
+ const codexBinary = await findCodexBinary();
54
+ const aggregator = createAggregator(codexBinary, projectRoot);
55
+ await aggregator.start(PROXY_PORT);
56
+ logger.info("All services started");
57
+ logger.info("Open http://localhost:5173 in browser");
58
+ logger.info("Press Ctrl+C to stop");
59
+ process.on("SIGINT", () => {
60
+ logger.info("Received SIGINT, shutting down...");
61
+ aggregator.stop();
62
+ process.exit(0);
63
+ });
64
+ process.on("SIGTERM", () => {
65
+ logger.info("Received SIGTERM, shutting down...");
66
+ aggregator.stop();
67
+ process.exit(0);
68
+ });
69
+ }
70
+ main().catch((error) => {
71
+ logger.errorWithStack("Fatal error:", error);
72
+ process.exit(1);
73
+ });
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ import { diffLines, diffWords, diffChars } from "diff";
3
+ function generateLineDiff(oldContent, newContent) {
4
+ if (!oldContent && !newContent) {
5
+ return [];
6
+ }
7
+ const oldLines = (oldContent || "").split("\n");
8
+ const newLines = (newContent || "").split("\n");
9
+ const changes = diffLines(oldLines.join("\n"), newLines.join("\n"));
10
+ const result = [];
11
+ let oldLineNum = 1;
12
+ let newLineNum = 1;
13
+ for (const change of changes) {
14
+ const lines = change.value.split("\n").filter((l) => l !== "" || change.value === "\n");
15
+ for (const line of lines) {
16
+ if (change.added) {
17
+ result.push({
18
+ type: "added",
19
+ content: line,
20
+ oldLineNumber: null,
21
+ newLineNumber: newLineNum++
22
+ });
23
+ } else if (change.removed) {
24
+ result.push({
25
+ type: "removed",
26
+ content: line,
27
+ oldLineNumber: oldLineNum++,
28
+ newLineNumber: null
29
+ });
30
+ } else {
31
+ result.push({
32
+ type: "unchanged",
33
+ content: line,
34
+ oldLineNumber: oldLineNum++,
35
+ newLineNumber: newLineNum++
36
+ });
37
+ }
38
+ }
39
+ }
40
+ return result;
41
+ }
42
+ function generateWordDiff(oldContent, newContent) {
43
+ if (!oldContent && !newContent) {
44
+ return [];
45
+ }
46
+ const changes = diffWords(oldContent || "", newContent || "");
47
+ const result = [];
48
+ for (const change of changes) {
49
+ result.push({
50
+ type: change.added ? "added" : change.removed ? "removed" : "unchanged",
51
+ content: change.value
52
+ });
53
+ }
54
+ return result;
55
+ }
56
+ function generateCharDiff(oldContent, newContent) {
57
+ if (!oldContent && !newContent) {
58
+ return [];
59
+ }
60
+ const changes = diffChars(oldContent || "", newContent || "");
61
+ const result = [];
62
+ for (const change of changes) {
63
+ result.push({
64
+ type: change.added ? "added" : change.removed ? "removed" : "unchanged",
65
+ content: change.value
66
+ });
67
+ }
68
+ return result;
69
+ }
70
+ function formatDiffAsText(diff, options = {}) {
71
+ const { showLineNumbers = true, prefixAdded = "+ ", prefixRemoved = "- ", prefixUnchanged = " " } = options;
72
+ return diff.map((line) => {
73
+ let prefix;
74
+ if (line.type === "added") {
75
+ prefix = prefixAdded;
76
+ } else if (line.type === "removed") {
77
+ prefix = prefixRemoved;
78
+ } else {
79
+ prefix = prefixUnchanged;
80
+ }
81
+ const lineNumStr = showLineNumbers ? `${String(line.oldLineNumber || "").padStart(4)} ${String(line.newLineNumber || "").padStart(4)} | ` : "";
82
+ return `${lineNumStr}${prefix}${line.content}`;
83
+ }).join("\n");
84
+ }
85
+ function getDiffStats(diff) {
86
+ const stats = {
87
+ added: 0,
88
+ removed: 0,
89
+ unchanged: 0
90
+ };
91
+ for (const line of diff) {
92
+ if (line.type === "added") {
93
+ stats.added++;
94
+ } else if (line.type === "removed") {
95
+ stats.removed++;
96
+ } else {
97
+ stats.unchanged++;
98
+ }
99
+ }
100
+ stats.total = stats.added + stats.removed + stats.unchanged;
101
+ return stats;
102
+ }
103
+ export {
104
+ formatDiffAsText,
105
+ generateCharDiff,
106
+ generateLineDiff,
107
+ generateWordDiff,
108
+ getDiffStats
109
+ };
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeSync } from "fs";
3
+ import { dirname, join } from "path";
4
+ import { homedir } from "os";
5
+ const DEFAULT_LOG_DIR = join(homedir(), ".codex-viewer");
6
+ class LogManager {
7
+ constructor(logDir = DEFAULT_LOG_DIR) {
8
+ this.logDir = logDir;
9
+ this.ensureLogDir();
10
+ }
11
+ ensureLogDir() {
12
+ if (!existsSync(this.logDir)) {
13
+ mkdirSync(this.logDir, { recursive: true });
14
+ }
15
+ }
16
+ getLogFilePath(projectName) {
17
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
18
+ return join(this.logDir, `${projectName}_${timestamp}.jsonl`);
19
+ }
20
+ appendEntry(logFile, entry) {
21
+ try {
22
+ const dir = dirname(logFile);
23
+ if (!existsSync(dir)) {
24
+ mkdirSync(dir, { recursive: true });
25
+ }
26
+ appendFileSync(logFile, JSON.stringify(entry) + "\n");
27
+ } catch (error) {
28
+ console.error("[LogManager] Failed to append entry:", error.message);
29
+ }
30
+ }
31
+ readLogFile(logFile) {
32
+ if (!existsSync(logFile)) {
33
+ return [];
34
+ }
35
+ try {
36
+ const content = readFileSync(logFile, "utf-8");
37
+ const lines = content.split("\n").filter((l) => l.trim());
38
+ return lines.map((line) => {
39
+ try {
40
+ return JSON.parse(line);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }).filter(Boolean);
45
+ } catch (error) {
46
+ console.error("[LogManager] Failed to read log file:", error.message);
47
+ return [];
48
+ }
49
+ }
50
+ listLogFiles(projectName) {
51
+ try {
52
+ if (!existsSync(this.logDir)) {
53
+ return [];
54
+ }
55
+ const files = readdirSync(this.logDir).filter((f) => f.startsWith(projectName + "_") && f.endsWith(".jsonl")).map((f) => {
56
+ const stats = statSync(join(this.logDir, f));
57
+ return {
58
+ name: f,
59
+ path: join(this.logDir, f),
60
+ size: stats.size,
61
+ modified: stats.mtime
62
+ };
63
+ }).sort((a, b) => b.modified - a.modified);
64
+ return files;
65
+ } catch (error) {
66
+ console.error("[LogManager] Failed to list log files:", error.message);
67
+ return [];
68
+ }
69
+ }
70
+ deleteOldLogs(projectName, keepCount = 10) {
71
+ try {
72
+ const files = this.listLogFiles(projectName);
73
+ const toDelete = files.slice(keepCount);
74
+ for (const file of toDelete) {
75
+ unlinkSync(file.path);
76
+ console.log(`[LogManager] Deleted old log: ${file.name}`);
77
+ }
78
+ } catch (error) {
79
+ console.error("[LogManager] Failed to delete old logs:", error.message);
80
+ }
81
+ }
82
+ getRecentLog(projectName) {
83
+ const files = this.listLogFiles(projectName);
84
+ if (files.length > 0) {
85
+ return files[0];
86
+ }
87
+ return null;
88
+ }
89
+ writeSessionMeta(logFile, meta) {
90
+ this.appendEntry(logFile, {
91
+ type: "session_meta",
92
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
93
+ ...meta
94
+ });
95
+ }
96
+ writeApiRequest(logFile, request) {
97
+ this.appendEntry(logFile, {
98
+ type: "api_request",
99
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
100
+ ...request
101
+ });
102
+ }
103
+ writeApiResponse(logFile, response) {
104
+ this.appendEntry(logFile, {
105
+ type: "api_response",
106
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
107
+ ...response
108
+ });
109
+ }
110
+ writeFileChange(logFile, change) {
111
+ this.appendEntry(logFile, {
112
+ type: "file_change",
113
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
114
+ ...change
115
+ });
116
+ }
117
+ }
118
+ function createLogManager(logDir) {
119
+ return new LogManager(logDir);
120
+ }
121
+ export {
122
+ LogManager,
123
+ createLogManager
124
+ };
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ function parseSSEStream(data) {
3
+ if (!data || typeof data !== "string") {
4
+ return null;
5
+ }
6
+ const lines = data.split("\n");
7
+ const event = {
8
+ type: null,
9
+ data: null,
10
+ id: null,
11
+ retry: null
12
+ };
13
+ for (const line of lines) {
14
+ if (line.startsWith("event:")) {
15
+ event.type = line.slice(6).trim();
16
+ } else if (line.startsWith("data:")) {
17
+ const value = line.slice(5).trim();
18
+ try {
19
+ event.data = JSON.parse(value);
20
+ } catch {
21
+ event.data = value;
22
+ }
23
+ } else if (line.startsWith("id:")) {
24
+ event.id = line.slice(3).trim();
25
+ } else if (line.startsWith("retry:")) {
26
+ event.retry = parseInt(line.slice(7).trim(), 10);
27
+ }
28
+ }
29
+ if (event.type === "null" && !event.data) {
30
+ return null;
31
+ }
32
+ return event;
33
+ }
34
+ function parseSSELines(lines) {
35
+ const events = [];
36
+ for (const line of lines) {
37
+ const event = parseSSEStream(line);
38
+ if (event) {
39
+ events.push(event);
40
+ }
41
+ }
42
+ return events;
43
+ }
44
+ function extractDelta(event) {
45
+ if (!event || !event.data) return null;
46
+ if (event.data.type === "content_block_delta") {
47
+ return event.data.delta;
48
+ }
49
+ return null;
50
+ }
51
+ function extractToolUse(event) {
52
+ if (!event || !event.data) return null;
53
+ if (event.data.type === "tool_use") {
54
+ return event.data;
55
+ }
56
+ return null;
57
+ }
58
+ function extractMessage(event) {
59
+ if (!event || !event.data) return null;
60
+ if (event.data.type === "message") {
61
+ return event.data.message;
62
+ }
63
+ return null;
64
+ }
65
+ export {
66
+ extractDelta,
67
+ extractMessage,
68
+ extractToolUse,
69
+ parseSSELines,
70
+ parseSSEStream
71
+ };