dsh-contextos-devops 0.0.0-stage → 1.48.31
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/LICENSE +21 -0
- package/README.md +207 -2
- package/assets/avatar.jpg +0 -0
- package/assets/client.css +3316 -0
- package/assets/icon-180.png +0 -0
- package/build/client.mjs +64 -0
- package/client.js +7622 -0
- package/cordis.patch.yml +3 -0
- package/icon.svg +15 -0
- package/index.js +345 -0
- package/lib/base/env.js +22 -0
- package/lib/base/field.js +25 -0
- package/lib/base/http.js +140 -0
- package/lib/base/platform.js +33 -0
- package/lib/base/registry.js +186 -0
- package/lib/base/state.js +66 -0
- package/lib/base/table.js +47 -0
- package/lib/base/texttable.js +26 -0
- package/lib/base/url.js +178 -0
- package/lib/core/README.md +17 -0
- package/lib/core/apps.js +675 -0
- package/lib/core/auth.js +110 -0
- package/lib/core/avatar.js +289 -0
- package/lib/core/backup.js +105 -0
- package/lib/core/codegen.js +401 -0
- package/lib/core/copilot.js +419 -0
- package/lib/core/data.js +157 -0
- package/lib/core/device.js +148 -0
- package/lib/core/files.js +152 -0
- package/lib/core/gobuild.js +63 -0
- package/lib/core/iam.js +122 -0
- package/lib/core/identity.js +22 -0
- package/lib/core/nodes.js +627 -0
- package/lib/core/ops.js +306 -0
- package/lib/core/project.js +393 -0
- package/lib/core/spaces.js +406 -0
- package/lib/core/terminal.js +137 -0
- package/lib/core/terminal2.js +141 -0
- package/lib/core/users.js +51 -0
- package/lib/core/vocab.js +158 -0
- package/lib/misc/async.js +15 -0
- package/lib/misc/shell.js +27 -0
- package/lib/misc/static.js +33 -0
- package/locale/en.json +10 -0
- package/locale/zh.json +10 -0
- package/package.json +91 -4
- package/src/client/README.md +37 -0
- package/src/client/parts/00-prelude.js +315 -0
- package/src/client/parts/05-bootstrap.js +61 -0
- package/src/client/parts/08-store.js +85 -0
- package/src/client/parts/10-ui.js +188 -0
- package/src/client/parts/12-debug.js +361 -0
- package/src/client/parts/15-layout.js +86 -0
- package/src/client/parts/20-devworkspace.js +789 -0
- package/src/client/parts/25-editor-ui.js +28 -0
- package/src/client/parts/30-modals.js +85 -0
- package/src/client/parts/32-cards.js +176 -0
- package/src/client/parts/34-forms.js +176 -0
- package/src/client/parts/36-data.js +267 -0
- package/src/client/parts/40-project.js +430 -0
- package/src/client/parts/45-map.js +109 -0
- package/src/client/parts/46-pm.js +158 -0
- package/src/client/parts/47-three.js +324 -0
- package/src/client/parts/49-msg.js +167 -0
- package/src/client/parts/50-chat.js +1422 -0
- package/src/client/parts/51-pmselection.js +31 -0
- package/src/client/parts/52-pmdata.js +29 -0
- package/src/client/parts/55-pm.js +146 -0
- package/src/client/parts/58-config.js +381 -0
- package/src/client/parts/59-monitor.js +262 -0
- package/src/client/parts/60-page.js +1518 -0
- package/src/client/styles/kit.css +20 -0
- package/src/client/styles/main.css +3294 -0
- package/src/client/styles/tokens.css +2 -0
- package/vendor/leaflet/leaflet.css +661 -0
- package/vendor/leaflet/leaflet.js +6 -0
- package/vendor/three/three.module.js +53044 -0
- package/vendor/xterm/addon-fit.js +2 -0
- package/vendor/xterm/xterm.css +218 -0
- package/vendor/xterm/xterm.js +2 -0
package/lib/core/ops.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// 服务端操作注册表:op → { method, noUser, run(body, ctx) }。
|
|
2
|
+
//
|
|
3
|
+
// 这里是插件 HTTP API 的**唯一入口清单**:index.js 只做分发,不再写业务分支。
|
|
4
|
+
// - method 与客户端 api() 的传输方法一一对应(GET 查询 / PUT 创建 / POST 其余),
|
|
5
|
+
// 由 scripts/op-check.mjs 交叉校验,防止两端漂移。
|
|
6
|
+
// - noUser: true 表示不预先 ensureUser(node)。这些操作在“节点还没起来”时也要能用
|
|
7
|
+
// (探活、扫描、git 状态、启停节点本身),提前建用户会卡 15s。
|
|
8
|
+
// - run() 抛错即失败;index.js 统一回 { ok: false, error }。
|
|
9
|
+
import fs from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { ctxRoot, stateDir } from '../base/env.js';
|
|
12
|
+
import { registryPath, listNodes, refreshNodes } from '../base/registry.js';
|
|
13
|
+
import { writeData, readData, removeData } from './data.js';
|
|
14
|
+
import { discoverDevices } from './device.js';
|
|
15
|
+
import { storeApps, storeAction, createStoreInstance, listApps, autogenApp, autogenService, autogenSpec, uniqueAppName, createInstance, createApp, appFields, isCommandReady } from './apps.js';
|
|
16
|
+
import { freePort, fieldOptions, pmCreateItem, pmModifyItem, pmRemoveItem, pmActItem, pmList, pmLog, projectStatus, seedProject } from './project.js';
|
|
17
|
+
import { listAvatars, ensureProject, createAvatar, setAvatar, updateAvatar, deleteAvatar, listBackgrounds, createBackground, setBackground, updateBackground, deleteBackground, saveUserMedia } from './avatar.js';
|
|
18
|
+
import { chatWithDSH, getLlm, planProject, chatProviders, appFunctionPlan, resolveWrite, codegenPlan } from './copilot.js';
|
|
19
|
+
import { listDir, readFile, writeFile, listTree, makeDir, renamePath, removePath, gitStatus } from './files.js';
|
|
20
|
+
import { deployNode, startNode, stopNode, openNode, openSpace, nodeTerminal, deleteNode, deleteSpace, deployOfficialSpaces, dockerDeploy, remoteDeploy, remoteLogin, runPipeline, sshHosts, sysControl, spaceTerminal } from './nodes.js';
|
|
21
|
+
import { listDataFiles, readDataFile, migrateStateKeys } from '../base/state.js';
|
|
22
|
+
import { spaceDir, startSpace, stopSpace, compileSpace, listSpaces, createSpace, initSpace, buildSpace, openTerminal } from './spaces.js';
|
|
23
|
+
import { codegenList, codegenRender, codegenPath, codegenWrite, codegenMerge, moduleInfo, renderFields, renderModel, detectClass, modelFields as readModelFields } from './codegen.js';
|
|
24
|
+
import { checkField } from './vocab.js';
|
|
25
|
+
import { http } from '../base/http.js';
|
|
26
|
+
|
|
27
|
+
const bodyUid = (body, name) => body[`${name}_uid`] || body[`${name}UID`] || body[`${name}Id`] || '';
|
|
28
|
+
const itemUid = (body) => body.item_uid || body.itemUID || body.itemId || '';
|
|
29
|
+
const str = (v) => (v == null ? '' : String(v));
|
|
30
|
+
|
|
31
|
+
/** 读节点根目录:优先用注册表里的 dir,找不到退回主仓根。 */
|
|
32
|
+
async function nodeRoot(node) {
|
|
33
|
+
const hit = (await listNodes()).find((n) => Number(n.port) === Number(node));
|
|
34
|
+
return (hit && hit.dir) || ctxRoot();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const OPS = {
|
|
38
|
+
// ---- 探活与注册表 ----
|
|
39
|
+
health: { method: 'GET', noUser: true, run: async () => ({ ctxRoot: ctxRoot(), nodes: await listNodes() }) },
|
|
40
|
+
nodes: { method: 'GET', noUser: true, run: async () => ({ ctxRoot: ctxRoot(), nodes: await listNodes() }) },
|
|
41
|
+
refreshNodes: { method: 'POST', noUser: true, run: async () => { const nodes = await refreshNodes(); return { count: nodes.length, nodes, file: registryPath() }; } },
|
|
42
|
+
|
|
43
|
+
// ---- 本机能力 ----
|
|
44
|
+
sshHosts: { method: 'GET', noUser: true, run: async () => await sshHosts() },
|
|
45
|
+
sysControl: { method: 'POST', noUser: true, run: async (b) => await sysControl({ action: b.action, value: b.value }) },
|
|
46
|
+
|
|
47
|
+
// ---- 文件与 git(节点没起来也要能看代码)----
|
|
48
|
+
git: { method: 'GET', noUser: true, run: async (b, c) => await gitStatus({ node: c.node, space: b.space }) },
|
|
49
|
+
listDir: { method: 'GET', noUser: true, run: async (b, c) => await listDir({ node: c.node, space: b.space, path: str(b.path) }) },
|
|
50
|
+
mkdir: { method: 'POST', noUser: true, run: async (b, c) => await makeDir({ node: c.node, space: b.space, path: str(b.path) }) },
|
|
51
|
+
renamePath: { method: 'POST', noUser: true, run: async (b, c) => await renamePath({ node: c.node, space: b.space, from: str(b.from), to: str(b.to) }) },
|
|
52
|
+
removePath: { method: 'POST', noUser: true, run: async (b, c) => await removePath({ node: c.node, space: b.space, path: str(b.path) }) },
|
|
53
|
+
tree: { method: 'GET', noUser: true, run: async (b, c) => await listTree({ node: c.node, space: b.space, path: str(b.path) }) },
|
|
54
|
+
spaceDir: { method: 'POST', noUser: true, run: async (b, c) => await spaceDir({ node: c.node, space: b.space }) },
|
|
55
|
+
readFile: { method: 'GET', noUser: true, run: async (b, c) => await readFile({ node: c.node, space: b.space, path: str(b.path) }) },
|
|
56
|
+
writeFile: { method: 'POST', noUser: true, run: async (b, c) => await writeFile({ node: c.node, space: b.space, path: b.path, content: str(b.content) }) },
|
|
57
|
+
compile: { method: 'POST', noUser: true, run: async (b, c) => await compileSpace({ node: c.node, space: b.space }) },
|
|
58
|
+
|
|
59
|
+
// ---- 节点 ----
|
|
60
|
+
deploy: { method: 'POST', noUser: true, run: async (b) => await deployNode({ port: b.port, name: b.name, mode: b.mode || 'copy', parent: b.parent || '', repo: b.repo || '', sourceBin: b.sourceBin || '', dir: b.dir || '' }) },
|
|
61
|
+
start: { method: 'POST', noUser: true, run: async (b) => await startNode({ port: b.port }) },
|
|
62
|
+
stop: { method: 'POST', noUser: true, run: async (b) => await stopNode({ port: b.port }) },
|
|
63
|
+
openNode: { method: 'POST', noUser: true, run: async (b) => await openNode({ port: b.port }) },
|
|
64
|
+
nodeTerminal: { method: 'POST', noUser: true, run: async (b) => await nodeTerminal({ port: b.port }) },
|
|
65
|
+
deleteNode: { method: 'POST', run: async (b) => await deleteNode({ port: b.port }) },
|
|
66
|
+
dockerDeploy: { method: 'POST', run: async (b) => await dockerDeploy({ port: b.port, dir: b.dir || '', image: b.image || '', name: b.name || '' }) },
|
|
67
|
+
remoteDeploy: { method: 'POST', run: async (b) => await remoteDeploy({ host: b.host || '', user: b.user || '', sshPort: b.ssh_port || 22, port: b.port, dir: b.dir || '', image: b.image || '' }) },
|
|
68
|
+
remoteLogin: { method: 'POST', run: async (b) => await remoteLogin({ url: b.url || '', user: b.user || '', pass: b.pass || '' }) },
|
|
69
|
+
pipeline: { method: 'POST', run: async (b) => await runPipeline({ port: Number(b.port) || 9188, space: b.space || 'devops_demo', app: b.app || 'note' }) },
|
|
70
|
+
|
|
71
|
+
// ---- 空间 ----
|
|
72
|
+
spaces: { method: 'GET', run: async (b, c) => await listSpaces({ node: c.node }) },
|
|
73
|
+
createSpace: { method: 'POST', run: async (b, c) => await createSpace({ node: c.node, name: b.name, repos: b.repos || '' }) },
|
|
74
|
+
initSpace: { method: 'POST', run: async (b, c) => await initSpace({ node: c.node, space: b.space, repos: b.repos || '' }) },
|
|
75
|
+
buildSpace: { method: 'POST', run: async (b, c) => await buildSpace({ node: c.node, space: b.space }) },
|
|
76
|
+
spaceStart: { method: 'POST', noUser: true, run: async (b, c) => await startSpace({ node: c.node, space: b.space }) },
|
|
77
|
+
spaceStop: { method: 'POST', noUser: true, run: async (b, c) => await stopSpace({ node: c.node, space: b.space }) },
|
|
78
|
+
spaceOpen: { method: 'POST', noUser: true, run: async (b, c) => await openSpace({ node: c.node, space: b.space }) },
|
|
79
|
+
spaceTerminal: { method: 'POST', noUser: true, run: async (b, c) => await spaceTerminal({ node: c.node, space: b.space }) },
|
|
80
|
+
openTerminal: { method: 'POST', run: async (b, c) => await openTerminal({ node: c.node, space: b.space || '' }) },
|
|
81
|
+
deleteSpace: { method: 'POST', run: async (b, c) => await deleteSpace({ node: c.node, space: b.space }) },
|
|
82
|
+
officialSpaces: { method: 'POST', run: async (b, c) => await deployOfficialSpaces({ node: c.node, keys: b.keys || [] }) },
|
|
83
|
+
spaceReady: { method: 'POST', run: async (b, c) => ({ ready: await isCommandReady({ node: c.node, space: b.space, command: b.command }) }) },
|
|
84
|
+
storageCheck: { method: 'POST', run: async (b, c) => { const { storageReady } = await import('./spaces.js'); return await storageReady({ node: c.node }); } },
|
|
85
|
+
backupSpace: { method: 'POST', run: async (b, c) => { const { backupSpace } = await import('./backup.js'); return await backupSpace({ node: c.node, space: b.space, tag: b.tag || '' }); } },
|
|
86
|
+
backups: { method: 'POST', run: async (b) => { const { listBackups } = await import('./backup.js'); return await listBackups({ limit: Number(b.limit) || 20 }); } },
|
|
87
|
+
sampleNodes: { method: 'POST', run: async () => { const { sampleNodes } = await import('./backup.js'); return await sampleNodes(); } },
|
|
88
|
+
monitorQuery: { method: 'POST', run: async (b, c) => { const { monitorQuery } = await import('./backup.js'); return await monitorQuery({ node: c.node, space: b.space || '', command: b.command, action: b.action || '', params: b.params || {} }); } },
|
|
89
|
+
|
|
90
|
+
// ---- 应用 ----
|
|
91
|
+
apps: { method: 'GET', run: async (b, c) => await listApps({ node: c.node, space: b.space, avatarUid: b.uid || '' }) },
|
|
92
|
+
createApp: { method: 'POST', run: async (b, c) => await createApp({ node: c.node, space: b.space, name: b.name, zone: b.zone || 'devops', help: b.help || '' }) },
|
|
93
|
+
createInstance: { method: 'POST', run: async (b, c) => await createInstance({ node: c.node, space: b.space, command: b.command, place: b.place || '', name: b.name || '我的应用', city: b.city || '深圳市', company: b.company || '云轩领航', uid: b.uid || '' }) },
|
|
94
|
+
nameCheck: { method: 'POST', run: async (b, c) => await uniqueAppName({ node: c.node, space: b.space, name: b.name }) },
|
|
95
|
+
appFields: { method: 'POST', run: async (b, c) => await appFields({ node: c.node, space: b.space, command: b.command || '' }) },
|
|
96
|
+
autogenSpec: { method: 'POST', run: async (b, c) => await autogenSpec({ node: c.node }) },
|
|
97
|
+
autogenApp: { method: 'POST', run: async (b, c) => { const args = { node: c.node, space: b.space, name: b.name, help: b.help || '', place: b.place || b.name, value: b.value || 'profile', trans: b.trans || '' }; let data = null; try { data = await autogenService(args); } catch { data = null; } if (!data || !data.created) data = await autogenApp(args); return data; } },
|
|
98
|
+
appPlan: { method: 'POST', run: async (b) => await appFunctionPlan({ name: b.name || '', help: b.help || '', text: b.text || '' }) },
|
|
99
|
+
resolveWrite: { method: 'POST', run: async (b) => await resolveWrite({ text: b.text || '', apps: b.apps || [], mode: b.mode === 'read' ? 'read' : 'write' }) },
|
|
100
|
+
|
|
101
|
+
// ---- 应用商店 ----
|
|
102
|
+
storeApps: { method: 'GET', run: async (b, c) => await storeApps({ node: c.node, lang: b.lang || 'zh' }) },
|
|
103
|
+
storeAction: { method: 'POST', run: async (b, c) => await storeAction({ node: c.node, space: b.space || '20240724-community', serviceUid: b.service_uid || '', action: b.action || '' }) },
|
|
104
|
+
createStoreInstance: { method: 'POST', run: async (b, c) => await createStoreInstance({ node: c.node, service: b.service || {}, name: b.name || '', city: b.city || '深圳市', company: b.company || '云轩领航' }) },
|
|
105
|
+
|
|
106
|
+
// ---- 代码生成 ----
|
|
107
|
+
codegenList: { method: 'POST', run: async () => codegenList() },
|
|
108
|
+
codegenRender: { method: 'POST', run: async (b) => codegenRender({ template: b.template, vars: b.vars || {} }) },
|
|
109
|
+
codegenInspect: { method: 'POST', run: async (b) => {
|
|
110
|
+
const root = await nodeRoot(b.node);
|
|
111
|
+
const file = `${b.zone || 'devops'}/${b.module}.go`;
|
|
112
|
+
const src = await fs.readFile(codegenPath({ root, space: b.space, file }), 'utf8');
|
|
113
|
+
const info = moduleInfo(src);
|
|
114
|
+
let modelFields = [];
|
|
115
|
+
if (info.class === 'platform') {
|
|
116
|
+
const mf = `${b.zone || 'devops'}/model/model.go`;
|
|
117
|
+
try { modelFields = readModelFields(await fs.readFile(codegenPath({ root, space: b.space, file: mf }), 'utf8')); } catch { modelFields = []; }
|
|
118
|
+
}
|
|
119
|
+
return { ...info, file, modelFields };
|
|
120
|
+
} },
|
|
121
|
+
codegenPlan: { method: 'POST', run: async (b) => await codegenPlan({ text: b.text || '', module: b.module || '', fields: b.fields || [] }) },
|
|
122
|
+
codegenApply: { method: 'POST', run: async (b) => {
|
|
123
|
+
const root = await nodeRoot(b.node);
|
|
124
|
+
const rawVars = { ...(b.vars || {}) };
|
|
125
|
+
if (b.field && !rawVars.field) rawVars.field = b.field;
|
|
126
|
+
if (b.template === 'field') {
|
|
127
|
+
const cf0 = checkField(rawVars.field || b.field);
|
|
128
|
+
if (!cf0.ok) throw new Error(`字段名「${rawVars.field || b.field}」不在框架通用词表里,请改用:${cf0.suggest}`);
|
|
129
|
+
rawVars.field = cf0.name; b.field = cf0.name;
|
|
130
|
+
}
|
|
131
|
+
if (b.type && !rawVars.type) rawVars.type = b.type;
|
|
132
|
+
if (!rawVars.type) rawVars.type = 'string';
|
|
133
|
+
if (!rawVars.class) {
|
|
134
|
+
try { rawVars.class = detectClass(await fs.readFile(codegenPath({ root, space: b.space, file: `${rawVars.zone || 'devops'}/${rawVars.module}.go` }), 'utf8')); } catch { rawVars.class = 'system'; }
|
|
135
|
+
}
|
|
136
|
+
if (b.zone && !rawVars.zone) rawVars.zone = b.zone;
|
|
137
|
+
if (b.module && !rawVars.module) rawVars.module = b.module;
|
|
138
|
+
for (const k of ['role', 'style', 'icon', 'help', 'label', 'Action', 'action', 'params']) if (b[k] && !rawVars[k]) rawVars[k] = b[k];
|
|
139
|
+
|
|
140
|
+
const out = codegenRender({ template: b.template, vars: rawVars });
|
|
141
|
+
if (!out.file) throw new Error('this template has no file; paste the snippet manually');
|
|
142
|
+
const target = codegenPath({ root, space: b.space, file: out.file });
|
|
143
|
+
let existing = '';
|
|
144
|
+
try { existing = await fs.readFile(target, 'utf8'); } catch { existing = ''; }
|
|
145
|
+
|
|
146
|
+
if (out.template === 'field') {
|
|
147
|
+
if (!existing) throw new Error(`module file not found: ${out.file}`);
|
|
148
|
+
const want = b.field || out.field;
|
|
149
|
+
const cf = checkField(want);
|
|
150
|
+
if (!cf.ok) throw new Error(`字段名「${want}」不在框架通用词表里,请改用:${cf.suggest}`);
|
|
151
|
+
const fname = cf.name;
|
|
152
|
+
const cls = detectClass(existing);
|
|
153
|
+
const content = renderFields(existing, fname, cls);
|
|
154
|
+
const w = await codegenWrite({ root, space: b.space, file: out.file, content });
|
|
155
|
+
let modelChanged = false;
|
|
156
|
+
if (cls === 'platform') {
|
|
157
|
+
const modelFile = `${str(out.file).split('/')[0]}/model/model.go`;
|
|
158
|
+
try {
|
|
159
|
+
const mp = codegenPath({ root, space: b.space, file: modelFile });
|
|
160
|
+
const msrc = await fs.readFile(mp, 'utf8');
|
|
161
|
+
const next = renderModel(msrc, fname, b.type || 'string', b.module || str(out.file).split('/')[1]?.replace(/\.go$/, '') || '');
|
|
162
|
+
if (next !== msrc) { await codegenWrite({ root, space: b.space, file: modelFile, content: next }); modelChanged = true; }
|
|
163
|
+
} catch { modelChanged = false; }
|
|
164
|
+
}
|
|
165
|
+
let built = false; let buildNote = '';
|
|
166
|
+
if (b.build !== false) { try { await buildSpace({ node: b.node, space: b.space }); built = true; } catch (e) { buildNote = e.message; } }
|
|
167
|
+
return { file: out.file, bytes: w.bytes, class: cls, field: fname, renamed: !!cf.renamed, fields: moduleInfo(content).fields, modelChanged, built, buildNote };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let content;
|
|
171
|
+
if (!existing) content = (out.body || '') + (out.actionMethod || '') + (out.method || '');
|
|
172
|
+
else if (out.whole) throw new Error(`refuse to overwrite existing file: ${out.file}`);
|
|
173
|
+
else content = codegenMerge({ existing, decl: out.body, methods: (out.actionMethod || '') + (out.method || '') });
|
|
174
|
+
const w = await codegenWrite({ root, space: b.space, file: out.file, content });
|
|
175
|
+
let build = null;
|
|
176
|
+
if (b.build !== false) build = await buildSpace({ node: b.node, space: b.space });
|
|
177
|
+
return { ...w, template: out.template, built: !!(build && build.rebuilt), build };
|
|
178
|
+
} },
|
|
179
|
+
|
|
180
|
+
// ---- 项目管理(PM)----
|
|
181
|
+
seedProject: { method: 'POST', run: async (b, c) => await seedProject({ node: c.node, avatarUid: b.uid || '' }) },
|
|
182
|
+
pmList: { method: 'GET', run: async (b, c) => await pmList({ node: c.node, avatarUid: b.uid || '', lang: b.lang || 'zh', targetUid: bodyUid(b, 'target'), planUid: bodyUid(b, 'plan'), taskUid: bodyUid(b, 'task'), reportUid: bodyUid(b, 'report'), scope: b.scope || 'target' }) },
|
|
183
|
+
pmTree: { method: 'GET', run: async (b, c) => await pmList({ node: c.node, avatarUid: b.uid || '', lang: b.lang || 'zh', targetUid: bodyUid(b, 'target'), planUid: bodyUid(b, 'plan'), taskUid: bodyUid(b, 'task'), reportUid: bodyUid(b, 'report'), scope: b.scope || 'target' }) },
|
|
184
|
+
pmCreate: { method: 'PUT', run: async (b, c) => await pmCreateItem({ node: c.node, avatarUid: b.uid || '', kind: b.kind || '', title: b.title || '', content: b.content || '', targetUid: bodyUid(b, 'target'), planUid: bodyUid(b, 'plan'), taskUid: bodyUid(b, 'task'), reportUid: bodyUid(b, 'report'), beginTime: b.begin_time || '', endTime: b.end_time || '', reportType: b.report_type || 'daily', score: b.score || '5', feedbackStatus: b.feedback_status || 'canFinish', uidOptions: b.uid_options || {} }) },
|
|
185
|
+
pmModify: { method: 'POST', run: async (b, c) => await pmModifyItem({ node: c.node, avatarUid: b.uid || '', kind: b.kind || '', uid: itemUid(b), title: b.title || '', content: b.content || '', beginTime: b.begin_time || '', endTime: b.end_time || '', reportType: b.report_type || '', score: b.score || '', feedbackStatus: b.feedback_status || '', uidOptions: b.uid_options || {} }) },
|
|
186
|
+
pmUpdate: { method: 'POST', run: async (b, c) => await pmModifyItem({ node: c.node, avatarUid: b.uid || '', kind: b.kind || '', uid: itemUid(b), title: b.title || '', content: b.content || '', beginTime: b.begin_time || '', endTime: b.end_time || '', reportType: b.report_type || '', score: b.score || '', feedbackStatus: b.feedback_status || '', uidOptions: b.uid_options || {} }) },
|
|
187
|
+
pmDelete: { method: 'POST', run: async (b, c) => await pmRemoveItem({ node: c.node, avatarUid: b.uid || '', kind: b.kind || '', uid: itemUid(b), uidOptions: b.uid_options || {} }) },
|
|
188
|
+
pmRemove: { method: 'POST', run: async (b, c) => await pmRemoveItem({ node: c.node, avatarUid: b.uid || '', kind: b.kind || '', uid: itemUid(b), uidOptions: b.uid_options || {} }) },
|
|
189
|
+
pmAct: { method: 'POST', run: async (b, c) => await pmActItem({ node: c.node, avatarUid: b.uid || '', kind: b.kind || '', uid: itemUid(b), action: b.action || '', uidOptions: b.uid_options || {} }) },
|
|
190
|
+
pmLog: { method: 'POST', run: async (b, c) => await pmLog({ node: c.node, space: b.space || '', logId: b.log_id || '' }) },
|
|
191
|
+
projectStatus: { method: 'GET', run: async (b, c) => await projectStatus({ node: c.node, avatarUid: b.uid || '' }) },
|
|
192
|
+
fieldOptions: { method: 'GET', run: async (b, c) => await fieldOptions({ node: c.node, space: b.space || '', command: b.command || '', field: b.field || '', projectUid: b.project_uid || '', uid: b.uid || '', sessid: b.sessid || '' }) },
|
|
193
|
+
freePort: { method: 'POST', run: async (b) => await freePort({ start: b.start || 9188 }) },
|
|
194
|
+
planProject: { method: 'POST', run: async (b, c) => await planProject({ node: c.node, avatarUid: b.uid || '', goal: b.goal || '' }) },
|
|
195
|
+
|
|
196
|
+
// ---- 分身与背景 ----
|
|
197
|
+
avatars: { method: 'GET', run: async (b, c) => await listAvatars({ node: c.node }) },
|
|
198
|
+
ensureProject: { method: 'POST', run: async (b, c) => await ensureProject({ node: c.node, avatarUid: b.uid || '' }) },
|
|
199
|
+
createAvatar: { method: 'POST', run: async (b, c) => await createAvatar({ node: c.node, name: b.name || '', city: b.city || '深圳市', company: b.company || '云轩领航', persona: b.persona || '', preferences: b.preferences || '', style: b.style || '', bio: b.bio || '' }) },
|
|
200
|
+
setAvatar: { method: 'POST', run: async (b, c) => await setAvatar({ node: c.node, uid: b.uid }) },
|
|
201
|
+
updateAvatar: { method: 'POST', run: async (b, c) => await updateAvatar({ node: c.node, uid: b.uid, patch: b.patch || {} }) },
|
|
202
|
+
deleteAvatar: { method: 'POST', run: async (b, c) => await deleteAvatar({ node: c.node, uid: b.uid }) },
|
|
203
|
+
backgrounds: { method: 'GET', run: async (b, c) => await listBackgrounds({ node: c.node }) },
|
|
204
|
+
createBackground: { method: 'POST', run: async (b, c) => await createBackground({ node: c.node, name: b.name || '' }) },
|
|
205
|
+
setBackground: { method: 'POST', run: async (b, c) => await setBackground({ node: c.node, id: b.id || '' }) },
|
|
206
|
+
updateBackground: { method: 'POST', run: async (b, c) => await updateBackground({ node: c.node, id: b.id || '', patch: b.patch || {} }) },
|
|
207
|
+
deleteBackground: { method: 'POST', run: async (b, c) => await deleteBackground({ node: c.node, id: b.id || '' }) },
|
|
208
|
+
saveUserMedia: { method: 'POST', run: async (b, c) => await saveUserMedia({ node: c.node, uid: b.uid || '', kind: b.kind || 'avatar', dataUrl: b.dataUrl || '', name: b.name || '' }) },
|
|
209
|
+
|
|
210
|
+
// ---- 设备 ----
|
|
211
|
+
devices: { method: 'GET', run: async (b, c) => await discoverDevices({ node: c.node }) },
|
|
212
|
+
|
|
213
|
+
// ---- 数据 ----
|
|
214
|
+
readData: { method: 'GET', run: async (b, c) => {
|
|
215
|
+
const sc = b.scope || {};
|
|
216
|
+
const inst = str(b.inst || sc.system_uid || sc.project_uid || Object.entries(sc).find(([k, v]) => /_uid$/.test(k) && k !== 'user_uid' && v)?.[1] || '');
|
|
217
|
+
const actor = str(b.uid || sc['user.uid'] || sc.user_uid || '');
|
|
218
|
+
return await readData({ node: c.node, space: b.space, command: b.command, query: actor ? { 'user.uid': actor } : {}, inst, record: str(b.record) });
|
|
219
|
+
} },
|
|
220
|
+
writeData: { method: 'POST', run: async (b, c) => await writeData({ node: c.node, space: b.space, command: b.command, fields: b.fields || {}, scope: b.scope || {} }) },
|
|
221
|
+
removeData: { method: 'POST', run: async (b) => await removeData(b) },
|
|
222
|
+
|
|
223
|
+
// ---- AI ----
|
|
224
|
+
llmInfo: { method: 'GET', run: async () => {
|
|
225
|
+
const info = [];
|
|
226
|
+
for (const p of chatProviders()) {
|
|
227
|
+
let models = [];
|
|
228
|
+
try { models = await getLlm().listModels(p.id); } catch { /* adapter without catalog */ }
|
|
229
|
+
info.push({ id: p.id, name: p.name, models: models.slice(0, 8).map((m) => m.id) });
|
|
230
|
+
}
|
|
231
|
+
return { mounted: !!getLlm(), providers: info };
|
|
232
|
+
} },
|
|
233
|
+
chat: { method: 'POST', run: async (b) => await chatWithDSH({ message: b.message, history: b.history || [], context: b.context || {}, provider: b.provider || '', model: b.model || '', mode: b.mode || 'factory' }) },
|
|
234
|
+
|
|
235
|
+
// ---- IAM ----
|
|
236
|
+
iamList: { method: 'POST', run: async (b, c) => { const { iamList } = await import('./iam.js'); return await iamList({ node: c.node, level: b.level || 'system', kind: b.kind || 'user', space: b.space || '', mod: b.mod || '', uid: b.uid || '' }); } },
|
|
237
|
+
iamCall: { method: 'POST', run: async (b, c) => { const { iamCall } = await import('./iam.js'); return await iamCall({ node: c.node, level: b.level || 'system', kind: b.kind || 'user', space: b.space || '', mod: b.mod || '', action: b.action || 'create', fields: b.fields || {}, args: b.args || [], uid: b.uid || '' }); } },
|
|
238
|
+
iamSubjects: { method: 'POST', run: async (b, c) => {
|
|
239
|
+
const { SUBJECTS, FUND, commandExists } = await import('./iam.js');
|
|
240
|
+
const space = b.space || '20240724-community';
|
|
241
|
+
const rows = async (list) => Promise.all(list.map(async (x) => ({ ...x, exists: await commandExists({ node: c.node, space, command: x.command }), url: `http://127.0.0.1:${c.node}/s/${space}/c/${x.command}` })));
|
|
242
|
+
return { space, subjects: await rows(SUBJECTS), funds: await rows(FUND) };
|
|
243
|
+
} },
|
|
244
|
+
iamEndpoints: { method: 'POST', run: async (b, c) => { const { iamEndpoints } = await import('./iam.js'); return iamEndpoints({ node: c.node, space: b.space || '20240724-community', mod: b.mod || '' }); } },
|
|
245
|
+
nodeAuth: { method: 'POST', run: async (b, c) => {
|
|
246
|
+
const { ensureNodeAuth, peekNodeAuth, adminSessid, toolToken } = await import('./auth.js');
|
|
247
|
+
if (b.kind === 'admin') return { node: Number(c.node), kind: 'admin', sessid: await adminSessid({ node: c.node, username: b.username || '' }) };
|
|
248
|
+
if (b.kind === 'tool') return { node: Number(c.node), kind: 'tool', token: await toolToken({ node: c.node }) };
|
|
249
|
+
if (b.kind === 'app') {
|
|
250
|
+
const { appSession } = await import('./auth.js');
|
|
251
|
+
const space = b.space || '20240724-community';
|
|
252
|
+
const s0 = await appSession({ node: c.node, space, userUid: b.userUid || b.uid || '' });
|
|
253
|
+
return { node: Number(c.node), space, sessid: s0.sessid, cookie: s0.cookie, kind: 'app' };
|
|
254
|
+
}
|
|
255
|
+
return b.peek ? peekNodeAuth({ node: c.node }) : await ensureNodeAuth({ node: c.node, username: b.username || '', force: !!b.force });
|
|
256
|
+
} },
|
|
257
|
+
|
|
258
|
+
// ---- 命令自省与数据文件 ----
|
|
259
|
+
commandStyle: { method: 'POST', run: async (b, c) => {
|
|
260
|
+
const r = await http(c.node, `/c/command/${b.command || 'dream'}`, { timeout: 8000 });
|
|
261
|
+
let style = {};
|
|
262
|
+
try { style = JSON.parse((JSON.parse(r.text).meta || [])[0] || '{}')._style || {}; } catch { style = {}; }
|
|
263
|
+
return { command: b.command || 'dream', style };
|
|
264
|
+
} },
|
|
265
|
+
files: { method: 'GET', run: async () => await listDataFiles() },
|
|
266
|
+
file: { method: 'GET', run: async (b) => await readDataFile({ name: b.name || '' }) },
|
|
267
|
+
|
|
268
|
+
// ---- 状态存储(state.json)----
|
|
269
|
+
stateGet: { method: 'GET', noUser: true, run: async () => {
|
|
270
|
+
const file = path.join(stateDir(), 'state.json');
|
|
271
|
+
let data = {};
|
|
272
|
+
try { data = JSON.parse(await fs.readFile(file, 'utf8')); } catch { return {}; }
|
|
273
|
+
const before = JSON.stringify(data);
|
|
274
|
+
migrateStateKeys(data);
|
|
275
|
+
if (JSON.stringify(data) !== before) { try { await fs.writeFile(file, JSON.stringify(data)); } catch { /* read-only fs */ } }
|
|
276
|
+
return data;
|
|
277
|
+
} },
|
|
278
|
+
stateSet: { method: 'POST', noUser: true, run: async (b) => {
|
|
279
|
+
const name = str(b.name);
|
|
280
|
+
if (!name) throw new Error('name is required');
|
|
281
|
+
const file = path.join(stateDir(), 'state.json');
|
|
282
|
+
let data = {};
|
|
283
|
+
try { data = JSON.parse(await fs.readFile(file, 'utf8')); } catch { /* first write */ }
|
|
284
|
+
migrateStateKeys(data);
|
|
285
|
+
data[name] = b.value;
|
|
286
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
287
|
+
await fs.writeFile(file, JSON.stringify(data));
|
|
288
|
+
return { saved: name };
|
|
289
|
+
} },
|
|
290
|
+
|
|
291
|
+
// ---- 客户端上报 ----
|
|
292
|
+
clientError: { method: 'POST', noUser: true, run: async (b) => {
|
|
293
|
+
try { await fs.appendFile(path.join(stateDir(), 'client-errors.log'), `[${new Date().toISOString()}] ${str(b.error)}\n`); } catch { /* ignore */ }
|
|
294
|
+
return { logged: true };
|
|
295
|
+
} },
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
/** 供 UI/索引使用:按名字分组,便于 docs/CODE-INDEX.md 生成。 */
|
|
299
|
+
export function opIndex() {
|
|
300
|
+
const rows = [];
|
|
301
|
+
for (const [op, entry] of Object.entries(OPS)) {
|
|
302
|
+
const source = String(entry.run).slice(0, 400);
|
|
303
|
+
rows.push({ op, method: entry.method, noUser: !!entry.noUser, source });
|
|
304
|
+
}
|
|
305
|
+
return rows;
|
|
306
|
+
}
|