@foggy-projects/deepseek-harness-plugin 0.4.0-beta.10

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.
Files changed (42) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +81 -0
  3. package/THIRD-PARTY-RUNTIME-NOTICES.md +16 -0
  4. package/cordis.patch.yml +6 -0
  5. package/docs/PUBLIC-BETA-READINESS.md +57 -0
  6. package/docs/WINDOWS-BETA-ACCEPTANCE.md +75 -0
  7. package/experience/linux/README.md +53 -0
  8. package/experience/linux/prepare.sh +188 -0
  9. package/lib/atomic-json.js +41 -0
  10. package/lib/client.js +453 -0
  11. package/lib/index.js +467 -0
  12. package/lib/python-runtime.js +339 -0
  13. package/lib/remote-descriptor.js +30 -0
  14. package/lib/remote.js +8 -0
  15. package/lib/skill-provider.js +111 -0
  16. package/lib/typert.js +9 -0
  17. package/lib/version.js +20 -0
  18. package/package.json +63 -0
  19. package/skills/foggy-deepseek-onboarding/SKILL.md +137 -0
  20. package/skills/foggy-deepseek-onboarding/assets/connection.schema.json +50 -0
  21. package/skills/foggy-deepseek-onboarding/assets/datasource.example.json +13 -0
  22. package/skills/foggy-deepseek-onboarding/assets/env.example +14 -0
  23. package/skills/foggy-deepseek-onboarding/assets/onboarding-state.schema.json +42 -0
  24. package/skills/foggy-deepseek-onboarding/assets/semantic-plan.example.json +8 -0
  25. package/skills/foggy-deepseek-onboarding/assets/semantic-plan.schema.json +21 -0
  26. package/skills/foggy-deepseek-onboarding/assets/versions.json +137 -0
  27. package/skills/foggy-deepseek-onboarding/references/onboarding-workflow.md +156 -0
  28. package/skills/foggy-deepseek-onboarding/scripts/doctor.ps1 +4 -0
  29. package/skills/foggy-deepseek-onboarding/scripts/doctor.sh +4 -0
  30. package/skills/foggy-deepseek-onboarding/scripts/install.ps1 +4 -0
  31. package/skills/foggy-deepseek-onboarding/scripts/install.sh +4 -0
  32. package/skills/foggy-deepseek-onboarding/scripts/invoke-onboarding.ps1 +30 -0
  33. package/skills/foggy-deepseek-onboarding/scripts/invoke-onboarding.sh +24 -0
  34. package/skills/foggy-deepseek-onboarding/scripts/onboard.ps1 +4 -0
  35. package/skills/foggy-deepseek-onboarding/scripts/onboard.sh +4 -0
  36. package/skills/foggy-deepseek-onboarding/scripts/onboarding.py +3027 -0
  37. package/skills/foggy-deepseek-onboarding/scripts/runtime-start.ps1 +4 -0
  38. package/skills/foggy-deepseek-onboarding/scripts/runtime-start.sh +4 -0
  39. package/skills/foggy-deepseek-onboarding/scripts/runtime-stop.ps1 +4 -0
  40. package/skills/foggy-deepseek-onboarding/scripts/runtime-stop.sh +4 -0
  41. package/skills/foggy-deepseek-onboarding/scripts/uninstall.ps1 +4 -0
  42. package/skills/foggy-deepseek-onboarding/scripts/uninstall.sh +4 -0
