dsh-reference-anything 0.2.4 → 0.2.5
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 +18 -2
- package/lib/opencli.js +6 -6
- package/opencli-plugin/common.js +33 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,13 @@ One `@` to reference them all.
|
|
|
22
22
|
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
<div align="center">
|
|
26
|
+
|
|
27
|
+
<img src="./images/demo.gif" alt="dsh-reference-anything demo" width="800" />
|
|
28
|
+
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
Within a unified `@` menu of DeepSeek Harness (DSH), reference commands, Skills, workspace files/folders, DSH sessions, and historical conversations from ChatGPT, Claude, Gemini, DeepSeek, Grok, and Kimi.
|
|
26
32
|
|
|
27
33
|
This plugin uses OpenCLI to access historical conversations through AI chat sessions that are already logged in. By default, only conversation titles are stored locally, and the agent fetches remote content on demand. An optional offline-mirror mode stores the latest complete conversation bodies locally.
|
|
28
34
|
|
|
@@ -31,7 +37,7 @@ This plugin uses OpenCLI to access historical conversations through AI chat sess
|
|
|
31
37
|
|
|
32
38
|
## News
|
|
33
39
|
|
|
34
|
-
- **2026-08-19 · v0.2.
|
|
40
|
+
- **2026-08-19 · v0.2.4** — Added automatic version checks and in-settings updates, Pill/Raw text input rendering modes, and reusable background browser sessions for more reliable OpenCLI synchronization and input interactions.
|
|
35
41
|
- **2026-08-18 · v0.2.0** — A redesigned Reference Anything settings page with local session statistics, paginated management, Provider/Profile selection, and sync status checks.
|
|
36
42
|
- **2026-08-18** — Introduced on-demand read protocol: references default to safe pointers, and the agent reads the body and attachments only after authorization.
|
|
37
43
|
- **2026-08-17** — Unified ChatGPT, Claude, Gemini, DeepSeek, Grok, and Kimi under the DSH `@` menu.
|
|
@@ -98,14 +104,20 @@ The `@` menu contains five groups: `Commands`, `Skills`, `Files and folders`, `D
|
|
|
98
104
|
|
|
99
105
|
Available only at the start of the draft. To browse all commands, use `@commands` or the native DSH `/` panel.
|
|
100
106
|
|
|
107
|
+
<p align="center"><img src="./images/at-commands.png" alt="Browse DSH commands from the @ menu" width="800" /></p>
|
|
108
|
+
|
|
101
109
|
#### @Skills — DSH skill library
|
|
102
110
|
|
|
103
111
|
Available only at the start of the draft. To browse all skills, use `@skills:` or the native DSH `/` panel.
|
|
104
112
|
|
|
113
|
+
<p align="center"><img src="./images/at-skills.png" alt="Browse DSH skills from the @ menu" width="800" /></p>
|
|
114
|
+
|
|
105
115
|
#### @Files and folders — workspace files and directories
|
|
106
116
|
|
|
107
117
|
Type `@files:` in the input box to browse all files and folders in the workspace. Search supports fuzzy matching on titles, so both `@cachedes` and `@cache-design` can match “Cache design notes.”
|
|
108
118
|
|
|
119
|
+
<p align="center"><img src="./images/at-files.png" alt="Browse workspace files and folders from the @ menu" width="800" /></p>
|
|
120
|
+
|
|
109
121
|
Features:
|
|
110
122
|
- Quick reference to workspace files with automatic workspace-boundary validation
|
|
111
123
|
- File references only write a validated path and type marker into the model context; file content is not preloaded
|
|
@@ -115,6 +127,8 @@ Features:
|
|
|
115
127
|
|
|
116
128
|
Type `@sessions:` to browse locally synced DSH sessions. Sessions are ranked by match quality, with recency as fallback ordering.
|
|
117
129
|
|
|
130
|
+
<p align="center"><img src="./images/at-sessions.png" alt="Browse DSH sessions from the @ menu" width="800" /></p>
|
|
131
|
+
|
|
118
132
|
Search capabilities:
|
|
119
133
|
- **Title match:** fuzzy search on session titles
|
|
120
134
|
- **Content search:** when title matches are insufficient, the synced session body is searched; matching excerpts are shown in the candidate row for UI display only and are not injected into model context
|
|
@@ -126,6 +140,8 @@ Full browsing is available on the settings page’s paginated list. Session refe
|
|
|
126
140
|
|
|
127
141
|
Supports historical conversations from ChatGPT, Claude, Gemini, DeepSeek, Grok, and Kimi.
|
|
128
142
|
|
|
143
|
+
<p align="center"><img src="./images/at-external-conversations.png" alt="Browse external conversations from the @ menu" width="800" /></p>
|
|
144
|
+
|
|
129
145
|
**Platform filtering:**
|
|
130
146
|
- Use `@chatgpt:cache` or `@claude:refactor` to filter a specific platform
|
|
131
147
|
- Short aliases are also accepted, such as `@gpt:` and `@ds:`
|
package/lib/opencli.js
CHANGED
|
@@ -14,10 +14,10 @@ const SITE = {
|
|
|
14
14
|
const REQUIRED_ADAPTER_COMMANDS = ['chatgpt', 'claude', 'gemini', 'deepseek', 'grok', 'kimi'];
|
|
15
15
|
const ADAPTER_PLUGIN_NAME = 'dsh-chat-history';
|
|
16
16
|
/**
|
|
17
|
-
* A persistent OpenCLI site session owns one mutable browser
|
|
17
|
+
* A persistent OpenCLI site session owns one mutable browser lease. Serialize
|
|
18
18
|
* commands that target the same executable/profile/site across runner
|
|
19
|
-
* instances so concurrent detail workers cannot navigate
|
|
20
|
-
*
|
|
19
|
+
* instances so concurrent detail workers cannot navigate or close each
|
|
20
|
+
* other's active tab. Different provider sites still run in parallel.
|
|
21
21
|
*/
|
|
22
22
|
const SITE_COMMAND_TAILS = new Map();
|
|
23
23
|
export class OpenCliError extends Error {
|
|
@@ -210,9 +210,9 @@ export class OpenCliRunner {
|
|
|
210
210
|
async json(site, operation, args, signal) {
|
|
211
211
|
const queueKey = [this.executable, ...this.prefixArgs, this.profile, site].join('\0');
|
|
212
212
|
return serializeSiteCommand(queueKey, async () => {
|
|
213
|
-
// Pin adapter traffic to one persistent background session.
|
|
214
|
-
//
|
|
215
|
-
//
|
|
213
|
+
// Pin adapter traffic to one persistent background session. Persistence
|
|
214
|
+
// suppresses OpenCLI's close-window action; the adapter closes only its
|
|
215
|
+
// completed task tab and leaves the background container reusable.
|
|
216
216
|
const stdout = await this.raw([
|
|
217
217
|
site, operation, ...args,
|
|
218
218
|
'--site-session', 'persistent', '--window', 'background', '-f', 'json',
|
package/opencli-plugin/common.js
CHANGED
|
@@ -38,7 +38,7 @@ async function evaluate(page, script, args, operation) {
|
|
|
38
38
|
return parseEvaluate(await page.evaluate(source), operation)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function assertResult(result, domain, operation) {
|
|
41
|
+
function assertResult(result, domain, operation) {
|
|
42
42
|
if (result?.code === 'AUTH') throw new AuthRequiredError(domain, result.message || `${operation} requires login`)
|
|
43
43
|
if (result?.code === 'RATE_LIMIT') {
|
|
44
44
|
throw new CommandExecutionError(`${operation}: ${result.message || 'provider rate limit reached'}`)
|
|
@@ -46,9 +46,26 @@ function assertResult(result, domain, operation) {
|
|
|
46
46
|
if (result?.ok !== true || !Array.isArray(result.rows)) {
|
|
47
47
|
throw new CommandExecutionError(`${operation}: ${result?.message || 'provider adapter did not return rows'}`)
|
|
48
48
|
}
|
|
49
|
-
return result.rows
|
|
50
|
-
}
|
|
51
|
-
|
|
49
|
+
return result.rows
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Close only the tab used by this adapter command. The persistent site session
|
|
54
|
+
* prevents OpenCLI from sending its `close-window` lease-release action, while
|
|
55
|
+
* Browser Bridge can keep and reuse the background container window.
|
|
56
|
+
*/
|
|
57
|
+
async function inTemporaryTab(page, task) {
|
|
58
|
+
try {
|
|
59
|
+
return await task()
|
|
60
|
+
} finally {
|
|
61
|
+
try {
|
|
62
|
+
await page.closeTab?.()
|
|
63
|
+
} catch {
|
|
64
|
+
// Preserve the provider result/error if the tab or browser closed first.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
52
69
|
export function registerProvider(config) {
|
|
53
70
|
const browserSession = SYNC_BROWSER_SESSION
|
|
54
71
|
|
|
@@ -64,7 +81,7 @@ export function registerProvider(config) {
|
|
|
64
81
|
navigateBefore: false,
|
|
65
82
|
args: [],
|
|
66
83
|
columns: ['identity'],
|
|
67
|
-
func: async (page) => {
|
|
84
|
+
func: async (page) => inTemporaryTab(page, async () => {
|
|
68
85
|
await page.goto(config.home, { settleMs: 500 })
|
|
69
86
|
const result = await evaluate(page, config.whoamiScript, {}, `${config.site} whoami`)
|
|
70
87
|
if (result?.code === 'AUTH') throw new AuthRequiredError(config.domain)
|
|
@@ -72,7 +89,7 @@ export function registerProvider(config) {
|
|
|
72
89
|
throw new CommandExecutionError(`${config.site} whoami: ${result?.message || 'stable account identity unavailable'}`)
|
|
73
90
|
}
|
|
74
91
|
return [{ identity: result.identity }]
|
|
75
|
-
},
|
|
92
|
+
}),
|
|
76
93
|
})
|
|
77
94
|
|
|
78
95
|
cli({
|
|
@@ -90,7 +107,7 @@ export function registerProvider(config) {
|
|
|
90
107
|
help: 'ISO 8601 instant; stop paging once the listing predates it. Honoured only while the provider pages newest-first',
|
|
91
108
|
}, { name: 'accountScope', help: 'Previously observed hashed account scope; mismatch forces a full listing' }],
|
|
92
109
|
columns: SYNC_INDEX_COLUMNS,
|
|
93
|
-
func: async (page, kwargs) => {
|
|
110
|
+
func: async (page, kwargs) => inTemporaryTab(page, async () => {
|
|
94
111
|
const since = String(kwargs.since || '').trim()
|
|
95
112
|
if (since && Number.isNaN(Date.parse(since))) throw new ArgumentError('since must be an ISO 8601 instant')
|
|
96
113
|
await page.goto(config.home, { settleMs: 1200 })
|
|
@@ -110,7 +127,7 @@ export function registerProvider(config) {
|
|
|
110
127
|
{ kind: 'identity', identity: identity.identity, sinceApplied: effectiveSince },
|
|
111
128
|
...rows.map(row => ({ kind: 'conversation', identity: '', sinceApplied: '', ...row })),
|
|
112
129
|
]
|
|
113
|
-
},
|
|
130
|
+
}),
|
|
114
131
|
})
|
|
115
132
|
|
|
116
133
|
cli({
|
|
@@ -128,7 +145,7 @@ export function registerProvider(config) {
|
|
|
128
145
|
help: 'ISO 8601 instant; stop paging once the listing predates it. Honoured only while the provider pages newest-first',
|
|
129
146
|
}],
|
|
130
147
|
columns: HISTORY_COLUMNS,
|
|
131
|
-
func: async (page, kwargs) => {
|
|
148
|
+
func: async (page, kwargs) => inTemporaryTab(page, async () => {
|
|
132
149
|
const since = String(kwargs.since || '').trim()
|
|
133
150
|
if (since && Number.isNaN(Date.parse(since))) throw new ArgumentError('since must be an ISO 8601 instant')
|
|
134
151
|
await page.goto(config.home, { settleMs: 1200 })
|
|
@@ -138,7 +155,7 @@ export function registerProvider(config) {
|
|
|
138
155
|
`${config.site} history-all`,
|
|
139
156
|
)
|
|
140
157
|
return rows
|
|
141
|
-
},
|
|
158
|
+
}),
|
|
142
159
|
})
|
|
143
160
|
|
|
144
161
|
cli({
|
|
@@ -156,7 +173,7 @@ export function registerProvider(config) {
|
|
|
156
173
|
{ name: 'accountScope', help: 'Expected hashed account scope; mismatch refuses the read' },
|
|
157
174
|
],
|
|
158
175
|
columns: DETAIL_COLUMNS,
|
|
159
|
-
func: async (page, kwargs) => {
|
|
176
|
+
func: async (page, kwargs) => inTemporaryTab(page, async () => {
|
|
160
177
|
const id = String(kwargs.id || '').trim()
|
|
161
178
|
if (!id) throw new ArgumentError('id must be a non-empty conversation id')
|
|
162
179
|
await page.goto(config.home, { settleMs: 600 })
|
|
@@ -180,7 +197,7 @@ export function registerProvider(config) {
|
|
|
180
197
|
const rows = assertResult(result, config.domain, `${config.site} detail`)
|
|
181
198
|
if (rows.length === 0) throw new EmptyResultError(`${config.site} detail`, `No messages found for ${id}`)
|
|
182
199
|
return rows
|
|
183
|
-
},
|
|
200
|
+
}),
|
|
184
201
|
})
|
|
185
202
|
|
|
186
203
|
cli({
|
|
@@ -199,7 +216,7 @@ export function registerProvider(config) {
|
|
|
199
216
|
{ name: 'maxBytes', type: 'int', default: 26214400, help: 'Maximum decoded bytes' },
|
|
200
217
|
],
|
|
201
218
|
columns: ['attachmentId', 'name', 'mimeType', 'size', 'status', 'localPath'],
|
|
202
|
-
func: async (page, kwargs) => {
|
|
219
|
+
func: async (page, kwargs) => inTemporaryTab(page, async () => {
|
|
203
220
|
const locator = String(kwargs.locator || '').trim()
|
|
204
221
|
const output = resolve(String(kwargs.output || ''))
|
|
205
222
|
const maxBytes = Number(kwargs.maxBytes ?? 26_214_400)
|
|
@@ -229,15 +246,14 @@ export function registerProvider(config) {
|
|
|
229
246
|
status: 'available',
|
|
230
247
|
localPath: output,
|
|
231
248
|
}]
|
|
232
|
-
},
|
|
249
|
+
}),
|
|
233
250
|
})
|
|
234
251
|
}
|
|
235
252
|
|
|
236
253
|
/**
|
|
237
254
|
* Keep one stable, background Browser Bridge session per provider site.
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* the same tab. If the user closes it manually, OpenCLI recreates it on demand.
|
|
255
|
+
* Persistence suppresses OpenCLI's `close-window` action; inTemporaryTab closes
|
|
256
|
+
* only the finished command's tab so the reusable container window stays alive.
|
|
241
257
|
*/
|
|
242
258
|
export const SYNC_BROWSER_SESSION = Object.freeze({
|
|
243
259
|
siteSession: 'persistent',
|
package/package.json
CHANGED