dsh-reference-anything 0.3.0 → 0.3.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/LICENSE +22 -22
- package/NOTICE.md +52 -25
- package/README.md +339 -286
- package/cordis.patch.yml +89 -18
- package/lib/client.js +13538 -591
- package/lib/contract.d.ts +155 -16
- package/lib/contract.js +68 -7
- package/lib/download-directory.d.ts +18 -0
- package/lib/download-directory.js +55 -0
- package/lib/errors.d.ts +4 -0
- package/lib/host.d.ts +96 -8
- package/lib/host.js +88 -19
- package/lib/index.d.ts +22 -2
- package/lib/index.js +57 -7
- package/lib/opencli.d.ts +5 -3
- package/lib/opencli.js +13 -6
- package/lib/openlist/host.d.ts +157 -0
- package/lib/openlist/host.js +873 -0
- package/lib/openlist/index.d.ts +84 -0
- package/lib/openlist/index.js +507 -0
- package/lib/render.d.ts +12 -7
- package/lib/render.js +27 -21
- package/lib/sources/cloud-drive/cache.d.ts +60 -0
- package/lib/sources/cloud-drive/cache.js +107 -0
- package/lib/sources/cloud-drive/index.d.ts +190 -0
- package/lib/sources/cloud-drive/index.js +526 -0
- package/lib/sources/cloud-drive/providers/http.d.ts +27 -0
- package/lib/sources/cloud-drive/providers/http.js +73 -0
- package/lib/sources/cloud-drive/providers/openlist.d.ts +59 -0
- package/lib/sources/cloud-drive/providers/openlist.js +535 -0
- package/lib/sources/cloud-drive/registry.d.ts +53 -0
- package/lib/sources/cloud-drive/registry.js +101 -0
- package/lib/sources/cloud-drive/types.d.ts +169 -0
- package/lib/sources/cloud-drive/types.js +17 -0
- package/lib/sources/file.d.ts +2 -0
- package/lib/sources/file.js +2 -0
- package/lib/sources/local-agent/adapters/claude-code.d.ts +16 -0
- package/lib/sources/local-agent/adapters/claude-code.js +171 -0
- package/lib/sources/local-agent/adapters/codex.d.ts +20 -0
- package/lib/sources/local-agent/adapters/codex.js +204 -0
- package/lib/sources/local-agent/adapters/cursor.d.ts +23 -0
- package/lib/sources/local-agent/adapters/cursor.js +121 -0
- package/lib/sources/local-agent/adapters/gemini-cli.d.ts +20 -0
- package/lib/sources/local-agent/adapters/gemini-cli.js +141 -0
- package/lib/sources/local-agent/adapters/grokbuild.d.ts +22 -0
- package/lib/sources/local-agent/adapters/grokbuild.js +76 -0
- package/lib/sources/local-agent/adapters/hermes.d.ts +19 -0
- package/lib/sources/local-agent/adapters/hermes.js +85 -0
- package/lib/sources/local-agent/adapters/kimi.d.ts +28 -0
- package/lib/sources/local-agent/adapters/kimi.js +261 -0
- package/lib/sources/local-agent/adapters/openclaw.d.ts +21 -0
- package/lib/sources/local-agent/adapters/openclaw.js +120 -0
- package/lib/sources/local-agent/adapters/opencode.d.ts +44 -0
- package/lib/sources/local-agent/adapters/opencode.js +236 -0
- package/lib/sources/local-agent/adapters/pi.d.ts +26 -0
- package/lib/sources/local-agent/adapters/pi.js +242 -0
- package/lib/sources/local-agent/adapters/qoder.d.ts +19 -0
- package/lib/sources/local-agent/adapters/qoder.js +93 -0
- package/lib/sources/local-agent/adapters/reasonix.d.ts +23 -0
- package/lib/sources/local-agent/adapters/reasonix.js +90 -0
- package/lib/sources/local-agent/adapters/shared.d.ts +163 -0
- package/lib/sources/local-agent/adapters/shared.js +415 -0
- package/lib/sources/local-agent/adapters/sqlite-shared.d.ts +92 -0
- package/lib/sources/local-agent/adapters/sqlite-shared.js +215 -0
- package/lib/sources/local-agent/adapters/zcode.d.ts +41 -0
- package/lib/sources/local-agent/adapters/zcode.js +159 -0
- package/lib/sources/local-agent/index.d.ts +222 -0
- package/lib/sources/local-agent/index.js +656 -0
- package/lib/sources/local-agent/page.d.ts +55 -0
- package/lib/sources/local-agent/page.js +83 -0
- package/lib/sources/local-agent/path.d.ts +8 -0
- package/lib/sources/local-agent/path.js +43 -0
- package/lib/sources/local-agent/registry.d.ts +68 -0
- package/lib/sources/local-agent/registry.js +116 -0
- package/lib/sources/local-agent/scan.d.ts +175 -0
- package/lib/sources/local-agent/scan.js +472 -0
- package/lib/sources/local-agent/sqlite.d.ts +45 -0
- package/lib/sources/local-agent/sqlite.js +113 -0
- package/lib/sources/local-agent/store.d.ts +93 -0
- package/lib/sources/local-agent/store.js +113 -0
- package/lib/sources/local-agent/types.d.ts +338 -0
- package/lib/sources/local-agent/types.js +20 -0
- package/lib/sources/web-chat/index.d.ts +5 -1
- package/lib/sources/web-chat/index.js +47 -8
- package/lib/store/spec.d.ts +50 -4
- package/lib/store/spec.js +2 -2
- package/lib/sync/index.d.ts +1 -0
- package/lib/sync/index.js +28 -8
- package/lib/tool.d.ts +5 -0
- package/lib/tool.js +418 -45
- package/lib/typert.js +13 -2
- package/lib/types.d.ts +28 -2
- package/lib/update.d.ts +6 -0
- package/lib/update.js +29 -1
- package/lib/web.js +5 -1
- package/lib/wire.d.ts +80 -9
- package/lib/wire.js +30 -4
- package/opencli-plugin/chatgpt.js +30 -27
- package/opencli-plugin/claude.js +12 -8
- package/opencli-plugin/common.js +266 -187
- package/opencli-plugin/deepseek.js +8 -5
- package/opencli-plugin/gemini.js +42 -38
- package/opencli-plugin/grok.js +22 -21
- package/opencli-plugin/kimi.js +34 -31
- package/opencli-plugin/opencli-plugin.json +6 -6
- package/opencli-plugin/package.json +8 -8
- package/package.json +182 -167
- package/lib/profile-mode.d.ts +0 -30
- package/lib/profile-mode.js +0 -90
package/lib/host.js
CHANGED
|
@@ -1,7 +1,65 @@
|
|
|
1
1
|
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
2
|
-
import {
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { validateDownloadDirectory } from "./download-directory.js";
|
|
3
5
|
export class ReferenceAnythingRemote extends TypertRemoteService {
|
|
4
6
|
constructor(ctx) { super(ctx, 'referenceAnything'); }
|
|
7
|
+
/**
|
|
8
|
+
* Discovery for the `@` menu's local-agent group.
|
|
9
|
+
*
|
|
10
|
+
* Resolved optionally rather than injected: `reference-anything-web` must
|
|
11
|
+
* still load in a profile where the local-agent plugin is switched off, and an
|
|
12
|
+
* absent service means an empty group, not a broken composer. Workspace
|
|
13
|
+
* scoping takes the session's own cwd, so a Web server serving projects from
|
|
14
|
+
* anywhere still scopes to the project the user is in.
|
|
15
|
+
*/
|
|
16
|
+
async agentSearch(agent, input, signal) {
|
|
17
|
+
const agents = this.ctx.get('referenceLocalAgents');
|
|
18
|
+
if (agents === undefined)
|
|
19
|
+
return [];
|
|
20
|
+
// A session with no cwd of its own is rare and has no workspace to scope
|
|
21
|
+
// to; falling back to the process's directory is the same guess the
|
|
22
|
+
// `ReferenceSource.list` seam makes, rather than an empty group.
|
|
23
|
+
const rows = await agents.listForWorkspace(input.query, input.limit, agent.session.header.cwd ?? process.cwd(), signal);
|
|
24
|
+
return rows.map(row => ({
|
|
25
|
+
id: row.ref.id,
|
|
26
|
+
kind: row.ref.id.slice(0, row.ref.id.indexOf(':')),
|
|
27
|
+
label: row.label,
|
|
28
|
+
provider: row.provider ?? '',
|
|
29
|
+
...row.updatedAt === undefined ? {} : { updatedAt: row.updatedAt },
|
|
30
|
+
...row.searchIncomplete === true ? { searchIncomplete: true } : {},
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
async agentStats(signal) {
|
|
34
|
+
signal.throwIfAborted();
|
|
35
|
+
const agents = this.ctx.get('referenceLocalAgents');
|
|
36
|
+
if (agents === undefined)
|
|
37
|
+
return [];
|
|
38
|
+
agents.setAgentDirectories(this.ctx.referenceChatHistory.getSettings().agentDirectories ?? {});
|
|
39
|
+
return (await agents.stats(signal)).map(row => ({ agent: row.kind, conversations: row.conversations }));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Discovery for the `@` menu's cloud-drive group.
|
|
43
|
+
*
|
|
44
|
+
* Optional for the same reason as `agentSearch` above. It takes no session:
|
|
45
|
+
* a drive is not scoped to a workspace, so there is nothing a cwd could
|
|
46
|
+
* narrow. `list` already refuses to name a download URL, so what comes back
|
|
47
|
+
* here is a label and a display path.
|
|
48
|
+
*/
|
|
49
|
+
async driveSearch(input, signal) {
|
|
50
|
+
const drives = this.ctx.get('referenceCloudDrive');
|
|
51
|
+
if (drives === undefined)
|
|
52
|
+
return [];
|
|
53
|
+
const rows = await drives.pickerList(input.query, input.limit, signal);
|
|
54
|
+
return rows.map(row => ({
|
|
55
|
+
id: row.ref.id,
|
|
56
|
+
label: row.label,
|
|
57
|
+
provider: row.provider ?? '',
|
|
58
|
+
...row.origin === undefined ? {} : { origin: row.origin },
|
|
59
|
+
...row.updatedAt === undefined ? {} : { updatedAt: row.updatedAt },
|
|
60
|
+
...row.isDirectory === true ? { isDirectory: true } : {},
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
5
63
|
search(input, signal) {
|
|
6
64
|
signal.throwIfAborted();
|
|
7
65
|
return this.ctx.referenceChatHistory.search(input.query, input.provider, input.limit, signal);
|
|
@@ -16,19 +74,6 @@ export class ReferenceAnythingRemote extends TypertRemoteService {
|
|
|
16
74
|
updateStatus(signal) { return this.ctx.referenceChatHistory.updateStatus(signal); }
|
|
17
75
|
checkUpdate(signal) { return this.ctx.referenceChatHistory.checkUpdate(signal); }
|
|
18
76
|
installUpdate(signal) { return this.ctx.referenceChatHistory.installUpdate(signal); }
|
|
19
|
-
async switchReferenceUiMode(input, signal) {
|
|
20
|
-
const settings = this.ctx.referenceChatHistory.getSettings();
|
|
21
|
-
const previousMode = settings.referenceUiMode ?? 'plugin';
|
|
22
|
-
const result = await switchProfileReferenceUiMode({ mode: input.mode, signal });
|
|
23
|
-
try {
|
|
24
|
-
await this.ctx.referenceChatHistory.updateSettings({ ...settings, referenceUiMode: input.mode });
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
await switchProfileReferenceUiMode({ mode: previousMode }).catch(() => undefined);
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
32
77
|
stats() { return this.ctx.referenceChatHistory.stats(); }
|
|
33
78
|
syncStart(input) {
|
|
34
79
|
return this.ctx.referenceChatHistory.sync.start(input.providers, input.mode);
|
|
@@ -36,7 +81,24 @@ export class ReferenceAnythingRemote extends TypertRemoteService {
|
|
|
36
81
|
syncStatus(input) { return this.ctx.referenceChatHistory.sync.status(input.jobId); }
|
|
37
82
|
syncCancel(input) { return this.ctx.referenceChatHistory.sync.cancel(input.jobId); }
|
|
38
83
|
settingsGet() { return this.ctx.referenceChatHistory.getSettings(); }
|
|
39
|
-
settingsUpdate(settings) {
|
|
84
|
+
async settingsUpdate(settings) {
|
|
85
|
+
const value = await this.ctx.referenceChatHistory.updateSettings(settings);
|
|
86
|
+
this.ctx.get('referenceLocalAgents')?.setAgentDirectories(value.agentDirectories ?? {});
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
async openDownloadDirectory(signal) {
|
|
90
|
+
signal.throwIfAborted();
|
|
91
|
+
const configured = this.ctx.referenceChatHistory.getSettings().cloudDriveDownloadDirectory;
|
|
92
|
+
const path = configured || tmpdir();
|
|
93
|
+
await validateDownloadDirectory(path);
|
|
94
|
+
const child = process.platform === 'win32'
|
|
95
|
+
? spawn('explorer.exe', [path], { detached: true, stdio: 'ignore' })
|
|
96
|
+
: process.platform === 'darwin'
|
|
97
|
+
? spawn('open', [path], { detached: true, stdio: 'ignore' })
|
|
98
|
+
: spawn('xdg-open', [path], { detached: true, stdio: 'ignore' });
|
|
99
|
+
child.unref();
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
40
102
|
browse(input, signal) {
|
|
41
103
|
signal.throwIfAborted();
|
|
42
104
|
return this.ctx.referenceChatHistory.browse(input.query, input.provider, input.limit, input.offset);
|
|
@@ -50,10 +112,6 @@ export class ReferenceAnythingRemote extends TypertRemoteService {
|
|
|
50
112
|
signal.throwIfAborted();
|
|
51
113
|
return this.ctx.referenceChatHistory.removeProvider(input.provider);
|
|
52
114
|
}
|
|
53
|
-
clearOlder(input, signal) {
|
|
54
|
-
signal.throwIfAborted();
|
|
55
|
-
return this.ctx.referenceChatHistory.removeOlderThan(input.days);
|
|
56
|
-
}
|
|
57
115
|
clearRemoteMissing(signal) {
|
|
58
116
|
signal.throwIfAborted();
|
|
59
117
|
return this.ctx.referenceChatHistory.removeRemoteMissing();
|
|
@@ -63,4 +121,15 @@ export class ReferenceAnythingRemote extends TypertRemoteService {
|
|
|
63
121
|
return this.ctx.referenceChatHistory.removeOldAccounts();
|
|
64
122
|
}
|
|
65
123
|
syncStates() { return this.ctx.referenceChatHistory.syncStates(); }
|
|
124
|
+
openListStatus(signal) { return this.ctx.openListManager.status(signal); }
|
|
125
|
+
openListInstall(signal) { return this.ctx.openListManager.install(signal); }
|
|
126
|
+
openListUpgrade(input, signal) { return this.ctx.openListManager.upgrade(input, signal); }
|
|
127
|
+
openListConnectExternal(input, signal) { return this.ctx.openListManager.connectExternal(input, signal); }
|
|
128
|
+
openListDisconnect(signal) { return this.ctx.openListManager.disconnect(signal); }
|
|
129
|
+
openListDrivers(signal) { return this.ctx.openListManager.drivers(signal); }
|
|
130
|
+
openListMounts(signal) { return this.ctx.openListManager.mounts(signal); }
|
|
131
|
+
openListCreateMount(input, signal) { return this.ctx.openListManager.createMount(input, signal); }
|
|
132
|
+
async openListDisableMount(input, signal) { await this.ctx.openListManager.disableMount(input.id, input.disabled ?? true, signal); return true; }
|
|
133
|
+
async openListRemoveMount(input, signal) { await this.ctx.openListManager.removeMount(input.id, signal); return true; }
|
|
134
|
+
openListReindex(input, signal) { return this.ctx.openListManager.reindexMount(input.id, signal); }
|
|
66
135
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* @module dsh-reference-anything
|
|
11
11
|
*/
|
|
12
12
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
13
|
+
import type { Session } from '@deepseek-ai/dsh-session';
|
|
13
14
|
import z from '@deepseek-ai/schemastery';
|
|
14
15
|
import type { ReferenceRef, ReferenceSnapshot, ReferenceSource, ReferenceSummary, ReferenceWindow } from './types.ts';
|
|
15
16
|
export type * from './types.ts';
|
|
@@ -52,10 +53,29 @@ export default class ReferenceRuntime extends Service {
|
|
|
52
53
|
registerSource(source: ReferenceSource): () => void;
|
|
53
54
|
/** @returns the registered source ids, in registration order. */
|
|
54
55
|
sourceIds(): string[];
|
|
55
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Whether a source's items may only be read after an explicit grant.
|
|
58
|
+
*
|
|
59
|
+
* An unregistered source fails closed: registration order must never decide
|
|
60
|
+
* authorization, and `read()` would reject it as `SOURCE_UNKNOWN` a moment
|
|
61
|
+
* later anyway.
|
|
62
|
+
*/
|
|
63
|
+
private gated;
|
|
64
|
+
/**
|
|
65
|
+
* Qualify the grant by source.
|
|
66
|
+
*
|
|
67
|
+
* Two sources can mint the same opaque id, and an unqualified key would let
|
|
68
|
+
* a grant for one silently authorize the other. NUL cannot occur in either
|
|
69
|
+
* half, so the join is unambiguous.
|
|
70
|
+
*/
|
|
71
|
+
private static grantKey;
|
|
72
|
+
private static contentMentions;
|
|
73
|
+
/** Authorize one task to read a gated reference named by its user or discovery tool. */
|
|
56
74
|
grant(sessionId: string, ref: ReferenceRef): void;
|
|
57
|
-
/** Enforce that a
|
|
75
|
+
/** Enforce that a gated URI originated in this task's mention/list surface. */
|
|
58
76
|
assertGranted(sessionId: string | undefined, ref: ReferenceRef): void;
|
|
77
|
+
/** Allow a gated URI granted live or named in this task's durable user/tool-result history. */
|
|
78
|
+
assertSessionGranted(session: Session | undefined, ref: ReferenceRef): void;
|
|
59
79
|
/** Release task-local grants when a host knows the task is gone. */
|
|
60
80
|
revoke(sessionId: string): void;
|
|
61
81
|
/**
|
package/lib/index.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
13
13
|
import z from '@deepseek-ai/schemastery';
|
|
14
14
|
import { ReferenceAnythingError } from "./errors.js";
|
|
15
|
+
import { mayContainReference, parseReferenceText } from "./uri.js";
|
|
15
16
|
export { ReferenceAnythingError } from "./errors.js";
|
|
16
17
|
export { REFERENCE_SCHEME, decodeReferenceUri, encodeReferenceUri, formatReferenceMention, mayContainReference, parseReferenceText, } from "./uri.js";
|
|
17
18
|
/** Fan-out cap applied to discovery when a caller names no limit of its own. */
|
|
@@ -57,22 +58,71 @@ export default class ReferenceRuntime extends Service {
|
|
|
57
58
|
sourceIds() {
|
|
58
59
|
return [...this.sources.keys()];
|
|
59
60
|
}
|
|
60
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Whether a source's items may only be read after an explicit grant.
|
|
63
|
+
*
|
|
64
|
+
* An unregistered source fails closed: registration order must never decide
|
|
65
|
+
* authorization, and `read()` would reject it as `SOURCE_UNKNOWN` a moment
|
|
66
|
+
* later anyway.
|
|
67
|
+
*/
|
|
68
|
+
gated(source) {
|
|
69
|
+
const registered = this.sources.get(source);
|
|
70
|
+
return registered === undefined || registered.requiresGrant === true;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Qualify the grant by source.
|
|
74
|
+
*
|
|
75
|
+
* Two sources can mint the same opaque id, and an unqualified key would let
|
|
76
|
+
* a grant for one silently authorize the other. NUL cannot occur in either
|
|
77
|
+
* half, so the join is unambiguous.
|
|
78
|
+
*/
|
|
79
|
+
static grantKey(ref) { return `${ref.source}\u0000${ref.id}`; }
|
|
80
|
+
static contentMentions(blocks, key) {
|
|
81
|
+
return blocks.some(block => {
|
|
82
|
+
if (block.type === 'tool-result')
|
|
83
|
+
return ReferenceRuntime.contentMentions(block.content, key);
|
|
84
|
+
if (block.type !== 'text' || !mayContainReference(block.text))
|
|
85
|
+
return false;
|
|
86
|
+
try {
|
|
87
|
+
return parseReferenceText(block.text).references.some(input => ReferenceRuntime.grantKey(input.ref) === key);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** Authorize one task to read a gated reference named by its user or discovery tool. */
|
|
61
95
|
grant(sessionId, ref) {
|
|
62
|
-
|
|
63
|
-
|
|
96
|
+
// Recorded unconditionally: a source may register after the mention that
|
|
97
|
+
// named it, and a grant for an ungated source is merely inert.
|
|
64
98
|
const values = this.grants.get(sessionId) ?? new Set();
|
|
65
|
-
values.add(ref
|
|
99
|
+
values.add(ReferenceRuntime.grantKey(ref));
|
|
66
100
|
this.grants.set(sessionId, values);
|
|
67
101
|
}
|
|
68
|
-
/** Enforce that a
|
|
102
|
+
/** Enforce that a gated URI originated in this task's mention/list surface. */
|
|
69
103
|
assertGranted(sessionId, ref) {
|
|
70
|
-
if (ref.source
|
|
104
|
+
if (!this.gated(ref.source))
|
|
71
105
|
return;
|
|
72
|
-
if (!sessionId || !this.grants.get(sessionId)?.has(ref
|
|
106
|
+
if (!sessionId || !this.grants.get(sessionId)?.has(ReferenceRuntime.grantKey(ref))) {
|
|
73
107
|
throw new ReferenceAnythingError('this task has not been granted access to that conversation reference', 'CONVERSATION_REFERENCE_NOT_GRANTED');
|
|
74
108
|
}
|
|
75
109
|
}
|
|
110
|
+
/** Allow a gated URI granted live or named in this task's durable user/tool-result history. */
|
|
111
|
+
assertSessionGranted(session, ref) {
|
|
112
|
+
const key = ReferenceRuntime.grantKey(ref);
|
|
113
|
+
if (session && !this.grants.get(String(session.id))?.has(key)) {
|
|
114
|
+
const mentioned = session.events.some(event => {
|
|
115
|
+
if (event.type === 'user/message')
|
|
116
|
+
return ReferenceRuntime.contentMentions(event.data.content, key);
|
|
117
|
+
if (event.type === 'tool/result')
|
|
118
|
+
return ReferenceRuntime.contentMentions(event.data.message.content, key);
|
|
119
|
+
return false;
|
|
120
|
+
});
|
|
121
|
+
if (mentioned)
|
|
122
|
+
this.grant(String(session.id), ref);
|
|
123
|
+
}
|
|
124
|
+
this.assertGranted(session && String(session.id), ref);
|
|
125
|
+
}
|
|
76
126
|
/** Release task-local grants when a host knows the task is gone. */
|
|
77
127
|
revoke(sessionId) { this.grants.delete(sessionId); }
|
|
78
128
|
/**
|
package/lib/opencli.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { ChatProvider } from './store/spec.ts';
|
|
2
2
|
import type { ProviderConversationRow, ProviderTurnRow } from './store/store.ts';
|
|
3
3
|
export declare const MIN_OPENCLI_VERSION = "1.8.6";
|
|
4
|
-
export declare const MIN_ADAPTER_VERSION = "0.2.
|
|
4
|
+
export declare const MIN_ADAPTER_VERSION = "0.2.3";
|
|
5
|
+
/** Allows a 25 MiB attachment to cross JSON/base64 stdout with bounded headroom. */
|
|
6
|
+
export declare const DEFAULT_OPENCLI_MAX_STDOUT_BYTES: number;
|
|
5
7
|
export declare const OPENCLI_NPM_PACKAGE = "@jackwener/opencli";
|
|
6
|
-
export type OpenCliErrorCode = 'EXTENSION_NOT_CONNECTED' | 'PROVIDER_TIMEOUT' | 'PROVIDER_NOT_LOGGED_IN' | 'PROVIDER_ACCOUNT_MISMATCH' | 'OPENCLI_CONFIGURATION' | 'OPENCLI_OUTPUT_TOO_LARGE' | 'OPENCLI_FAILED' | 'OPENCLI_INSTALL_FAILED';
|
|
8
|
+
export type OpenCliErrorCode = 'EXTENSION_NOT_CONNECTED' | 'PROVIDER_TIMEOUT' | 'PROVIDER_NOT_LOGGED_IN' | 'PROVIDER_ACCOUNT_MISMATCH' | 'PROVIDER_RATE_LIMIT' | 'OPENCLI_CONFIGURATION' | 'OPENCLI_OUTPUT_TOO_LARGE' | 'OPENCLI_FAILED' | 'OPENCLI_INSTALL_FAILED' | 'ATTACHMENT_TOO_LARGE';
|
|
7
9
|
export declare class OpenCliError extends Error {
|
|
8
10
|
readonly code: OpenCliErrorCode;
|
|
9
11
|
constructor(message: string, code: OpenCliErrorCode, options?: ErrorOptions);
|
|
@@ -95,7 +97,7 @@ export declare class OpenCliRunner {
|
|
|
95
97
|
/** Resolve account identity and history through one OpenCLI browser command. */
|
|
96
98
|
syncIndex(provider: ChatProvider, signal?: AbortSignal, since?: string, accountScope?: string): Promise<ProviderIndex>;
|
|
97
99
|
detail(provider: ChatProvider, id: string, signal?: AbortSignal, accountScope?: string): Promise<ProviderTurnRow[]>;
|
|
98
|
-
attachment(provider: ChatProvider, locator: string, output: string, maxBytes: number, signal?: AbortSignal): Promise<Record<string, unknown>>;
|
|
100
|
+
attachment(provider: ChatProvider, locator: string, output: string, maxBytes: number, signal?: AbortSignal, accountScope?: string): Promise<Record<string, unknown>>;
|
|
99
101
|
health(signal?: AbortSignal): Promise<OpenCliHealth>;
|
|
100
102
|
/** Inspect local OpenCLI, daemon, extension and plugin state without an active connectivity probe. */
|
|
101
103
|
quickHealth(signal?: AbortSignal): Promise<OpenCliHealth>;
|
package/lib/opencli.js
CHANGED
|
@@ -5,7 +5,9 @@ import { promisify } from 'node:util';
|
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
const execFile = promisify(nodeExecFile);
|
|
7
7
|
export const MIN_OPENCLI_VERSION = '1.8.6';
|
|
8
|
-
export const MIN_ADAPTER_VERSION = '0.2.
|
|
8
|
+
export const MIN_ADAPTER_VERSION = '0.2.3';
|
|
9
|
+
/** Allows a 25 MiB attachment to cross JSON/base64 stdout with bounded headroom. */
|
|
10
|
+
export const DEFAULT_OPENCLI_MAX_STDOUT_BYTES = 40 * 1024 * 1024;
|
|
9
11
|
export const OPENCLI_NPM_PACKAGE = '@jackwener/opencli';
|
|
10
12
|
const SITE = {
|
|
11
13
|
chatgpt: 'dsh-chatgpt', claude: 'dsh-claude', gemini: 'dsh-gemini',
|
|
@@ -55,7 +57,7 @@ export class OpenCliRunner {
|
|
|
55
57
|
this.profile = options.profile || '';
|
|
56
58
|
this.prefixArgs = options.prefixArgs ?? [];
|
|
57
59
|
this.timeoutMs = options.timeoutMs ?? 60_000;
|
|
58
|
-
this.maxStdoutBytes = options.maxStdoutBytes ??
|
|
60
|
+
this.maxStdoutBytes = options.maxStdoutBytes ?? DEFAULT_OPENCLI_MAX_STDOUT_BYTES;
|
|
59
61
|
}
|
|
60
62
|
async whoami(provider, signal) {
|
|
61
63
|
const rows = await this.json(SITE[provider], 'whoami', [], signal);
|
|
@@ -107,8 +109,11 @@ export class OpenCliRunner {
|
|
|
107
109
|
};
|
|
108
110
|
});
|
|
109
111
|
}
|
|
110
|
-
async attachment(provider, locator, output, maxBytes, signal) {
|
|
111
|
-
const rows = await this.json(SITE[provider], 'attachment', [
|
|
112
|
+
async attachment(provider, locator, output, maxBytes, signal, accountScope = '') {
|
|
113
|
+
const rows = await this.json(SITE[provider], 'attachment', [
|
|
114
|
+
locator, '--output', output, '--maxBytes', String(maxBytes),
|
|
115
|
+
...(accountScope ? ['--accountScope', accountScope] : []),
|
|
116
|
+
], signal);
|
|
112
117
|
return rows[0] ?? {};
|
|
113
118
|
}
|
|
114
119
|
async health(signal) {
|
|
@@ -246,8 +251,10 @@ export class OpenCliRunner {
|
|
|
246
251
|
const stderr = String(detail.stderr || detail.stdout || '').trim().slice(0, 2_000);
|
|
247
252
|
const exit = typeof detail.code === 'number' ? detail.code : undefined;
|
|
248
253
|
const code = stderr.includes('DSH_ACCOUNT_SCOPE_MISMATCH') ? 'PROVIDER_ACCOUNT_MISMATCH'
|
|
249
|
-
:
|
|
250
|
-
:
|
|
254
|
+
: stderr.includes('DSH_PROVIDER_RATE_LIMIT') ? 'PROVIDER_RATE_LIMIT'
|
|
255
|
+
: stderr.includes('DSH_ATTACHMENT_TOO_LARGE') ? 'ATTACHMENT_TOO_LARGE'
|
|
256
|
+
: exit === 69 ? 'EXTENSION_NOT_CONNECTED' : exit === 75 ? 'PROVIDER_TIMEOUT'
|
|
257
|
+
: exit === 77 ? 'PROVIDER_NOT_LOGGED_IN' : exit === 78 ? 'OPENCLI_CONFIGURATION' : 'OPENCLI_FAILED';
|
|
251
258
|
throw new OpenCliError(stderr || `OpenCLI exited with ${String(detail.code)}`, code, { cause: error });
|
|
252
259
|
}
|
|
253
260
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { chmod, copyFile, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
3
|
+
/** Deliberately fixed; upgrades are a conscious manifest change. */
|
|
4
|
+
export declare const OPENLIST_FIXED_VERSION = "v4.2.2";
|
|
5
|
+
export declare const OPENLIST_CREDENTIAL_DIRECTORY: string;
|
|
6
|
+
export declare const OPENLIST_CREDENTIAL_FILE: string;
|
|
7
|
+
export interface OpenListAsset {
|
|
8
|
+
readonly platform: string;
|
|
9
|
+
readonly arch: string;
|
|
10
|
+
readonly url: string;
|
|
11
|
+
readonly sha256: string;
|
|
12
|
+
readonly archive: 'tar.gz' | 'zip';
|
|
13
|
+
readonly binary: string;
|
|
14
|
+
}
|
|
15
|
+
export interface OpenListReleaseManifest {
|
|
16
|
+
readonly version: string;
|
|
17
|
+
readonly assets: readonly OpenListAsset[];
|
|
18
|
+
}
|
|
19
|
+
/** SHA-256 values were computed from the six named GitHub v4.2.2 release assets. */
|
|
20
|
+
export declare const OPENLIST_RELEASE: OpenListReleaseManifest;
|
|
21
|
+
export interface OpenListCredentials {
|
|
22
|
+
readonly endpoint: string;
|
|
23
|
+
readonly username?: string;
|
|
24
|
+
readonly password?: string;
|
|
25
|
+
readonly token?: string;
|
|
26
|
+
readonly mode?: 'managed' | 'external';
|
|
27
|
+
readonly version?: string; /** Host-only cache revision. */
|
|
28
|
+
readonly generation?: number;
|
|
29
|
+
}
|
|
30
|
+
export type OpenListAdminMutationState = 'not-started' | 'committed' | 'unknown';
|
|
31
|
+
/** Safe administration facts. No storage addition, credentials, or raw URLs cross this boundary. */
|
|
32
|
+
export interface OpenListMount {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly driver: string;
|
|
36
|
+
readonly enabled: boolean;
|
|
37
|
+
readonly status?: 'ready' | 'disabled' | 'error';
|
|
38
|
+
readonly error?: string;
|
|
39
|
+
readonly capacityUsed?: number;
|
|
40
|
+
readonly capacityTotal?: number;
|
|
41
|
+
readonly indexStatus?: 'idle' | 'running' | 'complete' | 'failed';
|
|
42
|
+
readonly indexProgress?: number;
|
|
43
|
+
readonly indexCount?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface OpenListDriverField {
|
|
46
|
+
readonly name: string;
|
|
47
|
+
readonly label: string;
|
|
48
|
+
readonly type: 'text' | 'password' | 'number' | 'boolean' | 'select';
|
|
49
|
+
readonly secret: boolean;
|
|
50
|
+
readonly required: boolean;
|
|
51
|
+
readonly hasDefault?: boolean;
|
|
52
|
+
readonly default?: string | number | boolean;
|
|
53
|
+
readonly options?: readonly {
|
|
54
|
+
readonly label: string;
|
|
55
|
+
readonly value: string;
|
|
56
|
+
}[];
|
|
57
|
+
}
|
|
58
|
+
export interface OpenListDriver {
|
|
59
|
+
readonly name: string;
|
|
60
|
+
readonly description?: string;
|
|
61
|
+
readonly quickAuth: boolean;
|
|
62
|
+
readonly fields: readonly OpenListDriverField[];
|
|
63
|
+
}
|
|
64
|
+
/** Host-only mount creation values. `addition` is serialized only on the Host. */
|
|
65
|
+
export interface OpenListMountInput {
|
|
66
|
+
readonly mountPath: string;
|
|
67
|
+
readonly driver: string;
|
|
68
|
+
readonly addition: Record<string, unknown>;
|
|
69
|
+
readonly order?: number;
|
|
70
|
+
readonly remark?: string;
|
|
71
|
+
}
|
|
72
|
+
export interface OpenListSeams {
|
|
73
|
+
readonly fetch?: typeof fetch;
|
|
74
|
+
readonly spawn?: typeof nodeSpawn;
|
|
75
|
+
readonly readFile?: typeof readFile;
|
|
76
|
+
readonly writeFile?: typeof writeFile;
|
|
77
|
+
readonly mkdir?: typeof mkdir;
|
|
78
|
+
readonly chmod?: typeof chmod;
|
|
79
|
+
readonly rename?: typeof rename;
|
|
80
|
+
readonly copyFile?: typeof copyFile;
|
|
81
|
+
readonly unlink?: typeof unlink;
|
|
82
|
+
readonly fsyncFile?: (path: string) => Promise<void>;
|
|
83
|
+
/** Required on Windows to apply a restrictive ACL (for example via icacls). */
|
|
84
|
+
readonly securePermissions?: (path: string, directory: boolean) => Promise<void>;
|
|
85
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
86
|
+
/** Test/integration seam for the executable replacement transaction. */
|
|
87
|
+
readonly install?: typeof installOpenList;
|
|
88
|
+
}
|
|
89
|
+
/** Exchange external username/password for the raw OpenList token. */
|
|
90
|
+
export declare function authenticateOpenList(endpoint: string, username: string, password: string, fetchImpl?: (input: string, init?: RequestInit) => Promise<Response>, signal?: AbortSignal): Promise<string>;
|
|
91
|
+
/** Download only a manifest-pinned binary after validating its SHA-256. */
|
|
92
|
+
export declare function installOpenList(dataRoot: string, manifest?: OpenListReleaseManifest, seams?: OpenListSeams, os?: NodeJS.Platform, cpu?: string, signal?: AbortSignal, retainBackup?: boolean): Promise<{
|
|
93
|
+
version: string;
|
|
94
|
+
path: string;
|
|
95
|
+
finish: (success: boolean) => Promise<void>;
|
|
96
|
+
}>;
|
|
97
|
+
/** Extract only the expected executable; archive paths never reach the filesystem. */
|
|
98
|
+
export declare function unpackOpenListBinary(archive: Uint8Array, asset: OpenListAsset): Uint8Array;
|
|
99
|
+
/** Select exactly the release asset for this OS and CPU architecture. */
|
|
100
|
+
export declare function selectOpenListAsset(manifest: OpenListReleaseManifest, os?: NodeJS.Platform, cpu?: string): OpenListAsset;
|
|
101
|
+
export declare function sha256(bytes: Uint8Array): string;
|
|
102
|
+
export declare function verifyOpenListAsset(bytes: Uint8Array, expected: string): boolean;
|
|
103
|
+
/** Validate an endpoint without leaking its full value in a diagnostic. */
|
|
104
|
+
export declare function validateOpenListEndpoint(value: string, managed?: boolean): string;
|
|
105
|
+
export declare function isLoopback(host: string): boolean;
|
|
106
|
+
/** Ask the kernel for a loopback port. Start immediately afterwards to minimize the normal port race. */
|
|
107
|
+
export declare function chooseOpenListPort(): Promise<number>;
|
|
108
|
+
export declare function readOpenListCredentials(path?: string, seams?: OpenListSeams): Promise<OpenListCredentials | undefined>;
|
|
109
|
+
export declare function writeOpenListCredentials(credentials: OpenListCredentials, path?: string, seams?: OpenListSeams, signal?: AbortSignal): Promise<void>;
|
|
110
|
+
/** A Host-private authenticated API wrapper with redacted errors and safe mount shapes. */
|
|
111
|
+
export declare class OpenListHostClient {
|
|
112
|
+
private credentials;
|
|
113
|
+
private readonly refreshToken?;
|
|
114
|
+
private readonly managed;
|
|
115
|
+
private readonly fetchImpl;
|
|
116
|
+
private readonly secretDefaults;
|
|
117
|
+
constructor(credentials: OpenListCredentials, seams?: OpenListSeams, refreshToken?: ((signal?: AbortSignal) => Promise<OpenListCredentials>) | undefined, managed?: boolean);
|
|
118
|
+
connect(signal?: AbortSignal): Promise<void>;
|
|
119
|
+
drivers(signal?: AbortSignal): Promise<string[]>;
|
|
120
|
+
driverInfo(signal?: AbortSignal): Promise<OpenListDriver[]>;
|
|
121
|
+
fillSecretDefaults(driver: string, supplied: Record<string, unknown>): Record<string, unknown>;
|
|
122
|
+
mounts(signal?: AbortSignal): Promise<OpenListMount[]>;
|
|
123
|
+
createMount(input: OpenListMountInput, signal?: AbortSignal): Promise<OpenListMount>;
|
|
124
|
+
updateMount(id: string, input: OpenListMountInput, signal?: AbortSignal): Promise<OpenListMount>;
|
|
125
|
+
/** Reauthentication patches only credential fields onto the full host-side row. */
|
|
126
|
+
updateMountPatch(id: string, additionPatch: Record<string, unknown>, signal?: AbortSignal): Promise<OpenListMount>;
|
|
127
|
+
removeMount(id: string, signal?: AbortSignal): Promise<void>;
|
|
128
|
+
disableMount(id: string, disabled?: boolean, signal?: AbortSignal): Promise<void>;
|
|
129
|
+
configureManagedIndex(signal?: AbortSignal): Promise<void>;
|
|
130
|
+
updateIndex(mountPath: string, signal?: AbortSignal): Promise<void>;
|
|
131
|
+
reindexMount(_id?: string, signal?: AbortSignal): Promise<{
|
|
132
|
+
supported: true;
|
|
133
|
+
}>;
|
|
134
|
+
indexProgress(signal?: AbortSignal): Promise<Pick<OpenListMount, 'indexStatus' | 'indexProgress' | 'indexCount'> | undefined>;
|
|
135
|
+
private request;
|
|
136
|
+
private mountRows;
|
|
137
|
+
private storageRow;
|
|
138
|
+
private mountRow;
|
|
139
|
+
}
|
|
140
|
+
/** Owns only a process it launched; it never kills a user-managed OpenList. */
|
|
141
|
+
export declare class ManagedOpenListRuntime {
|
|
142
|
+
private readonly seams;
|
|
143
|
+
private child;
|
|
144
|
+
constructor(seams?: OpenListSeams);
|
|
145
|
+
get running(): boolean;
|
|
146
|
+
start(binary: string, dataDirectory: string, port?: number, signal?: AbortSignal): Promise<string>;
|
|
147
|
+
/** Generate an administrator password without exposing it in argv. */
|
|
148
|
+
setAdminPassword(binary: string, dataDirectory: string, _password?: string, signal?: AbortSignal, mutationState?: (state: OpenListAdminMutationState) => void): Promise<string>;
|
|
149
|
+
stop(): Promise<void>;
|
|
150
|
+
}
|
|
151
|
+
/** v4.2.2 reads `scheme.address` and `scheme.http_port` from data/config.json. */
|
|
152
|
+
export declare function writeManagedOpenListConfig(dataDirectory: string, port: number, seams?: OpenListSeams, signal?: AbortSignal): Promise<void>;
|
|
153
|
+
export declare function parseRandomAdminPassword(output: string): string | undefined;
|
|
154
|
+
export declare function isSensitiveDriverField(name: string): boolean;
|
|
155
|
+
export declare function isApiPagesQuickDriver(name: string): boolean;
|
|
156
|
+
/** Recover the only crash window in Windows' two-rename executable replacement. */
|
|
157
|
+
export declare function recoverInterruptedReplacement(target: string, backup: string, marker: string, seams?: OpenListSeams, windows?: boolean, owner?: symbol): Promise<void>;
|