codex-devtools 0.1.10 → 0.2.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.
@@ -2,9 +2,10 @@
2
2
  const electron = require("electron");
3
3
  const fs = require("node:fs");
4
4
  const path = require("node:path");
5
- const CodexServiceContext = require("./chunks/CodexServiceContext-CRkTP14W.cjs");
6
- require("node:os");
5
+ const CodexServiceContext = require("./chunks/CodexServiceContext-BYe2UXME.cjs");
7
6
  require("node:events");
7
+ require("node:os");
8
+ require("node:crypto");
8
9
  require("node:buffer");
9
10
  require("node:readline");
10
11
  const IPC_CHANNELS = {
@@ -12,6 +13,7 @@ const IPC_CHANNELS = {
12
13
  SESSIONS_GET_SESSIONS: "get-sessions",
13
14
  SESSIONS_GET_DETAIL: "get-session-detail",
14
15
  SESSIONS_GET_CHUNKS: "get-session-chunks",
16
+ SESSIONS_GET_STATS: "get-session-stats",
15
17
  SEARCH_SESSIONS: "search-sessions",
16
18
  CONFIG_GET: "config:get",
17
19
  CONFIG_UPDATE: "config:update",
@@ -80,6 +82,7 @@ function registerSessionHandlers(ipcMain) {
80
82
  ipcMain.handle(IPC_CHANNELS.SESSIONS_GET_SESSIONS, handleGetSessions);
81
83
  ipcMain.handle(IPC_CHANNELS.SESSIONS_GET_DETAIL, handleGetSessionDetail);
82
84
  ipcMain.handle(IPC_CHANNELS.SESSIONS_GET_CHUNKS, handleGetSessionChunks);
85
+ ipcMain.handle(IPC_CHANNELS.SESSIONS_GET_STATS, handleGetSessionStats);
83
86
  logger$3.info("Session handlers registered");
84
87
  }
85
88
  function removeSessionHandlers(ipcMain) {
@@ -87,6 +90,7 @@ function removeSessionHandlers(ipcMain) {
87
90
  ipcMain.removeHandler(IPC_CHANNELS.SESSIONS_GET_SESSIONS);
88
91
  ipcMain.removeHandler(IPC_CHANNELS.SESSIONS_GET_DETAIL);
89
92
  ipcMain.removeHandler(IPC_CHANNELS.SESSIONS_GET_CHUNKS);
93
+ ipcMain.removeHandler(IPC_CHANNELS.SESSIONS_GET_STATS);
90
94
  logger$3.info("Session handlers removed");
91
95
  }
92
96
  async function handleGetProjects(_event) {
@@ -121,6 +125,45 @@ async function handleGetSessionChunks(_event, sessionId) {
121
125
  return null;
122
126
  }
123
127
  }
128
+ async function handleGetSessionStats(_event, scope) {
129
+ try {
130
+ return await serviceContext$1.getStats(scope);
131
+ } catch (error) {
132
+ logger$3.error("Error in get-session-stats", error);
133
+ return {
134
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
135
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "local",
136
+ scope: { type: "all" },
137
+ totals: {
138
+ sessions: 0,
139
+ archivedSessions: 0,
140
+ eventCount: 0,
141
+ durationMs: 0,
142
+ estimatedCostUsd: 0,
143
+ totalTokens: 0,
144
+ inputTokens: 0,
145
+ outputTokens: 0,
146
+ cachedTokens: 0,
147
+ reasoningTokens: 0
148
+ },
149
+ daily: [],
150
+ hourly: [],
151
+ topDays: [],
152
+ topHours: [],
153
+ models: [],
154
+ reasoningEfforts: [],
155
+ costCoverage: {
156
+ pricedTokens: 0,
157
+ unpricedTokens: 0,
158
+ unpricedModels: []
159
+ },
160
+ rates: {
161
+ updatedAt: null,
162
+ source: null
163
+ }
164
+ };
165
+ }
166
+ }
124
167
  const logger$2 = CodexServiceContext.createLogger("IPC:utility");
125
168
  let getAppVersion = () => "0.0.0";
126
169
  function initializeUtilityHandlers(options = {}) {
@@ -240,6 +283,7 @@ function disposeServices() {
240
283
  }
241
284
  }
242
285
  electron.app.setName(APP_DISPLAY_NAME);
286
+ process.title = APP_DISPLAY_NAME;
243
287
  void electron.app.whenReady().then(() => {
244
288
  const iconPath = resolveAppIconPath();
245
289
  if (iconPath && process.platform === "darwin" && electron.app.dock) {
@@ -5,10 +5,11 @@ const fastifyStatic = require("@fastify/static");
5
5
  const Fastify = require("fastify");
6
6
  const path = require("node:path");
7
7
  const node_url = require("node:url");
8
- const CodexServiceContext = require("./chunks/CodexServiceContext-CRkTP14W.cjs");
8
+ const CodexServiceContext = require("./chunks/CodexServiceContext-BYe2UXME.cjs");
9
9
  const fs = require("node:fs");
10
- require("node:os");
11
10
  require("node:events");
11
+ require("node:os");
12
+ require("node:crypto");
12
13
  require("node:buffer");
13
14
  require("node:readline");
14
15
  function _interopNamespaceDefault(e) {
@@ -29,7 +30,7 @@ function _interopNamespaceDefault(e) {
29
30
  }
30
31
  const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
31
32
  const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
32
- const logger$6 = CodexServiceContext.createLogger("HTTP:config");
33
+ const logger$7 = CodexServiceContext.createLogger("HTTP:config");
33
34
  const registerConfigRoutes = (app, services) => {
34
35
  app.get("/config", async () => {
35
36
  return services.getConfig();
@@ -38,12 +39,12 @@ const registerConfigRoutes = (app, services) => {
38
39
  try {
39
40
  return services.updateConfig(request.body.key, request.body.value);
40
41
  } catch (error) {
41
- logger$6.error("Error in PUT /config", error);
42
+ logger$7.error("Error in PUT /config", error);
42
43
  return null;
43
44
  }
44
45
  });
45
46
  };
46
- const logger$5 = CodexServiceContext.createLogger("HTTP:events");
47
+ const logger$6 = CodexServiceContext.createLogger("HTTP:events");
47
48
  const KEEPALIVE_INTERVAL_MS = 3e4;
48
49
  const sseClients = /* @__PURE__ */ new Set();
49
50
  const registerEventRoutes = (app) => {
@@ -54,14 +55,14 @@ const registerEventRoutes = (app) => {
54
55
  Connection: "keep-alive"
55
56
  });
56
57
  sseClients.add(reply);
57
- logger$5.info(`SSE client connected (total: ${sseClients.size})`);
58
+ logger$6.info(`SSE client connected (total: ${sseClients.size})`);
58
59
  const timer = setInterval(() => {
59
60
  reply.raw.write(":ping\n\n");
60
61
  }, KEEPALIVE_INTERVAL_MS);
61
62
  request.raw.on("close", () => {
62
63
  clearInterval(timer);
63
64
  sseClients.delete(reply);
64
- logger$5.info(`SSE client disconnected (total: ${sseClients.size})`);
65
+ logger$6.info(`SSE client disconnected (total: ${sseClients.size})`);
65
66
  });
66
67
  await reply;
67
68
  });
@@ -82,7 +83,7 @@ data: ${JSON.stringify(data)}
82
83
  const broadcastFileChangeEvent = (event) => {
83
84
  broadcastEvent("file-change", event);
84
85
  };
85
- const logger$4 = CodexServiceContext.createLogger("HTTP:projects");
86
+ const logger$5 = CodexServiceContext.createLogger("HTTP:projects");
86
87
  function decodeCwd(value) {
87
88
  try {
88
89
  return decodeURIComponent(value);
@@ -95,7 +96,7 @@ const registerProjectRoutes = (app, services) => {
95
96
  try {
96
97
  return await services.getProjects();
97
98
  } catch (error) {
98
- logger$4.error("Error in GET /projects", error);
99
+ logger$5.error("Error in GET /projects", error);
99
100
  return [];
100
101
  }
101
102
  });
@@ -104,19 +105,19 @@ const registerProjectRoutes = (app, services) => {
104
105
  try {
105
106
  return await services.getSessions(cwd);
106
107
  } catch (error) {
107
- logger$4.error(`Error in GET /projects/${cwd}/sessions`, error);
108
+ logger$5.error(`Error in GET /projects/${cwd}/sessions`, error);
108
109
  return [];
109
110
  }
110
111
  });
111
112
  };
112
- const logger$3 = CodexServiceContext.createLogger("HTTP:search");
113
+ const logger$4 = CodexServiceContext.createLogger("HTTP:search");
113
114
  const registerSearchRoutes = (app, services) => {
114
115
  app.get("/search", async (request) => {
115
116
  const query = request.query.q ?? "";
116
117
  try {
117
118
  return await services.searchSessions(query);
118
119
  } catch (error) {
119
- logger$3.error("Error in GET /search", error);
120
+ logger$4.error("Error in GET /search", error);
120
121
  return {
121
122
  query,
122
123
  totalMatches: 0,
@@ -126,13 +127,13 @@ const registerSearchRoutes = (app, services) => {
126
127
  }
127
128
  });
128
129
  };
129
- const logger$2 = CodexServiceContext.createLogger("HTTP:sessions");
130
+ const logger$3 = CodexServiceContext.createLogger("HTTP:sessions");
130
131
  const registerSessionRoutes = (app, services) => {
131
132
  app.get("/sessions/:id", async (request) => {
132
133
  try {
133
134
  return await services.getSessionDetail(request.params.id);
134
135
  } catch (error) {
135
- logger$2.error(`Error in GET /sessions/${request.params.id}`, error);
136
+ logger$3.error(`Error in GET /sessions/${request.params.id}`, error);
136
137
  return null;
137
138
  }
138
139
  });
@@ -140,11 +141,70 @@ const registerSessionRoutes = (app, services) => {
140
141
  try {
141
142
  return await services.getSessionChunks(request.params.id);
142
143
  } catch (error) {
143
- logger$2.error(`Error in GET /sessions/${request.params.id}/chunks`, error);
144
+ logger$3.error(`Error in GET /sessions/${request.params.id}/chunks`, error);
144
145
  return null;
145
146
  }
146
147
  });
147
148
  };
149
+ const logger$2 = CodexServiceContext.createLogger("HTTP:stats");
150
+ function decodeMaybeUri(value) {
151
+ try {
152
+ return decodeURIComponent(value);
153
+ } catch {
154
+ return value;
155
+ }
156
+ }
157
+ function parseStatsScope(scope, cwd) {
158
+ if (scope === "project" && cwd && cwd.trim().length > 0) {
159
+ return {
160
+ type: "project",
161
+ cwd: decodeMaybeUri(cwd)
162
+ };
163
+ }
164
+ return { type: "all" };
165
+ }
166
+ const registerStatsRoutes = (app, services) => {
167
+ app.get("/stats", async (request) => {
168
+ try {
169
+ const scope = parseStatsScope(request.query.scope, request.query.cwd);
170
+ return await services.getStats(scope);
171
+ } catch (error) {
172
+ logger$2.error("Error in GET /stats", error);
173
+ return {
174
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
175
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "local",
176
+ scope: { type: "all" },
177
+ totals: {
178
+ sessions: 0,
179
+ archivedSessions: 0,
180
+ eventCount: 0,
181
+ durationMs: 0,
182
+ estimatedCostUsd: 0,
183
+ totalTokens: 0,
184
+ inputTokens: 0,
185
+ outputTokens: 0,
186
+ cachedTokens: 0,
187
+ reasoningTokens: 0
188
+ },
189
+ daily: [],
190
+ hourly: [],
191
+ topDays: [],
192
+ topHours: [],
193
+ models: [],
194
+ reasoningEfforts: [],
195
+ costCoverage: {
196
+ pricedTokens: 0,
197
+ unpricedTokens: 0,
198
+ unpricedModels: []
199
+ },
200
+ rates: {
201
+ updatedAt: null,
202
+ source: null
203
+ }
204
+ };
205
+ }
206
+ });
207
+ };
148
208
  const readVersionFromPackageJson = () => {
149
209
  const candidatePaths = [
150
210
  path__namespace.resolve(__dirname, "../../package.json"),
@@ -177,6 +237,7 @@ const logger$1 = CodexServiceContext.createLogger("HTTP:routes");
177
237
  const registerHttpRoutes = (app, services) => {
178
238
  registerProjectRoutes(app, services.serviceContext);
179
239
  registerSessionRoutes(app, services.serviceContext);
240
+ registerStatsRoutes(app, services.serviceContext);
180
241
  registerSearchRoutes(app, services.serviceContext);
181
242
  registerConfigRoutes(app, services.serviceContext);
182
243
  registerUtilityRoutes(app, services.getVersion);
@@ -5,6 +5,7 @@ const IPC_CHANNELS = {
5
5
  SESSIONS_GET_SESSIONS: "get-sessions",
6
6
  SESSIONS_GET_DETAIL: "get-session-detail",
7
7
  SESSIONS_GET_CHUNKS: "get-session-chunks",
8
+ SESSIONS_GET_STATS: "get-session-stats",
8
9
  SEARCH_SESSIONS: "search-sessions",
9
10
  CONFIG_GET: "config:get",
10
11
  CONFIG_UPDATE: "config:update",
@@ -18,6 +19,7 @@ function createCodexDevtoolsApi(renderer = electron.ipcRenderer) {
18
19
  getSessions: (projectCwd) => renderer.invoke(IPC_CHANNELS.SESSIONS_GET_SESSIONS, projectCwd),
19
20
  getSessionDetail: (sessionId) => renderer.invoke(IPC_CHANNELS.SESSIONS_GET_DETAIL, sessionId),
20
21
  getSessionChunks: (sessionId) => renderer.invoke(IPC_CHANNELS.SESSIONS_GET_CHUNKS, sessionId),
22
+ getStats: (scope) => renderer.invoke(IPC_CHANNELS.SESSIONS_GET_STATS, scope),
21
23
  searchSessions: (query) => renderer.invoke(IPC_CHANNELS.SEARCH_SESSIONS, query),
22
24
  getConfig: () => renderer.invoke(IPC_CHANNELS.CONFIG_GET),
23
25
  updateConfig: (key, value) => renderer.invoke(IPC_CHANNELS.CONFIG_UPDATE, key, value),