@@ -0,0 +1,339 @@
1
+ import { execFile } from 'node:child_process'
2
+ import { createHash } from 'node:crypto'
3
+ import {
4
+ access,
5
+ copyFile,
6
+ mkdir,
7
+ open,
8
+ readFile,
9
+ rename,
10
+ rm,
11
+ stat,
12
+ writeFile,
13
+ } from 'node:fs/promises'
14
+ import { constants as fsConstants } from 'node:fs'
15
+ import { dirname, isAbsolute, join } from 'node:path'
16
+ import { promisify } from 'node:util'
17
+ import { compatible, versionParts } from './version.js'
18
+
19
+ const execFileAsync = promisify(execFile)
20
+ const MARKER_SCHEMA = 'foggy-managed-python/v1'
21
+
22
+ async function exists(path) {
23
+ try {
24
+ await access(path, fsConstants.F_OK)
25
+ return true
26
+ } catch {
27
+ return false
28
+ }
29
+ }
30
+
31
+ async function commandVersion(command) {
32
+ try {
33
+ const { stdout, stderr } = await execFileAsync(command, ['--version'], {
34
+ windowsHide: true,
35
+ timeout: 15_000,
36
+ maxBuffer: 256 * 1024,
37
+ })
38
+ return {
39
+ available: true,
40
+ output: `${stdout}\n${stderr}`.trim().split(/\r?\n/)[0] ?? '',
41
+ command,
42
+ }
43
+ } catch (error) {
44
+ return { available: false, output: '', command, error: error.code ?? 'COMMAND_FAILED' }
45
+ }
46
+ }
47
+
48
+ async function sha256(path) {
49
+ const file = await open(path, 'r')
50
+ const hash = createHash('sha256')
51
+ try {
52
+ for await (const chunk of file.readableWebStream()) hash.update(Buffer.from(chunk))
53
+ } finally {
54
+ await file.close()
55
+ }
56
+ return hash.digest('hex')
57
+ }
58
+
59
+ function pythonComponent(manifest) {
60
+ const component = manifest?.components?.python
61
+ if (!component?.version || !component?.assets) {
62
+ throw new Error('The Foggy component manifest does not define a managed Python runtime')
63
+ }
64
+ return component
65
+ }
66
+
67
+ export function pythonAssetKey(platform = process.platform, arch = process.arch) {
68
+ return `${platform}-${arch}`
69
+ }
70
+
71
+ export function selectPythonAsset(manifest, platform = process.platform, arch = process.arch) {
72
+ const component = pythonComponent(manifest)
73
+ const key = pythonAssetKey(platform, arch)
74
+ const asset = component.assets[key]
75
+ if (!asset) {
76
+ throw new Error(`Managed Python is not published for ${platform}/${arch}; set FOGGY_PYTHON to a compatible Python executable`)
77
+ }
78
+ if (!asset.file || !asset.url || !/^[a-f0-9]{64}$/.test(asset.sha256 || '')) {
79
+ throw new Error(`Managed Python asset ${key} is incomplete or has an invalid SHA256`)
80
+ }
81
+ return { ...asset, key }
82
+ }
83
+
84
+ export function managedPythonHome(installRoot, manifest) {
85
+ return join(installRoot, 'python', pythonComponent(manifest).version)
86
+ }
87
+
88
+ export function managedPythonExecutable(installRoot, manifest, platform = process.platform) {
89
+ const home = managedPythonHome(installRoot, manifest)
90
+ return platform === 'win32' ? join(home, 'python.exe') : join(home, 'bin', 'python3')
91
+ }
92
+
93
+ function markerPath(installRoot, manifest) {
94
+ return join(managedPythonHome(installRoot, manifest), '.foggy-managed-python.json')
95
+ }
96
+
97
+ async function readMarker(installRoot, manifest) {
98
+ try {
99
+ return JSON.parse(await readFile(markerPath(installRoot, manifest), 'utf8'))
100
+ } catch {
101
+ return null
102
+ }
103
+ }
104
+
105
+ function exactVersion(probe, expected) {
106
+ const actualParts = versionParts(probe.output)
107
+ const expectedParts = versionParts(expected)
108
+ return expectedParts.every((part, index) => actualParts[index] === part)
109
+ }
110
+
111
+ export async function probePythonRuntime({ installRoot, manifest, env = process.env } = {}) {
112
+ const minimum = manifest.components.cli.minimumPythonVersion
113
+ if (env.FOGGY_PYTHON) {
114
+ const probe = compatible(await commandVersion(env.FOGGY_PYTHON), minimum)
115
+ const isPython = /^Python\s+\d+/i.test(probe.output)
116
+ return {
117
+ ...probe,
118
+ available: probe.available && isPython,
119
+ source: 'override',
120
+ managed: false,
121
+ path: env.FOGGY_PYTHON,
122
+ version: probe.available && isPython ? versionParts(probe.output).slice(0, 3).join('.') : null,
123
+ error: probe.available && !isPython ? 'Configured executable is not Python' : probe.error,
124
+ }
125
+ }
126
+
127
+ const component = pythonComponent(manifest)
128
+ const command = managedPythonExecutable(installRoot, manifest)
129
+ const marker = await readMarker(installRoot, manifest)
130
+ if (!marker || marker.schemaVersion !== MARKER_SCHEMA || marker.version !== component.version) {
131
+ return {
132
+ available: false,
133
+ detected: false,
134
+ output: '',
135
+ error: 'Managed Python is not installed or its marker is invalid',
136
+ source: 'managed',
137
+ managed: true,
138
+ path: command,
139
+ version: component.version,
140
+ minimum,
141
+ }
142
+ }
143
+ const probe = compatible(await commandVersion(command), minimum)
144
+ const valid = probe.available && exactVersion(probe, component.version)
145
+ return {
146
+ ...probe,
147
+ available: valid,
148
+ detected: probe.detected,
149
+ ...(valid ? {} : { error: probe.error || `Expected Python ${component.version}` }),
150
+ source: 'managed',
151
+ managed: true,
152
+ path: command,
153
+ home: managedPythonHome(installRoot, manifest),
154
+ version: component.version,
155
+ asset: marker.asset,
156
+ }
157
+ }
158
+
159
+ async function safeRenameCorrupt(path) {
160
+ if (!await exists(path)) return null
161
+ const corrupt = `${path}.corrupt-${Date.now()}`
162
+ await rename(path, corrupt)
163
+ return corrupt
164
+ }
165
+
166
+ async function cachedAsset(asset, cacheDirs) {
167
+ for (const directory of cacheDirs) {
168
+ const candidate = join(directory, asset.file)
169
+ try {
170
+ if ((await stat(candidate)).isFile() && await sha256(candidate) === asset.sha256) return candidate
171
+ } catch {}
172
+ }
173
+ return null
174
+ }
175
+
176
+ async function materializeAsset(asset, destination, cacheDirs, onProgress) {
177
+ await mkdir(dirname(destination), { recursive: true })
178
+ if (await exists(destination) && await sha256(destination) === asset.sha256) {
179
+ onProgress?.({ fraction: 0.82, message: 'Using verified managed Python archive', currentFile: asset.file })
180
+ return { path: destination, source: 'existing' }
181
+ }
182
+ await safeRenameCorrupt(destination)
183
+ const temporary = `${destination}.download`
184
+ const cached = await cachedAsset(asset, cacheDirs)
185
+ if (cached) {
186
+ onProgress?.({ fraction: 0.35, message: 'Copying managed Python from verified cache', currentFile: asset.file })
187
+ await rm(temporary, { force: true })
188
+ await copyFile(cached, temporary)
189
+ } else {
190
+ let resumeBytes = 0
191
+ try {
192
+ resumeBytes = (await stat(temporary)).size
193
+ } catch {}
194
+ if (resumeBytes >= Number(asset.size || Number.MAX_SAFE_INTEGER)) {
195
+ if (await sha256(temporary) === asset.sha256) {
196
+ await rename(temporary, destination)
197
+ return { path: destination, source: 'resumed' }
198
+ }
199
+ await rm(temporary, { force: true })
200
+ resumeBytes = 0
201
+ }
202
+ const controller = new AbortController()
203
+ const timeout = setTimeout(() => controller.abort(), 20 * 60_000)
204
+ try {
205
+ const headers = resumeBytes > 0 ? { Range: `bytes=${resumeBytes}-` } : undefined
206
+ const response = await fetch(asset.url, { redirect: 'follow', signal: controller.signal, headers })
207
+ if (!response.ok || !response.body) throw new Error(`Managed Python download failed: HTTP ${response.status}`)
208
+ const resumed = resumeBytes > 0 && response.status === 206
209
+ if (!resumed) {
210
+ await rm(temporary, { force: true })
211
+ resumeBytes = 0
212
+ }
213
+ const expectedSize = Number(asset.size || (Number(response.headers.get('content-length')) + resumeBytes) || 0)
214
+ const file = await open(temporary, resumed ? 'a' : 'w')
215
+ let received = resumeBytes
216
+ try {
217
+ const reader = response.body.getReader()
218
+ while (true) {
219
+ const { done, value } = await reader.read()
220
+ if (done) break
221
+ const chunk = Buffer.from(value)
222
+ await file.write(chunk)
223
+ received += chunk.length
224
+ const fraction = expectedSize > 0 ? Math.min(received / expectedSize, 1) : 0
225
+ onProgress?.({
226
+ fraction: 0.05 + fraction * 0.72,
227
+ message: resumed ? 'Resuming managed Python download' : 'Downloading managed Python',
228
+ currentFile: asset.file,
229
+ bytes: { received, total: expectedSize || null },
230
+ })
231
+ }
232
+ await file.sync()
233
+ } finally {
234
+ await file.close()
235
+ }
236
+ } finally {
237
+ clearTimeout(timeout)
238
+ }
239
+ }
240
+ if (await sha256(temporary) !== asset.sha256) {
241
+ await rm(temporary, { force: true })
242
+ throw new Error(`Managed Python SHA256 mismatch for ${asset.file}`)
243
+ }
244
+ await rename(temporary, destination)
245
+ return { path: destination, source: cached ? 'cache' : 'download' }
246
+ }
247
+
248
+ function validateArchiveEntries(text) {
249
+ const entries = text.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)
250
+ if (!entries.length) throw new Error('Managed Python archive is empty')
251
+ for (const entry of entries) {
252
+ const normalized = entry.replaceAll('\\', '/')
253
+ if (isAbsolute(entry) || normalized.startsWith('/') || normalized.includes('../') || !normalized.startsWith('python/')) {
254
+ throw new Error(`Managed Python archive contains an unsafe entry: ${entry}`)
255
+ }
256
+ }
257
+ }
258
+
259
+ async function extractArchive(archive, installRoot, manifest, asset, onProgress) {
260
+ const pythonRoot = join(installRoot, 'python')
261
+ const target = managedPythonHome(installRoot, manifest)
262
+ const staging = join(pythonRoot, `.staging-${process.pid}-${Date.now()}`)
263
+ const backup = `${target}.previous-${Date.now()}`
264
+ await mkdir(staging, { recursive: true })
265
+ try {
266
+ onProgress?.({ fraction: 0.84, message: 'Inspecting managed Python archive', currentFile: asset.file })
267
+ const listed = await execFileAsync('tar', ['-tzf', archive], {
268
+ windowsHide: true,
269
+ timeout: 2 * 60_000,
270
+ maxBuffer: 16 * 1024 * 1024,
271
+ })
272
+ validateArchiveEntries(listed.stdout)
273
+ onProgress?.({ fraction: 0.88, message: 'Extracting managed Python', currentFile: asset.file })
274
+ await execFileAsync('tar', ['-xzf', archive, '-C', staging], {
275
+ windowsHide: true,
276
+ timeout: 5 * 60_000,
277
+ maxBuffer: 4 * 1024 * 1024,
278
+ })
279
+ const extracted = join(staging, 'python')
280
+ const executable = process.platform === 'win32' ? join(extracted, 'python.exe') : join(extracted, 'bin', 'python3')
281
+ if (!await exists(executable)) throw new Error('Managed Python archive did not contain the expected interpreter')
282
+ await writeFile(join(extracted, '.foggy-managed-python.json'), `${JSON.stringify({
283
+ schemaVersion: MARKER_SCHEMA,
284
+ version: pythonComponent(manifest).version,
285
+ distribution: pythonComponent(manifest).distribution,
286
+ buildRelease: pythonComponent(manifest).buildRelease,
287
+ platform: process.platform,
288
+ arch: process.arch,
289
+ asset: { file: asset.file, sha256: asset.sha256 },
290
+ installedAt: new Date().toISOString(),
291
+ }, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 })
292
+
293
+ let movedPrevious = false
294
+ if (await exists(target)) {
295
+ await rename(target, backup)
296
+ movedPrevious = true
297
+ }
298
+ try {
299
+ await rename(extracted, target)
300
+ } catch (error) {
301
+ if (movedPrevious && !await exists(target)) await rename(backup, target)
302
+ throw error
303
+ }
304
+ if (movedPrevious) await rm(backup, { recursive: true, force: true })
305
+ } finally {
306
+ await rm(staging, { recursive: true, force: true })
307
+ }
308
+ }
309
+
310
+ export async function ensurePythonRuntime({
311
+ installRoot,
312
+ manifest,
313
+ cacheDirs = [],
314
+ force = false,
315
+ env = process.env,
316
+ onProgress,
317
+ } = {}) {
318
+ if (env.FOGGY_PYTHON) {
319
+ const override = await probePythonRuntime({ installRoot, manifest, env })
320
+ if (!override.available) throw new Error(`FOGGY_PYTHON is not compatible: ${override.path}`)
321
+ onProgress?.({ fraction: 1, message: 'Using FOGGY_PYTHON override', currentFile: override.path })
322
+ return override
323
+ }
324
+
325
+ const current = await probePythonRuntime({ installRoot, manifest, env })
326
+ if (current.available && !force) {
327
+ onProgress?.({ fraction: 1, message: 'Managed Python is ready', currentFile: current.path })
328
+ return current
329
+ }
330
+
331
+ const asset = selectPythonAsset(manifest)
332
+ const archive = join(installRoot, 'downloads', 'python', asset.file)
333
+ const materialized = await materializeAsset(asset, archive, cacheDirs, onProgress)
334
+ await extractArchive(materialized.path, installRoot, manifest, asset, onProgress)
335
+ const installed = await probePythonRuntime({ installRoot, manifest, env })
336
+ if (!installed.available) throw new Error(installed.error || 'Managed Python verification failed')
337
+ onProgress?.({ fraction: 1, message: 'Managed Python is ready', currentFile: installed.path })
338
+ return installed
339
+ }
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod'
2
+
3
+ const resultSchema = z.unknown()
4
+
5
+ function descriptor(method) {
6
+ return {
7
+ id: `@foggy-projects/deepseek-harness-plugin#foggyIntegration/${method}`,
8
+ service: 'foggyIntegration',
9
+ namespace: 'foggyIntegration',
10
+ method,
11
+ invocation: { kind: 'direct' },
12
+ parameters: [],
13
+ result: {
14
+ mode: 'strict',
15
+ typeSymbol: '@foggy-projects/deepseek-harness-plugin#FoggyIntegrationResult',
16
+ schema: resultSchema,
17
+ },
18
+ sourceLocation: { file: 'lib/index.js', line: 1, column: 1 },
19
+ }
20
+ }
21
+
22
+ export const descriptors = [
23
+ descriptor('status'),
24
+ descriptor('plan'),
25
+ descriptor('initialize'),
26
+ descriptor('repair'),
27
+ descriptor('repairPython'),
28
+ descriptor('runtimeStart'),
29
+ descriptor('runtimeStop'),
30
+ ]
package/lib/remote.js ADDED
@@ -0,0 +1,8 @@
1
+ import { descriptors } from './remote-descriptor.js'
2
+
3
+ export const TYPERT_REMOTE = {
4
+ package: '@foggy-projects/deepseek-harness-plugin',
5
+ descriptors,
6
+ }
7
+
8
+ export default TYPERT_REMOTE
@@ -0,0 +1,111 @@
1
+ import { access, readFile } from 'node:fs/promises'
2
+ import { constants as fsConstants } from 'node:fs'
3
+ import { dirname, join } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { BUNDLED_SKILL_RANK } from '@deepseek-ai/dsh-skill'
6
+
7
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)))
8
+ const onboardingRoot = join(packageRoot, 'skills', 'foggy-deepseek-onboarding')
9
+ const onboardingPath = join(onboardingRoot, 'SKILL.md')
10
+ const providerName = 'foggy-managed-skills'
11
+ const invocation = { modelInvocable: true, userInvocable: true }
12
+
13
+ async function exists(path) {
14
+ try {
15
+ await access(path, fsConstants.F_OK)
16
+ return true
17
+ } catch {
18
+ return false
19
+ }
20
+ }
21
+
22
+ async function readOptionalJson(path) {
23
+ try {
24
+ return JSON.parse(await readFile(path, 'utf8'))
25
+ } catch {
26
+ return null
27
+ }
28
+ }
29
+
30
+ function parseSkill(source, expectedName) {
31
+ const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/)
32
+ if (!match) throw new Error(`Foggy Skill ${expectedName} has no valid frontmatter`)
33
+ const metadata = Object.fromEntries(match[1].split(/\r?\n/).flatMap((line) => {
34
+ const separator = line.indexOf(':')
35
+ if (separator < 1) return []
36
+ return [[line.slice(0, separator).trim(), line.slice(separator + 1).trim().replace(/^(['\"])(.*)\1$/, '$2')]]
37
+ }))
38
+ if (metadata.name !== expectedName || !metadata.description) {
39
+ throw new Error(`Foggy Skill ${expectedName} has invalid name or description metadata`)
40
+ }
41
+ return { metadata, content: match[2] }
42
+ }
43
+
44
+ function candidate(name, description, path, source) {
45
+ const root = dirname(path)
46
+ return {
47
+ name,
48
+ description,
49
+ invocation,
50
+ provider: providerName,
51
+ source,
52
+ resourceBase: { kind: 'directory', path: root },
53
+ rank: BUNDLED_SKILL_RANK,
54
+ locator: { name, path },
55
+ path,
56
+ }
57
+ }
58
+
59
+ async function managedAnalysisSkill(installRoot, manifest) {
60
+ const state = await readOptionalJson(join(installRoot, 'install-state.json'))
61
+ const path = state?.skills?.analysis?.path
62
+ if (!path || !await exists(join(path, 'SKILL.md'))) return null
63
+ const marker = await readOptionalJson(join(path, '.foggy-managed-skill.json'))
64
+ if (
65
+ marker?.schemaVersion !== 'foggy-managed-skill/v1'
66
+ || marker?.kind !== 'analysis'
67
+ || marker?.componentVersion !== manifest.components.analysisSkill.version
68
+ ) return null
69
+ return join(path, 'SKILL.md')
70
+ }
71
+
72
+ export function createFoggySkillProvider({ installRoot, versionsFile }) {
73
+ return {
74
+ name: providerName,
75
+ async list(options = {}) {
76
+ if (options.signal?.aborted) return []
77
+ const manifest = await readOptionalJson(versionsFile)
78
+ const onboarding = parseSkill(await readFile(onboardingPath, 'utf8'), 'foggy-deepseek-onboarding')
79
+ const result = [candidate(
80
+ 'foggy-deepseek-onboarding',
81
+ onboarding.metadata.description,
82
+ onboardingPath,
83
+ 'bundled',
84
+ )]
85
+ if (!manifest) return result
86
+ const analysisPath = await managedAnalysisSkill(installRoot, manifest)
87
+ if (analysisPath) {
88
+ const analysis = parseSkill(await readFile(analysisPath, 'utf8'), 'foggy-ai-analysis')
89
+ result.push(candidate('foggy-ai-analysis', analysis.metadata.description, analysisPath, 'bundled'))
90
+ }
91
+ return result
92
+ },
93
+ async get(selected, options = {}) {
94
+ if (options.signal?.aborted) return undefined
95
+ const locator = selected?.locator
96
+ if (!locator?.name || !locator?.path || !await exists(locator.path)) return undefined
97
+ const parsed = parseSkill(await readFile(locator.path, 'utf8'), locator.name)
98
+ return {
99
+ name: locator.name,
100
+ description: parsed.metadata.description,
101
+ invocation,
102
+ provider: providerName,
103
+ source: selected.source,
104
+ resourceBase: { kind: 'directory', path: dirname(locator.path) },
105
+ content: parsed.content,
106
+ path: locator.path,
107
+ metadata: parsed.metadata,
108
+ }
109
+ },
110
+ }
111
+ }
package/lib/typert.js ADDED
@@ -0,0 +1,9 @@
1
+ import { descriptors } from './remote-descriptor.js'
2
+
3
+ export const TYPERT = {
4
+ package: '@foggy-projects/deepseek-harness-plugin',
5
+ face: 'host',
6
+ schemas: [],
7
+ invocations: descriptors,
8
+ model: { services: [], events: [], objects: [] },
9
+ }
package/lib/version.js ADDED
@@ -0,0 +1,20 @@
1
+ export function versionParts(text) {
2
+ const match = String(text).match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/)
3
+ return match ? match.slice(1).map((part) => Number(part || 0)) : []
4
+ }
5
+
6
+ export function compatible(probe, minimum) {
7
+ if (!probe.available) return { ...probe, detected: false, minimum }
8
+ const actual = versionParts(probe.output)
9
+ const wanted = versionParts(minimum)
10
+ for (let index = 0; index < wanted.length; index += 1) {
11
+ if ((actual[index] ?? 0) > wanted[index]) return { ...probe, detected: true, available: true, minimum }
12
+ if ((actual[index] ?? 0) < wanted[index]) return { ...probe, detected: true, available: false, minimum }
13
+ }
14
+ return { ...probe, detected: true, available: true, minimum }
15
+ }
16
+
17
+ export function compatibleNode(version) {
18
+ const [major, minor] = String(version).split('.').map(Number)
19
+ return (major === 22 && minor >= 19) || major >= 24
20
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@foggy-projects/deepseek-harness-plugin",
3
+ "version": "0.4.0-beta.10",
4
+ "description": "Foggy Java data analysis engine integration for DeepSeek Harness",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "exports": {
8
+ ".": "./lib/index.js",
9
+ "./client": "./lib/client.js",
10
+ "./typert": "./lib/typert.js",
11
+ "./remote": "./lib/remote.js",
12
+ "./cordis.patch.yml": "./cordis.patch.yml",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "lib/**/*.js",
17
+ "skills/**",
18
+ "!skills/**/__pycache__/**",
19
+ "!skills/**/*.pyc",
20
+ "cordis.patch.yml",
21
+ "experience/linux/**",
22
+ "docs/PUBLIC-BETA-READINESS.md",
23
+ "docs/WINDOWS-BETA-ACCEPTANCE.md",
24
+ "THIRD-PARTY-RUNTIME-NOTICES.md",
25
+ "README.md"
26
+ ],
27
+ "license": "Apache-2.0",
28
+ "engines": {
29
+ "node": "^22.19.0 || >=24.0.0"
30
+ },
31
+ "dsh": {
32
+ "bundle": {
33
+ "patch": "./cordis.patch.yml"
34
+ },
35
+ "client": {
36
+ "platform": "web",
37
+ "inject": [
38
+ "@deepseek-ai/dsh-api-remotes",
39
+ "@deepseek-ai/dsh-client-runtime",
40
+ "@deepseek-ai/dsh-client-locale",
41
+ "@deepseek-ai/dsh-client-ui-settings",
42
+ "@deepseek-ai/dsh-client-ui-settings-plugins"
43
+ ]
44
+ }
45
+ },
46
+ "dependencies": {
47
+ "zod": "^4.4.3"
48
+ },
49
+ "peerDependencies": {
50
+ "@deepseek-ai/cordis": "^4.0.1",
51
+ "@deepseek-ai/dsh-api-remotes": "^0.1.2-rc.1",
52
+ "@deepseek-ai/dsh-client-locale": "^0.1.2-rc.1",
53
+ "@deepseek-ai/dsh-client-runtime": "^0.1.2-rc.1",
54
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.2-rc.1",
55
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.2-rc.1",
56
+ "@deepseek-ai/dsh-skill": "^0.1.2-rc.1",
57
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1"
58
+ },
59
+ "scripts": {
60
+ "check": "node --check lib/index.js && node --check lib/atomic-json.js && node --check lib/python-runtime.js && node --check lib/skill-provider.js && node --check lib/client.js && node --check lib/typert.js && node --check lib/remote.js",
61
+ "test": "node --test test/package.test.js && node test/run-onboarding-tests.js"
62
+ }
63
+ }