dsh-reference-anything 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,114 +1,114 @@
1
- import { parentPort } from 'node:worker_threads'
2
- import { readFile, readdir, stat } from 'node:fs/promises'
3
- import { join, resolve } from 'node:path'
4
- import { homedir } from 'node:os'
5
- import { readConversation } from './parsers.mjs'
6
- const channel = parentPort || { on: (...args) => process.on(...args), postMessage: value => process.send(value) }
7
- if (!parentPort) process.on('disconnect', () => process.exit(0))
8
-
9
- // Parsing stays off the picker thread. Only states and file fingerprints cross back.
10
- channel.on('message', async ({ id, target }) => {
11
- const dependencies = new Map()
12
- const inspect = async path => {
13
- try {
14
- const s = await stat(path)
15
- dependencies.set(path, `${s.size}:${s.mtimeMs}:${s.ctimeMs}`)
16
- return { type: s.isDirectory() ? 'directory' : 'file', size: s.size, mtimeMs: s.mtimeMs }
17
- } catch (error) {
18
- if (error.code !== 'ENOENT') throw error
19
- dependencies.set(path, 'missing')
20
- return undefined
21
- }
22
- }
23
- const fs = {
24
- resolve: async path => ({ displayPath: resolve(path) }),
25
- processPath: value => value.displayPath,
26
- stat: async value => inspect(value.displayPath),
27
- readText: async value => {
28
- const info = await inspect(value.displayPath)
29
- // shortcut: large sources stay unknown; use streaming probes before raising this background budget.
30
- if (info?.size > 8 * 1024 * 1024) throw new Error('Background validation budget exceeded')
31
- const raw = await readFile(value.displayPath, 'utf8')
32
- // A tolerant converter may skip broken lines; that must never prove emptiness.
33
- if (/\.jsonl$/i.test(value.displayPath)) for (const line of raw.split(/\r?\n/)) if (line.trim()) JSON.parse(line)
34
- return raw
35
- },
36
- listDir: async value => {
37
- await inspect(value.displayPath)
38
- return (await readdir(value.displayPath, { withFileTypes: true })).map(e => ({
39
- name: e.name, type: e.isDirectory() ? 'directory' : 'file', target: { displayPath: join(value.displayPath, e.name) },
40
- }))
41
- },
42
- }
43
- let state = 'unknown'
44
- try {
45
- const info = await inspect(target.path)
46
- if (!info || (info.type === 'file' && info.size === 0)) state = 'empty'
47
- else if (target.format === 'claude' && /\.json$/i.test(target.path)) {
48
- const meta = JSON.parse(await fs.readText({ displayPath: target.path }))
49
- if (meta.cliSessionId && !meta.messages) {
50
- // Match the exact CLI identity; never substitute another chat in the project.
51
- const root = join(homedir(), '.claude', 'projects')
52
- let found = false
53
- const walk = async dir => {
54
- await inspect(dir)
55
- for (const e of await readdir(dir, { withFileTypes: true })) {
56
- if (e.isDirectory()) await walk(join(dir, e.name))
57
- else if (e.name === `${meta.cliSessionId}.jsonl`) found = true
58
- }
59
- }
60
- if (await inspect(root)) await walk(root)
61
- state = found ? 'unknown' : 'empty'
62
- }
63
- } else {
64
- if (info.type === 'directory' && ['grokbuild', 'vibe', 'kimi'].includes(target.format)) {
65
- const names = target.format === 'grokbuild' ? ['chat_history.jsonl']
66
- : target.format === 'vibe' ? ['messages.jsonl'] : ['wire.jsonl', 'agents/main/wire.jsonl']
67
- const bodies = await Promise.all(names.map(name => inspect(join(target.path, name))))
68
- if (bodies.every(body => !body || body.size === 0)) {
69
- channel.postMessage({ id, state: 'empty', dependencies: [...dependencies] })
70
- return
71
- }
72
- }
73
- if (/\.(?:db|vscdb)$/i.test(target.path)) {
74
- await inspect(target.path + '-wal')
75
- await inspect(target.path + '-shm')
76
- const schemas = {
77
- opencode: ['session', 'message'], mimocode: ['session', 'message'], kilocode: ['session', 'message'],
78
- teleagent: ['session', 'message'], zcode: ['session', 'message'],
79
- hermes: ['sessions', 'messages'], goose: ['sessions', 'messages'], crush: ['sessions', 'messages'],
80
- }
81
- let schema = schemas[target.format]
82
- if (schema) {
83
- const { DatabaseSync } = await import('node:sqlite')
84
- const db = new DatabaseSync(target.path, { readOnly: true })
85
- try {
86
- const tables = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map(r => r.name))
87
- if (schema[0] === 'session' && tables.has('session_v2')) schema = ['session_v2', 'session_message']
88
- if (tables.has(schema[0]) && tables.has(schema[1])
89
- && db.prepare(`SELECT 1 FROM ${schema[0]} WHERE id = ? LIMIT 1`).get(target.sessionId)
90
- && !db.prepare(`SELECT 1 FROM ${schema[1]} WHERE session_id = ? LIMIT 1`).get(target.sessionId)) {
91
- channel.postMessage({ id, state: 'empty', dependencies: [...dependencies] })
92
- return
93
- }
94
- } finally { db.close() }
95
- }
96
- }
97
- if (target.format === 'chatgpt') {
98
- const exported = JSON.parse(await fs.readText({ displayPath: target.path }))
99
- const chat = Array.isArray(exported) ? exported.find(c => c.id === target.sessionId) : null
100
- if (chat?.mapping && Object.values(chat.mapping).every(node => node && !node.message)) {
101
- channel.postMessage({ id, state: 'empty', dependencies: [...dependencies] })
102
- return
103
- }
104
- }
105
- state = await readConversation(fs, { ...target, limit: 1, detail: 'full', probe: true })
106
- if (state === 'unknown' && ['pi', 'codex', 'openclaw', 'hermes', 'dsh', 'dsh4'].includes(target.format) && info.type === 'file' && !/\.(db|vscdb|zstd)$/i.test(target.path)) {
107
- const records = (await fs.readText({ displayPath: target.path })).split(/\r?\n/).filter(line => line.trim()).map(line => JSON.parse(line))
108
- const allowed = target.format === 'codex' ? ['session_meta'] : ['session', 'session_info', 'session/title']
109
- if (records.length && records.every(r => allowed.includes(r.type))) state = 'empty'
110
- }
111
- }
112
- } catch { state = 'unknown' }
113
- channel.postMessage({ id, state, dependencies: [...dependencies] })
114
- })
1
+ import { parentPort } from 'node:worker_threads'
2
+ import { readFile, readdir, stat } from 'node:fs/promises'
3
+ import { join, resolve } from 'node:path'
4
+ import { homedir } from 'node:os'
5
+ import { readConversation } from './parsers.mjs'
6
+ const channel = parentPort || { on: (...args) => process.on(...args), postMessage: value => process.send(value) }
7
+ if (!parentPort) process.on('disconnect', () => process.exit(0))
8
+
9
+ // Parsing stays off the picker thread. Only states and file fingerprints cross back.
10
+ channel.on('message', async ({ id, target }) => {
11
+ const dependencies = new Map()
12
+ const inspect = async path => {
13
+ try {
14
+ const s = await stat(path)
15
+ dependencies.set(path, `${s.size}:${s.mtimeMs}:${s.ctimeMs}`)
16
+ return { type: s.isDirectory() ? 'directory' : 'file', size: s.size, mtimeMs: s.mtimeMs }
17
+ } catch (error) {
18
+ if (error.code !== 'ENOENT') throw error
19
+ dependencies.set(path, 'missing')
20
+ return undefined
21
+ }
22
+ }
23
+ const fs = {
24
+ resolve: async path => ({ displayPath: resolve(path) }),
25
+ processPath: value => value.displayPath,
26
+ stat: async value => inspect(value.displayPath),
27
+ readText: async value => {
28
+ const info = await inspect(value.displayPath)
29
+ // shortcut: large sources stay unknown; use streaming probes before raising this background budget.
30
+ if (info?.size > 8 * 1024 * 1024) throw new Error('Background validation budget exceeded')
31
+ const raw = await readFile(value.displayPath, 'utf8')
32
+ // A tolerant converter may skip broken lines; that must never prove emptiness.
33
+ if (/\.jsonl$/i.test(value.displayPath)) for (const line of raw.split(/\r?\n/)) if (line.trim()) JSON.parse(line)
34
+ return raw
35
+ },
36
+ listDir: async value => {
37
+ await inspect(value.displayPath)
38
+ return (await readdir(value.displayPath, { withFileTypes: true })).map(e => ({
39
+ name: e.name, type: e.isDirectory() ? 'directory' : 'file', target: { displayPath: join(value.displayPath, e.name) },
40
+ }))
41
+ },
42
+ }
43
+ let state = 'unknown'
44
+ try {
45
+ const info = await inspect(target.path)
46
+ if (!info || (info.type === 'file' && info.size === 0)) state = 'empty'
47
+ else if (target.format === 'claude' && /\.json$/i.test(target.path)) {
48
+ const meta = JSON.parse(await fs.readText({ displayPath: target.path }))
49
+ if (meta.cliSessionId && !meta.messages) {
50
+ // Match the exact CLI identity; never substitute another chat in the project.
51
+ const root = join(homedir(), '.claude', 'projects')
52
+ let found = false
53
+ const walk = async dir => {
54
+ await inspect(dir)
55
+ for (const e of await readdir(dir, { withFileTypes: true })) {
56
+ if (e.isDirectory()) await walk(join(dir, e.name))
57
+ else if (e.name === `${meta.cliSessionId}.jsonl`) found = true
58
+ }
59
+ }
60
+ if (await inspect(root)) await walk(root)
61
+ state = found ? 'unknown' : 'empty'
62
+ }
63
+ } else {
64
+ if (info.type === 'directory' && ['grokbuild', 'vibe', 'kimi'].includes(target.format)) {
65
+ const names = target.format === 'grokbuild' ? ['chat_history.jsonl']
66
+ : target.format === 'vibe' ? ['messages.jsonl'] : ['wire.jsonl', 'agents/main/wire.jsonl']
67
+ const bodies = await Promise.all(names.map(name => inspect(join(target.path, name))))
68
+ if (bodies.every(body => !body || body.size === 0)) {
69
+ channel.postMessage({ id, state: 'empty', dependencies: [...dependencies] })
70
+ return
71
+ }
72
+ }
73
+ if (/\.(?:db|vscdb)$/i.test(target.path)) {
74
+ await inspect(target.path + '-wal')
75
+ await inspect(target.path + '-shm')
76
+ const schemas = {
77
+ opencode: ['session', 'message'], mimocode: ['session', 'message'], kilocode: ['session', 'message'],
78
+ teleagent: ['session', 'message'], zcode: ['session', 'message'],
79
+ hermes: ['sessions', 'messages'], goose: ['sessions', 'messages'], crush: ['sessions', 'messages'],
80
+ }
81
+ let schema = schemas[target.format]
82
+ if (schema) {
83
+ const { DatabaseSync } = await import('node:sqlite')
84
+ const db = new DatabaseSync(target.path, { readOnly: true })
85
+ try {
86
+ const tables = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map(r => r.name))
87
+ if (schema[0] === 'session' && tables.has('session_v2')) schema = ['session_v2', 'session_message']
88
+ if (tables.has(schema[0]) && tables.has(schema[1])
89
+ && db.prepare(`SELECT 1 FROM ${schema[0]} WHERE id = ? LIMIT 1`).get(target.sessionId)
90
+ && !db.prepare(`SELECT 1 FROM ${schema[1]} WHERE session_id = ? LIMIT 1`).get(target.sessionId)) {
91
+ channel.postMessage({ id, state: 'empty', dependencies: [...dependencies] })
92
+ return
93
+ }
94
+ } finally { db.close() }
95
+ }
96
+ }
97
+ if (target.format === 'chatgpt') {
98
+ const exported = JSON.parse(await fs.readText({ displayPath: target.path }))
99
+ const chat = Array.isArray(exported) ? exported.find(c => c.id === target.sessionId) : null
100
+ if (chat?.mapping && Object.values(chat.mapping).every(node => node && !node.message)) {
101
+ channel.postMessage({ id, state: 'empty', dependencies: [...dependencies] })
102
+ return
103
+ }
104
+ }
105
+ state = await readConversation(fs, { ...target, limit: 1, detail: 'full', probe: true })
106
+ if (state === 'unknown' && ['pi', 'codex', 'openclaw', 'hermes', 'dsh', 'dsh4'].includes(target.format) && info.type === 'file' && !/\.(db|vscdb|zstd)$/i.test(target.path)) {
107
+ const records = (await fs.readText({ displayPath: target.path })).split(/\r?\n/).filter(line => line.trim()).map(line => JSON.parse(line))
108
+ const allowed = target.format === 'codex' ? ['session_meta'] : ['session', 'session_info', 'session/title']
109
+ if (records.length && records.every(r => allowed.includes(r.type))) state = 'empty'
110
+ }
111
+ }
112
+ } catch { state = 'unknown' }
113
+ channel.postMessage({ id, state, dependencies: [...dependencies] })
114
+ })