codex-overleaf-link 1.1.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.
- package/LICENSE +21 -0
- package/README.md +457 -0
- package/bin/codex-overleaf-link.mjs +223 -0
- package/extension/src/shared/agentTranscript.js +1175 -0
- package/extension/src/shared/auditRecords.js +568 -0
- package/extension/src/shared/compatibility.js +372 -0
- package/extension/src/shared/compileAdapter.js +176 -0
- package/extension/src/shared/governanceRules.js +252 -0
- package/extension/src/shared/i18n.js +565 -0
- package/extension/src/shared/models.js +106 -0
- package/extension/src/shared/otText.js +505 -0
- package/extension/src/shared/projectFiles.js +180 -0
- package/extension/src/shared/reviewing.js +99 -0
- package/extension/src/shared/sensitiveScan.js +116 -0
- package/extension/src/shared/sessionState.js +1084 -0
- package/extension/src/shared/staleGuard.js +150 -0
- package/extension/src/shared/storageDb.js +986 -0
- package/extension/src/shared/storageKeys.js +29 -0
- package/extension/src/shared/storageMigration.js +168 -0
- package/extension/src/shared/summary.js +248 -0
- package/extension/src/shared/undoOperations.js +369 -0
- package/native-host/src/codexArgs.js +43 -0
- package/native-host/src/codexHome.js +538 -0
- package/native-host/src/codexModels.js +247 -0
- package/native-host/src/codexPrompt.js +192 -0
- package/native-host/src/codexPromptAssembly.js +411 -0
- package/native-host/src/codexSessionRunner.js +1247 -0
- package/native-host/src/commandApproval.js +914 -0
- package/native-host/src/debugLog.js +78 -0
- package/native-host/src/diffEngine.js +247 -0
- package/native-host/src/index.js +132 -0
- package/native-host/src/launcher.js +81 -0
- package/native-host/src/localSkills.js +476 -0
- package/native-host/src/manifest.js +226 -0
- package/native-host/src/mirrorSensitiveScan.js +119 -0
- package/native-host/src/mirrorWorkspace.js +1019 -0
- package/native-host/src/nativeDoctor.js +826 -0
- package/native-host/src/nativeEnvironment.js +315 -0
- package/native-host/src/nativeHostPlatform.js +112 -0
- package/native-host/src/nativeMessaging.js +60 -0
- package/native-host/src/nativeQuotas.js +294 -0
- package/native-host/src/nativeResponseBudget.js +194 -0
- package/native-host/src/runtimeInstaller.js +357 -0
- package/native-host/src/taskRunner.js +3 -0
- package/native-host/src/taskRunnerRuntime.js +1083 -0
- package/native-host/src/textPatch.js +287 -0
- package/package.json +40 -0
- package/scripts/codex-json-agent.mjs +269 -0
- package/scripts/install-native-host.mjs +255 -0
- package/scripts/npm-package-files-v1.1.1.txt +52 -0
- package/scripts/uninstall-native-host.mjs +298 -0
- package/scripts/verify-npm-package.mjs +296 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const {
|
|
6
|
+
getHomeDir,
|
|
7
|
+
getNativeHostPlatform
|
|
8
|
+
} = require('./nativeHostPlatform');
|
|
9
|
+
const {
|
|
10
|
+
getCodexOverleafSkillsRoot,
|
|
11
|
+
materializeProjectSkillsAsCodexSkills
|
|
12
|
+
} = require('./localSkills');
|
|
13
|
+
|
|
14
|
+
const COPIED_USER_CODEX_FILES = [
|
|
15
|
+
'auth.json',
|
|
16
|
+
'config.toml',
|
|
17
|
+
'AGENTS.md',
|
|
18
|
+
'installation_id',
|
|
19
|
+
'models_cache.json',
|
|
20
|
+
'version.json'
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const LINKED_USER_CODEX_DIRS = [
|
|
24
|
+
'rules',
|
|
25
|
+
'memories',
|
|
26
|
+
'vendor_imports'
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const LOCAL_SKILL_USER_CODEX_DIRS = [
|
|
30
|
+
'plugins',
|
|
31
|
+
'superpowers'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const LOCAL_SKILL_PLUGIN_HOME_ENTRIES = [
|
|
35
|
+
'plugins',
|
|
36
|
+
'superpowers',
|
|
37
|
+
path.join('.tmp', 'plugins'),
|
|
38
|
+
path.join('.tmp', 'plugins.sha'),
|
|
39
|
+
path.join('.tmp', 'app-server-remote-plugin-sync-v1'),
|
|
40
|
+
path.join('cache', 'codex_apps_tools')
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const LOCAL_SKILL_CONFIG_SECTIONS = [
|
|
44
|
+
'skills',
|
|
45
|
+
'plugins',
|
|
46
|
+
'mcp_servers',
|
|
47
|
+
'marketplaces'
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const HISTORY_DIRS = [
|
|
51
|
+
'sessions',
|
|
52
|
+
'archived_sessions',
|
|
53
|
+
'history'
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
function getUserCodexHome(env = process.env, options = {}) {
|
|
57
|
+
const home = getHomeDir({ ...options, env });
|
|
58
|
+
return path.resolve(env.CODEX_OVERLEAF_USER_CODEX_HOME || env.CODEX_HOME || path.join(home, '.codex'));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getPluginCodexHome(env = process.env, options = {}) {
|
|
62
|
+
const home = getHomeDir({ ...options, env });
|
|
63
|
+
return path.resolve(env.CODEX_OVERLEAF_CODEX_HOME || path.join(home, '.codex-overleaf', 'codex-home'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function preparePluginCodexHome(env = process.env, options = {}) {
|
|
67
|
+
const userHome = getUserCodexHome(env, options);
|
|
68
|
+
const pluginHome = getPluginCodexHome(env, options);
|
|
69
|
+
const loadCodexLocalSkills = options.loadCodexLocalSkills !== false;
|
|
70
|
+
const loadCodexOverleafSkills = options.loadCodexOverleafSkills !== false;
|
|
71
|
+
const installCodexOverleafSkillsTarget = options.installCodexOverleafSkillsTarget === true;
|
|
72
|
+
|
|
73
|
+
fs.mkdirSync(pluginHome, { recursive: true });
|
|
74
|
+
chmodIfPossible(pluginHome, 0o700);
|
|
75
|
+
if (samePath(userHome, pluginHome)) {
|
|
76
|
+
return { userHome, pluginHome, copied: [], linked: [], skippedLinks: [] };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const copied = [];
|
|
80
|
+
const linked = [];
|
|
81
|
+
const skippedLinks = [];
|
|
82
|
+
for (const fileName of COPIED_USER_CODEX_FILES) {
|
|
83
|
+
const source = path.join(userHome, fileName);
|
|
84
|
+
const target = path.join(pluginHome, fileName);
|
|
85
|
+
if (!isRegularFile(source)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
89
|
+
copyUserCodexFile(source, target, fileName, { loadCodexLocalSkills });
|
|
90
|
+
if (fileName === 'auth.json') {
|
|
91
|
+
chmodIfPossible(target, 0o600);
|
|
92
|
+
}
|
|
93
|
+
copied.push(fileName);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!loadCodexLocalSkills) {
|
|
97
|
+
for (const entryName of LOCAL_SKILL_PLUGIN_HOME_ENTRIES) {
|
|
98
|
+
removePluginHomeEntry(pluginHome, entryName, skippedLinks);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const userDirsToLink = loadCodexLocalSkills
|
|
103
|
+
? [...LINKED_USER_CODEX_DIRS, ...LOCAL_SKILL_USER_CODEX_DIRS]
|
|
104
|
+
: LINKED_USER_CODEX_DIRS;
|
|
105
|
+
for (const dirName of userDirsToLink) {
|
|
106
|
+
const source = path.join(userHome, dirName);
|
|
107
|
+
const target = path.join(pluginHome, dirName);
|
|
108
|
+
if (!isDirectory(source)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const linkResult = ensureSymlink(source, target, options);
|
|
112
|
+
if (!linkResult.ok) {
|
|
113
|
+
skippedLinks.push({
|
|
114
|
+
name: dirName,
|
|
115
|
+
reason: linkResult.reason
|
|
116
|
+
});
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
linked.push(dirName);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const skillsResult = composePluginSkillsDirectory({
|
|
123
|
+
userHome,
|
|
124
|
+
pluginHome,
|
|
125
|
+
env,
|
|
126
|
+
options,
|
|
127
|
+
loadCodexLocalSkills,
|
|
128
|
+
loadCodexOverleafSkills,
|
|
129
|
+
installCodexOverleafSkillsTarget,
|
|
130
|
+
projectLocalSkills: options.projectLocalSkills || null
|
|
131
|
+
});
|
|
132
|
+
if (skillsResult.linked) {
|
|
133
|
+
linked.push('skills');
|
|
134
|
+
}
|
|
135
|
+
skippedLinks.push(...skillsResult.skippedLinks);
|
|
136
|
+
|
|
137
|
+
return { userHome, pluginHome, copied, linked, skippedLinks };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function copyUserCodexFile(source, target, fileName, options = {}) {
|
|
141
|
+
if (fileName !== 'config.toml' || options.loadCodexLocalSkills !== false) {
|
|
142
|
+
fs.copyFileSync(source, target);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const content = fs.readFileSync(source, 'utf8');
|
|
146
|
+
fs.writeFileSync(target, sanitizeCodexConfigForLocalSkillIsolation(content), 'utf8');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function sanitizeCodexConfigForLocalSkillIsolation(content) {
|
|
150
|
+
const output = [];
|
|
151
|
+
let skippedSection = false;
|
|
152
|
+
|
|
153
|
+
for (const line of String(content || '').split(/\r?\n/)) {
|
|
154
|
+
const sectionName = parseTomlSectionName(line);
|
|
155
|
+
if (sectionName) {
|
|
156
|
+
skippedSection = isLocalSkillConfigSection(sectionName);
|
|
157
|
+
if (!skippedSection) {
|
|
158
|
+
output.push(line);
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (skippedSection || /^\s*notify\s*=/.test(line)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
output.push(line);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return `${output.join('\n').replace(/\n{3,}/g, '\n\n').trim()}\n`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function parseTomlSectionName(line) {
|
|
172
|
+
const match = String(line || '').match(/^\s*\[{1,2}\s*([^\]]+?)\s*\]{1,2}\s*(?:#.*)?$/);
|
|
173
|
+
return match ? match[1].trim() : '';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isLocalSkillConfigSection(sectionName) {
|
|
177
|
+
return LOCAL_SKILL_CONFIG_SECTIONS.some(prefix => sectionName === prefix || sectionName.startsWith(`${prefix}.`));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function removePluginHomeEntry(pluginHome, entryName, skippedLinks) {
|
|
181
|
+
const target = path.join(pluginHome, entryName);
|
|
182
|
+
if (!isSafePluginHomePath(target, pluginHome)) {
|
|
183
|
+
skippedLinks.push({
|
|
184
|
+
name: entryName,
|
|
185
|
+
reason: 'unsafe_target'
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function buildCodexHomeEnv(env = process.env, options = {}) {
|
|
193
|
+
const prepared = preparePluginCodexHome(env, options);
|
|
194
|
+
return {
|
|
195
|
+
...env,
|
|
196
|
+
CODEX_HOME: prepared.pluginHome,
|
|
197
|
+
CODEX_OVERLEAF_CODEX_HOME: prepared.pluginHome,
|
|
198
|
+
CODEX_OVERLEAF_USER_CODEX_HOME: prepared.userHome
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function composePluginSkillsDirectory({
|
|
203
|
+
userHome,
|
|
204
|
+
pluginHome,
|
|
205
|
+
env,
|
|
206
|
+
options = {},
|
|
207
|
+
loadCodexLocalSkills = true,
|
|
208
|
+
loadCodexOverleafSkills = true,
|
|
209
|
+
installCodexOverleafSkillsTarget = false,
|
|
210
|
+
projectLocalSkills = null
|
|
211
|
+
} = {}) {
|
|
212
|
+
const targetRoot = path.join(pluginHome, 'skills');
|
|
213
|
+
if (!isSafePluginHomePath(targetRoot, pluginHome)) {
|
|
214
|
+
return { linked: false, skippedLinks: [{ name: 'skills', reason: 'unsafe_target' }] };
|
|
215
|
+
}
|
|
216
|
+
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
217
|
+
|
|
218
|
+
if (installCodexOverleafSkillsTarget) {
|
|
219
|
+
const overleafSkillsRoot = getCodexOverleafSkillsRoot({ env });
|
|
220
|
+
fs.mkdirSync(overleafSkillsRoot, { recursive: true });
|
|
221
|
+
const linkResult = ensureSymlink(overleafSkillsRoot, targetRoot, options);
|
|
222
|
+
return linkResult.ok
|
|
223
|
+
? { linked: true, skippedLinks: [] }
|
|
224
|
+
: { linked: false, skippedLinks: [{ name: 'skills', reason: linkResult.reason }] };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const sources = [];
|
|
228
|
+
if (loadCodexLocalSkills) {
|
|
229
|
+
sources.push({
|
|
230
|
+
name: 'codex-local',
|
|
231
|
+
root: path.join(userHome, 'skills'),
|
|
232
|
+
replaceExisting: false
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (loadCodexOverleafSkills) {
|
|
236
|
+
sources.push({
|
|
237
|
+
name: 'codex-overleaf',
|
|
238
|
+
root: getCodexOverleafSkillsRoot({ env }),
|
|
239
|
+
replaceExisting: true
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let linked = false;
|
|
244
|
+
const skippedLinks = [];
|
|
245
|
+
for (const source of sources) {
|
|
246
|
+
if (!isDirectory(source.root)) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
for (const entry of fs.readdirSync(source.root, { withFileTypes: true })) {
|
|
250
|
+
if (!entry.isDirectory() || !isSafeSkillEntryName(entry.name)) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const sourcePath = path.join(source.root, entry.name);
|
|
254
|
+
const targetPath = path.join(targetRoot, entry.name);
|
|
255
|
+
if (!isSafePluginHomePath(targetPath, pluginHome)) {
|
|
256
|
+
skippedLinks.push({ name: `skills/${entry.name}`, reason: 'unsafe_target' });
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (source.replaceExisting) {
|
|
260
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
261
|
+
}
|
|
262
|
+
const linkResult = ensureSymlink(sourcePath, targetPath, options);
|
|
263
|
+
if (!linkResult.ok) {
|
|
264
|
+
skippedLinks.push({
|
|
265
|
+
name: `skills/${entry.name}`,
|
|
266
|
+
reason: linkResult.reason
|
|
267
|
+
});
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
linked = true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (projectLocalSkills?.projectId) {
|
|
275
|
+
try {
|
|
276
|
+
const materialized = materializeProjectSkillsAsCodexSkills({
|
|
277
|
+
projectId: projectLocalSkills.projectId,
|
|
278
|
+
rootDir: projectLocalSkills.rootDir,
|
|
279
|
+
projectRoot: projectLocalSkills.projectRoot,
|
|
280
|
+
targetRoot
|
|
281
|
+
});
|
|
282
|
+
if (materialized.installed.length) {
|
|
283
|
+
linked = true;
|
|
284
|
+
}
|
|
285
|
+
for (const item of materialized.skipped) {
|
|
286
|
+
skippedLinks.push({
|
|
287
|
+
name: `skills/${item.id || 'project-local'}`,
|
|
288
|
+
reason: item.reason || 'project_skill_skipped'
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
} catch (error) {
|
|
292
|
+
skippedLinks.push({
|
|
293
|
+
name: 'skills/project-local',
|
|
294
|
+
reason: error.message || 'project_skill_materialize_failed'
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!linked && !skippedLinks.length) {
|
|
300
|
+
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
301
|
+
}
|
|
302
|
+
return { linked, skippedLinks };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function clearPluginCodexHistory(optionsOrEnv = {}, maybeEnv = null) {
|
|
306
|
+
const { options, env } = normalizeClearHistoryArgs(optionsOrEnv, maybeEnv);
|
|
307
|
+
const pluginHome = getPluginCodexHome(env);
|
|
308
|
+
|
|
309
|
+
if (options.threadId) {
|
|
310
|
+
return clearPluginCodexThreadHistory(pluginHome, options.threadId);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (options.scope !== 'all') {
|
|
314
|
+
return {
|
|
315
|
+
pluginHome,
|
|
316
|
+
removed: [],
|
|
317
|
+
scope: 'thread',
|
|
318
|
+
skipped: true,
|
|
319
|
+
reason: 'missing_thread_id'
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const removed = [];
|
|
324
|
+
|
|
325
|
+
for (const dirName of HISTORY_DIRS) {
|
|
326
|
+
const target = path.join(pluginHome, dirName);
|
|
327
|
+
if (!isInsideDirectory(target, pluginHome) || !fs.existsSync(target)) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
331
|
+
removed.push(dirName);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
pluginHome,
|
|
336
|
+
removed,
|
|
337
|
+
scope: 'all'
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function normalizeClearHistoryArgs(optionsOrEnv, maybeEnv) {
|
|
342
|
+
if (maybeEnv) {
|
|
343
|
+
return {
|
|
344
|
+
options: normalizeClearHistoryOptions(optionsOrEnv),
|
|
345
|
+
env: maybeEnv
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const first = optionsOrEnv || {};
|
|
350
|
+
const looksLikeEnv = hasAnyOwn(first, [
|
|
351
|
+
'HOME',
|
|
352
|
+
'CODEX_HOME',
|
|
353
|
+
'CODEX_OVERLEAF_CODEX_HOME',
|
|
354
|
+
'CODEX_OVERLEAF_USER_CODEX_HOME'
|
|
355
|
+
]) && !hasAnyOwn(first, ['scope', 'threadId', 'sessionId']);
|
|
356
|
+
|
|
357
|
+
if (looksLikeEnv) {
|
|
358
|
+
return {
|
|
359
|
+
options: { scope: 'all' },
|
|
360
|
+
env: first
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
options: normalizeClearHistoryOptions(first),
|
|
366
|
+
env: process.env
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function normalizeClearHistoryOptions(options = {}) {
|
|
371
|
+
return {
|
|
372
|
+
...options,
|
|
373
|
+
threadId: String(options.threadId || '').trim(),
|
|
374
|
+
scope: options.scope || (options.threadId ? 'thread' : '')
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function clearPluginCodexThreadHistory(pluginHome, threadId) {
|
|
379
|
+
const removed = [];
|
|
380
|
+
for (const dirName of HISTORY_DIRS) {
|
|
381
|
+
const root = path.join(pluginHome, dirName);
|
|
382
|
+
if (!isInsideDirectory(root, pluginHome) || !fs.existsSync(root)) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
for (const filePath of listRegularFiles(root)) {
|
|
386
|
+
if (!fileContainsThreadId(filePath, threadId)) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
fs.rmSync(filePath, { force: true });
|
|
390
|
+
removed.push(toForwardSlashPath(path.relative(pluginHome, filePath)));
|
|
391
|
+
removeEmptyParents(path.dirname(filePath), root);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
pluginHome,
|
|
396
|
+
removed,
|
|
397
|
+
scope: 'thread',
|
|
398
|
+
threadId
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function listRegularFiles(root) {
|
|
403
|
+
const files = [];
|
|
404
|
+
const stack = [root];
|
|
405
|
+
while (stack.length) {
|
|
406
|
+
const current = stack.pop();
|
|
407
|
+
let entries;
|
|
408
|
+
try {
|
|
409
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
410
|
+
} catch {
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
for (const entry of entries) {
|
|
414
|
+
const child = path.join(current, entry.name);
|
|
415
|
+
if (entry.isDirectory()) {
|
|
416
|
+
stack.push(child);
|
|
417
|
+
} else if (entry.isFile()) {
|
|
418
|
+
files.push(child);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return files;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function fileContainsThreadId(filePath, threadId) {
|
|
426
|
+
try {
|
|
427
|
+
const stat = fs.statSync(filePath);
|
|
428
|
+
if (!stat.isFile() || stat.size > 5 * 1024 * 1024) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
return fs.readFileSync(filePath, 'utf8').includes(JSON.stringify(threadId));
|
|
432
|
+
} catch {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function removeEmptyParents(startDir, stopDir) {
|
|
438
|
+
let current = path.resolve(startDir);
|
|
439
|
+
const stop = path.resolve(stopDir);
|
|
440
|
+
while (current && current !== stop && isInsideDirectory(current, stop)) {
|
|
441
|
+
try {
|
|
442
|
+
fs.rmdirSync(current);
|
|
443
|
+
} catch {
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
current = path.dirname(current);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function hasAnyOwn(object, keys) {
|
|
451
|
+
return keys.some(key => Object.prototype.hasOwnProperty.call(object, key));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function toForwardSlashPath(filePath) {
|
|
455
|
+
return filePath.split(path.sep).join('/');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function isRegularFile(filePath) {
|
|
459
|
+
try {
|
|
460
|
+
return fs.statSync(filePath).isFile();
|
|
461
|
+
} catch {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function isDirectory(filePath) {
|
|
467
|
+
try {
|
|
468
|
+
return fs.statSync(filePath).isDirectory();
|
|
469
|
+
} catch {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function isSafeSkillEntryName(value) {
|
|
475
|
+
const name = String(value || '');
|
|
476
|
+
return Boolean(name)
|
|
477
|
+
&& name !== '.'
|
|
478
|
+
&& name !== '..'
|
|
479
|
+
&& !name.includes('/')
|
|
480
|
+
&& !name.includes('\\')
|
|
481
|
+
&& !name.includes('\0');
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function isSafePluginHomePath(target, pluginHome) {
|
|
485
|
+
return path.resolve(target) === path.resolve(pluginHome) || isInsideDirectory(target, pluginHome);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function ensureSymlink(source, target, options = {}) {
|
|
489
|
+
try {
|
|
490
|
+
const existing = fs.lstatSync(target);
|
|
491
|
+
if (existing.isSymbolicLink() && path.resolve(fs.readlinkSync(target)) === path.resolve(source)) {
|
|
492
|
+
return { ok: true };
|
|
493
|
+
}
|
|
494
|
+
return { ok: false, reason: 'target_exists' };
|
|
495
|
+
} catch (error) {
|
|
496
|
+
if (error.code !== 'ENOENT') {
|
|
497
|
+
return { ok: false, reason: error.code || 'lstat_failed' };
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
try {
|
|
502
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
503
|
+
fs.symlinkSync(source, target, getDirectorySymlinkType(options));
|
|
504
|
+
return { ok: true };
|
|
505
|
+
} catch (error) {
|
|
506
|
+
return { ok: false, reason: error.code || 'link_failed' };
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function getDirectorySymlinkType(options = {}) {
|
|
511
|
+
return getNativeHostPlatform(options) === 'win32' ? 'junction' : 'dir';
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function chmodIfPossible(target, mode) {
|
|
515
|
+
try {
|
|
516
|
+
fs.chmodSync(target, mode);
|
|
517
|
+
} catch {
|
|
518
|
+
// Best effort: native host still works on filesystems that do not support POSIX modes.
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function samePath(left, right) {
|
|
523
|
+
return path.resolve(left) === path.resolve(right);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function isInsideDirectory(target, parent) {
|
|
527
|
+
const relative = path.relative(path.resolve(parent), path.resolve(target));
|
|
528
|
+
return relative && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
module.exports = {
|
|
532
|
+
buildCodexHomeEnv,
|
|
533
|
+
clearPluginCodexHistory,
|
|
534
|
+
composePluginSkillsDirectory,
|
|
535
|
+
getPluginCodexHome,
|
|
536
|
+
getUserCodexHome,
|
|
537
|
+
preparePluginCodexHome
|
|
538
|
+
};
|