yo-moss-ai 0.0.9 → 0.0.11

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 (57) hide show
  1. package/bin/bootstrap-deps.mjs +255 -0
  2. package/bin/runtime-manifest.json +67 -0
  3. package/bin/yo-moss-ai.mjs +18 -1
  4. package/dist/assets/esp-flasher-CLKzE7JE.js +2 -2
  5. package/dist/assets/index-CZ_NROJp.js +8 -8
  6. package/dist-server/camera.mjs +1 -1
  7. package/dist-server/ffmpeg.mjs +1 -1
  8. package/dist-server/index.mjs +1 -1
  9. package/dist-server/ir-import-validate.mjs +1 -1
  10. package/dist-server/mcp/backoff.mjs +1 -1
  11. package/dist-server/mcp/bridge-manager.mjs +1 -1
  12. package/dist-server/mcp/builtin-tools.mjs +1 -1
  13. package/dist-server/mcp/dedupe-servers.mjs +1 -1
  14. package/dist-server/mcp/doctor.mjs +1 -1
  15. package/dist-server/mcp/fixtures/echo-mcp.mjs +1 -1
  16. package/dist-server/mcp/fixtures/hotkey-mcp.mjs +1 -1
  17. package/dist-server/mcp/fixtures/onvif-mcp.mjs +1 -1
  18. package/dist-server/mcp/fixtures/shell-mcp.mjs +1 -1
  19. package/dist-server/mcp/hotkey-agent-docs.mjs +1 -1
  20. package/dist-server/mcp/hotkey-driver.mjs +1 -1
  21. package/dist-server/mcp/hotkey-handlers.mjs +1 -1
  22. package/dist-server/mcp/hotkey-import-validate.mjs +1 -1
  23. package/dist-server/mcp/hotkey-store.mjs +1 -1
  24. package/dist-server/mcp/http-client.mjs +1 -1
  25. package/dist-server/mcp/http-pipe.mjs +1 -1
  26. package/dist-server/mcp/local-probe.mjs +1 -1
  27. package/dist-server/mcp/marketplace.mjs +1 -1
  28. package/dist-server/mcp/onvif-handlers.mjs +1 -1
  29. package/dist-server/mcp/parse-server-json.mjs +1 -1
  30. package/dist-server/mcp/paths.mjs +1 -1
  31. package/dist-server/mcp/routes.mjs +1 -1
  32. package/dist-server/mcp/schema.mjs +1 -1
  33. package/dist-server/mcp/settings.mjs +1 -1
  34. package/dist-server/mcp/shell-approval.mjs +1 -1
  35. package/dist-server/mcp/shell-policy.mjs +1 -1
  36. package/dist-server/mcp/shell-trash.mjs +1 -1
  37. package/dist-server/mcp/stdio-pipe.mjs +1 -1
  38. package/dist-server/mcp/store.mjs +1 -1
  39. package/dist-server/mcp/task-notify.mjs +1 -1
  40. package/dist-server/mcp/ws-connect.mjs +1 -1
  41. package/dist-server/media-cache.mjs +1 -0
  42. package/package.json +6 -4
  43. package/dist/earth-propulsion.webp +0 -0
  44. package/dist/models/mediapipe/blaze_face_full_range.tflite +0 -0
  45. package/dist/models/mediapipe/blaze_face_short_range.tflite +0 -0
  46. package/dist/models/mediapipe/wasm/vision_wasm_internal.js +0 -8841
  47. package/dist/models/mediapipe/wasm/vision_wasm_internal.wasm +0 -0
  48. package/dist/models/mediapipe/wasm/vision_wasm_nosimd_internal.js +0 -8832
  49. package/dist/models/mediapipe/wasm/vision_wasm_nosimd_internal.wasm +0 -0
  50. package/dist/stl/moss.stl +0 -0
  51. package/dist/stl/planetary_engine.stl +0 -0
  52. package/dist/textures/earth-lights.png +0 -0
  53. package/dist/textures/earth-night.jpg +0 -0
  54. package/dist/textures/earth_clouds.jpg +0 -0
  55. package/dist/textures/moon-base.jpg +0 -0
  56. package/dist/textures/moon-bump.jpg +0 -0
  57. package/dist/textures/wandering.png +0 -0
