@cueai/omni-reader-mcp 1.1.1 → 1.1.2
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 +26 -10
- package/dist/artifact-store.d.ts +1 -0
- package/dist/artifact-store.js +20 -3
- package/dist/cli/arguments.d.ts +1 -0
- package/dist/cli/arguments.js +7 -1
- package/dist/cli/setup.js +10 -4
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/operation-journal.d.ts +8 -0
- package/dist/operation-journal.js +26 -1
- package/dist/operation-manager.js +28 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,17 +33,33 @@ Bridge 运行时只使用以下产品配置:
|
|
|
33
33
|
始终使用已审核的精确版本,不使用隐式 `latest`:
|
|
34
34
|
|
|
35
35
|
```sh
|
|
36
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
36
|
+
npx -y @cueai/omni-reader-mcp@1.1.2 setup
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
交互 setup 支持 Hermes、Cursor、Claude Desktop 和通用 stdio 配置。非交互安装使用同一参数解析和写入逻辑:
|
|
40
40
|
|
|
41
41
|
```sh
|
|
42
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
43
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
44
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
42
|
+
npx -y @cueai/omni-reader-mcp@1.1.2 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
|
|
43
|
+
npx -y @cueai/omni-reader-mcp@1.1.2 setup --client cursor --add-root /absolute/minimum/root --yes --json
|
|
44
|
+
npx -y @cueai/omni-reader-mcp@1.1.2 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
Agent/脚本在 pty 下运行(stdin 仍是 TTY)时,用 `--headless`(别名 `--non-interactive`)显式声明非交互,无需 `--yes` 且绝不读 stdin:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
npx -y @cueai/omni-reader-mcp@1.1.2 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 缓存与 journal 隔离
|
|
54
|
+
|
|
55
|
+
本地解析缓存与操作 journal 默认位于用户级缓存目录(macOS `~/Library/Caches/cue/omni-reader-mcp`、Windows `%LOCALAPPDATA%\Cue\omni-reader-mcp\Cache`、Linux `~/.cache/cue/omni-reader-mcp`)。若 Agent 的文件监视器(如 WorkBuddy)锁定该目录下的文件导致写入失败,设置 `OMNI_CACHE_DIR` 将缓存与 journal 一起隔离到不被监视的目录:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
export OMNI_CACHE_DIR=/var/cache/omni-reader
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
显式 `OMNI_CACHE_DIR` 不会被项目重叠检查改写到临时目录;仍禁止与项目目录互相包含。
|
|
62
|
+
|
|
47
63
|
setup 会:
|
|
48
64
|
|
|
49
65
|
1. 展示 user-scope 配置目标和变更;
|
|
@@ -112,23 +128,23 @@ Agent 不应自动拆分、转码或上传公共站点;应请用户提供符
|
|
|
112
128
|
## 命令
|
|
113
129
|
|
|
114
130
|
```sh
|
|
115
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
116
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
117
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
118
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
131
|
+
npx -y @cueai/omni-reader-mcp@1.1.2 doctor
|
|
132
|
+
npx -y @cueai/omni-reader-mcp@1.1.2 doctor --json
|
|
133
|
+
npx -y @cueai/omni-reader-mcp@1.1.2 clean
|
|
134
|
+
npx -y @cueai/omni-reader-mcp@1.1.2 uninstall --yes --json
|
|
119
135
|
```
|
|
120
136
|
|
|
121
137
|
不带命令运行固定版本会启动 stdio MCP server:
|
|
122
138
|
|
|
123
139
|
```sh
|
|
124
|
-
npx -y @cueai/omni-reader-mcp@1.1.
|
|
140
|
+
npx -y @cueai/omni-reader-mcp@1.1.2
|
|
125
141
|
```
|
|
126
142
|
|
|
127
143
|
`doctor --json` 返回 package/npm/client adapter、Key present/absent、allowed-root 安全状态、endpoint compatibility、artifact、cache mode、onboarding 和 reload 状态;不会输出 Key、私有来源路径或内容。
|
|
128
144
|
|
|
129
145
|
## 卸载与回滚
|
|
130
146
|
|
|
131
|
-
`uninstall --yes --json` 只删除当前受信的 1.1.
|
|
147
|
+
`uninstall --yes --json` 只删除当前受信的 1.1.2 Bridge entry;存在匹配的可信备份时恢复原 URL-only `omni-reader` entry。卸载不会删除用户源文件,也不会静默删除未过期的本地结果。
|
|
132
148
|
|
|
133
149
|
需要回滚 1.1.1 时:
|
|
134
150
|
|
package/dist/artifact-store.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export interface ArtifactReadResult {
|
|
|
44
44
|
interface ArtifactFinalizeInput extends ReleasedMetadata {
|
|
45
45
|
readonly resultId: string;
|
|
46
46
|
}
|
|
47
|
+
export declare function syncDirectory(directory: string, platform?: NodeJS.Platform): Promise<void>;
|
|
47
48
|
export declare function defaultArtifactRoot(options?: DefaultArtifactRootOptions): string;
|
|
48
49
|
export declare function selectArtifactRoot(options: SelectArtifactRootOptions): Promise<SelectedArtifactRoot>;
|
|
49
50
|
export declare class ArtifactStore {
|
package/dist/artifact-store.js
CHANGED
|
@@ -53,14 +53,21 @@ async function removeIfPresent(filePath) {
|
|
|
53
53
|
throw error;
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
async function syncDirectory(directory) {
|
|
56
|
+
export async function syncDirectory(directory, platform = process.platform) {
|
|
57
|
+
// Windows cannot fsync a directory handle: opening a directory with
|
|
58
|
+
// O_RDONLY raises EPERM. File fsync still provides primary durability.
|
|
59
|
+
if (platform === "win32")
|
|
60
|
+
return;
|
|
57
61
|
let handle;
|
|
58
62
|
try {
|
|
59
63
|
handle = await open(directory, "r");
|
|
60
64
|
await handle.sync();
|
|
61
65
|
}
|
|
62
66
|
catch (error) {
|
|
63
|
-
if (!errno(error, "EINVAL") &&
|
|
67
|
+
if (!errno(error, "EINVAL") &&
|
|
68
|
+
!errno(error, "ENOTSUP") &&
|
|
69
|
+
!errno(error, "EISDIR") &&
|
|
70
|
+
!errno(error, "EPERM")) {
|
|
64
71
|
throw error;
|
|
65
72
|
}
|
|
66
73
|
}
|
|
@@ -232,6 +239,14 @@ export function defaultArtifactRoot(options = {}) {
|
|
|
232
239
|
const platform = options.platform ?? process.platform;
|
|
233
240
|
const homeDirectory = options.homeDirectory ?? os.homedir();
|
|
234
241
|
const env = options.env ?? process.env;
|
|
242
|
+
const paths = platform === "win32" ? path.win32 : path.posix;
|
|
243
|
+
const configured = env.OMNI_CACHE_DIR;
|
|
244
|
+
if (configured !== undefined && configured.length > 0) {
|
|
245
|
+
// Explicit isolation: Agents' file watchers lock files under the default
|
|
246
|
+
// cache (WorkBuddy dogfood). An explicit root skips the overlap fallback;
|
|
247
|
+
// ArtifactStore.open still rejects a project-inside-cache layout.
|
|
248
|
+
return paths.resolve(configured);
|
|
249
|
+
}
|
|
235
250
|
if (platform === "darwin") {
|
|
236
251
|
return path.posix.join(homeDirectory, "Library", "Caches", "cue", "omni-reader-mcp");
|
|
237
252
|
}
|
|
@@ -287,12 +302,14 @@ function selectionPathsOverlap(left, right, platform) {
|
|
|
287
302
|
export async function selectArtifactRoot(options) {
|
|
288
303
|
const platform = options.platform ?? process.platform;
|
|
289
304
|
const rootDirectory = defaultArtifactRoot(options);
|
|
305
|
+
const env = options.env ?? process.env;
|
|
306
|
+
const explicitRoot = env.OMNI_CACHE_DIR !== undefined && env.OMNI_CACHE_DIR.length > 0;
|
|
290
307
|
const [rootIdentity, projectIdentity, homeIdentity] = await Promise.all([
|
|
291
308
|
selectionPathIdentity(rootDirectory, platform),
|
|
292
309
|
selectionPathIdentity(options.projectDirectory, platform),
|
|
293
310
|
selectionPathIdentity(options.homeDirectory ?? os.homedir(), platform),
|
|
294
311
|
]);
|
|
295
|
-
if (!selectionPathsOverlap(rootIdentity, projectIdentity, platform)) {
|
|
312
|
+
if (explicitRoot || !selectionPathsOverlap(rootIdentity, projectIdentity, platform)) {
|
|
296
313
|
return { mode: "default", rootDirectory };
|
|
297
314
|
}
|
|
298
315
|
const paths = platform === "win32" ? path.win32 : path.posix;
|
package/dist/cli/arguments.d.ts
CHANGED
package/dist/cli/arguments.js
CHANGED
|
@@ -32,12 +32,13 @@ function parseSetupArguments(values, platform) {
|
|
|
32
32
|
let allowedRoot;
|
|
33
33
|
let addRoot;
|
|
34
34
|
let yes = false;
|
|
35
|
+
let headless = false;
|
|
35
36
|
let json = false;
|
|
36
37
|
const seen = new Set();
|
|
37
38
|
const paths = pathsFor(platform);
|
|
38
39
|
for (let index = 0; index < values.length; index += 1) {
|
|
39
40
|
const flag = values[index];
|
|
40
|
-
if (!["--client", "--allowed-root", "--add-root", "--yes", "--json"].includes(flag)) {
|
|
41
|
+
if (!["--client", "--allowed-root", "--add-root", "--yes", "--headless", "--non-interactive", "--json"].includes(flag)) {
|
|
41
42
|
throw new CliUsageError(`Unknown setup flag: ${flag}`);
|
|
42
43
|
}
|
|
43
44
|
if (seen.has(flag))
|
|
@@ -47,6 +48,10 @@ function parseSetupArguments(values, platform) {
|
|
|
47
48
|
yes = true;
|
|
48
49
|
continue;
|
|
49
50
|
}
|
|
51
|
+
if (flag === "--headless" || flag === "--non-interactive") {
|
|
52
|
+
headless = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
50
55
|
if (flag === "--json") {
|
|
51
56
|
json = true;
|
|
52
57
|
continue;
|
|
@@ -83,6 +88,7 @@ function parseSetupArguments(values, platform) {
|
|
|
83
88
|
allowedRoots: allowedRoot === undefined ? [] : [allowedRoot],
|
|
84
89
|
addRoots: addRoot === undefined ? [] : [addRoot],
|
|
85
90
|
yes,
|
|
91
|
+
headless,
|
|
86
92
|
json,
|
|
87
93
|
};
|
|
88
94
|
}
|
package/dist/cli/setup.js
CHANGED
|
@@ -39,14 +39,20 @@ async function interactiveArguments(options) {
|
|
|
39
39
|
}
|
|
40
40
|
async function selectedTargetAndRoots(options) {
|
|
41
41
|
const requested = options.arguments;
|
|
42
|
+
// --headless (alias: --non-interactive) declares a non-interactive run
|
|
43
|
+
// explicitly, so setup works even when the caller is attached to a TTY
|
|
44
|
+
// (WorkBuddy runs under a pty). Without the flag, non-TTY is inferred.
|
|
45
|
+
const headless = requested.headless || !options.stdinIsTTY;
|
|
42
46
|
if (requested.client === undefined) {
|
|
43
|
-
if (
|
|
44
|
-
throw new CliUsageError("
|
|
47
|
+
if (headless) {
|
|
48
|
+
throw new CliUsageError("Headless setup requires: setup --client hermes --allowed-root <absolute-path> --headless --json");
|
|
45
49
|
}
|
|
46
50
|
const selected = await interactiveArguments(options);
|
|
47
51
|
return { ...selected, interactive: true };
|
|
48
52
|
}
|
|
49
|
-
|
|
53
|
+
// Only an explicit --headless bypasses the consent gate; a non-TTY
|
|
54
|
+
// without --yes still hard-errors so scripts cannot silently write.
|
|
55
|
+
if (!requested.headless && !options.stdinIsTTY && !requested.yes) {
|
|
50
56
|
throw new CliUsageError("Non-interactive setup requires --yes.");
|
|
51
57
|
}
|
|
52
58
|
const existing = await configuredAllowedRoots(requested.client, options);
|
|
@@ -55,7 +61,7 @@ async function selectedTargetAndRoots(options) {
|
|
|
55
61
|
: requested.addRoots.length > 0
|
|
56
62
|
? [...new Set([...existing, ...requested.addRoots])]
|
|
57
63
|
: existing;
|
|
58
|
-
return { target: requested.client, roots, interactive: !requested.yes };
|
|
64
|
+
return { target: requested.client, roots, interactive: headless ? false : !requested.yes };
|
|
59
65
|
}
|
|
60
66
|
function writePreview(options, prepared) {
|
|
61
67
|
options.write(`Target: ${prepared.displayPath}\n`);
|
package/dist/constants.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const CUBE_GRANT_PROTOCOL_VERSION = "omni.parse_grant.v1";
|
|
|
6
6
|
export declare const GRANTED_STREAM_PROTOCOL_VERSION = "omni.granted_parse_stream.v1";
|
|
7
7
|
export declare const DEFAULT_CUBE_BASE_URL = "https://mcp.cuecue.cn";
|
|
8
8
|
export declare const DEFAULT_IIIS_GRANTED_BASE_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/";
|
|
9
|
-
export declare const BRIDGE_RELEASE_VERSION = "1.1.
|
|
9
|
+
export declare const BRIDGE_RELEASE_VERSION = "1.1.2";
|
|
10
10
|
export declare const REMOTE_OMNI_MCP_URL = "https://mcp.cuecue.cn/api/omni-reader/mcp/";
|
|
11
11
|
export declare const FOREGROUND_BUDGET_MS = 15000;
|
|
12
12
|
export declare const STATUS_LONG_POLL_MAX_MS = 20000;
|
package/dist/constants.js
CHANGED
|
@@ -6,7 +6,7 @@ export const CUBE_GRANT_PROTOCOL_VERSION = "omni.parse_grant.v1";
|
|
|
6
6
|
export const GRANTED_STREAM_PROTOCOL_VERSION = "omni.granted_parse_stream.v1";
|
|
7
7
|
export const DEFAULT_CUBE_BASE_URL = "https://mcp.cuecue.cn";
|
|
8
8
|
export const DEFAULT_IIIS_GRANTED_BASE_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/";
|
|
9
|
-
export const BRIDGE_RELEASE_VERSION = "1.1.
|
|
9
|
+
export const BRIDGE_RELEASE_VERSION = "1.1.2";
|
|
10
10
|
export const REMOTE_OMNI_MCP_URL = "https://mcp.cuecue.cn/api/omni-reader/mcp/";
|
|
11
11
|
export const FOREGROUND_BUDGET_MS = 15_000;
|
|
12
12
|
export const STATUS_LONG_POLL_MAX_MS = 20_000;
|
|
@@ -61,6 +61,14 @@ export interface OperationJournalOptions {
|
|
|
61
61
|
readonly rootDirectory?: string;
|
|
62
62
|
readonly now?: () => Date;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Rename with a Windows fallback. Node's rename() rejects replacing an
|
|
66
|
+
* existing destination on Windows with EPERM instead of overwriting
|
|
67
|
+
* atomically; WorkBuddy dogfood hit this on every journal update because
|
|
68
|
+
* the record file already exists. The record lock makes the journal
|
|
69
|
+
* single-writer, so remove-then-rename cannot race a concurrent writer.
|
|
70
|
+
*/
|
|
71
|
+
export declare function renameWithFallback(sourcePath: string, destinationPath: string, platform?: NodeJS.Platform): Promise<void>;
|
|
64
72
|
export declare class OperationJournal {
|
|
65
73
|
#private;
|
|
66
74
|
constructor(options?: OperationJournalOptions);
|
|
@@ -56,6 +56,12 @@ function bridgeError(code, message, retryable) {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
function defaultRootDirectory() {
|
|
59
|
+
const configured = process.env.OMNI_CACHE_DIR;
|
|
60
|
+
if (configured !== undefined && configured.length > 0) {
|
|
61
|
+
return process.platform === "win32"
|
|
62
|
+
? path.win32.resolve(configured)
|
|
63
|
+
: path.posix.resolve(configured);
|
|
64
|
+
}
|
|
59
65
|
if (process.platform === "darwin") {
|
|
60
66
|
return path.join(homedir(), "Library", "Caches", "cue", "omni-reader-mcp");
|
|
61
67
|
}
|
|
@@ -199,6 +205,25 @@ async function closeQuietly(handle) {
|
|
|
199
205
|
function sleep(milliseconds) {
|
|
200
206
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
201
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Rename with a Windows fallback. Node's rename() rejects replacing an
|
|
210
|
+
* existing destination on Windows with EPERM instead of overwriting
|
|
211
|
+
* atomically; WorkBuddy dogfood hit this on every journal update because
|
|
212
|
+
* the record file already exists. The record lock makes the journal
|
|
213
|
+
* single-writer, so remove-then-rename cannot race a concurrent writer.
|
|
214
|
+
*/
|
|
215
|
+
export async function renameWithFallback(sourcePath, destinationPath, platform = process.platform) {
|
|
216
|
+
try {
|
|
217
|
+
await rename(sourcePath, destinationPath);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
if (platform !== "win32" || error.code !== "EPERM") {
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
await unlink(destinationPath);
|
|
224
|
+
await rename(sourcePath, destinationPath);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
202
227
|
function assertStableTransition(current, updated) {
|
|
203
228
|
for (const [before, after] of [
|
|
204
229
|
[current.operationId, updated.operationId],
|
|
@@ -553,7 +578,7 @@ export class OperationJournal {
|
|
|
553
578
|
await handle.sync();
|
|
554
579
|
await handle.close();
|
|
555
580
|
handle = undefined;
|
|
556
|
-
await
|
|
581
|
+
await renameWithFallback(temporaryPath, recordPath);
|
|
557
582
|
if (process.platform !== "win32")
|
|
558
583
|
await chmod(recordPath, 0o600);
|
|
559
584
|
await this.#syncDirectory();
|
|
@@ -541,11 +541,37 @@ function remoteContext(value) {
|
|
|
541
541
|
}
|
|
542
542
|
return context;
|
|
543
543
|
}
|
|
544
|
+
function _userFacingMessage(code, result) {
|
|
545
|
+
const error = result.error;
|
|
546
|
+
if (!error.file_uploaded && !error.parser_started) {
|
|
547
|
+
if (code === "antibot_blocked" || code === "empty_content") {
|
|
548
|
+
return "目标网站返回了验证码或空白页面,解析器无法提取内容。可尝试用本地 Bridge 下载页面后上传。";
|
|
549
|
+
}
|
|
550
|
+
if (code === "SERVICE_TEMPORARILY_UNAVAILABLE") {
|
|
551
|
+
return "解析服务暂时不可用,请稍后重试。";
|
|
552
|
+
}
|
|
553
|
+
if (code === "ssrf_blocked") {
|
|
554
|
+
return "该链接指向内部/私有地址,已被安全策略拦截。";
|
|
555
|
+
}
|
|
556
|
+
return `文件上传失败(${code})。请检查网络连接,或尝试用本地 Bridge 下载后上传。`;
|
|
557
|
+
}
|
|
558
|
+
if (!error.parser_started) {
|
|
559
|
+
return `文件已上传但解析未能启动(${code})。可能是文件格式不支持或文件损坏。`;
|
|
560
|
+
}
|
|
561
|
+
return `解析失败(${code})。可尝试更换链接或文件格式。`;
|
|
562
|
+
}
|
|
544
563
|
function remoteFailure(result) {
|
|
545
564
|
const error = result.error;
|
|
565
|
+
// Map internal error codes to user-facing messages so the caller (and the
|
|
566
|
+
// user) can understand what happened without reading protocol internals.
|
|
567
|
+
const code = error.code || "UNKNOWN_ERROR";
|
|
568
|
+
let message = error.message || "";
|
|
569
|
+
if (!message || message === code) {
|
|
570
|
+
message = _userFacingMessage(code, result);
|
|
571
|
+
}
|
|
546
572
|
return new OmniBridgeError({
|
|
547
|
-
code
|
|
548
|
-
message
|
|
573
|
+
code,
|
|
574
|
+
message,
|
|
549
575
|
...(error.failure_scope === undefined ? {} : { failureScope: error.failure_scope }),
|
|
550
576
|
sourceKind: "url",
|
|
551
577
|
...(error.user_action === undefined ? {} : { userAction: error.user_action }),
|