codex-lens 0.1.19 → 0.1.21

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.
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Codex Lens</title>
7
- <script type="module" crossorigin src="./assets/main-CEqXetbU.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/main-7-y-Utze.css">
7
+ <script type="module" crossorigin src="./assets/main-Dv8f8BcE.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/main-CYNmzqDG.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-lens",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "A visualization tool for Codex that monitors API requests and file system changes",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/aggregator.js CHANGED
@@ -9,7 +9,7 @@ import { createLogger } from './lib/logger.js';
9
9
  import { spawnCodex, writeToPty, resizePty, killPty, onPtyData, onPtyExit, getPtyState, getOutputBuffer } from './pty-manager.js';
10
10
  import path from 'path';
11
11
  import { fileURLToPath } from 'url';
12
- import { readFileSync, existsSync } from 'fs';
12
+ import { readFileSync, existsSync, writeFileSync } from 'fs';
13
13
 
14
14
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
15
 
@@ -107,6 +107,25 @@ class Aggregator {
107
107
  res.json({ success: true });
108
108
  });
109
109
 
110
+ app.post('/api/save-file', (req, res) => {
111
+ const { path: filePath, content } = req.body;
112
+ if (!filePath) {
113
+ return res.status(400).json({ error: 'Path is required' });
114
+ }
115
+ if (content === undefined) {
116
+ return res.status(400).json({ error: 'Content is required' });
117
+ }
118
+
119
+ try {
120
+ writeFileSync(filePath, content, 'utf-8');
121
+ logger.info(`File saved: ${filePath}`);
122
+ res.json({ success: true, path: filePath });
123
+ } catch (error) {
124
+ logger.error(`Failed to save file: ${filePath} - ${error.message}`);
125
+ res.status(500).json({ error: error.message });
126
+ }
127
+ });
128
+
110
129
  await new Promise((resolve) => {
111
130
  this.httpServer = createServer(app);
112
131
 
@@ -12,6 +12,7 @@ export function App() {
12
12
  const [latestVersion, setLatestVersion] = useState(null);
13
13
  const [hasUpdate, setHasUpdate] = useState(false);
14
14
  const [projectName, setProjectName] = useState('');
15
+ const [saving, setSaving] = useState(false);
15
16
  const wsRef = useRef(null);
16
17
 
17
18
  useEffect(() => {
@@ -20,9 +21,18 @@ export function App() {
20
21
  document.addEventListener('click', handleDocumentClick);
21
22
  document.addEventListener('keydown', handleKeyDown);
22
23
 
24
+ const preventBrowserSave = (e) => {
25
+ if (e.ctrlKey && e.key === 's') {
26
+ e.preventDefault();
27
+ e.stopPropagation();
28
+ }
29
+ };
30
+ window.addEventListener('keydown', preventBrowserSave, true);
31
+
23
32
  return () => {
24
33
  document.removeEventListener('click', handleDocumentClick);
25
34
  document.removeEventListener('keydown', handleKeyDown);
35
+ window.removeEventListener('keydown', preventBrowserSave, true);
26
36
  if (wsRef.current) {
27
37
  wsRef.current.close();
28
38
  }
@@ -54,6 +64,14 @@ export function App() {
54
64
  }
55
65
 
56
66
  function handleKeyDown(e) {
67
+ if (e.ctrlKey && e.key === 's') {
68
+ e.preventDefault();
69
+ if (activeTabId) {
70
+ saveCurrentFile();
71
+ }
72
+ return;
73
+ }
74
+
57
75
  if (!activeTabId) return;
58
76
 
59
77
  if (e.ctrlKey && e.key === 'w') {
@@ -69,6 +87,44 @@ export function App() {
69
87
  }
70
88
  }
71
89
 
90
+ async function saveCurrentFile() {
91
+ const activeTab = tabs.find(t => t.id === activeTabId);
92
+ if (!activeTab || activeTab.isDiff) return;
93
+
94
+ setSaving(true);
95
+ try {
96
+ const port = window.location.port === '5173' ? '5174' : window.location.port;
97
+ const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
98
+ const response = await fetch(`${protocol}//${window.location.hostname}:${port}/api/save-file`, {
99
+ method: 'POST',
100
+ headers: { 'Content-Type': 'application/json' },
101
+ body: JSON.stringify({ path: activeTab.path, content: activeTab.content })
102
+ });
103
+
104
+ if (response.ok) {
105
+ setTabs(prevTabs => prevTabs.map(t =>
106
+ t.id === activeTabId ? { ...t, isModified: false } : t
107
+ ));
108
+ } else {
109
+ const error = await response.json();
110
+ console.error('Failed to save file:', error);
111
+ }
112
+ } catch (error) {
113
+ console.error('Failed to save file:', error);
114
+ } finally {
115
+ setSaving(false);
116
+ }
117
+ }
118
+
119
+ function handleContentChange(newContent) {
120
+ setTabs(prevTabs => prevTabs.map(t => {
121
+ if (t.id === activeTabId) {
122
+ return { ...t, content: newContent, isModified: true };
123
+ }
124
+ return t;
125
+ }));
126
+ }
127
+
72
128
  function connectWebSocket() {
73
129
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
74
130
  const host = window.location.hostname;
@@ -282,6 +338,8 @@ export function App() {
282
338
  diff={activeTab.diff}
283
339
  isDiff={activeTab.isDiff}
284
340
  filePath={activeTab.path}
341
+ onChange={handleContentChange}
342
+ isModified={activeTab.isModified}
285
343
  />
286
344
  )}
287
345
  </div>
@@ -325,7 +383,10 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
325
383
  onClick={() => onTabClick(tab.id)}
326
384
  onContextMenu={(e) => onContextMenu(e, tab.id)}
327
385
  >
328
- <span className="tab-name">{tab.name}</span>
386
+ <span className="tab-name">
387
+ {tab.isModified && <span className="tab-modified">*</span>}
388
+ {tab.name}
389
+ </span>
329
390
  <button
330
391
  className="tab-close"
331
392
  onClick={(e) => {
@@ -1,6 +1,6 @@
1
- import React, { useMemo, useRef } from 'react';
1
+ import React, { useMemo, useRef, useState, useCallback } from 'react';
2
2
  import CodeMirror from '@uiw/react-codemirror';
3
- import { EditorView, Decoration, ViewPlugin, ViewUpdate } from '@codemirror/view';
3
+ import { EditorView, Decoration, ViewPlugin, keymap } from '@codemirror/view';
4
4
  import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
5
5
  import { tags as t } from '@lezer/highlight';
6
6
  import { RangeSetBuilder } from '@codemirror/state';
@@ -215,8 +215,11 @@ const darkHighlightStyle = HighlightStyle.define([
215
215
 
216
216
  const syntaxTheme = syntaxHighlighting(darkHighlightStyle);
217
217
 
218
- export function CodeViewer({ content, diff, isDiff, filePath }) {
218
+ export function CodeViewer({ content, diff, isDiff, filePath, onChange, isModified }) {
219
219
  const editorRef = useRef(null);
220
+ const [localContent, setLocalContent] = useState(content || '');
221
+
222
+ const isEditable = !isDiff;
220
223
 
221
224
  const extensions = useMemo(() => {
222
225
  const exts = [
@@ -254,14 +257,27 @@ export function CodeViewer({ content, diff, isDiff, filePath }) {
254
257
  return content || '';
255
258
  }, [content, diff, isDiff]);
256
259
 
260
+ const handleChange = useCallback((value) => {
261
+ setLocalContent(value);
262
+ if (onChange) {
263
+ onChange(value);
264
+ }
265
+ }, [onChange]);
266
+
267
+ React.useEffect(() => {
268
+ if (!isDiff) {
269
+ setLocalContent(content || '');
270
+ }
271
+ }, [content, isDiff]);
272
+
257
273
  return (
258
274
  <div className="code-viewer-codemirror">
259
275
  <CodeMirror
260
- value={code}
276
+ value={isDiff ? code : localContent}
261
277
  height="100%"
262
278
  theme={darkTheme}
263
279
  extensions={extensions}
264
- editable={false}
280
+ editable={isEditable}
265
281
  basicSetup={{
266
282
  lineNumbers: true,
267
283
  foldGutter: false,
@@ -269,6 +285,7 @@ export function CodeViewer({ content, diff, isDiff, filePath }) {
269
285
  highlightSelectionMatches: false,
270
286
  bracketMatching: true,
271
287
  }}
288
+ onChange={isEditable ? handleChange : undefined}
272
289
  onCreateEditor={(view) => {
273
290
  editorRef.current = view;
274
291
  }}
package/src/global.css CHANGED
@@ -357,6 +357,12 @@ body {
357
357
  white-space: nowrap;
358
358
  }
359
359
 
360
+ .tab-modified {
361
+ color: var(--accent-color);
362
+ font-weight: bold;
363
+ margin-right: 2px;
364
+ }
365
+
360
366
  .tab-close {
361
367
  background: none;
362
368
  border: none;