@aloth/olcli 0.15.2 → 0.16.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/README.md +140 -437
- package/dist/browser-auth.d.ts +144 -0
- package/dist/browser-auth.d.ts.map +1 -0
- package/dist/browser-auth.js +442 -0
- package/dist/browser-auth.js.map +1 -0
- package/dist/cli.js +455 -177
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +52 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +199 -42
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +40 -7
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +50 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +115 -0
- package/dist/errors.js.map +1 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +27 -19
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/move-plan.d.ts +75 -0
- package/dist/move-plan.d.ts.map +1 -0
- package/dist/move-plan.js +168 -0
- package/dist/move-plan.js.map +1 -0
- package/dist/paths.d.ts +18 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +45 -0
- package/dist/paths.js.map +1 -1
- package/dist/project-match.d.ts +37 -0
- package/dist/project-match.d.ts.map +1 -0
- package/dist/project-match.js +97 -0
- package/dist/project-match.js.map +1 -0
- package/dist/proxy.d.ts +52 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +189 -0
- package/dist/proxy.js.map +1 -0
- package/dist/remote-helper.js +3 -2
- package/dist/remote-helper.js.map +1 -1
- package/dist/term.d.ts +25 -0
- package/dist/term.d.ts.map +1 -0
- package/dist/term.js +42 -0
- package/dist/term.js.map +1 -0
- package/package.json +9 -4
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure planning logic for `olcli rename|mv` and the MCP tool `rename_entity`.
|
|
3
|
+
*
|
|
4
|
+
* Overleaf has two separate endpoints: `rename` changes an entity's name and
|
|
5
|
+
* keeps its folder, `move` changes its folder and keeps its name. A request
|
|
6
|
+
* such as `mv sub/a.txt chapters/b.txt` therefore needs up to three kinds of
|
|
7
|
+
* writes: create missing folders, rename, move. Every check that can refuse
|
|
8
|
+
* the request runs here, over the project tree, before any of them is sent,
|
|
9
|
+
* so a refusal never leaves a half-done move behind. The same plan feeds the
|
|
10
|
+
* MCP confirmation dialog, which is why it lives apart from the client.
|
|
11
|
+
*/
|
|
12
|
+
/** A request that was refused before anything was written. */
|
|
13
|
+
export class MoveRefusedError extends Error {
|
|
14
|
+
reason;
|
|
15
|
+
constructor(message, reason) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.reason = reason;
|
|
18
|
+
this.name = 'MoveRefusedError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function join(folder, name) {
|
|
22
|
+
return folder ? `${folder}/${name}` : name;
|
|
23
|
+
}
|
|
24
|
+
function checkSegment(segment, whole) {
|
|
25
|
+
if (segment === '' || segment === '.' || segment === '..') {
|
|
26
|
+
throw new MoveRefusedError(`Invalid destination path: ${whole}`, 'invalid_path');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Split the destination as the user wrote it into folder and name.
|
|
31
|
+
*
|
|
32
|
+
* - no `/`: a new name in the current folder (plain rename)
|
|
33
|
+
* - ends in `/`: a folder; the name is kept (`/` alone is the project root)
|
|
34
|
+
* - otherwise: folder part and name; a leading `/` means the project root
|
|
35
|
+
*/
|
|
36
|
+
export function parseDestination(destination, currentFolder, currentName) {
|
|
37
|
+
if (!destination.includes('/')) {
|
|
38
|
+
checkSegment(destination, destination);
|
|
39
|
+
return { folder: currentFolder, name: destination };
|
|
40
|
+
}
|
|
41
|
+
const stripped = destination.replace(/^\/+/, '');
|
|
42
|
+
let folder;
|
|
43
|
+
let name;
|
|
44
|
+
if (stripped === '' || stripped.endsWith('/')) {
|
|
45
|
+
folder = stripped.replace(/\/+$/, '');
|
|
46
|
+
name = currentName;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const cut = stripped.lastIndexOf('/');
|
|
50
|
+
folder = cut === -1 ? '' : stripped.slice(0, cut);
|
|
51
|
+
name = cut === -1 ? stripped : stripped.slice(cut + 1);
|
|
52
|
+
checkSegment(name, destination);
|
|
53
|
+
}
|
|
54
|
+
if (folder)
|
|
55
|
+
for (const segment of folder.split('/'))
|
|
56
|
+
checkSegment(segment, destination);
|
|
57
|
+
return { folder, name };
|
|
58
|
+
}
|
|
59
|
+
function indexTree(root) {
|
|
60
|
+
const entities = new Map();
|
|
61
|
+
const order = [];
|
|
62
|
+
const add = (entry) => {
|
|
63
|
+
entities.set(entry.path, entry);
|
|
64
|
+
order.push(entry);
|
|
65
|
+
};
|
|
66
|
+
const walk = (folder, path) => {
|
|
67
|
+
// Same order as OverleafClient.findEntityByPath: docs, files, folders.
|
|
68
|
+
for (const doc of folder.docs || [])
|
|
69
|
+
add({ id: doc._id, type: 'doc', name: doc.name, path: join(path, doc.name), parentPath: path });
|
|
70
|
+
for (const file of folder.fileRefs || [])
|
|
71
|
+
add({ id: file._id, type: 'file', name: file.name, path: join(path, file.name), parentPath: path });
|
|
72
|
+
for (const sub of folder.folders || []) {
|
|
73
|
+
const subPath = join(path, sub.name);
|
|
74
|
+
add({ id: sub._id, type: 'folder', name: sub.name, path: subPath, parentPath: path });
|
|
75
|
+
walk(sub, subPath);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
walk(root, '');
|
|
79
|
+
return { entities, order };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Work out what `rename|mv <source> <destination>` does in a project tree,
|
|
83
|
+
* or throw MoveRefusedError. Reads only.
|
|
84
|
+
*
|
|
85
|
+
* The source is looked up by its full path; as before, a bare name also
|
|
86
|
+
* matches an entity of that name in any folder.
|
|
87
|
+
*/
|
|
88
|
+
export function planEntityMove(root, source, destination) {
|
|
89
|
+
const { entities, order } = indexTree(root);
|
|
90
|
+
const wanted = source.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
91
|
+
let entity = wanted ? entities.get(wanted) : undefined;
|
|
92
|
+
if (!entity && wanted && !wanted.includes('/'))
|
|
93
|
+
entity = order.find(e => e.name === wanted);
|
|
94
|
+
if (!entity)
|
|
95
|
+
throw new MoveRefusedError(`File not found: ${source}`, 'not_found');
|
|
96
|
+
const folderId = (path) => {
|
|
97
|
+
if (path === '')
|
|
98
|
+
return root._id;
|
|
99
|
+
const found = entities.get(path);
|
|
100
|
+
return found?.type === 'folder' ? found.id : undefined;
|
|
101
|
+
};
|
|
102
|
+
const { folder: targetFolderPath, name: newName } = parseDestination(destination, entity.parentPath, entity.name);
|
|
103
|
+
const to = join(targetFolderPath, newName);
|
|
104
|
+
if (entity.type === 'folder' && (targetFolderPath === entity.path || targetFolderPath.startsWith(`${entity.path}/`))) {
|
|
105
|
+
const message = targetFolderPath === entity.path
|
|
106
|
+
? `Cannot move folder ${entity.path} into itself`
|
|
107
|
+
: `Cannot move folder ${entity.path} into its own subfolder ${targetFolderPath}`;
|
|
108
|
+
throw new MoveRefusedError(message, 'into_itself');
|
|
109
|
+
}
|
|
110
|
+
// Walk the target folder path: existing folders are reused, the first
|
|
111
|
+
// missing segment and everything below it is created.
|
|
112
|
+
const knownFolders = { '': root._id };
|
|
113
|
+
const createFolders = [];
|
|
114
|
+
if (targetFolderPath) {
|
|
115
|
+
let prefix = '';
|
|
116
|
+
for (const segment of targetFolderPath.split('/')) {
|
|
117
|
+
prefix = join(prefix, segment);
|
|
118
|
+
if (createFolders.length > 0) {
|
|
119
|
+
createFolders.push(prefix);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const existing = entities.get(prefix);
|
|
123
|
+
if (!existing) {
|
|
124
|
+
createFolders.push(prefix);
|
|
125
|
+
}
|
|
126
|
+
else if (existing.type !== 'folder') {
|
|
127
|
+
throw new MoveRefusedError(`Cannot move into ${targetFolderPath}: ${prefix} is a ${existing.type}, not a folder`, 'not_a_folder');
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
knownFolders[prefix] = existing.id;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const sourceFolderId = folderId(entity.parentPath);
|
|
135
|
+
const base = {
|
|
136
|
+
entity: { id: entity.id, type: entity.type, name: entity.name, path: entity.path },
|
|
137
|
+
from: entity.path,
|
|
138
|
+
to,
|
|
139
|
+
newName,
|
|
140
|
+
targetFolderPath,
|
|
141
|
+
targetFolderId: createFolders.length ? undefined : folderId(targetFolderPath),
|
|
142
|
+
sourceFolderPath: entity.parentPath,
|
|
143
|
+
sourceFolderId,
|
|
144
|
+
createFolders,
|
|
145
|
+
knownFolders,
|
|
146
|
+
};
|
|
147
|
+
if (to === entity.path) {
|
|
148
|
+
return { ...base, rename: false, move: false, noop: true, order: 'rename-then-move' };
|
|
149
|
+
}
|
|
150
|
+
if (entities.has(to)) {
|
|
151
|
+
throw new MoveRefusedError(`Destination already exists: ${to}`, 'exists');
|
|
152
|
+
}
|
|
153
|
+
const rename = newName !== entity.name;
|
|
154
|
+
const move = targetFolderPath !== entity.parentPath;
|
|
155
|
+
let planOrder = 'rename-then-move';
|
|
156
|
+
if (rename && move) {
|
|
157
|
+
// Overleaf refuses a name that is taken in the folder, so the step in
|
|
158
|
+
// between must not collide either.
|
|
159
|
+
const renameClash = entities.has(join(entity.parentPath, newName));
|
|
160
|
+
const moveClash = createFolders.length === 0 && entities.has(join(targetFolderPath, entity.name));
|
|
161
|
+
if (renameClash && moveClash) {
|
|
162
|
+
throw new MoveRefusedError(`Cannot move ${entity.path} to ${to} in one step: ${join(entity.parentPath, newName)} and ${join(targetFolderPath, entity.name)} both exist. Rename or move it through a free name first`, 'intermediate_clash');
|
|
163
|
+
}
|
|
164
|
+
planOrder = renameClash ? 'move-then-rename' : 'rename-then-move';
|
|
165
|
+
}
|
|
166
|
+
return { ...base, rename, move, noop: false, order: planOrder };
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=move-plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"move-plan.js","sourceRoot":"","sources":["../src/move-plan.ts"],"names":[],"mappings":"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"}
|
package/dist/paths.d.ts
CHANGED
|
@@ -37,4 +37,22 @@ export declare function normalizeRemotePath(path: string): string;
|
|
|
37
37
|
* 'figures/diagram.png' still lands in the 'figures' folder.
|
|
38
38
|
*/
|
|
39
39
|
export declare function resolveRemotePath(localPath: string, destination?: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Old local name for a project title: everything outside [a-zA-Z0-9-_]
|
|
42
|
+
* became "_". Kept so that a directory created by olcli 0.15 or earlier can
|
|
43
|
+
* still be found; see `defaultProjectDir`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function legacyLocalName(name: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* A project title as a local file or directory name.
|
|
48
|
+
*
|
|
49
|
+
* Letters, digits and symbols from any script are kept, including emoji, so
|
|
50
|
+
* "Masterarbeit Müller" stays readable instead of becoming
|
|
51
|
+
* "Masterarbeit_M_ller". Replaced with "_": whitespace (as before, so ASCII
|
|
52
|
+
* titles keep the names they had), characters that are unsafe in a path on
|
|
53
|
+
* any platform (/ \ : * ? " < > |) and control characters. Leading dots and
|
|
54
|
+
* trailing dots or spaces are dropped, Windows device names get a suffix, and
|
|
55
|
+
* the result is capped at 200 UTF-8 bytes without splitting a character.
|
|
56
|
+
*/
|
|
57
|
+
export declare function safeLocalName(name: string): string;
|
|
40
58
|
//# sourceMappingURL=paths.d.ts.map
|
package/dist/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMlF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAexD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAyBjF"}
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMlF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAexD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAyBjF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsBlD"}
|
package/dist/paths.js
CHANGED
|
@@ -82,4 +82,49 @@ export function resolveRemotePath(localPath, destination) {
|
|
|
82
82
|
}
|
|
83
83
|
return normalized;
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Old local name for a project title: everything outside [a-zA-Z0-9-_]
|
|
87
|
+
* became "_". Kept so that a directory created by olcli 0.15 or earlier can
|
|
88
|
+
* still be found; see `defaultProjectDir`.
|
|
89
|
+
*/
|
|
90
|
+
export function legacyLocalName(name) {
|
|
91
|
+
return name.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
92
|
+
}
|
|
93
|
+
const WINDOWS_RESERVED = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])$/i;
|
|
94
|
+
const MAX_NAME_BYTES = 200;
|
|
95
|
+
/**
|
|
96
|
+
* A project title as a local file or directory name.
|
|
97
|
+
*
|
|
98
|
+
* Letters, digits and symbols from any script are kept, including emoji, so
|
|
99
|
+
* "Masterarbeit Müller" stays readable instead of becoming
|
|
100
|
+
* "Masterarbeit_M_ller". Replaced with "_": whitespace (as before, so ASCII
|
|
101
|
+
* titles keep the names they had), characters that are unsafe in a path on
|
|
102
|
+
* any platform (/ \ : * ? " < > |) and control characters. Leading dots and
|
|
103
|
+
* trailing dots or spaces are dropped, Windows device names get a suffix, and
|
|
104
|
+
* the result is capped at 200 UTF-8 bytes without splitting a character.
|
|
105
|
+
*/
|
|
106
|
+
export function safeLocalName(name) {
|
|
107
|
+
let out = name
|
|
108
|
+
.normalize('NFC')
|
|
109
|
+
.trim()
|
|
110
|
+
// Windows drops trailing dots and spaces, and a leading dot hides the
|
|
111
|
+
// directory on Unix; neither belongs to the title.
|
|
112
|
+
.replace(/^\.+/, '')
|
|
113
|
+
.replace(/[.\s]+$/, '')
|
|
114
|
+
// eslint-disable-next-line no-control-regex
|
|
115
|
+
.replace(/[\u0000-\u001f\u007f/\\:*?"<>|]/g, '_')
|
|
116
|
+
.replace(/\s/g, '_');
|
|
117
|
+
if (WINDOWS_RESERVED.test(out.split('.')[0]))
|
|
118
|
+
out = `${out}_`;
|
|
119
|
+
if (Buffer.byteLength(out) > MAX_NAME_BYTES) {
|
|
120
|
+
let cut = '';
|
|
121
|
+
for (const ch of out) {
|
|
122
|
+
if (Buffer.byteLength(cut + ch) > MAX_NAME_BYTES)
|
|
123
|
+
break;
|
|
124
|
+
cut += ch;
|
|
125
|
+
}
|
|
126
|
+
out = cut;
|
|
127
|
+
}
|
|
128
|
+
return out || 'project';
|
|
129
|
+
}
|
|
85
130
|
//# sourceMappingURL=paths.js.map
|
package/dist/paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,YAAoB;IACjE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC3C,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrD,MAAM,GAAG,GAAa,EAAE,CAAC;IAEzB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG;YAAE,SAAS;QAChD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,wEAAwE;YACxE,GAAG,CAAC,GAAG,EAAE,CAAC;YACV,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpB,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,WAAoB;IACvE,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,UAAU,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;QACpD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,6BAA6B,WAAW,EAAE,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAE/E,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC;QAC5D,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,qCAAqC,SAAS,EAAE,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,UAAU,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAClD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,qCAAqC,SAAS,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,YAAoB;IACjE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC3C,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrD,MAAM,GAAG,GAAa,EAAE,CAAC;IAEzB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG;YAAE,SAAS;QAChD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,wEAAwE;YACxE,GAAG,CAAC,GAAG,EAAE,CAAC;YACV,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpB,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,WAAoB;IACvE,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,UAAU,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;QACpD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,6BAA6B,WAAW,EAAE,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAE/E,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC;QAC5D,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,qCAAqC,SAAS,EAAE,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,UAAU,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAClD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,qCAAqC,SAAS,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,gBAAgB,GAAG,wCAAwC,CAAC;AAClE,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,GAAG,GAAG,IAAI;SACX,SAAS,CAAC,KAAK,CAAC;SAChB,IAAI,EAAE;QACP,sEAAsE;QACtE,mDAAmD;SAClD,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;SACnB,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;QACvB,4CAA4C;SAC3C,OAAO,CAAC,kCAAkC,EAAE,GAAG,CAAC;SAChD,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACvB,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAE9D,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,cAAc,EAAE,CAAC;QAC5C,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACrB,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,cAAc;gBAAE,MAAM;YACxD,GAAG,IAAI,EAAE,CAAC;QACZ,CAAC;QACD,GAAG,GAAG,GAAG,CAAC;IACZ,CAAC;IACD,OAAO,GAAG,IAAI,SAAS,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find a project by what the user typed.
|
|
3
|
+
*
|
|
4
|
+
* Order, first hit wins:
|
|
5
|
+
* 1. exact name (case-sensitive), so two projects that differ only in case
|
|
6
|
+
* can still be told apart
|
|
7
|
+
* 2. exact name, case-insensitive
|
|
8
|
+
* 3. a unique case-insensitive prefix
|
|
9
|
+
*
|
|
10
|
+
* Several projects sharing the name or the prefix is an error that lists
|
|
11
|
+
* them; nothing matching is an error that suggests the closest names. Both
|
|
12
|
+
* are typed so callers can exit non-zero without parsing the message.
|
|
13
|
+
*/
|
|
14
|
+
export interface NamedProject {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class ProjectNotFoundError extends Error {
|
|
19
|
+
readonly query: string;
|
|
20
|
+
readonly suggestions: string[];
|
|
21
|
+
constructor(query: string, suggestions: string[]);
|
|
22
|
+
}
|
|
23
|
+
export declare class AmbiguousProjectError extends Error {
|
|
24
|
+
readonly query: string;
|
|
25
|
+
readonly candidates: NamedProject[];
|
|
26
|
+
constructor(query: string, candidates: NamedProject[]);
|
|
27
|
+
}
|
|
28
|
+
/** Levenshtein distance; names are short, so the quadratic table is fine. */
|
|
29
|
+
export declare function editDistance(a: string, b: string): number;
|
|
30
|
+
/**
|
|
31
|
+
* Up to `limit` project names closest to `query`: names containing it first,
|
|
32
|
+
* then by edit distance, dropping anything further than about half the query.
|
|
33
|
+
*/
|
|
34
|
+
export declare function suggestProjectNames(projects: NamedProject[], query: string, limit?: number): string[];
|
|
35
|
+
/** Resolve `query` to one project or throw ProjectNotFoundError / AmbiguousProjectError. */
|
|
36
|
+
export declare function matchProject<T extends NamedProject>(projects: T[], query: string): T;
|
|
37
|
+
//# sourceMappingURL=project-match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-match.d.ts","sourceRoot":"","sources":["../src/project-match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;gBAEnB,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE;CASjD;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC;gBAExB,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE;CAQtD;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAYzD;AAID;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,MAAM,EAAE,CAiBhG;AAED,4FAA4F;AAC5F,wBAAgB,YAAY,CAAC,CAAC,SAAS,YAAY,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,CAepF"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find a project by what the user typed.
|
|
3
|
+
*
|
|
4
|
+
* Order, first hit wins:
|
|
5
|
+
* 1. exact name (case-sensitive), so two projects that differ only in case
|
|
6
|
+
* can still be told apart
|
|
7
|
+
* 2. exact name, case-insensitive
|
|
8
|
+
* 3. a unique case-insensitive prefix
|
|
9
|
+
*
|
|
10
|
+
* Several projects sharing the name or the prefix is an error that lists
|
|
11
|
+
* them; nothing matching is an error that suggests the closest names. Both
|
|
12
|
+
* are typed so callers can exit non-zero without parsing the message.
|
|
13
|
+
*/
|
|
14
|
+
export class ProjectNotFoundError extends Error {
|
|
15
|
+
query;
|
|
16
|
+
suggestions;
|
|
17
|
+
constructor(query, suggestions) {
|
|
18
|
+
const hint = suggestions.length > 0
|
|
19
|
+
? `\nDid you mean: ${suggestions.map((s) => `"${s}"`).join(', ')}?`
|
|
20
|
+
: '\nList your projects with: olcli list';
|
|
21
|
+
super(`Project not found: ${query}${hint}`);
|
|
22
|
+
this.name = 'ProjectNotFoundError';
|
|
23
|
+
this.query = query;
|
|
24
|
+
this.suggestions = suggestions;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class AmbiguousProjectError extends Error {
|
|
28
|
+
query;
|
|
29
|
+
candidates;
|
|
30
|
+
constructor(query, candidates) {
|
|
31
|
+
const shown = candidates.slice(0, 10).map((p) => ` ${p.name} (${p.id})`);
|
|
32
|
+
if (candidates.length > 10)
|
|
33
|
+
shown.push(` ... and ${candidates.length - 10} more`);
|
|
34
|
+
super(`"${query}" matches ${candidates.length} projects:\n${shown.join('\n')}\nUse the full name or the project ID.`);
|
|
35
|
+
this.name = 'AmbiguousProjectError';
|
|
36
|
+
this.query = query;
|
|
37
|
+
this.candidates = candidates;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Levenshtein distance; names are short, so the quadratic table is fine. */
|
|
41
|
+
export function editDistance(a, b) {
|
|
42
|
+
const x = [...a];
|
|
43
|
+
const y = [...b];
|
|
44
|
+
let prev = Array.from({ length: y.length + 1 }, (_, j) => j);
|
|
45
|
+
for (let i = 1; i <= x.length; i++) {
|
|
46
|
+
const row = [i];
|
|
47
|
+
for (let j = 1; j <= y.length; j++) {
|
|
48
|
+
row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (x[i - 1] === y[j - 1] ? 0 : 1));
|
|
49
|
+
}
|
|
50
|
+
prev = row;
|
|
51
|
+
}
|
|
52
|
+
return prev[y.length];
|
|
53
|
+
}
|
|
54
|
+
const fold = (s) => s.normalize('NFC').toLocaleLowerCase();
|
|
55
|
+
/**
|
|
56
|
+
* Up to `limit` project names closest to `query`: names containing it first,
|
|
57
|
+
* then by edit distance, dropping anything further than about half the query.
|
|
58
|
+
*/
|
|
59
|
+
export function suggestProjectNames(projects, query, limit = 3) {
|
|
60
|
+
const q = fold(query);
|
|
61
|
+
const maxDistance = Math.max(2, Math.ceil(q.length / 2));
|
|
62
|
+
const scored = projects.map((p) => {
|
|
63
|
+
const name = fold(p.name);
|
|
64
|
+
const contains = name.includes(q) || (name.length >= 3 && q.includes(name));
|
|
65
|
+
// Compare against the start of the name as well, so a short query for a
|
|
66
|
+
// long title is not dismissed for the length difference alone.
|
|
67
|
+
const distance = Math.min(editDistance(q, name), editDistance(q, name.slice(0, q.length)) + 1);
|
|
68
|
+
return { name: p.name, contains, distance };
|
|
69
|
+
});
|
|
70
|
+
return scored
|
|
71
|
+
.filter((s) => s.contains || s.distance <= maxDistance)
|
|
72
|
+
.sort((a, b) => Number(b.contains) - Number(a.contains) || a.distance - b.distance || a.name.localeCompare(b.name))
|
|
73
|
+
.map((s) => s.name)
|
|
74
|
+
.filter((name, index, all) => all.indexOf(name) === index)
|
|
75
|
+
.slice(0, limit);
|
|
76
|
+
}
|
|
77
|
+
/** Resolve `query` to one project or throw ProjectNotFoundError / AmbiguousProjectError. */
|
|
78
|
+
export function matchProject(projects, query) {
|
|
79
|
+
const exact = projects.filter((p) => p.name === query);
|
|
80
|
+
if (exact.length === 1)
|
|
81
|
+
return exact[0];
|
|
82
|
+
if (exact.length > 1)
|
|
83
|
+
throw new AmbiguousProjectError(query, exact);
|
|
84
|
+
const q = fold(query);
|
|
85
|
+
const insensitive = projects.filter((p) => fold(p.name) === q);
|
|
86
|
+
if (insensitive.length === 1)
|
|
87
|
+
return insensitive[0];
|
|
88
|
+
if (insensitive.length > 1)
|
|
89
|
+
throw new AmbiguousProjectError(query, insensitive);
|
|
90
|
+
const prefixed = q ? projects.filter((p) => fold(p.name).startsWith(q)) : [];
|
|
91
|
+
if (prefixed.length === 1)
|
|
92
|
+
return prefixed[0];
|
|
93
|
+
if (prefixed.length > 1)
|
|
94
|
+
throw new AmbiguousProjectError(query, prefixed);
|
|
95
|
+
throw new ProjectNotFoundError(query, suggestProjectNames(projects, query));
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=project-match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-match.js","sourceRoot":"","sources":["../src/project-match.ts"],"names":[],"mappings":"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"}
|
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTPS_PROXY, HTTP_PROXY and NO_PROXY for every request olcli makes.
|
|
3
|
+
*
|
|
4
|
+
* The client talks to Overleaf through node:http and node:https (see
|
|
5
|
+
* httpRequest in client.ts), and the collaboration socket is Socket.IO
|
|
6
|
+
* xhr-polling over the same function, so routing that one function through
|
|
7
|
+
* the proxy covers all traffic. Node's own env-proxy support only exists in
|
|
8
|
+
* recent releases and has to be switched on with NODE_USE_ENV_PROXY, and
|
|
9
|
+
* undici's EnvHttpProxyAgent only serves fetch(), so this is done here with
|
|
10
|
+
* no dependency:
|
|
11
|
+
*
|
|
12
|
+
* - an https:// target goes through a CONNECT tunnel, then TLS end to end,
|
|
13
|
+
* so the proxy never sees the cookie;
|
|
14
|
+
* - an http:// target (self-hosted instances) is sent to the proxy with the
|
|
15
|
+
* absolute URL as the request target, as HTTP/1.1 proxies expect.
|
|
16
|
+
*
|
|
17
|
+
* Lower-case variable names win over upper-case ones, as in curl.
|
|
18
|
+
*/
|
|
19
|
+
import * as http from 'node:http';
|
|
20
|
+
import * as https from 'node:https';
|
|
21
|
+
/**
|
|
22
|
+
* True for localhost (and its subdomains), 127.0.0.0/8, ::1 and the
|
|
23
|
+
* IPv4-mapped ::ffff:127.x addresses. Brackets around IPv6 are accepted.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isLoopbackHost(hostname: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* True when NO_PROXY exempts `target`. Entries are comma or space separated:
|
|
28
|
+
* `*` matches everything, `example.com` and `.example.com` match the host and
|
|
29
|
+
* its subdomains, `host:port` matches only that port, and an IP matches
|
|
30
|
+
* itself. Loopback (localhost, 127.0.0.0/8, ::1) is always reached
|
|
31
|
+
* directly, whatever NO_PROXY says: a proxy cannot reach this machine's
|
|
32
|
+
* loopback interface, and the browser sign-in talks to a local browser.
|
|
33
|
+
*/
|
|
34
|
+
export declare function bypassesProxy(target: URL, env?: NodeJS.ProcessEnv): boolean;
|
|
35
|
+
/** The proxy for `target`, or undefined when it should be reached directly. */
|
|
36
|
+
export declare function proxyForUrl(target: URL, env?: NodeJS.ProcessEnv): URL | undefined;
|
|
37
|
+
export interface ProxiedRequest {
|
|
38
|
+
/** What to pass to http(s).request as the URL. */
|
|
39
|
+
url: string;
|
|
40
|
+
/** Extra request options (agent, headers) for the proxy. */
|
|
41
|
+
options: http.RequestOptions;
|
|
42
|
+
/** The transport module to call request() on. */
|
|
43
|
+
transport: typeof http | typeof https;
|
|
44
|
+
/** The proxy in use, for --verbose; undefined for a direct request. */
|
|
45
|
+
proxy?: URL;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* How to send a request to `reqUrl`, honoring the proxy variables in `env`.
|
|
49
|
+
* The headers passed in are extended, never replaced.
|
|
50
|
+
*/
|
|
51
|
+
export declare function routeRequest(reqUrl: string, headers: Record<string, string>, env?: NodeJS.ProcessEnv): ProxiedRequest;
|
|
52
|
+
//# sourceMappingURL=proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAUpC;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAaxD;AAMD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAwBxF;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,GAAG,GAAG,SAAS,CAU9F;AAgED,MAAM,WAAW,cAAc;IAC7B,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC;IAC7B,iDAAiD;IACjD,SAAS,EAAE,OAAO,IAAI,GAAG,OAAO,KAAK,CAAC;IACtC,uEAAuE;IACvE,KAAK,CAAC,EAAE,GAAG,CAAC;CACb;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,cAAc,CA2BhB"}
|