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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +207 -2
  3. package/assets/avatar.jpg +0 -0
  4. package/assets/client.css +3316 -0
  5. package/assets/icon-180.png +0 -0
  6. package/build/client.mjs +64 -0
  7. package/client.js +7622 -0
  8. package/cordis.patch.yml +3 -0
  9. package/icon.svg +15 -0
  10. package/index.js +345 -0
  11. package/lib/base/env.js +22 -0
  12. package/lib/base/field.js +25 -0
  13. package/lib/base/http.js +140 -0
  14. package/lib/base/platform.js +33 -0
  15. package/lib/base/registry.js +186 -0
  16. package/lib/base/state.js +66 -0
  17. package/lib/base/table.js +47 -0
  18. package/lib/base/texttable.js +26 -0
  19. package/lib/base/url.js +178 -0
  20. package/lib/core/README.md +17 -0
  21. package/lib/core/apps.js +675 -0
  22. package/lib/core/auth.js +110 -0
  23. package/lib/core/avatar.js +289 -0
  24. package/lib/core/backup.js +105 -0
  25. package/lib/core/codegen.js +401 -0
  26. package/lib/core/copilot.js +419 -0
  27. package/lib/core/data.js +157 -0
  28. package/lib/core/device.js +148 -0
  29. package/lib/core/files.js +152 -0
  30. package/lib/core/gobuild.js +63 -0
  31. package/lib/core/iam.js +122 -0
  32. package/lib/core/identity.js +22 -0
  33. package/lib/core/nodes.js +627 -0
  34. package/lib/core/ops.js +306 -0
  35. package/lib/core/project.js +393 -0
  36. package/lib/core/spaces.js +406 -0
  37. package/lib/core/terminal.js +137 -0
  38. package/lib/core/terminal2.js +141 -0
  39. package/lib/core/users.js +51 -0
  40. package/lib/core/vocab.js +158 -0
  41. package/lib/misc/async.js +15 -0
  42. package/lib/misc/shell.js +27 -0
  43. package/lib/misc/static.js +33 -0
  44. package/locale/en.json +10 -0
  45. package/locale/zh.json +10 -0
  46. package/package.json +91 -4
  47. package/src/client/README.md +37 -0
  48. package/src/client/parts/00-prelude.js +315 -0
  49. package/src/client/parts/05-bootstrap.js +61 -0
  50. package/src/client/parts/08-store.js +85 -0
  51. package/src/client/parts/10-ui.js +188 -0
  52. package/src/client/parts/12-debug.js +361 -0
  53. package/src/client/parts/15-layout.js +86 -0
  54. package/src/client/parts/20-devworkspace.js +789 -0
  55. package/src/client/parts/25-editor-ui.js +28 -0
  56. package/src/client/parts/30-modals.js +85 -0
  57. package/src/client/parts/32-cards.js +176 -0
  58. package/src/client/parts/34-forms.js +176 -0
  59. package/src/client/parts/36-data.js +267 -0
  60. package/src/client/parts/40-project.js +430 -0
  61. package/src/client/parts/45-map.js +109 -0
  62. package/src/client/parts/46-pm.js +158 -0
  63. package/src/client/parts/47-three.js +324 -0
  64. package/src/client/parts/49-msg.js +167 -0
  65. package/src/client/parts/50-chat.js +1422 -0
  66. package/src/client/parts/51-pmselection.js +31 -0
  67. package/src/client/parts/52-pmdata.js +29 -0
  68. package/src/client/parts/55-pm.js +146 -0
  69. package/src/client/parts/58-config.js +381 -0
  70. package/src/client/parts/59-monitor.js +262 -0
  71. package/src/client/parts/60-page.js +1518 -0
  72. package/src/client/styles/kit.css +20 -0
  73. package/src/client/styles/main.css +3294 -0
  74. package/src/client/styles/tokens.css +2 -0
  75. package/vendor/leaflet/leaflet.css +661 -0
  76. package/vendor/leaflet/leaflet.js +6 -0
  77. package/vendor/three/three.module.js +53044 -0
  78. package/vendor/xterm/addon-fit.js +2 -0
  79. package/vendor/xterm/xterm.css +218 -0
  80. package/vendor/xterm/xterm.js +2 -0