@@ -0,0 +1,255 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import http from 'node:http'
5
+ import https from 'node:https'
6
+ import { spawn } from 'node:child_process'
7
+ import { pipeline } from 'node:stream/promises'
8
+
9
+ export const PROGRESS_BAR_WIDTH = 28
10
+
11
+ export function mediaCacheDir() {
12
+ return process.env.MOSS_MEDIA_ROOT || path.join(os.homedir(), '.moss-desktop', 'media')
13
+ }
14
+
15
+ export function runtimeDepsRoot() {
16
+ return process.env.MOSS_RUNTIME_DEPS_ROOT || path.join(os.homedir(), '.moss-desktop', 'runtime-deps')
17
+ }
18
+
19
+ /** 纯函数:生成终端进度条文本(单行覆盖用) */
20
+ export function formatProgressBar(label, ratio, width = PROGRESS_BAR_WIDTH) {
21
+ const pct = Math.min(100, Math.max(0, Math.round(ratio * 100)))
22
+ const filled = Math.round((pct / 100) * width)
23
+ const bar = '█'.repeat(filled) + '░'.repeat(width - filled)
24
+ return `${label} [${bar}] ${pct}%`
25
+ }
26
+
27
+ function renderBar(label, ratio) {
28
+ process.stdout.write(`\r\x1b[2K${formatProgressBar(label, ratio)}`)
29
+ }
30
+
31
+ export function cdnUrls(version, relativePath) {
32
+ const encoded = relativePath.split('/').map((part) => encodeURIComponent(part)).join('/')
33
+ return [
34
+ `https://cdn.jsdelivr.net/npm/yo-moss-ai-media@${version}/${encoded}`,
35
+ `https://unpkg.com/yo-moss-ai-media@${version}/${encoded}`,
36
+ ]
37
+ }
38
+
39
+ export function followDownload(url, dest, onChunk, redirects = 0) {
40
+ if (redirects > 5) return Promise.reject(new Error('下载重定向过多'))
41
+ return new Promise((resolve, reject) => {
42
+ const client = url.startsWith('http:') ? http : https
43
+ const req = client.get(
44
+ url,
45
+ { headers: { 'User-Agent': 'yo-moss-ai' }, timeout: 120_000 },
46
+ (res) => {
47
+ const location = res.headers.location
48
+ if (res.statusCode >= 300 && res.statusCode < 400 && location) {
49
+ res.resume()
50
+ followDownload(new URL(location, url).href, dest, onChunk, redirects + 1).then(resolve, reject)
51
+ return
52
+ }
53
+ if (res.statusCode !== 200) {
54
+ res.resume()
55
+ reject(new Error(`HTTP ${res.statusCode}`))
56
+ return
57
+ }
58
+ fs.mkdirSync(path.dirname(dest), { recursive: true })
59
+ const tmp = `${dest}.download`
60
+ const out = fs.createWriteStream(tmp)
61
+ res.on('data', (chunk) => {
62
+ onChunk?.(chunk.length)
63
+ })
64
+ pipeline(res, out)
65
+ .then(() => {
66
+ fs.renameSync(tmp, dest)
67
+ resolve()
68
+ })
69
+ .catch((err) => {
70
+ try {
71
+ fs.unlinkSync(tmp)
72
+ } catch {
73
+ // ignore
74
+ }
75
+ reject(err)
76
+ })
77
+ },
78
+ )
79
+ req.on('timeout', () => req.destroy(new Error('下载超时')))
80
+ req.on('error', reject)
81
+ })
82
+ }
83
+
84
+ export function fileReady(filePath, expectedSize) {
85
+ try {
86
+ const stat = fs.statSync(filePath)
87
+ return stat.isFile() && (!expectedSize || stat.size === expectedSize)
88
+ } catch {
89
+ return false
90
+ }
91
+ }
92
+
93
+ function runNpmInstall(cwd) {
94
+ return new Promise((resolve, reject) => {
95
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'
96
+ const child = spawn(npmCmd, ['install', '--omit=dev', '--no-audit', '--no-fund', '--loglevel=error'], {
97
+ cwd,
98
+ stdio: ['ignore', 'ignore', 'pipe'],
99
+ shell: process.platform === 'win32',
100
+ })
101
+ let errText = ''
102
+ child.stderr.on('data', (chunk) => {
103
+ errText += String(chunk)
104
+ })
105
+ child.on('exit', (code) => {
106
+ if (code === 0) resolve()
107
+ else reject(new Error(errText.trim() || `npm install 失败,退出码 ${code}`))
108
+ })
109
+ })
110
+ }
111
+
112
+ export async function ensureRuntimeNpmDeps(npmDeps, depsRoot = runtimeDepsRoot(), installImpl = runNpmInstall) {
113
+ fs.mkdirSync(depsRoot, { recursive: true })
114
+ const pkgPath = path.join(depsRoot, 'package.json')
115
+ const deps = Object.fromEntries(npmDeps.map((item) => [item.name, item.version]))
116
+ const pkg = {
117
+ name: 'yo-moss-ai-runtime-deps',
118
+ private: true,
119
+ dependencies: deps,
120
+ overrides: { phin: 'npm:phn@^0.2.8' },
121
+ }
122
+ fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`)
123
+
124
+ const marker = path.join(depsRoot, '.installed.json')
125
+ const nextMarker = JSON.stringify(deps)
126
+ if (fs.existsSync(marker) && fs.readFileSync(marker, 'utf8') === nextMarker) {
127
+ const nutEntry = path.join(depsRoot, 'node_modules', '@nut-tree-fork', 'nut-js', 'dist', 'index.js')
128
+ if (fs.existsSync(nutEntry)) return
129
+ }
130
+
131
+ await installImpl(depsRoot)
132
+ fs.writeFileSync(marker, nextMarker)
133
+ }
134
+
135
+ export function loadManifest(root) {
136
+ const manifestPath = path.join(root, 'bin', 'runtime-manifest.json')
137
+ if (!fs.existsSync(manifestPath)) return null
138
+ return JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
139
+ }
140
+
141
+ export function hasBundledMedia(distDir) {
142
+ return fs.existsSync(path.join(distDir, 'models'))
143
+ || fs.existsSync(path.join(distDir, 'textures'))
144
+ || fs.existsSync(path.join(distDir, 'stl'))
145
+ }
146
+
147
+ export function runtimeDepsReady(npmDeps, depsRoot = runtimeDepsRoot()) {
148
+ if (!npmDeps.length) return true
149
+ const nutEntry = path.join(depsRoot, 'node_modules', '@nut-tree-fork', 'nut-js', 'dist', 'index.js')
150
+ return fs.existsSync(nutEntry)
151
+ }
152
+
153
+ export function mediaCacheReady(files, cacheDir = mediaCacheDir()) {
154
+ if (!files.length) return true
155
+ return files.every((file) => fileReady(path.join(cacheDir, file.path), file.size))
156
+ }
157
+
158
+ /**
159
+ * npx 安装精简包后,启动服务前下载媒体资源与扩展 npm 依赖。
160
+ */
161
+ export async function bootstrapRuntimeDeps(opts) {
162
+ const {
163
+ root,
164
+ version,
165
+ cacheDir = mediaCacheDir(),
166
+ depsRoot = runtimeDepsRoot(),
167
+ downloadFile = followDownload,
168
+ installNpmDeps = (npmDeps) => ensureRuntimeNpmDeps(npmDeps, depsRoot),
169
+ } = opts
170
+
171
+ const distDir = path.join(root, 'dist')
172
+ if (process.env.MOSS_SKIP_BOOTSTRAP === '1' || hasBundledMedia(distDir)) {
173
+ return { skipped: true }
174
+ }
175
+
176
+ const manifest = loadManifest(root)
177
+ if (!manifest) {
178
+ console.warn('缺少 bin/runtime-manifest.json,跳过资源预下载')
179
+ return { skipped: true }
180
+ }
181
+
182
+ const mediaVersion = manifest.mediaVersion || version
183
+ const mediaFiles = manifest.mediaFiles || []
184
+ const npmDeps = manifest.npmDeps || []
185
+ const totalBytes = mediaFiles.reduce((sum, file) => sum + (file.size || 0), 0)
186
+ const needMedia = !mediaCacheReady(mediaFiles, cacheDir)
187
+ const needNpm = !runtimeDepsReady(npmDeps, depsRoot)
188
+
189
+ if (!needMedia && !needNpm) {
190
+ process.env.MOSS_MEDIA_ROOT = cacheDir
191
+ process.env.MOSS_RUNTIME_DEPS = path.join(depsRoot, 'node_modules')
192
+ return { skipped: false, cached: true }
193
+ }
194
+
195
+ console.log('')
196
+ if (needMedia) {
197
+ const mb = (totalBytes / 1024 / 1024).toFixed(0)
198
+ console.log(`正在下载运行资源(约 ${mb} MB),完成后自动启动服务…`)
199
+ } else if (needNpm) {
200
+ console.log('正在安装扩展依赖,完成后自动启动服务…')
201
+ }
202
+
203
+ if (needMedia) {
204
+ let doneBytes = 0
205
+ renderBar('下载资源', 0)
206
+
207
+ for (const file of mediaFiles) {
208
+ const dest = path.join(cacheDir, file.path)
209
+ if (fileReady(dest, file.size)) {
210
+ doneBytes += file.size || 0
211
+ renderBar('下载资源', totalBytes ? doneBytes / totalBytes : 1)
212
+ continue
213
+ }
214
+
215
+ const fileSize = file.size || 0
216
+ let fileDone = 0
217
+ let lastError
218
+ for (const url of cdnUrls(mediaVersion, file.path)) {
219
+ try {
220
+ await downloadFile(url, dest, (chunkLen) => {
221
+ fileDone += chunkLen
222
+ const current = doneBytes + Math.min(fileDone, fileSize || fileDone)
223
+ renderBar('下载资源', totalBytes ? current / totalBytes : 1)
224
+ })
225
+ lastError = null
226
+ break
227
+ } catch (err) {
228
+ lastError = err
229
+ fileDone = 0
230
+ }
231
+ }
232
+ if (lastError) throw new Error(`无法下载 ${file.path}: ${lastError.message}`)
233
+
234
+ doneBytes += fileSize || fs.statSync(dest).size
235
+ renderBar('下载资源', totalBytes ? doneBytes / totalBytes : 1)
236
+ }
237
+ process.stdout.write('\n')
238
+ }
239
+
240
+ if (needNpm) {
241
+ renderBar('安装扩展依赖', 0)
242
+ try {
243
+ await installNpmDeps(npmDeps)
244
+ renderBar('安装扩展依赖', 1)
245
+ process.stdout.write('\n')
246
+ } catch (err) {
247
+ process.stdout.write('\n')
248
+ console.warn('扩展依赖安装失败,快捷键/媒体键可能不可用:', err.message)
249
+ }
250
+ }
251
+
252
+ process.env.MOSS_MEDIA_ROOT = cacheDir
253
+ process.env.MOSS_RUNTIME_DEPS = path.join(depsRoot, 'node_modules')
254
+ return { skipped: false, cached: false }
255
+ }
@@ -0,0 +1,67 @@
1
+ {
2
+ "mediaVersion": "0.0.11",
3
+ "mediaFiles": [
4
+ {
5
+ "path": "models/mediapipe/blaze_face_full_range.tflite",
6
+ "size": 1083786
7
+ },
8
+ {
9
+ "path": "models/mediapipe/blaze_face_short_range.tflite",
10
+ "size": 229746
11
+ },
12
+ {
13
+ "path": "models/mediapipe/wasm/vision_wasm_internal.js",
14
+ "size": 323377
15
+ },
16
+ {
17
+ "path": "models/mediapipe/wasm/vision_wasm_internal.wasm",
18
+ "size": 11756954
19
+ },
20
+ {
21
+ "path": "models/mediapipe/wasm/vision_wasm_nosimd_internal.js",
22
+ "size": 323180
23
+ },
24
+ {
25
+ "path": "models/mediapipe/wasm/vision_wasm_nosimd_internal.wasm",
26
+ "size": 10960242
27
+ },
28
+ {
29
+ "path": "textures/earth-lights.png",
30
+ "size": 734910
31
+ },
32
+ {
33
+ "path": "textures/earth-night.jpg",
34
+ "size": 8277759
35
+ },
36
+ {
37
+ "path": "textures/earth_clouds.jpg",
38
+ "size": 12343415
39
+ },
40
+ {
41
+ "path": "textures/moon-base.jpg",
42
+ "size": 1591331
43
+ },
44
+ {
45
+ "path": "textures/moon-bump.jpg",
46
+ "size": 1021019
47
+ },
48
+ {
49
+ "path": "textures/wandering.png",
50
+ "size": 309032
51
+ },
52
+ {
53
+ "path": "stl/moss.stl",
54
+ "size": 1649184
55
+ },
56
+ {
57
+ "path": "stl/planetary_engine.stl",
58
+ "size": 5807034
59
+ }
60
+ ],
61
+ "npmDeps": [
62
+ {
63
+ "name": "@nut-tree-fork/nut-js",
64
+ "version": "^4.2.0"
65
+ }
66
+ ]
67
+ }
@@ -2,6 +2,15 @@
2
2
  import fs from 'node:fs'
3
3
  import path from 'node:path'
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url'
5
+ import { bootstrapRuntimeDeps } from './bootstrap-deps.mjs'
6
+
7
+ const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
8
+ let pkgVersion = ''
9
+ try {
10
+ pkgVersion = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version
11
+ } catch {
12
+ // ignore
13
+ }
5
14
 
6
15
  const major = Number(process.versions.node.split('.')[0])
7
16
  if (major < 20) {
@@ -10,7 +19,6 @@ if (major < 20) {
10
19
  process.exit(1)
11
20
  }
12
21
 
13
- const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
14
22
  const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
15
23
  const server = path.join(root, 'dist-server', 'index.mjs')
16
24
 
@@ -19,6 +27,15 @@ if (!fs.existsSync(server)) {
19
27
  process.exit(1)
20
28
  }
21
29
 
30
+ console.log(`yo-moss-ai${pkgVersion ? ` v${pkgVersion}` : ''}`)
31
+
32
+ try {
33
+ await bootstrapRuntimeDeps({ root, version: pkg.version })
34
+ } catch (err) {
35
+ console.error('\n运行资源准备失败:', err?.message || err)
36
+ process.exit(1)
37
+ }
38
+
22
39
  process.env.NODE_ENV = 'production'
23
40
  process.env.MOSS_OPEN_BROWSER = process.env.MOSS_OPEN_BROWSER || '1'
24
41
  process.env.MOSS_VERSION = pkg.version