tide-commander 1.78.0 → 1.80.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/__seed_auth__.html +15 -0
- package/dist/assets/{BossLogsModal-BJUaVgOz.js → BossLogsModal-BLPz-zG4.js} +1 -1
- package/dist/assets/{BossSpawnModal-CLzUp9FQ.js → BossSpawnModal-Bq1v2vF7.js} +1 -1
- package/dist/assets/{ControlsModal-BK-nIx6P.js → ControlsModal-C89UyHcA.js} +1 -1
- package/dist/assets/{DockerLogsModal-wW7EV327.js → DockerLogsModal-j2ccIOAn.js} +1 -1
- package/dist/assets/{EmbeddedEditor-B7LC5gF5.js → EmbeddedEditor-DbPvjE6L.js} +1 -1
- package/dist/assets/{GmailOAuthSetup-C_kETU4w.js → GmailOAuthSetup-BpXMS91b.js} +1 -1
- package/dist/assets/{GoogleOAuthSetup-Dn1hZsMs.js → GoogleOAuthSetup-e7A0yZBj.js} +1 -1
- package/dist/assets/{IframeModal-8t96ASFF.js → IframeModal-BWSM0KU4.js} +1 -1
- package/dist/assets/{IntegrationsPanel-C0TjN-6q.js → IntegrationsPanel-C9Zpu_4p.js} +2 -2
- package/dist/assets/{LogViewerModal-J_Wv4RxY.js → LogViewerModal-D_S9YZRw.js} +1 -1
- package/dist/assets/{MonitoringModal-CUy6EbeR.js → MonitoringModal-ClOTO0Uf.js} +1 -1
- package/dist/assets/{PM2LogsModal-CgYX33GL.js → PM2LogsModal-BTtFTV-5.js} +1 -1
- package/dist/assets/{RestoreArchivedAreaModal-ILOa64df.js → RestoreArchivedAreaModal-_4ptM0i8.js} +1 -1
- package/dist/assets/{Scene2DCanvas-B9_c0Cr2.js → Scene2DCanvas-Bk8TmqUL.js} +1 -1
- package/dist/assets/{SceneManager-BuMbzzM8.js → SceneManager-DvTJnHJW.js} +1 -1
- package/dist/assets/{SkillsPanel-Bp0UiQRG.js → SkillsPanel-t1aTNgcy.js} +1 -1
- package/dist/assets/{SpawnModal-DjzhiAMO.js → SpawnModal-C0bzyux7.js} +1 -1
- package/dist/assets/{SubordinateAssignmentModal-CWaAMKp5.js → SubordinateAssignmentModal-CoZ8oWod.js} +1 -1
- package/dist/assets/{TriggerManagerPanel-DpCFDRa5.js → TriggerManagerPanel-DdqYdLrT.js} +1 -1
- package/dist/assets/{WorkflowEditorPanel-D-Vcfxmm.js → WorkflowEditorPanel-hoviKYSD.js} +1 -1
- package/dist/assets/{index-7TaAASV1.js → index-B9l1lyBZ.js} +2 -2
- package/dist/assets/{index-rGRqmeiB.js → index-CRvdO7Kd.js} +1 -1
- package/dist/assets/{index-DyidqysN.js → index-CmD0ROmj.js} +1 -1
- package/dist/assets/{index-BRLyVoUe.js → index-D2pHMTZ5.js} +1 -1
- package/dist/assets/{index-DX-7fZ78.js → index-DDyLKl-p.js} +3 -3
- package/dist/assets/{index-BeUHeagl.js → index-DEhCjM_2.js} +1 -1
- package/dist/assets/{index-D6--gGWa.js → index-DGoXs77Q.js} +1 -1
- package/dist/assets/{index-1yqGGDiA.js → index-DZtAf5eK.js} +1 -1
- package/dist/assets/{index-C2Fyjj0L.css → index-OGMIDcJ3.css} +1 -1
- package/dist/assets/{index-COln_-Fk.js → index-_voSk9UQ.js} +1 -1
- package/dist/assets/main-D1tIQI_9.css +1 -0
- package/dist/assets/{main-COvyQg-T.js → main-D8MIGzmr.js} +70 -70
- package/dist/assets/{web-N_FykYu_.js → web-BrE2NRKk.js} +1 -1
- package/dist/assets/{web-CpCf-DCG.js → web-cmqeYKAz.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/src/packages/server/codex/backend.js +5 -0
- package/dist/src/packages/server/codex/json-event-parser.js +19 -4
- package/dist/src/packages/server/routes/files.js +67 -68
- package/package.json +1 -1
- package/dist/assets/main-Co2mNETL.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c5 as a}from"./main-
|
|
1
|
+
import{c5 as a}from"./main-D8MIGzmr.js";import{ImpactStyle as i,NotificationType as r}from"./index-B9l1lyBZ.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class h extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{h as HapticsWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c5 as s}from"./main-
|
|
1
|
+
import{c5 as s}from"./main-D8MIGzmr.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class l extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{l as LocalNotificationsWeb};
|
package/dist/index.html
CHANGED
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png" />
|
|
23
23
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
|
|
24
24
|
<title>Tide Commander</title>
|
|
25
|
-
<script type="module" crossorigin src="/assets/main-
|
|
25
|
+
<script type="module" crossorigin src="/assets/main-D8MIGzmr.js"></script>
|
|
26
26
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react--Eh9ivFN.js">
|
|
27
27
|
<link rel="modulepreload" crossorigin href="/assets/vendor-three-Chj50gSY.js">
|
|
28
|
-
<link rel="stylesheet" crossorigin href="/assets/main-
|
|
28
|
+
<link rel="stylesheet" crossorigin href="/assets/main-D1tIQI_9.css">
|
|
29
29
|
</head>
|
|
30
30
|
<body>
|
|
31
31
|
<div id="app"></div>
|
|
@@ -63,6 +63,11 @@ export class CodexBackend {
|
|
|
63
63
|
this.parser.setWorkingDirectory(config.workingDir);
|
|
64
64
|
this.pendingStdinPrompt = buildCodexPrompt(config);
|
|
65
65
|
const args = ['exec', '--experimental-json'];
|
|
66
|
+
// Codex renamed [features].collab → [features].multi_agent. Enable the new
|
|
67
|
+
// flag explicitly so subagent orchestration (collab_tool_call items) works
|
|
68
|
+
// without the user needing `[features].collab = true` in ~/.codex/config.toml,
|
|
69
|
+
// which Codex now emits a deprecation error for on every turn.
|
|
70
|
+
args.push('--enable', 'multi_agent');
|
|
66
71
|
const codexConfig = config.codexConfig;
|
|
67
72
|
const fullAuto = codexConfig?.fullAuto !== false;
|
|
68
73
|
if (fullAuto) {
|
|
@@ -85,6 +85,8 @@ function parseItem(item) {
|
|
|
85
85
|
receiver_thread_ids: asStringArray(item.receiver_thread_ids),
|
|
86
86
|
prompt: item.prompt === null ? null : asString(item.prompt),
|
|
87
87
|
agents_states: parseCollabAgentStates(item.agents_states),
|
|
88
|
+
message: asString(item.message),
|
|
89
|
+
error: asString(item.error),
|
|
88
90
|
};
|
|
89
91
|
}
|
|
90
92
|
function parseUsage(usage) {
|
|
@@ -147,6 +149,7 @@ function parseResponsePayload(payload) {
|
|
|
147
149
|
export class CodexJsonEventParser {
|
|
148
150
|
activeToolByItemId = new Map();
|
|
149
151
|
lastAgentMessageText;
|
|
152
|
+
lastErrorText;
|
|
150
153
|
lastModelUsageSnapshot;
|
|
151
154
|
enableFileDiffEnrichment;
|
|
152
155
|
workingDirectory;
|
|
@@ -409,6 +412,11 @@ export class CodexJsonEventParser {
|
|
|
409
412
|
parseItemCompleted(item) {
|
|
410
413
|
if (!item?.type)
|
|
411
414
|
return [];
|
|
415
|
+
if (item.type === 'error') {
|
|
416
|
+
const errorMessage = item.message || item.text || item.error || 'Codex emitted an error';
|
|
417
|
+
this.lastErrorText = errorMessage;
|
|
418
|
+
return [{ type: 'error', errorMessage }];
|
|
419
|
+
}
|
|
412
420
|
if (item.type === 'reasoning' && item.text) {
|
|
413
421
|
return [{ type: 'thinking', text: item.text, isStreaming: false }];
|
|
414
422
|
}
|
|
@@ -536,10 +544,17 @@ export class CodexJsonEventParser {
|
|
|
536
544
|
cacheRead: usage.cached_input_tokens,
|
|
537
545
|
},
|
|
538
546
|
};
|
|
539
|
-
// Include the last agent message as resultText for boss delegation processing
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
547
|
+
// Include the last agent message as resultText for boss delegation processing.
|
|
548
|
+
// If an error item arrived in the same turn, append it so the boss/UI sees the failure.
|
|
549
|
+
if (this.lastAgentMessageText || this.lastErrorText) {
|
|
550
|
+
const parts = [];
|
|
551
|
+
if (this.lastAgentMessageText)
|
|
552
|
+
parts.push(this.lastAgentMessageText);
|
|
553
|
+
if (this.lastErrorText)
|
|
554
|
+
parts.push(`[Error] ${this.lastErrorText}`);
|
|
555
|
+
event.resultText = parts.join('\n\n');
|
|
556
|
+
this.lastAgentMessageText = undefined;
|
|
557
|
+
this.lastErrorText = undefined;
|
|
543
558
|
}
|
|
544
559
|
if (this.lastModelUsageSnapshot && (this.lastModelUsageSnapshot.contextWindow
|
|
545
560
|
|| this.lastModelUsageSnapshot.inputTokens !== undefined
|
|
@@ -16,6 +16,31 @@ if (!fs.existsSync(TEMP_DIR)) {
|
|
|
16
16
|
}
|
|
17
17
|
log.log(` Temp upload directory: ${TEMP_DIR}`);
|
|
18
18
|
const router = Router();
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a path query against an optional baseDir. Absolute paths pass through
|
|
21
|
+
* unchanged; relative paths are resolved via path.resolve(baseDir, rawPath).
|
|
22
|
+
* If no usable baseDir is supplied, the server's own cwd is used so file-modal
|
|
23
|
+
* links like `../../../tmp/foo.md` open even from contexts without an explicit
|
|
24
|
+
* agent cwd (e.g. spotlight, flat view). The client should pass the agent cwd
|
|
25
|
+
* as baseDir whenever it has one, which takes precedence.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveAndValidateFilePath(rawPath, baseDir, fallbackBaseDir = process.cwd()) {
|
|
28
|
+
if (!rawPath) {
|
|
29
|
+
return { ok: false, status: 400, error: 'Missing path parameter' };
|
|
30
|
+
}
|
|
31
|
+
if (path.isAbsolute(rawPath)) {
|
|
32
|
+
return { ok: true, path: rawPath };
|
|
33
|
+
}
|
|
34
|
+
const effectiveBase = baseDir && path.isAbsolute(baseDir) ? baseDir : fallbackBaseDir;
|
|
35
|
+
if (!path.isAbsolute(effectiveBase)) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
status: 400,
|
|
39
|
+
error: 'Cannot resolve relative path: no absolute baseDir and server cwd is not absolute',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return { ok: true, path: path.resolve(effectiveBase, rawPath) };
|
|
43
|
+
}
|
|
19
44
|
// Prevent browser from caching git-related GET responses (status, diff, branch, etc.)
|
|
20
45
|
// Without this, browsers may serve stale cached data — e.g. deleted files still appearing.
|
|
21
46
|
router.use('/git-*path', (_req, res, next) => {
|
|
@@ -26,23 +51,19 @@ router.use('/git-*path', (_req, res, next) => {
|
|
|
26
51
|
// GET /api/files/read - Read file contents
|
|
27
52
|
router.get('/read', async (req, res) => {
|
|
28
53
|
try {
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
31
|
-
res.status(
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
// Security: ensure path is absolute and doesn't contain ..
|
|
35
|
-
if (!path.isAbsolute(filePath)) {
|
|
36
|
-
res.status(400).json({ error: 'Path must be absolute' });
|
|
54
|
+
const resolution = resolveAndValidateFilePath(req.query.path, req.query.baseDir);
|
|
55
|
+
if (!resolution.ok) {
|
|
56
|
+
res.status(resolution.status).json({ error: resolution.error });
|
|
37
57
|
return;
|
|
38
58
|
}
|
|
59
|
+
const filePath = resolution.path;
|
|
39
60
|
if (!fs.existsSync(filePath)) {
|
|
40
|
-
res.status(404).json({ error: 'File not found' });
|
|
61
|
+
res.status(404).json({ error: 'File not found', path: filePath });
|
|
41
62
|
return;
|
|
42
63
|
}
|
|
43
64
|
const stats = fs.statSync(filePath);
|
|
44
65
|
if (stats.isDirectory()) {
|
|
45
|
-
res.status(400).json({ error: 'Path is a directory' });
|
|
66
|
+
res.status(400).json({ error: 'Path is a directory', path: filePath });
|
|
46
67
|
return;
|
|
47
68
|
}
|
|
48
69
|
// Limit file size to 1MB
|
|
@@ -152,15 +173,12 @@ router.get('/resolve', async (req, res) => {
|
|
|
152
173
|
// GET /api/files/exists - Check if a file exists
|
|
153
174
|
router.get('/exists', async (req, res) => {
|
|
154
175
|
try {
|
|
155
|
-
const
|
|
156
|
-
if (!
|
|
157
|
-
res.status(
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
if (!path.isAbsolute(filePath)) {
|
|
161
|
-
res.status(400).json({ error: 'Path must be absolute' });
|
|
176
|
+
const resolution = resolveAndValidateFilePath(req.query.path, req.query.baseDir);
|
|
177
|
+
if (!resolution.ok) {
|
|
178
|
+
res.status(resolution.status).json({ error: resolution.error });
|
|
162
179
|
return;
|
|
163
180
|
}
|
|
181
|
+
const filePath = resolution.path;
|
|
164
182
|
const exists = fs.existsSync(filePath);
|
|
165
183
|
res.json({ exists, path: filePath });
|
|
166
184
|
}
|
|
@@ -172,22 +190,19 @@ router.get('/exists', async (req, res) => {
|
|
|
172
190
|
// GET /api/files/info - Get file info without content
|
|
173
191
|
router.get('/info', async (req, res) => {
|
|
174
192
|
try {
|
|
175
|
-
const
|
|
176
|
-
if (!
|
|
177
|
-
res.status(
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
if (!path.isAbsolute(filePath)) {
|
|
181
|
-
res.status(400).json({ error: 'Path must be absolute' });
|
|
193
|
+
const resolution = resolveAndValidateFilePath(req.query.path, req.query.baseDir);
|
|
194
|
+
if (!resolution.ok) {
|
|
195
|
+
res.status(resolution.status).json({ error: resolution.error });
|
|
182
196
|
return;
|
|
183
197
|
}
|
|
198
|
+
const filePath = resolution.path;
|
|
184
199
|
if (!fs.existsSync(filePath)) {
|
|
185
|
-
res.status(404).json({ error: 'File not found' });
|
|
200
|
+
res.status(404).json({ error: 'File not found', path: filePath });
|
|
186
201
|
return;
|
|
187
202
|
}
|
|
188
203
|
const stats = fs.statSync(filePath);
|
|
189
204
|
if (stats.isDirectory()) {
|
|
190
|
-
res.status(400).json({ error: 'Path is a directory' });
|
|
205
|
+
res.status(400).json({ error: 'Path is a directory', path: filePath });
|
|
191
206
|
return;
|
|
192
207
|
}
|
|
193
208
|
const extension = path.extname(filePath).toLowerCase();
|
|
@@ -208,23 +223,20 @@ router.get('/info', async (req, res) => {
|
|
|
208
223
|
// GET /api/files/binary - Read binary file (for images, PDFs, downloads)
|
|
209
224
|
router.get('/binary', async (req, res) => {
|
|
210
225
|
try {
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
res.status(400).json({ error: 'Missing path parameter' });
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
if (!path.isAbsolute(filePath)) {
|
|
218
|
-
res.status(400).json({ error: 'Path must be absolute' });
|
|
226
|
+
const resolution = resolveAndValidateFilePath(req.query.path, req.query.baseDir);
|
|
227
|
+
if (!resolution.ok) {
|
|
228
|
+
res.status(resolution.status).json({ error: resolution.error });
|
|
219
229
|
return;
|
|
220
230
|
}
|
|
231
|
+
const filePath = resolution.path;
|
|
232
|
+
const download = req.query.download === 'true';
|
|
221
233
|
if (!fs.existsSync(filePath)) {
|
|
222
|
-
res.status(404).json({ error: 'File not found' });
|
|
234
|
+
res.status(404).json({ error: 'File not found', path: filePath });
|
|
223
235
|
return;
|
|
224
236
|
}
|
|
225
237
|
const stats = fs.statSync(filePath);
|
|
226
238
|
if (stats.isDirectory()) {
|
|
227
|
-
res.status(400).json({ error: 'Path is a directory' });
|
|
239
|
+
res.status(400).json({ error: 'Path is a directory', path: filePath });
|
|
228
240
|
return;
|
|
229
241
|
}
|
|
230
242
|
// Limit file size to 50MB for binary files
|
|
@@ -280,18 +292,14 @@ router.get('/binary', async (req, res) => {
|
|
|
280
292
|
// GET /api/files/list - List directory contents
|
|
281
293
|
router.get('/list', async (req, res) => {
|
|
282
294
|
try {
|
|
283
|
-
const
|
|
284
|
-
if (!
|
|
285
|
-
res.status(
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
// Security: ensure path is absolute
|
|
289
|
-
if (!path.isAbsolute(dirPath)) {
|
|
290
|
-
res.status(400).json({ error: 'Path must be absolute' });
|
|
295
|
+
const resolution = resolveAndValidateFilePath(req.query.path, req.query.baseDir);
|
|
296
|
+
if (!resolution.ok) {
|
|
297
|
+
res.status(resolution.status).json({ error: resolution.error });
|
|
291
298
|
return;
|
|
292
299
|
}
|
|
300
|
+
const dirPath = resolution.path;
|
|
293
301
|
if (!fs.existsSync(dirPath)) {
|
|
294
|
-
res.status(404).json({ error: 'Directory not found' });
|
|
302
|
+
res.status(404).json({ error: 'Directory not found', path: dirPath });
|
|
295
303
|
return;
|
|
296
304
|
}
|
|
297
305
|
const stats = fs.statSync(dirPath);
|
|
@@ -420,18 +428,15 @@ function copyPathToDirectory(sourcePath, targetDir) {
|
|
|
420
428
|
// GET /api/files/tree - Get recursive directory tree
|
|
421
429
|
router.get('/tree', async (req, res) => {
|
|
422
430
|
try {
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
res.status(400).json({ error: 'Missing path parameter' });
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
if (!path.isAbsolute(dirPath)) {
|
|
430
|
-
res.status(400).json({ error: 'Path must be absolute' });
|
|
431
|
+
const resolution = resolveAndValidateFilePath(req.query.path, req.query.baseDir);
|
|
432
|
+
if (!resolution.ok) {
|
|
433
|
+
res.status(resolution.status).json({ error: resolution.error });
|
|
431
434
|
return;
|
|
432
435
|
}
|
|
436
|
+
const dirPath = resolution.path;
|
|
437
|
+
const maxDepth = parseInt(req.query.depth) || 5;
|
|
433
438
|
if (!fs.existsSync(dirPath)) {
|
|
434
|
-
res.status(404).json({ error: 'Directory not found' });
|
|
439
|
+
res.status(404).json({ error: 'Directory not found', path: dirPath });
|
|
435
440
|
return;
|
|
436
441
|
}
|
|
437
442
|
const stats = fs.statSync(dirPath);
|
|
@@ -1043,15 +1048,12 @@ router.post('/git-discard', async (req, res) => {
|
|
|
1043
1048
|
// GET /api/files/git-original - Get original file content from git HEAD
|
|
1044
1049
|
router.get('/git-original', async (req, res) => {
|
|
1045
1050
|
try {
|
|
1046
|
-
const
|
|
1047
|
-
if (!
|
|
1048
|
-
res.status(
|
|
1049
|
-
return;
|
|
1050
|
-
}
|
|
1051
|
-
if (!path.isAbsolute(filePath)) {
|
|
1052
|
-
res.status(400).json({ error: 'Path must be absolute' });
|
|
1051
|
+
const resolution = resolveAndValidateFilePath(req.query.path, req.query.baseDir);
|
|
1052
|
+
if (!resolution.ok) {
|
|
1053
|
+
res.status(resolution.status).json({ error: resolution.error });
|
|
1053
1054
|
return;
|
|
1054
1055
|
}
|
|
1056
|
+
const filePath = resolution.path;
|
|
1055
1057
|
// Find git root
|
|
1056
1058
|
let gitRoot;
|
|
1057
1059
|
try {
|
|
@@ -1105,15 +1107,12 @@ router.get('/git-original', async (req, res) => {
|
|
|
1105
1107
|
// GET /api/files/git-diff - Get unified diff for a file
|
|
1106
1108
|
router.get('/git-diff', async (req, res) => {
|
|
1107
1109
|
try {
|
|
1108
|
-
const
|
|
1109
|
-
if (!
|
|
1110
|
-
res.status(
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
if (!path.isAbsolute(filePath)) {
|
|
1114
|
-
res.status(400).json({ error: 'Path must be absolute' });
|
|
1110
|
+
const resolution = resolveAndValidateFilePath(req.query.path, req.query.baseDir);
|
|
1111
|
+
if (!resolution.ok) {
|
|
1112
|
+
res.status(resolution.status).json({ error: resolution.error });
|
|
1115
1113
|
return;
|
|
1116
1114
|
}
|
|
1115
|
+
const filePath = resolution.path;
|
|
1117
1116
|
// Find git root
|
|
1118
1117
|
let gitRoot;
|
|
1119
1118
|
try {
|