@yufengtadian/freedom-cli 1.13.1 → 1.13.3

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 (76) hide show
  1. package/LICENSE +40 -0
  2. package/README.md +119 -15
  3. package/lib/agents.js +448 -0
  4. package/lib/build.js +189 -45
  5. package/lib/cli.js +258 -9
  6. package/lib/config.js +20 -1
  7. package/lib/desktop.js +133 -0
  8. package/lib/dev.js +149 -0
  9. package/lib/mcp.js +217 -0
  10. package/lib/release.js +90 -0
  11. package/lib/security.js +113 -54
  12. package/lib/shell.js +170 -63
  13. package/lib/toolchain.js +339 -0
  14. package/lib/tui.js +37 -1
  15. package/lib/utils.js +51 -0
  16. package/lib/verify.js +48 -12
  17. package/lib/webkit.js +45 -0
  18. package/package.json +3 -2
  19. package/shell/darwin-arm64/freedom-shell +0 -0
  20. package/shell/linux-x64/freedom-shell +0 -0
  21. package/shell/win-x64/freedom-shell.exe +0 -0
  22. package/skill/freedom/SKILL.md +159 -0
  23. package/templates/desktop/app.html +559 -0
  24. package/templates/desktop/backend/desktop.mjs +327 -0
  25. package/templates/desktop/freedom.config.js +32 -0
  26. package/templates/desktop/icon.ico +0 -0
  27. package/templates/go/go.mod +3 -0
  28. package/templates/go/pkg/freedom/anti_debug_linux.go +69 -0
  29. package/templates/go/pkg/freedom/anti_debug_other.go +5 -3
  30. package/templates/go/pkg/freedom/anti_debug_windows.go +238 -11
  31. package/templates/go/pkg/freedom/assets/freedom.js +3 -0
  32. package/templates/go/pkg/freedom/assets_embed.go +1 -0
  33. package/templates/go/pkg/freedom/backend_proc.go +5 -5
  34. package/templates/go/pkg/freedom/events_windows.go +5 -5
  35. package/templates/go/pkg/freedom/freedom.go +64 -10
  36. package/templates/go/pkg/freedom/msgwindow_windows.go +8 -8
  37. package/templates/go/pkg/freedom/resources.go +119 -14
  38. package/templates/go/pkg/freedom/securetemp.go +61 -0
  39. package/templates/go/pkg/freedom/securetemp_other.go +20 -0
  40. package/templates/go/pkg/freedom/securetemp_windows.go +25 -0
  41. package/templates/go/pkg/freedom/security.go +293 -64
  42. package/templates/go/pkg/freedom/shutdown.go +55 -0
  43. package/templates/go/pkg/freedom/shutdown_windows.go +47 -0
  44. package/templates/go/pkg/freedom/store.go +11 -3
  45. package/templates/go/pkg/freedom/syscap_windows.go +33 -33
  46. package/templates/go/pkg/freedom/sysint_windows.go +2 -2
  47. package/templates/go/pkg/freedom/tray_windows.go +18 -18
  48. package/templates/go/pkg/freedom/updater.go +3 -3
  49. package/templates/go/pkg/freedom/window_other.go +7 -1
  50. package/templates/go/pkg/freedom/window_windows.go +185 -17
  51. package/templates/go/third_party/webview_go/CHANGELOG.md +15 -0
  52. package/templates/go/third_party/webview_go/FREEDOM-PATCH.md +32 -0
  53. package/templates/go/third_party/webview_go/LICENSE +22 -0
  54. package/templates/go/third_party/webview_go/README.md +50 -0
  55. package/templates/go/third_party/webview_go/glue.c +36 -0
  56. package/templates/go/third_party/webview_go/go.mod +3 -0
  57. package/templates/go/third_party/webview_go/go.sum +0 -0
  58. package/templates/go/third_party/webview_go/libs/mswebview2/LICENSE +27 -0
  59. package/templates/go/third_party/webview_go/libs/mswebview2/include/WebView2.h +23568 -0
  60. package/templates/go/third_party/webview_go/libs/mswebview2/include/vendor.go +2 -0
  61. package/templates/go/third_party/webview_go/libs/mswebview2/vendor.go +2 -0
  62. package/templates/go/third_party/webview_go/libs/mswebview2/version.txt +1 -0
  63. package/templates/go/third_party/webview_go/libs/webview/LICENSE +22 -0
  64. package/templates/go/third_party/webview_go/libs/webview/include/vendor.go +2 -0
  65. package/templates/go/third_party/webview_go/libs/webview/include/webview.h +3598 -0
  66. package/templates/go/third_party/webview_go/libs/webview/vendor.go +2 -0
  67. package/templates/go/third_party/webview_go/libs/webview/version.txt +1 -0
  68. package/templates/go/third_party/webview_go/webkit2_40.go +21 -0
  69. package/templates/go/third_party/webview_go/webkit2_41.go +8 -0
  70. package/templates/go/third_party/webview_go/webview.cc +1 -0
  71. package/templates/go/third_party/webview_go/webview.go +332 -0
  72. package/templates/installer/app.nsi +43 -0
  73. package/templates/project/freedom.config.js +14 -0
  74. package/templates/project/freedom.d.ts +77 -0
  75. package/templates/project-minimal/freedom.config.js +14 -0
  76. package/templates/project-minimal/freedom.d.ts +77 -0