@@ -0,0 +1,141 @@
1
+ // 核心业务:内嵌终端(WebSocket + Python pty.fork 真 PTY)。
2
+ import crypto from 'node:crypto';
3
+ import os from 'node:os';
4
+ import fsp from 'node:fs';
5
+ import { spawn } from 'node:child_process';
6
+ import { ptySupported } from '../base/platform.js';
7
+
8
+ // ---- embedded terminal (WebSocket + PTY via /usr/bin/script) ----
9
+ function wsAccept(key) {
10
+ return crypto.createHash('sha1').update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64');
11
+ }
12
+ function wsFrame(data) {
13
+ const payload = Buffer.isBuffer(data) ? data : Buffer.from(String(data), 'utf8');
14
+ const len = payload.length;
15
+ let header;
16
+ if (len < 126) header = Buffer.from([0x81, len]);
17
+ else if (len < 65536) header = Buffer.from([0x81, 126, (len >> 8) & 0xff, len & 0xff]);
18
+ else {
19
+ header = Buffer.alloc(10);
20
+ header[0] = 0x81; header[1] = 127;
21
+ header.writeBigUInt64BE(BigInt(len), 2);
22
+ }
23
+ return Buffer.concat([header, payload]);
24
+ }
25
+ const PTY_HELPER = `
26
+ import os, sys, pty, select, fcntl, termios, struct, signal
27
+ def main():
28
+ cwd = sys.argv[1]
29
+ shell = sys.argv[2] if len(sys.argv) > 2 else (os.environ.get('SHELL') or '/bin/zsh')
30
+ os.chdir(cwd)
31
+ pid, fd = pty.fork()
32
+ if pid == 0:
33
+ os.environ['TERM'] = 'xterm-256color'
34
+ os.execvp(shell, [shell])
35
+ def nonblock(f):
36
+ fcntl.fcntl(f, fcntl.F_SETFL, fcntl.fcntl(f, fcntl.F_GETFL) | os.O_NONBLOCK)
37
+ nonblock(fd); nonblock(0)
38
+ while True:
39
+ r, _, _ = select.select([fd, 0], [], [])
40
+ if fd in r:
41
+ try:
42
+ data = os.read(fd, 65536)
43
+ if not data: break
44
+ os.write(1, data)
45
+ except OSError:
46
+ pass
47
+ if 0 in r:
48
+ try:
49
+ data = os.read(0, 65536)
50
+ if not data: break
51
+ if data.startswith(b'\\x00CTX_RESIZE '):
52
+ try:
53
+ cols, rows = data.decode().strip().split(' ')[1:3]
54
+ fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack('HHHH', int(rows), int(cols), 0, 0))
55
+ os.kill(pid, signal.SIGWINCH)
56
+ except Exception:
57
+ pass
58
+ else:
59
+ os.write(fd, data)
60
+ except OSError:
61
+ pass
62
+ main()
63
+ `;
64
+
65
+ export function terminalUpgrade(req, socket, head) {
66
+ const key = req.headers['sec-websocket-key'];
67
+ if (!key) { socket.destroy(); return; }
68
+ if (!ptySupported) {
69
+ // Windows 无 pty.fork:明确拒绝,客户端会回落为「用 DSH 自带终端」。
70
+ socket.write('HTTP/1.1 501 Not Implemented\r\nConnection: close\r\n\r\n');
71
+ socket.end();
72
+ return;
73
+ }
74
+ socket.write(
75
+ 'HTTP/1.1 101 Switching Protocols\r\n' +
76
+ 'Upgrade: websocket\r\n' +
77
+ 'Connection: Upgrade\r\n' +
78
+ `Sec-WebSocket-Accept: ${wsAccept(key)}\r\n\r\n`,
79
+ );
80
+ let cwd = os.homedir();
81
+ try {
82
+ const p = new URL(req.url || '/', 'http://x').searchParams.get('cwd');
83
+ if (p) cwd = decodeURIComponent(p);
84
+ } catch { /* keep home */ }
85
+ if (!fsp.existsSync(cwd)) cwd = os.homedir();
86
+
87
+ let shell = process.env.SHELL || '/bin/zsh';
88
+ try {
89
+ const q = new URL(req.url || '/', 'http://x').searchParams.get('shell');
90
+ if (q && /^[a-zA-Z0-9_\/.-]+$/.test(q)) shell = q.startsWith('/') ? q : `/bin/${q}`;
91
+ } catch {}
92
+ const python = process.env.PYTHON || 'python3';
93
+ const child = spawn(python, ['-c', PTY_HELPER, cwd, shell], {
94
+ env: { ...process.env, PROMPT_EOL_MARK: '', TERM: 'xterm-256color' }, stdio: ['pipe', 'pipe', 'pipe'],
95
+ });
96
+
97
+ let buf = Buffer.alloc(0);
98
+ const feed = (chunk) => {
99
+ buf = Buffer.concat([buf, chunk]);
100
+ while (buf.length >= 2) {
101
+ const b0 = buf[0], b1 = buf[1];
102
+ const opcode = b0 & 0x0f;
103
+ const masked = (b1 & 0x80) !== 0;
104
+ let len = b1 & 0x7f, off = 2;
105
+ if (len === 126) { if (buf.length < 4) return; len = buf.readUInt16BE(2); off = 4; }
106
+ else if (len === 127) { if (buf.length < 10) return; len = Number(buf.readBigUInt64BE(2)); off = 10; }
107
+ let mask;
108
+ if (masked) { if (buf.length < off + 4) return; mask = buf.subarray(off, off + 4); off += 4; }
109
+ if (buf.length < off + len) return;
110
+ const payload = Buffer.from(buf.subarray(off, off + len));
111
+ buf = buf.subarray(off + len);
112
+ if (masked) for (let i = 0; i < payload.length; i++) payload[i] ^= mask[i & 3];
113
+ if (opcode === 0x8) { try { child.kill(); } catch {} return; }
114
+ if (opcode === 0x9) { socket.write(wsFrame(Buffer.concat([Buffer.from([0x8a, 0]), payload]))); continue; }
115
+ if (opcode === 0x2 || opcode === 0x1) { try { child.stdin.write(payload); } catch {} }
116
+ }
117
+ };
118
+ let rxN = 0, ptyAcc = Buffer.alloc(0), ptyTimer = 0;
119
+ const flushPty = () => {
120
+ ptyTimer = 0;
121
+ const raw = ptyAcc.toString('utf8');
122
+ ptyAcc = Buffer.alloc(0);
123
+ // zsh PROMPT_SP:清行标记,某些终端渲染会把它显示成乱码,直接丢弃
124
+ const clean = raw.replace(/\x1b\[1m\x1b\[7m%\x1b\[27m\x1b\[1m\x1b\[0m *\r \r/g, '');
125
+ if (!clean) return;
126
+ if (rxN < 3) {
127
+ rxN++;
128
+ try { fsp.appendFileSync('/tmp/cx-term-rx.log', `--- chunk ${rxN} len=${clean.length} text=${JSON.stringify(clean.slice(0, 120))}\n`); } catch {}
129
+ }
130
+ try { socket.write(wsFrame(Buffer.from(clean, 'utf8'))); } catch {}
131
+ };
132
+ child.stdout.on('data', (d) => { ptyAcc = Buffer.concat([ptyAcc, d]); if (!ptyTimer) ptyTimer = setTimeout(flushPty, 30); });
133
+ child.stderr.on('data', (d) => { try { socket.write(wsFrame(d)); } catch {} });
134
+ child.on('exit', () => { try { socket.end(); } catch {} });
135
+ socket.on('data', feed);
136
+ socket.on('error', () => { try { child.kill(); } catch {} });
137
+ socket.on('close', () => { try { child.kill(); } catch {} });
138
+ if (head && head.length) feed(head);
139
+ }
140
+
141
+
@@ -0,0 +1,51 @@
1
+ // 核心业务:DSH 用户(每个节点一个 dsh_copilot_<port>,uid 持久化)。
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { stateDir } from '../base/env.js';
5
+ import { http, encode, USER_CACHE } from '../base/http.js';
6
+
7
+ let USERS_LOADED = false;
8
+ const PENDING = new Map();
9
+
10
+ export function usersFile() {
11
+ return path.join(stateDir(), 'users.json');
12
+ }
13
+
14
+ export async function loadUsers() {
15
+ if (USERS_LOADED) return;
16
+ USERS_LOADED = true;
17
+ try {
18
+ const raw = JSON.parse(await fs.readFile(usersFile(), 'utf8'));
19
+ for (const [port, value] of Object.entries(raw || {})) {
20
+ if (value && value.uid) USER_CACHE.set(Number(port), value);
21
+ }
22
+ } catch { /* first run */ }
23
+ }
24
+
25
+ export async function saveUsers() {
26
+ await fs.mkdir(stateDir(), { recursive: true });
27
+ const out = {};
28
+ for (const [port, value] of USER_CACHE.entries()) out[String(port)] = value;
29
+ await fs.writeFile(usersFile(), JSON.stringify(out, null, 2));
30
+ }
31
+
32
+ export async function ensureUser(node) {
33
+ const port = Number(node);
34
+ if (!port) throw new Error('node is required');
35
+ await loadUsers();
36
+ const found = USER_CACHE.get(port);
37
+ if (found?.uid) return found.uid;
38
+ if (PENDING.has(port)) return PENDING.get(port);
39
+ const job = (async () => {
40
+ const username = `dsh_copilot_${port}`;
41
+ const r = await http(port, `/c/aaa.user/action/create/userrole/tech/username/${encode(username)}/usernick/${encode('DSH Copilot')}`, { method: 'PUT', timeout: 15000 });
42
+ if (r.status !== 200) throw new Error(`create user failed: HTTP ${r.status}`);
43
+ const uid = r.text.trim();
44
+ if (!/^[0-9a-f]{32}$/i.test(uid)) throw new Error(`create user returned no uid: ${r.text.slice(0, 120)}`);
45
+ USER_CACHE.set(port, { uid, username, created: new Date().toISOString() });
46
+ await saveUsers();
47
+ return uid;
48
+ })().finally(() => PENDING.delete(port));
49
+ PENDING.set(port, job);
50
+ return job;
51
+ }
@@ -0,0 +1,158 @@
1
+ // 框架通用词表:字段名与操作名只用这些,不自定义。
2
+ // 来源:usr/icebergs/base/mdb/mdb.go、usr/icebergs/base/aaa/aaa.go、平台 model。
3
+ export const FIELDS = {
4
+ uid: '记录主键', id: '自增编号', time: '时间', name: '名称', title: '标题', text: '文本',
5
+ content: '内容', type: '类型', status: '状态', order: '排序', score: '分数', group: '分组',
6
+ valid: '有效', enable: '启用', member: '成员', expire: '过期', icon: '图标', help: '说明',
7
+ user_uid: '用户', place_uid: '实例', project_uid: '项目', target_uid: '目标', plan_uid: '计划',
8
+ task_uid: '任务', report_uid: '报告', feedback_uid: '反馈', image: '图片', link: '链接',
9
+ begin_time: '开始时间', end_time: '结束时间', process_time: '处理时间', finished_time: '完成时间',
10
+ };
11
+
12
+ export const ACTIONS = {
13
+ create: '创建', insert: '插入', modify: '修改', update: '更新', remove: '删除', delete: '删除',
14
+ select: '查询', list: '列表', detail: '详情', export: '导出', import: '导入', enable: '启用',
15
+ disable: '停用', prunes: '清理',
16
+ };
17
+
18
+ // 中文 → 框架字段名
19
+ export const FIELD_ALIAS = {
20
+ 名称: 'name', 名字: 'name', 标题: 'title', 内容: 'content', 说明: 'text', 备注: 'text', 描述: 'text',
21
+ 类型: 'type', 状态: 'status', 顺序: 'order', 排序: 'order', 分数: 'score', 分组: 'group',
22
+ 用户: 'user_uid', 成员: 'member', 时间: 'time', 图片: 'image', 链接: 'link',
23
+ };
24
+
25
+ // 中文动作 → 框架动作名
26
+ export const ACTION_ALIAS = {
27
+ 创建: 'create', 新建: 'create', 新增: 'create', 添加: 'create',
28
+ 修改: 'modify', 编辑: 'modify', 更新: 'update',
29
+ 删除: 'remove', 移除: 'remove',
30
+ 查询: 'select', 查看: 'select', 列表: 'list', 详情: 'detail',
31
+ 导出: 'export', 导入: 'import', 启用: 'enable', 停用: 'disable',
32
+ };
33
+
34
+ export const ROLES = {
35
+ root: '系统角色', tech: '技术人员', void: '普通', creator: '创建人', leader: '负责人',
36
+ worker: '执行人', member: '成员', visitor: '访客', server: '服务',
37
+ };
38
+
39
+ export const STYLES = ['guiding', 'notice', 'danger', 'primary', 'success', 'warning'];
40
+
41
+ export function fieldOf(zh) {
42
+ const k = String(zh || '').trim();
43
+ return FIELD_ALIAS[k] || (FIELDS[k] ? k : '');
44
+ }
45
+
46
+ export function actionOf(zh) {
47
+ const k = String(zh || '').trim();
48
+ return ACTION_ALIAS[k] || (ACTIONS[k] ? k : '');
49
+ }
50
+
51
+ export function checkField(name) {
52
+ const n = String(name || '').trim();
53
+ if (FIELDS[n]) return { ok: true, name: n, help: FIELDS[n] };
54
+ const alt = fieldOf(n);
55
+ if (alt) return { ok: true, name: alt, help: FIELDS[alt], renamed: alt !== n };
56
+ return { ok: false, name: n, suggest: Object.keys(FIELDS).join(', ') };
57
+ }
58
+
59
+ export function checkAction(name) {
60
+ const n = String(name || '').trim().toLowerCase();
61
+ if (ACTIONS[n]) return { ok: true, name: n, label: ACTIONS[n] };
62
+ const alt = actionOf(n);
63
+ if (alt) return { ok: true, name: alt, label: ACTIONS[alt], renamed: alt !== n };
64
+ return { ok: false, name: n, suggest: Object.keys(ACTIONS).join(', ') };
65
+ }
66
+
67
+ // 生成声明标签:name / help / icon / role / style
68
+ export function declAttrs({ name = '', params = '', help = '', icon = '', role = '', style = '' } = {}) {
69
+ const out = [];
70
+ out.push(`name:"${[name, params].filter(Boolean).join(' ')}"`);
71
+ if (help) out.push(`help:"${help}"`);
72
+ if (icon) out.push(`icon:"${icon}"`);
73
+ if (role) out.push(`role:"${role}"`);
74
+ if (style) out.push(`style:"${style}"`);
75
+ return out.join(' ');
76
+ }
77
+
78
+ // 参数声明文法(usr/icebergs/info.go SplitCmd):
79
+ // name*(:text|textarea|select|button|password)=value@action
80
+ // - name 后跟 * 表示必填(need:must)
81
+ // - :type 指定类型;select 的候选值用逗号分隔写进 =value
82
+ // - @action 绑定按钮动作(create/modify/remove/img/date…)
83
+ // - 后缀自动推导:_info/_content → textarea;_uid/_type/_status、type/status → select;
84
+ // _image → text+img;_time → select+date
85
+ export const PARAM_TYPES = ['text', 'textarea', 'select', 'button', 'password', 'img', 'video', 'date'];
86
+
87
+ export function autoType(name) {
88
+ const n = String(name || '');
89
+ if (/_info$|_content$|^content$|^text$/.test(n)) return 'textarea';
90
+ if (/_uid$|_type$|_status$|^type$|^status$/.test(n)) return 'select';
91
+ if (/_image$|^image$/.test(n)) return 'img';
92
+ if (/_time$/.test(n)) return 'date';
93
+ return 'text';
94
+ }
95
+
96
+ export function paramOf({ name, type = '', required = false, value = '', action = '' } = {}) {
97
+ const n = String(name || '').trim();
98
+ if (!n) return '';
99
+ const t = type || autoType(n);
100
+ let s = n + (required ? '*' : '');
101
+ if (t && t !== 'text') s += `:${t}`;
102
+ if (value) s += `=${value}`;
103
+ if (action) s += `@${action}`;
104
+ return s;
105
+ }
106
+
107
+ export function paramSpec(list = []) {
108
+ return list.map((x) => (typeof x === 'string' ? paramOf({ name: x, required: /^[a-z_]+$/.test(x) && x !== 'content' }) : paramOf(x))).join(' ');
109
+ }
110
+
111
+ // 模块常用函数(框架约定,顺序固定)
112
+ export const MODULE_FUNCS = [
113
+ { name: 'Init', sig: 'func (s %s) Init(m *ice.Message, arg ...string) { s.Hash.Init(m, arg...) }', rank: 0, help: '启动时加载(注册数据、初始化缓存)' },
114
+ { name: 'Exit', sig: 'func (s %s) Exit(m *ice.Message, arg ...string) { s.Hash.Exit(m, arg...) }', rank: 1, help: '停止时卸载(关闭句柄、落盘)' },
115
+ { name: 'Inputs', sig: 'func (s %s) Inputs(m *ice.Message, arg ...string) { s.Hash.Inputs(m, arg...) }', rank: 2, help: '参数补全(前端联动取值)' },
116
+ { name: 'Create', sig: 'func (s %s) Create(m *ice.Message, arg ...string) { s.ValueCreate(m, arg...) }', rank: 3, help: '新增' },
117
+ { name: 'Modify', sig: 'func (s %s) Modify(m *ice.Message, arg ...string) { s.ValueModify(m, arg...) }', rank: 4, help: '修改' },
118
+ { name: 'Remove', sig: 'func (s %s) Remove(m *ice.Message, arg ...string) { s.ValueRemove(m, arg...) }', rank: 5, help: '删除' },
119
+ { name: 'List', sig: 'func (s %s) List(m *ice.Message, arg ...string) *ice.Message { s.Select(m, arg...); return m }', rank: 7, help: '列表' },
120
+ ];
121
+
122
+ // 自定义动作落在 Remove 之后、List 之前
123
+ export function funcRank(name) {
124
+ const hit = MODULE_FUNCS.find((f) => f.name === String(name));
125
+ return hit ? hit.rank : 6;
126
+ }
127
+
128
+ export function moduleSkeleton(mod = 'note', cls = 'system') {
129
+ const base = cls === 'platform' ? 'Table' : 'Hash';
130
+ const body = MODULE_FUNCS.map((f) => f.sig.replace('%s', mod).replace(/s\.Hash\./g, `s.${base}.`)).join('\n');
131
+ return `${body}\n`;
132
+ }
133
+
134
+ // 权限检查:接口声明 role 之外,函数内还可自查
135
+ // 系统角色:m.IsTech()(tech/root,等价 aaa.IsTechOrRoot(m))
136
+ // 成员角色:s.IsCreator / s.IsLeader / s.IsWorker(model.USER_ROLE 0..3)
137
+ export const ROLE_LEVEL = { visitor: 0, creator: 1, leader: 2, worker: 3 };
138
+
139
+ export function guardOf(role) {
140
+ const r = String(role || '').trim();
141
+ if (!r) return '';
142
+ if (r === 'root' || r === 'tech') return 'if m.WarnNotRight(!m.IsTech()) { return }';
143
+ if (r === 'creator') return 'if m.WarnNotRight(!s.IsCreator(m)) { return }';
144
+ if (r === 'leader') return 'if m.WarnNotRight(!s.IsLeader(m)) { return }';
145
+ if (r === 'worker') return 'if m.WarnNotRight(!s.IsWorker(m)) { return }';
146
+ if (r === 'member' || r === 'visitor') return 'if m.WarnNotRight(!s.IsWorker(m)) { return }';
147
+ return '';
148
+ }
149
+
150
+ // 从中文里识别角色
151
+ export function roleOf(text = '') {
152
+ const t = String(text);
153
+ if (/(系统管理员|管理员|运维|技术)/.test(t)) return 'tech';
154
+ if (/(负责人|主管|经理|领导)/.test(t)) return 'leader';
155
+ if (/(创建人|发起人)/.test(t)) return 'creator';
156
+ if (/(执行人|员工|经办人|操作员)/.test(t)) return 'worker';
157
+ return '';
158
+ }
@@ -0,0 +1,15 @@
1
+ // 外部辅助:通用异步等待。
2
+ export async function waitFor(fn, { timeout = 40000, interval = 700, label = 'condition' } = {}) {
3
+ const started = Date.now();
4
+ let last;
5
+ while (Date.now() - started < timeout) {
6
+ try {
7
+ last = await fn();
8
+ if (last) return last;
9
+ } catch (error) {
10
+ last = error;
11
+ }
12
+ await new Promise((r) => setTimeout(r, interval));
13
+ }
14
+ throw new Error(`timeout waiting for ${label}`);
15
+ }
@@ -0,0 +1,27 @@
1
+ // 外部辅助工具(叶子层):进程执行与平台 shell 参数。misc 不 import base/core。
2
+ import { spawn } from 'node:child_process';
3
+
4
+ export const isWin = process.platform === 'win32';
5
+
6
+ /** shell 执行参数:Unix 用 /bin/sh -c,Windows 用 cmd /c。 */
7
+ export function shellArgs(script) {
8
+ return isWin ? ['cmd.exe', ['/d', '/s', '/c', script]] : ['/bin/sh', ['-c', script]];
9
+ }
10
+
11
+ export function runShell(script, cwd, { timeoutMs = 900000 } = {}) {
12
+ const [cmd, args] = shellArgs(script);
13
+ return new Promise((resolve, reject) => {
14
+ const child = spawn(cmd, args, { cwd, env: { ...process.env }, stdio: ['ignore', 'pipe', 'pipe'] });
15
+ let out = '';
16
+ let err = '';
17
+ child.stdout.on('data', (d) => { out += d; if (out.length > 65536) out = out.slice(-32768); });
18
+ child.stderr.on('data', (d) => { err += d; if (err.length > 65536) err = err.slice(-32768); });
19
+ const t = setTimeout(() => { try { child.kill('SIGKILL'); } catch {} reject(new Error('shell timeout')); }, timeoutMs);
20
+ child.on('error', (e) => { clearTimeout(t); reject(e); });
21
+ child.on('close', (code) => { clearTimeout(t); resolve({ code, out, err }); });
22
+ });
23
+ }
24
+
25
+ export function spawnDetached(cmd, args) {
26
+ try { spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref(); } catch { /* 打不开就静默 */ }
27
+ }
@@ -0,0 +1,33 @@
1
+ // 外部辅助:静态资源前缀路由(monaco/xterm/three/leaflet)。
2
+ import fsp from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ export const STATIC_MIME = {
6
+ '.js': 'application/javascript; charset=utf-8',
7
+ '.css': 'text/css; charset=utf-8',
8
+ '.json': 'application/json; charset=utf-8',
9
+ '.ttf': 'font/ttf',
10
+ '.woff': 'font/woff',
11
+ '.woff2': 'font/woff2',
12
+ '.svg': 'image/svg+xml',
13
+ '.map': 'application/json',
14
+ '.html': 'text/html; charset=utf-8',
15
+ '.png': 'image/png',
16
+ };
17
+
18
+ export const makeStatic = (prefix, root) => (req, res) => {
19
+ try {
20
+ const pathname = decodeURIComponent(new URL(req.url || '/', 'http://x').pathname);
21
+ const rel = pathname.slice(prefix.length).replace(/^\/+/, '');
22
+ const file = path.normalize(path.join(root, rel));
23
+ if (file !== root && !file.startsWith(root + path.sep)) { res.writeHead(403); res.end(); return; }
24
+ const data = fsp.readFileSync(file);
25
+ const ext = path.extname(file).toLowerCase();
26
+ res.writeHead(200, { 'content-type': STATIC_MIME[ext] || 'application/octet-stream', 'cache-control': 'public, max-age=3600' });
27
+ res.end(data);
28
+ } catch {
29
+ res.writeHead(404);
30
+ res.end();
31
+ }
32
+ };
33
+
package/locale/en.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "meta": {
3
+ "title": "ContextOS DevOps",
4
+ "description": "Deploy ContextOS nodes, create project spaces and apps, and read/write app records from one panel."
5
+ },
6
+ "contextosDevops": {
7
+ "title": "ContextOS DevOps",
8
+ "subtitle": "Nodes · Spaces · Apps · Data"
9
+ }
10
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "meta": {
3
+ "title": "ContextOS DevOps",
4
+ "description": "部署 ContextOS 服务节点、创建项目空间与应用、写入并回读应用数据的一站式 DevOps 面板。"
5
+ },
6
+ "contextosDevops": {
7
+ "title": "ContextOS DevOps",
8
+ "subtitle": "节点 · 空间 · 应用 · 数据"
9
+ }
10
+ }
package/package.json CHANGED
@@ -1,6 +1,93 @@
1
1
  {
2
2
  "name": "dsh-contextos-devops",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.48.31",
4
+ "type": "module",
5
+ "description": "ContextOS DevOps for DeepSeek Harness: one dashboard for cluster nodes, project spaces, apps, avatars, PM boards and IoT devices, with an in-app Copilot that can deploy and scaffold.",
6
+ "exports": {
7
+ ".": "./index.js",
8
+ "./client": "./client.js",
9
+ "./locale/*.json": "./locale/*.json",
10
+ "./icon": "./icon.svg"
11
+ },
12
+ "files": [
13
+ "index.js",
14
+ "client.js",
15
+ "lib/",
16
+ "cordis.patch.yml",
17
+ "locale/*.json",
18
+ "icon.svg",
19
+ "README.md",
20
+ "vendor/xterm/*",
21
+ "vendor/three/*",
22
+ "vendor/leaflet/*",
23
+ "src/client/**",
24
+ "build/**",
25
+ "assets/**",
26
+ "LICENSE",
27
+ "assets/icon-180.png"
28
+ ],
29
+ "dsh": {
30
+ "bundle": {
31
+ "patch": "./cordis.patch.yml"
32
+ },
33
+ "client": {
34
+ "platform": "web",
35
+ "immediately": true,
36
+ "inject": [
37
+ "@deepseek-ai/dsh-client-ui-layout",
38
+ "@deepseek-ai/dsh-client-ui-sidebar"
39
+ ]
40
+ }
41
+ },
42
+ "scripts": {
43
+ "build:client": "node build/client.mjs && node scripts/gate.mjs && node scripts/load-check.mjs && node scripts/hook-order-check.mjs && node scripts/op-check.mjs && node scripts/store-check.mjs && node scripts/ui-check.mjs && node scripts/ui-probe.mjs --soft && node scripts/i18n-check.mjs && node scripts/context-check.mjs && node scripts/docs-check.mjs && node scripts/docs-budget.mjs --check && node scripts/publish-check.mjs",
44
+ "watch:client": "node build/client.mjs --watch",
45
+ "check:ui": "node scripts/ui-check.mjs",
46
+ "check:probe": "node scripts/ui-probe.mjs",
47
+ "check:context": "node scripts/context-check.mjs",
48
+ "verify:factory": "node scripts/accept-factory.mjs",
49
+ "check:i18n": "node scripts/i18n-check.mjs",
50
+ "gate": "node scripts/gate.mjs",
51
+ "verify:ui": "node scripts/ui-verify.mjs",
52
+ "release": "npm run build:client && node scripts/ui-verify.mjs",
53
+ "check:hooks": "node scripts/hook-order-check.mjs",
54
+ "check:ops": "node scripts/op-check.mjs",
55
+ "check:store": "node scripts/store-check.mjs",
56
+ "check:docs": "node scripts/docs-check.mjs",
57
+ "check:load": "node scripts/load-check.mjs",
58
+ "stats:ledger": "node scripts/ledger-stats.mjs",
59
+ "check:budget": "node scripts/docs-budget.mjs --check",
60
+ "push:all": "node scripts/push-all.mjs",
61
+ "check:publish": "node scripts/publish-check.mjs",
62
+ "publish:npm": "node scripts/publish-npm.mjs"
63
+ },
64
+ "license": "MIT",
65
+ "author": {
66
+ "name": "shy",
67
+ "url": "https://shylinux.com"
68
+ },
69
+ "homepage": "https://github.com/contextos-devops/dsh-contextos-devops#readme",
70
+ "repository": {
71
+ "type": "git",
72
+ "url": "git+https://github.com/contextos-devops/dsh-contextos-devops.git"
73
+ },
74
+ "bugs": {
75
+ "url": "https://github.com/contextos-devops/dsh-contextos-devops/issues"
76
+ },
77
+ "keywords": [
78
+ "dsh",
79
+ "dsh-plugin",
80
+ "deepseek",
81
+ "deepseek-harness",
82
+ "contextos",
83
+ "devops",
84
+ "deploy",
85
+ "copilot",
86
+ "cluster",
87
+ "project-management"
88
+ ],
89
+ "engines": {
90
+ "node": ">=20",
91
+ "dsh": ">=0.2.2-rc.1 <1.0.0"
92
+ }
93
+ }
@@ -0,0 +1,37 @@
1
+ # Frontend source layout
2
+
3
+ `client.js` is generated by `build/client.mjs`.
4
+
5
+ Source fragments intentionally share the lexical scope of the DSH
6
+ `window.__ModuleLoader__.load({ factory(require) { ... } })` factory. They are
7
+ not standalone ES modules yet.
8
+
9
+ ## Fragments (build order)
10
+
11
+ ```text
12
+ parts/00-prelude.js ModuleLoader shell, constants, icons, translations
13
+ parts/05-bootstrap.js api(), events, timeouts
14
+ parts/10-ui.js Panel/Btn/Field/Tabs/EmptyState
15
+ parts/20-devworkspace.js Monaco, xterm, file tree, Git, preview
16
+ parts/30-modals.js DropSelect/MenuButton/Modal/Scene/AvatarEdit
17
+ parts/32-cards.js NodeCard/SpaceCard/ItemIcon/AppRow/StoreRow
18
+ parts/34-forms.js Deploy/Space/App/Code/Instance/StoreInstance
19
+ parts/36-data.js Avatar/Device/Data modals
20
+ parts/40-project.js InitOverlay, PmCalendar, MultiFilter
21
+ parts/45-map.js Leaflet task map
22
+ parts/46-pm.js PM form, RichText
23
+ parts/47-three.js ThreeAvatar, ThreeCity, TechBackdrop
24
+ parts/50-chat.js ChatPanel and action execution
25
+ parts/60-page.js Page, Boundary, plugin registration
26
+ ```
27
+
28
+ ## Rules
29
+
30
+ - Edit files in `src/client/parts/` and `src/client/styles/`.
31
+ - Do not edit `client.js` directly.
32
+ - Keep fragments in build order; later fragments can use earlier declarations.
33
+ - CSS order is `main.css` then `kit.css`.
34
+ - Build: `npm run build:client`.
35
+ - Validate: `node --check client.js`.
36
+ - The default avatar is served from `assets/avatar.jpg`, not embedded in JS.
37
+ - Styles are built into `assets/client.css` and loaded via a cached `<link>`; only critical shell CSS stays inline.