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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +457 -0
  3. package/bin/codex-overleaf-link.mjs +223 -0
  4. package/extension/src/shared/agentTranscript.js +1175 -0
  5. package/extension/src/shared/auditRecords.js +568 -0
  6. package/extension/src/shared/compatibility.js +372 -0
  7. package/extension/src/shared/compileAdapter.js +176 -0
  8. package/extension/src/shared/governanceRules.js +252 -0
  9. package/extension/src/shared/i18n.js +565 -0
  10. package/extension/src/shared/models.js +106 -0
  11. package/extension/src/shared/otText.js +505 -0
  12. package/extension/src/shared/projectFiles.js +180 -0
  13. package/extension/src/shared/reviewing.js +99 -0
  14. package/extension/src/shared/sensitiveScan.js +116 -0
  15. package/extension/src/shared/sessionState.js +1084 -0
  16. package/extension/src/shared/staleGuard.js +150 -0
  17. package/extension/src/shared/storageDb.js +986 -0
  18. package/extension/src/shared/storageKeys.js +29 -0
  19. package/extension/src/shared/storageMigration.js +168 -0
  20. package/extension/src/shared/summary.js +248 -0
  21. package/extension/src/shared/undoOperations.js +369 -0
  22. package/native-host/src/codexArgs.js +43 -0
  23. package/native-host/src/codexHome.js +538 -0
  24. package/native-host/src/codexModels.js +247 -0
  25. package/native-host/src/codexPrompt.js +192 -0
  26. package/native-host/src/codexPromptAssembly.js +411 -0
  27. package/native-host/src/codexSessionRunner.js +1247 -0
  28. package/native-host/src/commandApproval.js +914 -0
  29. package/native-host/src/debugLog.js +78 -0
  30. package/native-host/src/diffEngine.js +247 -0
  31. package/native-host/src/index.js +132 -0
  32. package/native-host/src/launcher.js +81 -0
  33. package/native-host/src/localSkills.js +476 -0
  34. package/native-host/src/manifest.js +226 -0
  35. package/native-host/src/mirrorSensitiveScan.js +119 -0
  36. package/native-host/src/mirrorWorkspace.js +1019 -0
  37. package/native-host/src/nativeDoctor.js +826 -0
  38. package/native-host/src/nativeEnvironment.js +315 -0
  39. package/native-host/src/nativeHostPlatform.js +112 -0
  40. package/native-host/src/nativeMessaging.js +60 -0
  41. package/native-host/src/nativeQuotas.js +294 -0
  42. package/native-host/src/nativeResponseBudget.js +194 -0
  43. package/native-host/src/runtimeInstaller.js +357 -0
  44. package/native-host/src/taskRunner.js +3 -0
  45. package/native-host/src/taskRunnerRuntime.js +1083 -0
  46. package/native-host/src/textPatch.js +287 -0
  47. package/package.json +40 -0
  48. package/scripts/codex-json-agent.mjs +269 -0
  49. package/scripts/install-native-host.mjs +255 -0
  50. package/scripts/npm-package-files-v1.1.1.txt +52 -0
  51. package/scripts/uninstall-native-host.mjs +298 -0
  52. 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
+ };