Files
dsh-easy-desktop/plugins/dsh-desktop-vision/index.js
Tommy f8a3c2dc6f Add README, plugin citations, and multi-platform release CI.
Ship screenshots and third-party notices for the desktop shell, and
package Windows, macOS, deb, rpm, and Flatpak from GitHub Releases.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-15 22:02:13 +08:00

182 lines
5.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { homedir } from 'node:os'
import { dirname, join } from 'node:path'
export const name = 'dsh-desktop-vision'
export const inject = []
const PROVIDERS = [
{
id: 'openai',
label: 'OpenAI 兼容',
baseUrl: 'https://api.openai.com/v1',
apiUrl: 'https://platform.openai.com/api-keys',
example: 'gpt-4o',
},
{
id: 'gemini-api',
label: 'Gemini API',
baseUrl: 'https://generativelanguage.googleapis.com',
apiUrl: 'https://aistudio.google.com/apikey',
example: 'gemini-3.6-flash',
},
{
id: 'anthropic',
label: 'Anthropic API',
baseUrl: 'https://api.anthropic.com',
apiUrl: 'https://console.anthropic.com/settings/keys',
example: 'claude-haiku-4-5-20251001',
},
{
id: 'antigravity-cli',
label: 'Antigravity CLI免费',
baseUrl: '',
apiUrl: 'https://antigravity.google/',
example: 'gemini-3.6-flash-low',
},
{
id: 'claude-cli',
label: 'Claude Code 登录',
baseUrl: '',
apiUrl: 'https://code.claude.com',
example: 'haiku',
},
]
const PROVIDER_IDS = new Set(PROVIDERS.map((item) => item.id))
function providerOf(id) {
return PROVIDERS.find((entry) => entry.id === id)
}
function officialBaseUrl(provider) {
const item = providerOf(provider)
return item ? item.baseUrl : ''
}
function officialApiUrl(provider) {
const item = providerOf(provider)
return item ? item.apiUrl : ''
}
function officialExample(provider) {
const item = providerOf(provider)
return item ? item.example : ''
}
const FORM_FIELDS = ['apiKey', 'baseUrl', 'model']
function configPath() {
const home = process.env.MODLENS_HOME || join(homedir(), '.modlens')
return join(home, 'config.json')
}
function loadConfig() {
try {
const data = JSON.parse(readFileSync(configPath(), 'utf8'))
return data && typeof data === 'object' ? data : {}
} catch {
return {}
}
}
function formOf(cfg, provider) {
const id = PROVIDER_IDS.has(provider) ? provider : 'openai'
const entry =
cfg.providers && typeof cfg.providers === 'object' && typeof cfg.providers[id] === 'object'
? cfg.providers[id]
: {}
return {
provider: id,
apiKey: String(entry.apiKey || ''),
baseUrl: String(entry.baseUrl || officialBaseUrl(id)),
officialBaseUrl: officialBaseUrl(id),
apiUrl: officialApiUrl(id),
example: officialExample(id),
model: String(entry.model || ''),
}
}
function saveForm(values) {
const provider = String(values.provider || 'openai').trim()
if (!PROVIDER_IDS.has(provider)) {
const error = new Error(`unknown provider: ${provider}`)
error.status = 400
throw error
}
const cfg = loadConfig()
cfg.provider = provider
if (!cfg.providers || typeof cfg.providers !== 'object') cfg.providers = {}
if (!cfg.providers[provider] || typeof cfg.providers[provider] !== 'object') {
cfg.providers[provider] = {}
}
const entry = cfg.providers[provider]
for (const field of FORM_FIELDS) {
const raw = String(values[field] || '').trim()
if (raw) entry[field] = raw
else delete entry[field]
}
const path = configPath()
mkdirSync(dirname(path), { recursive: true })
writeFileSync(path, `${JSON.stringify(cfg, null, 2)}\n`, { encoding: 'utf8' })
try {
chmodSync(path, 0o600)
} catch {
// best-effort; some filesystems ignore mode
}
return formOf(cfg, provider)
}
async function readJsonBody(req) {
const chunks = []
for await (const chunk of req) chunks.push(chunk)
const raw = Buffer.concat(chunks).toString('utf8').trim()
if (!raw) return {}
return JSON.parse(raw)
}
function json(res, status, body) {
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })
res.end(JSON.stringify(body))
}
export function apply(ctx) {
if (typeof ctx.inject !== 'function') return
ctx.inject(['webServer'], (scope) => {
try {
scope.webServer.register({
name: 'dsh-desktop-modlens',
kind: 'exact',
path: '/dsh-desktop/modlens',
handler: async (req, res) => {
try {
if (req.method === 'GET') {
const cfg = loadConfig()
const provider = new URL(req.url, 'http://localhost').searchParams.get('provider')
json(res, 200, {
...formOf(cfg, provider || cfg.provider || 'openai'),
options: PROVIDERS.map((item) => ({
id: item.id,
label: item.label,
officialBaseUrl: item.baseUrl,
apiUrl: item.apiUrl,
example: item.example,
})),
})
return
}
if (req.method === 'PUT' || req.method === 'POST') {
const body = await readJsonBody(req)
json(res, 200, { ...saveForm(body), saved: true })
return
}
res.writeHead(405).end()
} catch (error) {
json(res, error.status || 500, { error: String(error?.message || error) })
}
},
})
} catch (error) {
console.error(`[dsh-desktop-vision] config route skipped: ${error}`)
}
})
}