@foggy-projects/deepseek-harness-plugin 0.4.0-beta.7 → 0.4.0-beta.8
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 +15 -6
- package/THIRD-PARTY-RUNTIME-NOTICES.md +16 -0
- package/docs/PUBLIC-BETA-READINESS.md +51 -0
- package/docs/WINDOWS-BETA-ACCEPTANCE.md +71 -0
- package/experience/linux/README.md +5 -6
- package/experience/linux/prepare.sh +7 -9
- package/lib/client.js +25 -4
- package/lib/index.js +100 -30
- package/lib/python-runtime.js +339 -0
- package/lib/remote-descriptor.js +1 -0
- package/lib/version.js +5 -0
- package/package.json +6 -3
- package/skills/foggy-deepseek-onboarding/SKILL.md +9 -3
- package/skills/foggy-deepseek-onboarding/assets/versions.json +46 -2
- package/skills/foggy-deepseek-onboarding/scripts/doctor.ps1 +2 -2
- package/skills/foggy-deepseek-onboarding/scripts/doctor.sh +1 -2
- package/skills/foggy-deepseek-onboarding/scripts/install.ps1 +2 -2
- package/skills/foggy-deepseek-onboarding/scripts/install.sh +1 -2
- package/skills/foggy-deepseek-onboarding/scripts/invoke-onboarding.ps1 +30 -0
- package/skills/foggy-deepseek-onboarding/scripts/invoke-onboarding.sh +24 -0
- package/skills/foggy-deepseek-onboarding/scripts/onboard.ps1 +2 -2
- package/skills/foggy-deepseek-onboarding/scripts/onboard.sh +1 -2
- package/skills/foggy-deepseek-onboarding/scripts/onboarding.py +27 -22
- package/skills/foggy-deepseek-onboarding/scripts/runtime-start.ps1 +2 -2
- package/skills/foggy-deepseek-onboarding/scripts/runtime-start.sh +1 -2
- package/skills/foggy-deepseek-onboarding/scripts/runtime-stop.ps1 +2 -2
- package/skills/foggy-deepseek-onboarding/scripts/runtime-stop.sh +1 -2
- package/skills/foggy-deepseek-onboarding/scripts/uninstall.ps1 +2 -2
- package/skills/foggy-deepseek-onboarding/scripts/uninstall.sh +1 -2
|
@@ -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
|
+
}
|
package/lib/remote-descriptor.js
CHANGED
package/lib/version.js
CHANGED
|
@@ -13,3 +13,8 @@ export function compatible(probe, minimum) {
|
|
|
13
13
|
}
|
|
14
14
|
return { ...probe, detected: true, available: true, minimum }
|
|
15
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foggy-projects/deepseek-harness-plugin",
|
|
3
|
-
"version": "0.4.0-beta.
|
|
3
|
+
"version": "0.4.0-beta.8",
|
|
4
4
|
"description": "Foggy Java data analysis engine integration for DeepSeek Harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -19,11 +19,14 @@
|
|
|
19
19
|
"!skills/**/*.pyc",
|
|
20
20
|
"cordis.patch.yml",
|
|
21
21
|
"experience/linux/**",
|
|
22
|
+
"docs/PUBLIC-BETA-READINESS.md",
|
|
23
|
+
"docs/WINDOWS-BETA-ACCEPTANCE.md",
|
|
24
|
+
"THIRD-PARTY-RUNTIME-NOTICES.md",
|
|
22
25
|
"README.md"
|
|
23
26
|
],
|
|
24
27
|
"license": "Apache-2.0",
|
|
25
28
|
"engines": {
|
|
26
|
-
"node": "
|
|
29
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
27
30
|
},
|
|
28
31
|
"dsh": {
|
|
29
32
|
"bundle": {
|
|
@@ -54,7 +57,7 @@
|
|
|
54
57
|
"@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2"
|
|
55
58
|
},
|
|
56
59
|
"scripts": {
|
|
57
|
-
"check": "node --check lib/index.js && node --check lib/skill-provider.js && node --check lib/client.js && node --check lib/typert.js && node --check lib/remote.js",
|
|
60
|
+
"check": "node --check lib/index.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",
|
|
58
61
|
"test": "node --test test/package.test.js && python test/onboarding_unit.py"
|
|
59
62
|
}
|
|
60
63
|
}
|
|
@@ -20,6 +20,10 @@ copy this Skill into the current workspace.
|
|
|
20
20
|
workspace as `--project-root`. The absence of `foggy-runtime` from `PATH` is not evidence that the
|
|
21
21
|
managed CLI is missing; the plugin intentionally installs it in an isolated environment and records
|
|
22
22
|
its absolute command in the global install state.
|
|
23
|
+
- The plugin downloads and verifies a pinned private Python runtime before running this Skill. Do not
|
|
24
|
+
search for, install, or repair a system Python. Wrappers resolve the interpreter recorded in the
|
|
25
|
+
global install state. If private Python is missing, use the plugin's Python repair action. Only use
|
|
26
|
+
`FOGGY_PYTHON` or `FOGGY_ONBOARDING_PYTHON` when the user explicitly supplied an advanced override.
|
|
23
27
|
- Do not independently download or reinstall the CLI. If the global install state, managed marker, or
|
|
24
28
|
analysis Skill is missing or invalid, ask the user to open the Foggy plugin settings and use
|
|
25
29
|
the matching component repair action. Repair restores managed components and invalidates the
|
|
@@ -71,9 +75,11 @@ For every new-database onboarding session, this Skill is the orchestration autho
|
|
|
71
75
|
|
|
72
76
|
1. Run `scripts/doctor.ps1 --project-root <current-session-workspace>` on Windows or
|
|
73
77
|
`bash scripts/doctor.sh --project-root <current-session-workspace>` on Linux.
|
|
74
|
-
2. If the pinned CLI, Launcher, or global managed analysis Skill is missing, use the
|
|
75
|
-
plugin's Repair action.
|
|
76
|
-
|
|
78
|
+
2. If private Python, the pinned CLI, Launcher, or global managed analysis Skill is missing, use the
|
|
79
|
+
Foggy plugin's matching Repair action. First-time Python bootstrap requires the plugin UI unless
|
|
80
|
+
the user explicitly supplies `FOGGY_ONBOARDING_PYTHON`. Use a matching install script only after
|
|
81
|
+
private Python exists and the plugin UI is unavailable; use `--dry-run` first when paths or
|
|
82
|
+
permissions are uncertain.
|
|
77
83
|
3. Run `runtime-start` and require successful `wait-ready` plus `capabilities`. Record engine,
|
|
78
84
|
Runtime API version, schema version, security mode, URL, namespace, PID, and evidence path. If the
|
|
79
85
|
recorded Runtime is already running, `runtime-start` verifies and reuses it instead of starting a
|
|
@@ -1,12 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "foggy-deepseek-onboarding-versions/v1",
|
|
3
|
-
"packageVersion": "0.4.0-beta.
|
|
4
|
-
"validatedAt": "2026-09-
|
|
3
|
+
"packageVersion": "0.4.0-beta.8",
|
|
4
|
+
"validatedAt": "2026-09-03",
|
|
5
5
|
"components": {
|
|
6
6
|
"deepseekHarness": {
|
|
7
7
|
"version": "0.1.1-rc.2",
|
|
8
8
|
"minimumNodeVersion": "22.19.0"
|
|
9
9
|
},
|
|
10
|
+
"python": {
|
|
11
|
+
"version": "3.12.13",
|
|
12
|
+
"distribution": "astral-sh/python-build-standalone",
|
|
13
|
+
"buildRelease": "20260718",
|
|
14
|
+
"license": "Python-2.0 AND MPL-2.0",
|
|
15
|
+
"assets": {
|
|
16
|
+
"win32-x64": {
|
|
17
|
+
"file": "cpython-3.12.13+20260718-x86_64-pc-windows-msvc-install_only_stripped.tar.gz",
|
|
18
|
+
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260718/cpython-3.12.13%2B20260718-x86_64-pc-windows-msvc-install_only_stripped.tar.gz",
|
|
19
|
+
"sha256": "0d422a1439ec308e03f47df551bc30f5994727c456e414b026d202bcda9b7c1c",
|
|
20
|
+
"size": 21932298
|
|
21
|
+
},
|
|
22
|
+
"win32-arm64": {
|
|
23
|
+
"file": "cpython-3.12.13+20260718-aarch64-pc-windows-msvc-install_only_stripped.tar.gz",
|
|
24
|
+
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260718/cpython-3.12.13%2B20260718-aarch64-pc-windows-msvc-install_only_stripped.tar.gz",
|
|
25
|
+
"sha256": "596808d2592a282c45921a324d5503ef0d4f488d104c1c1f12a1d5e7dea9d463",
|
|
26
|
+
"size": 20652311
|
|
27
|
+
},
|
|
28
|
+
"linux-x64": {
|
|
29
|
+
"file": "cpython-3.12.13+20260718-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
|
|
30
|
+
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260718/cpython-3.12.13%2B20260718-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
|
|
31
|
+
"sha256": "5854aa6ec71cad00334d5065633c210b2e7feb40956767a59a91791cadcf0b79",
|
|
32
|
+
"size": 34199823
|
|
33
|
+
},
|
|
34
|
+
"linux-arm64": {
|
|
35
|
+
"file": "cpython-3.12.13+20260718-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz",
|
|
36
|
+
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260718/cpython-3.12.13%2B20260718-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz",
|
|
37
|
+
"sha256": "f226576b91491ffa5739aa85726521e9031f4d87f80627d64ed348ac77cb31e9",
|
|
38
|
+
"size": 29195696
|
|
39
|
+
},
|
|
40
|
+
"darwin-x64": {
|
|
41
|
+
"file": "cpython-3.12.13+20260718-x86_64-apple-darwin-install_only_stripped.tar.gz",
|
|
42
|
+
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260718/cpython-3.12.13%2B20260718-x86_64-apple-darwin-install_only_stripped.tar.gz",
|
|
43
|
+
"sha256": "8e6b7e6533bdf746287008edf91102e7bee0a6ca1d24f16c4514237cafd706c5",
|
|
44
|
+
"size": 24685843
|
|
45
|
+
},
|
|
46
|
+
"darwin-arm64": {
|
|
47
|
+
"file": "cpython-3.12.13+20260718-aarch64-apple-darwin-install_only_stripped.tar.gz",
|
|
48
|
+
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260718/cpython-3.12.13%2B20260718-aarch64-apple-darwin-install_only_stripped.tar.gz",
|
|
49
|
+
"sha256": "9a1e9e06175c10efd8378b904b07fa21bd791ab3345d7cdffeb4a76c9ff55903",
|
|
50
|
+
"size": 25000146
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
10
54
|
"cli": {
|
|
11
55
|
"version": "0.1.23",
|
|
12
56
|
"minimumPythonVersion": "3.11.0",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
$ErrorActionPreference = "Stop"
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
& (Join-Path $PSScriptRoot 'invoke-onboarding.ps1') doctor @args
|
|
3
|
+
exit $LASTEXITCODE
|
|
4
4
|
exit $LASTEXITCODE
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
4
|
-
|
|
5
|
-
exec "$PYTHON_COMMAND" "$SCRIPT_DIR/onboarding.py" doctor "$@"
|
|
4
|
+
exec bash "$SCRIPT_DIR/invoke-onboarding.sh" doctor "$@"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
$ErrorActionPreference = "Stop"
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
& (Join-Path $PSScriptRoot 'invoke-onboarding.ps1') install @args
|
|
3
|
+
exit $LASTEXITCODE
|
|
4
4
|
exit $LASTEXITCODE
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
4
|
-
|
|
5
|
-
exec "$PYTHON_COMMAND" "$SCRIPT_DIR/onboarding.py" install "$@"
|
|
4
|
+
exec bash "$SCRIPT_DIR/invoke-onboarding.sh" install "$@"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(ValueFromRemainingArguments = $true)]
|
|
3
|
+
[string[]]$OnboardingArgs
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
$pythonCommand = $env:FOGGY_ONBOARDING_PYTHON
|
|
7
|
+
if (-not $pythonCommand) {
|
|
8
|
+
$installRoot = if ($env:FOGGY_INSTALL_ROOT) {
|
|
9
|
+
$env:FOGGY_INSTALL_ROOT
|
|
10
|
+
} elseif ($env:LOCALAPPDATA) {
|
|
11
|
+
Join-Path $env:LOCALAPPDATA 'Foggy\DeepSeekHarness'
|
|
12
|
+
}
|
|
13
|
+
if ($installRoot) {
|
|
14
|
+
$statePath = Join-Path $installRoot 'install-state.json'
|
|
15
|
+
if (Test-Path -LiteralPath $statePath) {
|
|
16
|
+
try {
|
|
17
|
+
$state = Get-Content -Raw -LiteralPath $statePath | ConvertFrom-Json
|
|
18
|
+
$pythonCommand = $state.python.command
|
|
19
|
+
} catch {}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (-not $pythonCommand -or -not (Test-Path -LiteralPath $pythonCommand)) {
|
|
25
|
+
Write-Error 'Foggy private Python is unavailable. Initialize or repair it in DeepSeek Harness plugin settings, or set FOGGY_ONBOARDING_PYTHON explicitly.'
|
|
26
|
+
exit 1
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
& $pythonCommand (Join-Path $PSScriptRoot 'onboarding.py') @OnboardingArgs
|
|
30
|
+
exit $LASTEXITCODE
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
python_command="${FOGGY_ONBOARDING_PYTHON:-}"
|
|
5
|
+
if [[ -z "$python_command" ]]; then
|
|
6
|
+
if [[ -n "${FOGGY_INSTALL_ROOT:-}" ]]; then
|
|
7
|
+
install_root="$FOGGY_INSTALL_ROOT"
|
|
8
|
+
elif [[ -n "${XDG_DATA_HOME:-}" ]]; then
|
|
9
|
+
install_root="$XDG_DATA_HOME/foggy/deepseek-harness"
|
|
10
|
+
else
|
|
11
|
+
install_root="$HOME/.local/share/foggy/deepseek-harness"
|
|
12
|
+
fi
|
|
13
|
+
state_path="$install_root/install-state.json"
|
|
14
|
+
if [[ -f "$state_path" ]]; then
|
|
15
|
+
python_command="$(node -e 'const fs=require("node:fs"); const state=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); process.stdout.write(state.python?.command || "")' "$state_path")"
|
|
16
|
+
fi
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if [[ -z "$python_command" || ! -x "$python_command" ]]; then
|
|
20
|
+
echo 'Foggy private Python is unavailable. Initialize or repair it in DeepSeek Harness plugin settings, or set FOGGY_ONBOARDING_PYTHON explicitly.' >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
exec "$python_command" "$(dirname "$0")/onboarding.py" "$@"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
$ErrorActionPreference = "Stop"
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
& (Join-Path $PSScriptRoot 'invoke-onboarding.ps1') @args
|
|
3
|
+
exit $LASTEXITCODE
|
|
4
4
|
exit $LASTEXITCODE
|