meshy-dsh 0.1.1 → 0.2.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.
- package/README.md +23 -5
- package/README.zh.md +6 -2
- package/cordis.patch.yml +6 -3
- package/lib/client.js +3604 -0
- package/lib/client.viewer.js +4237 -0
- package/lib/index.js +133 -0
- package/package.json +15 -2
package/lib/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Host half of the Meshy settings page. In Web and Desktop it serves four authenticated routes
|
|
2
|
+
// under /api that run the Meshy CLI with fixed arguments; in tui and headless, where there is
|
|
3
|
+
// no `connection` service, it does nothing. The skills never call these routes.
|
|
4
|
+
import { spawn } from 'node:child_process'
|
|
5
|
+
|
|
6
|
+
export const name = 'meshy-dsh'
|
|
7
|
+
|
|
8
|
+
const WIN = process.platform === 'win32'
|
|
9
|
+
const MESHY = WIN ? 'meshy.cmd' : 'meshy'
|
|
10
|
+
const NPM = WIN ? 'npm.cmd' : 'npm'
|
|
11
|
+
// 0.7.3 is the first CLI whose --no-wait output leaves the device code out. Exact, because
|
|
12
|
+
// npm exec keeps reusing whichever matching version it cached first.
|
|
13
|
+
const TEMPORARY = ['exec', '--yes', '--package=meshy-cli@0.7.3', '--', 'meshy']
|
|
14
|
+
const JSON_FLAGS = ['--format', 'json', '--no-update-check']
|
|
15
|
+
const MAX_OUTPUT = 1 << 20
|
|
16
|
+
const FLOW_ID = /^[A-Za-z0-9_-]{16,64}$/
|
|
17
|
+
const REGIONS = new Set(['global', 'cn'])
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Run one command with fixed argv (no shell outside Windows, where .cmd shims need one; every
|
|
21
|
+
* argument is a constant or validated), capped output and a deadline.
|
|
22
|
+
* @returns {Promise<{ code: number | null, stdout: string, timedOut: boolean }>}
|
|
23
|
+
*/
|
|
24
|
+
export function run(cmd, args, timeoutMs) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const child = spawn(cmd, args, { shell: WIN, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true })
|
|
27
|
+
let stdout = ''
|
|
28
|
+
let timedOut = false
|
|
29
|
+
const timer = setTimeout(() => { timedOut = true; child.kill() }, timeoutMs)
|
|
30
|
+
child.stdout.setEncoding('utf8').on('data', (chunk) => {
|
|
31
|
+
if (stdout.length < MAX_OUTPUT) stdout += chunk
|
|
32
|
+
})
|
|
33
|
+
child.once('error', (err) => { clearTimeout(timer); reject(err) })
|
|
34
|
+
child.once('close', (code) => { clearTimeout(timer); resolve({ code, stdout, timedOut }) })
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** `meshy` from PATH when it is 0.7.3 or a later 0.7.x, else the temporary package. */
|
|
39
|
+
async function runner() {
|
|
40
|
+
const probe = await run(MESHY, ['--version'], 10_000).catch(() => undefined)
|
|
41
|
+
const patch = /^0\.7\.(\d+)$/.exec(probe?.stdout.trim() ?? '')?.[1]
|
|
42
|
+
return patch !== undefined && Number(patch) >= 3 ? [MESHY, []] : [NPM, TEMPORARY]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function meshy(args, timeoutMs = 60_000) {
|
|
46
|
+
const [cmd, prefix] = await runner()
|
|
47
|
+
const out = await run(cmd, [...prefix, ...args, ...JSON_FLAGS], timeoutMs)
|
|
48
|
+
let envelope
|
|
49
|
+
try {
|
|
50
|
+
envelope = JSON.parse(out.stdout)
|
|
51
|
+
} catch {
|
|
52
|
+
envelope = { ok: false, error: { message: out.timedOut ? 'timed out' : 'the Meshy CLI printed no result' } }
|
|
53
|
+
}
|
|
54
|
+
return { ...envelope, timedOut: out.timedOut, runner: prefix.length ? 'npm exec' : 'meshy' }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const json = (body, status = 200) => Response.json(body, { status, headers: { 'cache-control': 'no-store' } })
|
|
58
|
+
const failure = (envelope, status = 502) => json({ error: envelope.error?.message ?? 'the Meshy CLI failed' }, status)
|
|
59
|
+
|
|
60
|
+
async function body(request) {
|
|
61
|
+
try {
|
|
62
|
+
const value = await request.json()
|
|
63
|
+
return typeof value === 'object' && value !== null ? value : undefined
|
|
64
|
+
} catch {
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function status() {
|
|
70
|
+
const [cmd, prefix] = await runner()
|
|
71
|
+
const version = await run(cmd, [...prefix, '--version'], 120_000).catch(() => undefined)
|
|
72
|
+
if (!version || version.code !== 0) return json({ cli: null, error: 'Meshy CLI not found: Node.js 22.12+ with npm is needed' })
|
|
73
|
+
const auth = await meshy(['auth', 'status'])
|
|
74
|
+
const r = auth.result ?? {}
|
|
75
|
+
return json({
|
|
76
|
+
cli: { version: version.stdout.trim(), runner: prefix.length ? 'npm exec' : 'meshy' },
|
|
77
|
+
auth: {
|
|
78
|
+
authenticated: r.authenticated === true,
|
|
79
|
+
verified: r.verified === true,
|
|
80
|
+
// How it signed in, never the credential: an OAuth access token starts msy_at_.
|
|
81
|
+
method: r.authenticated !== true ? null : String(r.credential ?? '').startsWith('msy_at_') ? 'browser' : 'apiKey',
|
|
82
|
+
region: typeof r.base_url_v1 === 'string' && r.base_url_v1.includes('meshy.cn') ? 'cn' : 'global',
|
|
83
|
+
balance: typeof r.balance?.balance === 'number' ? r.balance.balance : null,
|
|
84
|
+
hint: r.hint ?? null,
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function loginStart(request) {
|
|
90
|
+
const input = await body(request)
|
|
91
|
+
const region = input?.region ?? 'global'
|
|
92
|
+
if (!input || !REGIONS.has(region)) return json({ error: 'region must be global or cn' }, 400)
|
|
93
|
+
const started = await meshy(['auth', 'login', '--device', '--no-wait', '--region', region])
|
|
94
|
+
const r = started.result ?? {}
|
|
95
|
+
if (!started.ok || typeof r.verification_url !== 'string' || !r.verification_url.startsWith('https://')) return failure(started)
|
|
96
|
+
// Only what the page shows: an older CLI also prints the device code, a bearer secret.
|
|
97
|
+
return json({ verification_url: r.verification_url, user_code: r.user_code, flow_id: r.flow_id, expires_in: r.expires_in })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function loginFinish(request) {
|
|
101
|
+
const input = await body(request)
|
|
102
|
+
if (typeof input?.flow_id !== 'string' || !FLOW_ID.test(input.flow_id)) return json({ error: 'invalid flow_id' }, 400)
|
|
103
|
+
// `=` keeps an id that starts with "-" from reading as an option.
|
|
104
|
+
const done = await meshy(['auth', 'login', `--device-flow=${input.flow_id}`], 60_000)
|
|
105
|
+
if (done.timedOut) return json({ status: 'pending' }, 202)
|
|
106
|
+
if (!done.ok) return failure(done)
|
|
107
|
+
return json({ status: done.result?.status ?? 'logged_in' })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function logout() {
|
|
111
|
+
const out = await meshy(['auth', 'logout'])
|
|
112
|
+
return out.ok ? json({ status: 'logged_out' }) : failure(out)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const ROUTES = [
|
|
116
|
+
['/api/meshy.status', 'GET', status],
|
|
117
|
+
['/api/meshy.login', 'POST', loginStart],
|
|
118
|
+
['/api/meshy.login.finish', 'POST', loginFinish],
|
|
119
|
+
['/api/meshy.logout', 'POST', logout],
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
export function apply(ctx) {
|
|
123
|
+
ctx.inject(['connection'], (ctx) => {
|
|
124
|
+
for (const [path, method, handle] of ROUTES) {
|
|
125
|
+
ctx.effect(() => ctx.connection.fetch.register({
|
|
126
|
+
path,
|
|
127
|
+
methods: [method],
|
|
128
|
+
requestBody: 'buffered',
|
|
129
|
+
fetch: (request) => handle(request).catch((err) => json({ error: String(err?.message ?? err) }, 500)),
|
|
130
|
+
}), `meshy-dsh: ${path}`)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meshy-dsh",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Meshy skills for DeepSeek Harness: textured, rigged and animated 3D models or print-ready files from text or images, through meshy-cli.",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Meshy skills for DeepSeek Harness: textured, rigged and animated 3D models or print-ready files from text or images, through meshy-cli, with a Meshy settings page and a 3D preview for GLB files.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh-plugin",
|
|
7
7
|
"dsh",
|
|
@@ -24,10 +24,13 @@
|
|
|
24
24
|
},
|
|
25
25
|
"bugs": "https://github.com/meshy-dev/meshy-dsh/issues",
|
|
26
26
|
"license": "MIT",
|
|
27
|
+
"type": "module",
|
|
27
28
|
"publishConfig": {
|
|
28
29
|
"access": "public"
|
|
29
30
|
},
|
|
30
31
|
"exports": {
|
|
32
|
+
".": "./lib/index.js",
|
|
33
|
+
"./client": "./lib/client.js",
|
|
31
34
|
"./package.json": "./package.json",
|
|
32
35
|
"./locale/*.json": "./locale/*.json",
|
|
33
36
|
"./icon": "./icon.svg"
|
|
@@ -36,9 +39,11 @@
|
|
|
36
39
|
"cordis.patch.yml",
|
|
37
40
|
"locale/*.json",
|
|
38
41
|
"icon.svg",
|
|
42
|
+
"lib",
|
|
39
43
|
"skills"
|
|
40
44
|
],
|
|
41
45
|
"scripts": {
|
|
46
|
+
"build": "tsdown",
|
|
42
47
|
"test": "node --test tests/*.test.mjs",
|
|
43
48
|
"sync": "node scripts/sync-skills.mjs",
|
|
44
49
|
"smoke": "node scripts/smoke.mjs"
|
|
@@ -49,6 +54,14 @@
|
|
|
49
54
|
"dsh": {
|
|
50
55
|
"bundle": {
|
|
51
56
|
"patch": "./cordis.patch.yml"
|
|
57
|
+
},
|
|
58
|
+
"client": {
|
|
59
|
+
"platform": "web"
|
|
52
60
|
}
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"qrcode-generator": "^2.0.4",
|
|
64
|
+
"three": "^0.187.0",
|
|
65
|
+
"tsdown": "^0.23.4"
|
|
53
66
|
}
|
|
54
67
|
}
|