@@ -0,0 +1,559 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Freedom Desktop</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0f1115;
10
+ --bg-raised: #161a21;
11
+ --bg-sunken: #0b0d11;
12
+ --line: #232935;
13
+ --line-strong: #2f3746;
14
+ --text: #e6e9ef;
15
+ --text-dim: #96a0b0;
16
+ --text-faint: #6b7686;
17
+ --accent: #f2a541;
18
+ --accent-soft: rgba(242, 165, 65, 0.14);
19
+ --ok: #4ec9a5;
20
+ --warn: #e0b341;
21
+ --err: #ef6d6d;
22
+ --mono: ui-monospace, "Cascadia Mono", Consolas, "JetBrains Mono", monospace;
23
+ --sans: "Segoe UI", "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
24
+ --r: 10px;
25
+ --sp: 16px;
26
+ }
27
+ * { box-sizing: border-box; }
28
+ html, body { height: 100%; }
29
+ body {
30
+ margin: 0; background: var(--bg); color: var(--text);
31
+ font: 14px/1.55 var(--sans); -webkit-font-smoothing: antialiased;
32
+ display: grid; grid-template-columns: 208px 1fr; grid-template-rows: 100%;
33
+ overflow: hidden; user-select: none;
34
+ }
35
+
36
+ /* ---------- 侧栏 ---------- */
37
+ .side {
38
+ background: var(--bg-sunken); border-right: 1px solid var(--line);
39
+ display: flex; flex-direction: column; padding: 18px 12px 12px;
40
+ }
41
+ .brand { display: flex; align-items: center; gap: 9px; padding: 0 8px 16px; }
42
+ .brand .mark {
43
+ width: 24px; height: 24px; border-radius: 7px;
44
+ background: linear-gradient(150deg, var(--accent), #b8620f);
45
+ box-shadow: 0 0 0 1px rgba(255,255,255,.08) inset;
46
+ }
47
+ .brand b { font-size: 14px; letter-spacing: .3px; }
48
+ .brand span { color: var(--text-faint); font: 11px var(--mono); }
49
+ nav { display: flex; flex-direction: column; gap: 2px; }
50
+ nav button {
51
+ all: unset; cursor: pointer; display: flex; align-items: center; gap: 10px;
52
+ padding: 9px 10px; border-radius: 8px; color: var(--text-dim); font-size: 13px;
53
+ }
54
+ nav button:hover { background: #141922; color: var(--text); }
55
+ nav button[aria-current="true"] { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
56
+ nav button i { width: 14px; text-align: center; font-style: normal; opacity: .85; }
57
+ .side-foot { margin-top: auto; padding: 10px 8px 0; border-top: 1px solid var(--line); color: var(--text-faint); font: 11px/1.7 var(--mono); }
58
+ .side-foot b { color: var(--text-dim); font-weight: 600; }
59
+
60
+ /* ---------- 主区 ---------- */
61
+ main { display: flex; flex-direction: column; min-width: 0; }
62
+ header {
63
+ height: 52px; flex: 0 0 52px; border-bottom: 1px solid var(--line);
64
+ display: flex; align-items: center; gap: 12px; padding: 0 var(--sp);
65
+ background: var(--bg-raised);
66
+ }
67
+ header h1 { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: .2px; }
68
+ header .crumb { color: var(--text-faint); font: 11px var(--mono); max-width: 46ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
69
+ header .spacer { flex: 1; }
70
+ .scroll { flex: 1; overflow: auto; padding: 18px var(--sp) 24px; user-select: text; }
71
+
72
+ /* ---------- 控件 ---------- */
73
+ .row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
74
+ .row + .row { margin-top: 10px; }
75
+ label.f { display: flex; flex-direction: column; gap: 5px; font-size: 11px; color: var(--text-faint); letter-spacing: .4px; text-transform: uppercase; }
76
+ input, select {
77
+ background: var(--bg-sunken); color: var(--text); border: 1px solid var(--line-strong);
78
+ border-radius: 7px; padding: 7px 10px; font: 13px var(--sans); min-width: 130px; outline: none;
79
+ }
80
+ input:focus, select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
81
+ input.wide { min-width: 380px; flex: 1; }
82
+ button.act {
83
+ background: var(--accent); color: #241403; border: 0; border-radius: 7px;
84
+ padding: 8px 14px; font: 600 13px var(--sans); cursor: pointer;
85
+ }
86
+ button.act:hover { filter: brightness(1.07); }
87
+ button.act:active { transform: translateY(1px); }
88
+ button.act[disabled] { background: var(--line-strong); color: var(--text-faint); cursor: not-allowed; }
89
+ button.ghost {
90
+ background: transparent; color: var(--text-dim); border: 1px solid var(--line-strong);
91
+ border-radius: 7px; padding: 7px 12px; font: 13px var(--sans); cursor: pointer;
92
+ }
93
+ button.ghost:hover { color: var(--text); border-color: var(--text-faint); }
94
+ .chk { display: flex; align-items: center; gap: 6px; font: 12px var(--sans); color: var(--text-dim); cursor: pointer; }
95
+ .chk input { min-width: 0; width: 14px; height: 14px; accent-color: var(--accent); }
96
+
97
+ /* ---------- 卡片 / 表格 ---------- */
98
+ .grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
99
+ .card {
100
+ background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--r);
101
+ padding: 14px 16px;
102
+ }
103
+ .card h3 { margin: 0 0 10px; font-size: 12px; letter-spacing: .5px; text-transform: uppercase; color: var(--text-faint); font-weight: 600; }
104
+ .kv { display: grid; grid-template-columns: 92px 1fr; gap: 4px 10px; font: 12px/1.8 var(--mono); }
105
+ .kv dt { color: var(--text-faint); }
106
+ .kv dd { margin: 0; color: var(--text); word-break: break-all; }
107
+ section.block { margin-bottom: 22px; }
108
+ section.block > h2 { font-size: 13px; margin: 0 0 10px; color: var(--text-dim); font-weight: 600; letter-spacing: .3px; }
109
+ ul.list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
110
+ ul.list li {
111
+ display: flex; align-items: center; gap: 10px; padding: 9px 12px;
112
+ background: var(--bg-raised); border: 1px solid var(--line); border-radius: 8px; font: 12px var(--mono);
113
+ }
114
+ ul.list li b { font-family: var(--sans); font-size: 13px; font-weight: 600; }
115
+ ul.list li .grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-dim); }
116
+ ul.list li[data-current="1"] { border-color: var(--accent); background: var(--accent-soft); }
117
+ .pill { font: 11px var(--mono); padding: 2px 7px; border-radius: 20px; border: 1px solid var(--line-strong); color: var(--text-dim); }
118
+ .pill.ok { color: var(--ok); border-color: rgba(78,201,165,.4); }
119
+ .pill.no { color: var(--text-faint); }
120
+ .empty { color: var(--text-faint); font-size: 13px; padding: 10px 2px; }
121
+
122
+ /* ---------- 控制台 ---------- */
123
+ .console {
124
+ flex: 0 0 auto; border-top: 1px solid var(--line); background: var(--bg-sunken);
125
+ display: flex; flex-direction: column; transition: height .18s ease; height: 216px;
126
+ }
127
+ .console.min { height: 34px; }
128
+ .console .bar {
129
+ height: 34px; flex: 0 0 34px; display: flex; align-items: center; gap: 10px; padding: 0 12px;
130
+ border-bottom: 1px solid var(--line); font: 11px var(--mono); color: var(--text-faint); cursor: pointer;
131
+ }
132
+ .console .bar b { color: var(--text-dim); font: 600 11px var(--sans); letter-spacing: .6px; text-transform: uppercase; }
133
+ .console pre {
134
+ margin: 0; padding: 10px 12px; overflow: auto; flex: 1;
135
+ font: 12px/1.65 var(--mono); color: var(--text-dim); user-select: text; white-space: pre-wrap; word-break: break-all;
136
+ }
137
+ .console pre .t { color: var(--text-faint); }
138
+ .console pre .cmd { color: var(--accent); }
139
+ .console pre .bad { color: var(--err); }
140
+ .console pre .good { color: var(--ok); }
141
+ .toast {
142
+ position: fixed; right: 18px; bottom: 18px; max-width: 60vw; padding: 10px 14px; border-radius: 8px;
143
+ background: var(--bg-raised); border: 1px solid var(--line-strong); color: var(--text); font-size: 13px;
144
+ box-shadow: 0 10px 30px rgba(0,0,0,.45); opacity: 0; transform: translateY(8px); transition: .18s; pointer-events: none;
145
+ }
146
+ .toast.show { opacity: 1; transform: none; }
147
+ .toast.err { border-color: rgba(239,109,109,.5); }
148
+ </style>
149
+ </head>
150
+ <body>
151
+ <aside class="side">
152
+ <div class="brand"><span class="mark"></span><div><b>Freedom</b> <span id="ver">…</span></div></div>
153
+ <nav id="nav">
154
+ <button data-view="overview" aria-current="true"><i>◧</i>概览</button>
155
+ <button data-view="projects"><i>▤</i>项目</button>
156
+ <button data-view="build"><i>⛭</i>打包</button>
157
+ <button data-view="config"><i>⚙</i>配置</button>
158
+ <button data-view="shell"><i>▣</i>壳与后端</button>
159
+ <button data-view="release"><i>↥</i>发布</button>
160
+ <button data-view="agents"><i>◇</i>Agent 集成</button>
161
+ </nav>
162
+ <div class="side-foot" id="foot">加载中…</div>
163
+ </aside>
164
+
165
+ <main>
166
+ <header>
167
+ <h1 id="title">概览</h1>
168
+ <span class="crumb" id="crumb"></span>
169
+ <span class="spacer"></span>
170
+ <button class="ghost" id="btn-refresh">刷新</button>
171
+ </header>
172
+ <div class="scroll" id="scroll"></div>
173
+
174
+ <div class="console" id="console">
175
+ <div class="bar" id="console-bar">
176
+ <b>输出</b><span id="console-meta">等待任务…</span>
177
+ <span class="spacer" style="flex:1"></span>
178
+ <span id="console-toggle">收起</span>
179
+ </div>
180
+ <pre id="log"></pre>
181
+ </div>
182
+ </main>
183
+
184
+ <div class="toast" id="toast"></div>
185
+
186
+ <script>
187
+ (function () {
188
+ 'use strict';
189
+ var f = window.freedom;
190
+ var state = { view: 'overview', info: null, projects: { recent: [], current: null } };
191
+ var $ = function (id) { return document.getElementById(id); };
192
+
193
+ function esc(s) {
194
+ return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) {
195
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
196
+ });
197
+ }
198
+
199
+ function toast(msg, isErr) {
200
+ var t = $('toast');
201
+ t.textContent = msg;
202
+ t.className = 'toast show' + (isErr ? ' err' : '');
203
+ clearTimeout(toast._t);
204
+ toast._t = setTimeout(function () { t.className = 'toast' + (isErr ? ' err' : ''); }, isErr ? 5200 : 2600);
205
+ }
206
+
207
+ // ---- 日志控制台:后端以 {event:'log', data:{id,line}} 逐行推送 ----
208
+ var lineCount = 0;
209
+ function appendLog(d) {
210
+ var line = String(d && d.line != null ? d.line : '');
211
+ if (!line) return;
212
+ var pre = $('log');
213
+ var cls = '';
214
+ if (/\[freedom\]|失败|error|Error/.test(line)) cls = /失败|error|Error/.test(line) ? 'bad' : 'cmd';
215
+ if (/✓|完成|通过/.test(line)) cls = 'good';
216
+ var ts = new Date().toTimeString().slice(0, 8);
217
+ pre.insertAdjacentHTML('beforeend',
218
+ '<span class="t">' + ts + '</span> <span class="' + cls + '">' + esc(line) + '</span>\n');
219
+ if (++lineCount > 4000) { pre.innerHTML = ''; lineCount = 1; }
220
+ pre.scrollTop = pre.scrollHeight;
221
+ }
222
+ function logCmd(label) {
223
+ var pre = $('log');
224
+ pre.insertAdjacentHTML('beforeend', '\n<span class="cmd">$ freedom ' + esc(label) + '</span>\n');
225
+ pre.scrollTop = pre.scrollHeight;
226
+ $('console-meta').textContent = label;
227
+ }
228
+
229
+ // ---- 调用后端 ----
230
+ function call(method) {
231
+ var params = Array.prototype.slice.call(arguments, 1);
232
+ if (!f || !f.invoke) return Promise.reject(new Error('未检测到 freedom 桥接(请在桌面壳内运行)'));
233
+ return f.invoke.apply(f, [method].concat(params)).catch(function (e) {
234
+ var msg = (e && (e.message || e.error)) || String(e);
235
+ toast(msg, true);
236
+ throw new Error(msg);
237
+ });
238
+ }
239
+
240
+ // ================= 视图 =================
241
+ var views = {};
242
+
243
+ views.overview = function () {
244
+ var i = state.info || {};
245
+ var p = state.projects;
246
+ return '' +
247
+ '<section class="block"><h2>运行时</h2><div class="grid">' +
248
+ card('Freedom CLI', kv({ 版本: i.cliVersion || '未知', 入口: i.cliEntry || '未定位', Node: i.nodeVersion || '-', 平台: i.platform || '-' })) +
249
+ card('当前项目', kv({ 路径: p.current || '未选择', 最近: p.recent.length + ' 个', 打包目录: p.current ? p.current + '\\dist' : '-' })) +
250
+ card('Desktop 自身', kv({ 目录: i.desktopDir || '-', 界面: '由 freedom build 自举打包', 后端: 'node backend/desktop.mjs(NDJSON/stdio)' })) +
251
+ '</div></section>' +
252
+ '<section class="block"><h2>快速上手</h2>' +
253
+ '<div class="row"><input class="wide" id="ov-dir" placeholder="项目目录,如 D:\\apps\\my-app" />' +
254
+ '<button class="act" data-run="ov-init">新建项目(init)</button>' +
255
+ '<button class="ghost" data-run="ov-open">打开已有项目</button>' +
256
+ '<button class="ghost" data-run="ov-build">打包当前项目</button>' +
257
+ '<button class="ghost" data-run="ov-docs">打开使用教程</button></div>' +
258
+ '<div class="row"><span class="empty">界面本身即 freedom 产物:静态单文件前端 + 任意语言进程后端,无第三方依赖。</span></div>' +
259
+ '</section>';
260
+ };
261
+
262
+ views.projects = function () {
263
+ var p = state.projects;
264
+ var rows = p.recent.length
265
+ ? p.recent.map(function (d) {
266
+ return '<li data-current="' + (d === p.current ? 1 : 0) + '">' +
267
+ '<b>' + esc(base(d)) + '</b><span class="grow">' + esc(d) + '</span>' +
268
+ (d === p.current ? '<span class="pill ok">当前</span>' : '<button class="ghost" data-run="pr-use" data-arg="' + esc(d) + '">设为当前</button>') +
269
+ '<button class="ghost" data-run="pr-forget" data-arg="' + esc(d) + '">移除</button></li>';
270
+ }).join('')
271
+ : '<div class="empty">还没有项目。用上方 init 新建,或把已有 freedom 项目路径添加进来。</div>';
272
+ return '' +
273
+ '<section class="block"><h2>选择当前项目</h2><ul class="list">' + rows + '</ul></section>' +
274
+ '<section class="block"><h2>新建 / 添加</h2>' +
275
+ '<div class="row"><input class="wide" id="pj-dir" placeholder="绝对路径" />' +
276
+ '<label class="f">模板<select id="pj-tpl"><option value="full">full(自绘标题栏示例)</option><option value="minimal">minimal(原生标题栏)</option></select></label>' +
277
+ '<button class="act" data-run="pj-init">init 新建</button>' +
278
+ '<button class="ghost" data-run="pj-add">添加已有</button></div>' +
279
+ '</section>';
280
+ };
281
+
282
+ views.build = function () {
283
+ var p = state.projects;
284
+ return '' +
285
+ '<section class="block"><h2>打包当前项目</h2>' +
286
+ '<div class="empty" style="padding-bottom:6px">目标:' + esc(p.current || '未选择项目') + '</div>' +
287
+ '<div class="row">' +
288
+ '<label class="f">平台<select id="bd-plat"><option value="">当前平台</option><option value="win">win</option><option value="mac">mac</option><option value="linux">linux</option><option value="all">all(三平台)</option></select></label>' +
289
+ '<label class="f">安全模式<select id="bd-sec"><option value="">配置默认</option><option value="none">none</option><option value="basic">basic</option><option value="high">high(app.bin 加密)</option></select></label>' +
290
+ '<label class="chk"><input type="checkbox" id="bd-inst" /> 安装包(便携 zip + NSIS)</label>' +
291
+ '<label class="chk"><input type="checkbox" id="bd-nc" /> 忽略前端缓存</label>' +
292
+ '</div>' +
293
+ '<div class="row"><button class="act" data-run="bd-run">freedom build</button>' +
294
+ '<button class="ghost" data-run="bd-verify">freedom verify(产物校验)</button>' +
295
+ '<button class="ghost" data-run="bd-dev">freedom dev(热更窗口)</button>' +
296
+ '<button class="ghost" data-run="bd-devstop">停止 dev</button></div>' +
297
+ '</section>' +
298
+ '<section class="block"><h2>dev 热更说明</h2><div class="empty">' +
299
+ 'dev 会拉起项目自己的前端 dev server,把壳窗口指向其 localhost 地址(HMR 改码即见);退出界面或点“停止 dev”会树杀子进程。' +
300
+ '</div></section>';
301
+ };
302
+
303
+ views.config = function () {
304
+ return '' +
305
+ '<section class="block"><h2>当前配置(freedom config)</h2>' +
306
+ '<div class="row"><button class="act" data-run="cf-load">读取配置</button></div>' +
307
+ '<pre class="card" id="cf-text" style="margin-top:10px;font:12px/1.7 var(--mono);white-space:pre-wrap">尚未读取。</pre>' +
308
+ '</section>' +
309
+ '<section class="block"><h2>修改单项(freedom config set)</h2>' +
310
+ '<div class="row"><label class="f">键<select id="cf-key">' +
311
+ ['name','width','height','minWidth','minHeight','center','debug','titlebar','security','outDir','url','singleInstance','icon']
312
+ .map(function (k) { return '<option>' + k + '</option>'; }).join('') +
313
+ '</select></label>' +
314
+ '<label class="f">值<input id="cf-val" placeholder="true / 1024 / native / high …" /></label>' +
315
+ '<button class="act" data-run="cf-set" style="align-self:flex-end">写入</button></div>' +
316
+ '<div class="row"><span class="empty">注释态的可选键(如 url、singleInstance)无需手动取消注释,写入时会自动补上。</span></div>' +
317
+ '</section>';
318
+ };
319
+
320
+ views.shell = function () {
321
+ return '' +
322
+ '<section class="block"><h2>预编译壳</h2>' +
323
+ '<div class="row"><button class="act" data-run="sh-list">列出本地已就绪</button>' +
324
+ '<label class="f">下载平台<select id="sh-plat"><option value="win-x64">win-x64</option><option value="darwin-arm64">darwin-arm64</option><option value="linux-x64">linux-x64</option><option value="linux-arm64">linux-arm64</option></select></label>' +
325
+ '<button class="ghost" data-run="sh-dl">下载壳</button></div>' +
326
+ '<pre class="card" id="sh-text" style="margin-top:10px;font:12px/1.7 var(--mono);white-space:pre-wrap">尚未读取。</pre>' +
327
+ '</section>' +
328
+ '<section class="block"><h2>任意语言后端</h2><div class="empty">' +
329
+ '在 freedom.config.js 里写 <code>backend: { command: \'node\', args: [\'backend/main.mjs\'] }</code>(Go / Rust / Python 同理),' +
330
+ '打包时项目 backend/ 目录会分发到 resources/backend/,壳以 NDJSON over stdio 与其通信。' +
331
+ '</div></section>';
332
+ };
333
+
334
+ views.release = function () {
335
+ return '' +
336
+ '<section class="block"><h2>自更新发布环</h2>' +
337
+ '<div class="row"><button class="act" data-run="rl-keygen">freedom keygen(生成 ed25519 密钥)</button></div>' +
338
+ '<div class="row"><span class="empty">公钥填入 freedom.config.js 的 updater.publicKey,并设置 updater.manifestURL。</span></div>' +
339
+ '<div class="row" style="margin-top:14px">' +
340
+ '<label class="f">产物<input class="wide" id="rl-art" placeholder="dist/freedom-desktop-win-x64-portable.zip" /></label>' +
341
+ '<label class="f">下载地址<input class="wide" id="rl-url" placeholder="https://your.host/freedom-desktop-win-x64-portable.zip" /></label>' +
342
+ '</div>' +
343
+ '<div class="row"><label class="f">版本<input id="rl-ver" placeholder="留空取配置/包版本" /></label>' +
344
+ '<label class="f">说明<input class="wide" id="rl-notes" placeholder="本次更新内容" /></label>' +
345
+ '<button class="act" data-run="rl-manifest" style="align-self:flex-end">freedom manifest(签名 latest.json)</button></div>' +
346
+ '</section>';
347
+ };
348
+
349
+ views.agents = function () {
350
+ var known = ['claude-code','codex','qoder','codebuddy','hermes','zcode','cursor','claude-desktop','gemini-cli','opencode','trae','pi','tianshu','workbuddy','deepseek'];
351
+ return '' +
352
+ '<section class="block"><h2>Agent 集成(skill / MCP)</h2>' +
353
+ '<div class="empty" style="padding-bottom:8px">把 Freedom 使用技能与 MCP 工具装进各 Agent。已在本机取证到配置格式的为 ready,其余会输出可粘贴片段并支持 --config/--format 覆写。</div>' +
354
+ '<div class="grid" id="ag-check" style="grid-template-columns:repeat(auto-fit,minmax(150px,1fr))">' +
355
+ known.map(function (a) {
356
+ return '<label class="chk" style="padding:6px 8px;border:1px solid var(--line);border-radius:8px"><input type="checkbox" value="' + a + '" checked /> ' + a + '</label>';
357
+ }).join('') +
358
+ '</div>' +
359
+ '<div class="row" style="margin-top:12px">' +
360
+ '<label class="chk"><input type="checkbox" id="ag-dry" checked /> 预览(dry-run,不落盘)</label>' +
361
+ '<button class="act" data-run="ag-skill">安装 Skill</button>' +
362
+ '<button class="act" data-run="ag-mcp">安装 MCP</button>' +
363
+ '<button class="ghost" data-run="ag-list">查看支持矩阵</button>' +
364
+ '</div>' +
365
+ '<pre class="card" id="ag-text" style="margin-top:12px;font:12px/1.7 var(--mono);white-space:pre-wrap">尚未读取。</pre>' +
366
+ '</section>';
367
+ };
368
+
369
+ function card(title, body) { return '<div class="card"><h3>' + esc(title) + '</h3>' + body + '</div>'; }
370
+ function kv(obj) {
371
+ return '<dl class="kv">' + Object.keys(obj).map(function (k) {
372
+ return '<dt>' + esc(k) + '</dt><dd>' + esc(obj[k]) + '</dd>';
373
+ }).join('') + '</dl>';
374
+ }
375
+ function base(d) { return String(d).split(/[\\/]/).filter(Boolean).pop() || d; }
376
+
377
+ // ================= 渲染与动作 =================
378
+ function render() {
379
+ $('scroll').innerHTML = (views[state.view] || views.overview)();
380
+ $('nav').querySelectorAll('button').forEach(function (b) {
381
+ b.setAttribute('aria-current', String(b.dataset.view === state.view));
382
+ });
383
+ $('title').textContent = {
384
+ overview: '概览', projects: '项目', build: '打包', config: '配置', shell: '壳与后端', release: '发布', agents: 'Agent 集成'
385
+ }[state.view];
386
+ $('crumb').textContent = state.projects.current || '';
387
+ }
388
+
389
+ var actions = {
390
+ 'ov-init': function () { return actions['pj-init']($('ov-dir').value, 'full'); },
391
+ 'ov-open': function () { return prAdd($('ov-dir').value); },
392
+ 'ov-build': function () { return actions['bd-run'](); },
393
+ 'ov-docs': function () { return call('docs.open'); },
394
+ 'pj-init': function (dir, tpl) {
395
+ var d = dir || $('pj-dir').value.trim();
396
+ if (!d) { toast('请填写项目目录', true); return; }
397
+ var template = tpl || ($('pj-tpl') ? $('pj-tpl').value : 'full');
398
+ logCmd('init ' + d);
399
+ return call('project.init', d, template).then(refreshAll);
400
+ },
401
+ 'pj-add': function () { return prAdd($('pj-dir').value); },
402
+ 'pr-use': function (d) { logCmd('(切换项目)'); return call('project.open', d).then(refreshAll); },
403
+ 'pr-forget': function (d) { return call('project.forget', d).then(refreshAll); },
404
+ 'bd-run': function () {
405
+ if (!state.projects.current) { toast('请先选择项目', true); return; }
406
+ var o = {
407
+ dir: state.projects.current,
408
+ platform: $('bd-plat') ? $('bd-plat').value : '',
409
+ security: $('bd-sec') ? $('bd-sec').value : '',
410
+ installer: !!($('bd-inst') && $('bd-inst').checked),
411
+ noCache: !!($('bd-nc') && $('bd-nc').checked),
412
+ };
413
+ var label = 'build' + (o.platform ? ' --platform ' + o.platform : '') + (o.security ? ' --security ' + o.security : '') + (o.installer ? ' --installer' : '') + (o.noCache ? ' --no-cache' : '');
414
+ logCmd(label);
415
+ return call('project.build', o).then(function (r) {
416
+ toast(r.code === 0 ? '打包完成' : '打包失败(退出码 ' + r.code + ')', r.code !== 0);
417
+ return refreshAll();
418
+ });
419
+ },
420
+ 'bd-verify': function () {
421
+ if (!state.projects.current) { toast('请先选择项目', true); return; }
422
+ logCmd('verify');
423
+ return call('project.verify', state.projects.current).then(function (r) {
424
+ toast(r.code === 0 ? '产物校验通过' : '产物校验有失败项', r.code !== 0);
425
+ });
426
+ },
427
+ 'bd-dev': function () {
428
+ if (!state.projects.current) { toast('请先选择项目', true); return; }
429
+ logCmd('dev');
430
+ return call('project.dev', { dir: state.projects.current });
431
+ },
432
+ 'bd-devstop': function () { logCmd('(停止 dev)'); return call('project.devStop'); },
433
+ 'cf-load': function () {
434
+ if (!state.projects.current) { toast('请先选择项目', true); return; }
435
+ logCmd('config');
436
+ return call('project.config', state.projects.current).then(function (r) {
437
+ $('cf-text').textContent = r.text || '(无输出)';
438
+ });
439
+ },
440
+ 'cf-set': function () {
441
+ if (!state.projects.current) { toast('请先选择项目', true); return; }
442
+ var k = $('cf-key').value, v = $('cf-val').value.trim();
443
+ logCmd('config set ' + k + ' ' + v);
444
+ return call('project.configSet', state.projects.current, k, v).then(function (r) {
445
+ toast(r.code === 0 ? '已写入 ' + k : '写入失败', r.code !== 0);
446
+ return actions['cf-load']();
447
+ });
448
+ },
449
+ 'sh-list': function () {
450
+ logCmd('shell list');
451
+ return call('shell.list').then(function (r) { $('sh-text').textContent = r.text || '(无输出)'; });
452
+ },
453
+ 'sh-dl': function () {
454
+ logCmd('shell download ' + $('sh-plat').value);
455
+ return call('shell.download', $('sh-plat').value).then(function (r) {
456
+ toast(r.code === 0 ? '壳已就绪' : '下载失败', r.code !== 0);
457
+ return actions['sh-list']();
458
+ });
459
+ },
460
+ 'rl-keygen': function () {
461
+ if (!state.projects.current) { toast('请先选择项目', true); return; }
462
+ logCmd('keygen');
463
+ return call('release.keygen', state.projects.current).then(function (r) {
464
+ toast(r.code === 0 ? '密钥已生成(公钥见输出区)' : '生成失败', r.code !== 0);
465
+ });
466
+ },
467
+ 'rl-manifest': function () {
468
+ if (!state.projects.current) { toast('请先选择项目', true); return; }
469
+ var o = {
470
+ dir: state.projects.current,
471
+ artifact: $('rl-art').value.trim(),
472
+ url: $('rl-url').value.trim(),
473
+ version: $('rl-ver').value.trim(),
474
+ notes: $('rl-notes').value.trim(),
475
+ };
476
+ if (!o.artifact || !o.url) { toast('产物路径与下载地址都要填', true); return; }
477
+ logCmd('manifest --artifact ' + o.artifact + ' --url ' + o.url);
478
+ return call('release.manifest', o).then(function (r) {
479
+ toast(r.code === 0 ? 'latest.json 已签名' : '签名失败', r.code !== 0);
480
+ });
481
+ },
482
+ 'ag-list': function () {
483
+ logCmd('agents');
484
+ return call('agents.list').then(function (r) { $('ag-text').textContent = r.text || '(无输出)'; });
485
+ },
486
+ 'ag-skill': function () { return agInstall('skill'); },
487
+ 'ag-mcp': function () { return agInstall('mcp'); },
488
+ 'rp-rebuild': function () { logCmd('desktop --rebuild'); return call('desktop.rebuild'); },
489
+ };
490
+
491
+ function prAdd(v) {
492
+ var d = (v || '').trim();
493
+ if (!d) { toast('请填写目录', true); return; }
494
+ logCmd('(添加项目)');
495
+ return call('project.open', d).then(function () { return refreshAll(); }).catch(function () {});
496
+ }
497
+
498
+ function agInstall(what) {
499
+ var boxes = Array.prototype.slice.call($('ag-check').querySelectorAll('input:checked')).map(function (b) { return b.value; });
500
+ var dry = !!($('ag-dry') && $('ag-dry').checked);
501
+ if (!boxes.length) { toast('至少选择一个 Agent', true); return; }
502
+ logCmd(what + ' install --agent ' + boxes.join(',') + (dry ? ' --dry-run' : ''));
503
+ return call('agents.install', { what: what, agents: boxes, dryRun: dry }).then(function (r) {
504
+ toast(r.code === 0 ? (dry ? '预览完成(见输出区)' : '已安装(见输出区)') : '执行失败(退出码 ' + r.code + ')', r.code !== 0);
505
+ });
506
+ }
507
+
508
+ function refreshAll() {
509
+ return Promise.all([
510
+ call('app.info').then(function (i) { state.info = i; $('ver').textContent = 'v' + (i.cliVersion || '?'); }).catch(function () {}),
511
+ call('project.list').then(function (p) { state.projects = p; }).catch(function () {}),
512
+ ]).then(function () {
513
+ var i = state.info || {};
514
+ $('foot').innerHTML = '<b>CLI</b> v' + esc(i.cliVersion || '?') + '<br><b>平台</b> ' + esc(i.platform || '-') +
515
+ '<br><b>Desktop</b> ' + esc(i.desktopDir ? base(i.desktopDir) : '-');
516
+ render();
517
+ });
518
+ }
519
+
520
+ // ---- 事件绑定 ----
521
+ $('nav').addEventListener('click', function (e) {
522
+ var b = e.target.closest('button[data-view]');
523
+ if (!b) return;
524
+ state.view = b.dataset.view;
525
+ render();
526
+ });
527
+ $('scroll').addEventListener('click', function (e) {
528
+ var b = e.target.closest('[data-run]');
529
+ if (!b) return;
530
+ var fn = actions[b.dataset.run];
531
+ if (!fn) return;
532
+ b.disabled = true;
533
+ Promise.resolve(fn(b.dataset.arg)).catch(function () {}).then(function () { b.disabled = false; });
534
+ });
535
+ $('btn-refresh').addEventListener('click', refreshAll);
536
+ $('console-bar').addEventListener('click', function () {
537
+ var c = $('console');
538
+ c.classList.toggle('min');
539
+ $('console-toggle').textContent = c.classList.contains('min') ? '展开' : '收起';
540
+ });
541
+ document.addEventListener('keydown', function (e) {
542
+ if (e.key >= '1' && e.key <= '7' && !/input|select|textarea/i.test((e.target.tagName || ''))) {
543
+ var btns = $('nav').querySelectorAll('button');
544
+ var t = btns[Number(e.key) - 1];
545
+ if (t) { state.view = t.dataset.view; render(); }
546
+ }
547
+ });
548
+
549
+ if (f && f.on) {
550
+ f.on('log', appendLog);
551
+ f.on('dev.exit', function (d) { toast('dev 已结束(code=' + (d && d.code) + ')'); });
552
+ }
553
+
554
+ render();
555
+ refreshAll();
556
+ })();
557
+ </script>
558
+ </body>
559
+ </html>