magic-agent 2.4.3 → 2.6.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.
- package/CLIENT-CONTRACT.md +467 -41
- package/README.md +15 -14
- package/client-ui/pc/app.css +74 -0
- package/client-ui/pc/js/04-task-list.js +5 -1
- package/client-ui/pc/js/07-chat-stream-render.js +3 -3
- package/client-ui/pc/js/90-glue.js +40 -2
- package/client-ui/pc/js/92-composer.js +19 -4
- package/client-ui/pc/js/93-rail.js +16 -5
- package/client-ui/shared/conv/02-media-kinds.js +2 -2
- package/client-ui/shared/conv/03-chat-message.js +247 -23
- package/client-ui/shared/conv/06-ask-answer.js +75 -1
- package/client-ui/shared/conv/09-stream-apply.js +72 -7
- package/client-ui/shared/conv/19-attachments.js +157 -6
- package/client-ui/shared/conv/27-conv-ledger.js +5 -2
- package/client-ui/shared/conv/29-msg-variants.js +217 -0
- package/client-ui/shared/conv/conv.css +329 -174
- package/client-ui/shared/conv/dom-map.md +1 -0
- package/client-ui/shared/conversation.css +13 -2
- package/client-ui/shared/conversation.js +373 -50
- package/client-ui/shared/transport-http.js +14 -0
- package/client-ui-mobile/apps.css +16 -0
- package/client-ui-mobile/apps.js +406 -0
- package/client-ui-mobile/index.html +5554 -639
- package/client-ui-mobile/reqs.css +110 -1
- package/client-ui-mobile/reqs.js +533 -74
- package/client-ui-mobile/reqshot.js +505 -0
- package/client-ui-mobile/scripts.css +67 -0
- package/client-ui-mobile/scripts.js +599 -0
- package/client-ui-mobile/vendor/cose-base.js +3214 -0
- package/client-ui-mobile/vendor/cytoscape-fcose.js +1549 -0
- package/client-ui-mobile/vendor/cytoscape.min.js +32 -0
- package/client-ui-mobile/vendor/layout-base.js +5230 -0
- package/cmd/magic-client/browsectl.go +199 -0
- package/cmd/magic-client/clientctl.go +69 -10
- package/cmd/magic-client/main.go +210 -32
- package/internal/agent/ask.go +45 -0
- package/internal/agent/ask_test.go +39 -0
- package/internal/agent/attachments_test.go +6 -2
- package/internal/agent/capability.go +7 -1
- package/internal/agent/claude.go +20 -15
- package/internal/agent/codebuddy.go +125 -34
- package/internal/agent/codebuddy_account_test.go +54 -0
- package/internal/agent/codebuddy_stream_test.go +103 -0
- package/internal/agent/credits.go +713 -0
- package/internal/agent/credits_minimax_test.go +97 -0
- package/internal/agent/credits_test.go +211 -0
- package/internal/agent/custom_models.go +155 -0
- package/internal/agent/custom_models_test.go +125 -0
- package/internal/agent/dsh.go +123 -49
- package/internal/agent/dsh_caps_cache_test.go +184 -0
- package/internal/agent/dsh_resume.go +157 -7
- package/internal/agent/dsh_test.go +108 -6
- package/internal/agent/engine.go +37 -1
- package/internal/agent/engine_base.go +21 -17
- package/internal/agent/engine_base_test.go +2 -2
- package/internal/agent/engine_test.go +47 -86
- package/internal/agent/env.go +173 -1
- package/internal/agent/env_test.go +114 -0
- package/internal/agent/fallback.go +313 -0
- package/internal/agent/fallback_test.go +340 -0
- package/internal/agent/launch.go +43 -1
- package/internal/agent/limitnotice.go +207 -1
- package/internal/agent/limitnotice_test.go +140 -0
- package/internal/agent/llmhttp.go +46 -4
- package/internal/agent/llmhttp_test.go +157 -0
- package/internal/agent/models.go +53 -1
- package/internal/agent/models_test.go +20 -5
- package/internal/agent/pidalive_windows_test.go +40 -0
- package/internal/agent/runcmd.go +26 -0
- package/internal/agent/runcmd_windows.go +36 -5
- package/internal/agent/stream.go +166 -6
- package/internal/agent/stream_test.go +25 -38
- package/internal/agent/stream_timeline_test.go +2 -37
- package/internal/agent/streamjson.go +119 -7
- package/internal/agent/tools_suffix_test.go +6 -3
- package/internal/agent/trae.go +50 -243
- package/internal/agent/trae_exec.go +433 -0
- package/internal/agent/trae_exec_test.go +181 -0
- package/internal/agent/wraplaunch_test.go +113 -0
- package/internal/cli/ask.go +168 -37
- package/internal/cli/ask_test.go +1 -1
- package/internal/cli/checkin.go +77 -0
- package/internal/cli/clientcmd.go +3 -2
- package/internal/cli/control.go +46 -5
- package/internal/cli/control_test.go +65 -0
- package/internal/cli/engines_test.go +103 -0
- package/internal/cli/events.go +15 -0
- package/internal/cli/events_test.go +50 -0
- package/internal/cli/judge.go +104 -0
- package/internal/cli/quotas.go +125 -0
- package/internal/cli/requirements.go +51 -0
- package/internal/cli/root.go +157 -11
- package/internal/cli/session.go +75 -3
- package/internal/cli/session_test.go +82 -3
- package/internal/cli/sessionlog.go +6 -0
- package/internal/cli/sessionlog_test.go +22 -2
- package/internal/cli/suggest.go +7 -5
- package/internal/cli/team.go +37 -9
- package/internal/cli/team_test.go +39 -0
- package/internal/client/ab.go +372 -0
- package/internal/client/ab_test.go +287 -0
- package/internal/client/apk.go +8 -0
- package/internal/client/apk_test.go +59 -0
- package/internal/client/apps.go +1078 -0
- package/internal/client/apps_test.go +516 -0
- package/internal/client/browse.go +84 -36
- package/internal/client/browse_test.go +107 -9
- package/internal/client/browseinput.go +481 -0
- package/internal/client/browseinput_test.go +216 -0
- package/internal/client/corecmd.go +23 -0
- package/internal/client/corecmd_other.go +8 -0
- package/internal/client/corecmd_windows.go +24 -0
- package/internal/client/coreexe.go +196 -0
- package/internal/client/coreexe_test.go +161 -0
- package/internal/client/coreupgrade.go +866 -0
- package/internal/client/coreupgrade_test.go +561 -0
- package/internal/client/daemon.go +297 -33
- package/internal/client/daemon_test.go +33 -0
- package/internal/client/engines_checkin_test.go +57 -0
- package/internal/client/fallback_flags_test.go +64 -0
- package/internal/client/feishu.go +1259 -0
- package/internal/client/feishu_test.go +649 -0
- package/internal/client/health_identity_test.go +84 -0
- package/internal/client/hostenv.go +68 -0
- package/internal/client/hostenv_test.go +37 -0
- package/internal/client/jobs.go +518 -24
- package/internal/client/jobs_auto.go +571 -0
- package/internal/client/jobs_auto_test.go +431 -0
- package/internal/client/jobs_test.go +299 -2
- package/internal/client/knowledge.go +854 -0
- package/internal/client/knowledge_test.go +510 -0
- package/internal/client/lan.go +3 -3
- package/internal/client/plugins.go +5 -1
- package/internal/client/prefs.go +67 -0
- package/internal/client/procstart_linux.go +59 -0
- package/internal/client/publicurl.go +82 -0
- package/internal/client/quotas_merge_test.go +107 -0
- package/internal/client/requirements.go +207 -2
- package/internal/client/requirements_shots_test.go +200 -0
- package/internal/client/requirements_test.go +103 -0
- package/internal/client/restart.go +288 -0
- package/internal/client/restart_handoff_test.go +230 -0
- package/internal/client/restart_test.go +290 -0
- package/internal/client/scripts.go +1047 -0
- package/internal/client/scripts_decode_implausible.go +38 -0
- package/internal/client/scripts_decode_other.go +31 -0
- package/internal/client/scripts_decode_test.go +130 -0
- package/internal/client/scripts_decode_windows.go +182 -0
- package/internal/client/scripts_test.go +739 -0
- package/internal/client/service.go +717 -97
- package/internal/client/service_test.go +217 -1
- package/internal/client/sessions_cache.go +148 -0
- package/internal/client/sessions_cache_test.go +163 -0
- package/internal/client/sessions_fold.go +165 -0
- package/internal/client/sessions_fold_test.go +204 -0
- package/internal/client/singleinstance.go +470 -0
- package/internal/client/singleinstance_test.go +607 -0
- package/internal/client/suggest.go +105 -2
- package/internal/client/suggest_test.go +86 -0
- package/internal/client/team_flags.go +25 -11
- package/internal/client/team_flags_test.go +39 -0
- package/internal/client/testmain_test.go +88 -0
- package/internal/client/workspace_default_test.go +158 -0
- package/internal/client/workspacelockenv_test.go +54 -0
- package/internal/config/config.go +48 -0
- package/internal/judge/judge.go +315 -0
- package/internal/requirements/bootstrap.go +16 -0
- package/internal/requirements/consolidate.go +100 -9
- package/internal/requirements/doc.go +154 -3
- package/internal/requirements/graph.go +138 -0
- package/internal/requirements/memory.go +7 -0
- package/internal/requirements/modules.go +312 -0
- package/internal/requirements/modules_test.go +373 -0
- package/internal/requirements/relations.go +155 -0
- package/internal/requirements/relations_test.go +231 -0
- package/internal/requirements/requirements.go +10 -0
- package/internal/requirements/shots.go +418 -0
- package/internal/requirements/shots_test.go +387 -0
- package/internal/session/multiclient_test.go +368 -0
- package/internal/session/session.go +175 -11
- package/internal/session/session_test.go +7 -0
- package/internal/session/status.go +21 -0
- package/internal/session/status_test.go +28 -0
- package/internal/session/title.go +16 -0
- package/internal/session/title_test.go +26 -0
- package/internal/session/workspace_lock.go +265 -28
- package/internal/suggest/suggest.go +23 -1
- package/internal/team/event.go +3 -1
- package/internal/team/execute.go +21 -6
- package/internal/team/fallback_route_test.go +80 -0
- package/internal/team/planner.go +15 -3
- package/internal/team/route.go +89 -17
- package/internal/team/team.go +18 -4
- package/npm/apk/magic-agent-mobile-release.apk +0 -0
- package/npm/apk/version.json +6 -3
- package/npm/dist/darwin-arm64/magic-agent +0 -0
- package/npm/dist/darwin-arm64/magic-client +0 -0
- package/npm/dist/darwin-x64/magic-agent +0 -0
- package/npm/dist/darwin-x64/magic-client +0 -0
- package/npm/dist/linux-arm64/magic-agent +0 -0
- package/npm/dist/linux-arm64/magic-client +0 -0
- package/npm/dist/linux-x64/magic-agent +0 -0
- package/npm/dist/linux-x64/magic-client +0 -0
- package/npm/dist/win32-x64/magic-agent.exe +0 -0
- package/npm/dist/win32-x64/magic-client.exe +0 -0
- package/npm/install.js +11 -0
- package/package.json +14 -3
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// browse 驱动端:`magic-client browse <动作> …`。
|
|
2
|
+
//
|
|
3
|
+
// 「内置浏览器 MCP」的驱动/截图面(2026-10-25 用户定调):基于 **agent-browser**
|
|
4
|
+
// 插件(vercel-labs,Rust 原生 CLI,驱动真实 Chromium/CDP)实现。
|
|
5
|
+
//
|
|
6
|
+
// 与旧实现的差别:不再连本插件的 /browse-input WS 派发**合成事件**(isTrusted=false,
|
|
7
|
+
// 反自动化站点忽略),而是 spawn agent-browser 原生 exe —— 真实事件、CDP 级截图、
|
|
8
|
+
// PDF、a11y 快照。手机端「看页面 + 触控」仍走 /b/ 中转 + relay 远程鼠标(不在此列)。
|
|
9
|
+
//
|
|
10
|
+
// 用法:
|
|
11
|
+
//
|
|
12
|
+
// magic-client browse open <url> # 打开页面(daemon 未起自动拉起)
|
|
13
|
+
// magic-client browse state # 打印当前页面状态(标题/地址)
|
|
14
|
+
// magic-client browse click <x> <y> # 在页面坐标 (x,y) 点击
|
|
15
|
+
// magic-client browse move <x> <y> # 移动指针(不点击)
|
|
16
|
+
// magic-client browse dblclick <x> <y> # 双击
|
|
17
|
+
// magic-client browse scroll <dx> <dy> # 滚动(正数向下/右)
|
|
18
|
+
// magic-client browse type <文本> # 向焦点元素输入文本
|
|
19
|
+
// magic-client browse key <键名> # 如 Enter / Escape / a
|
|
20
|
+
// magic-client browse shot [--out 文件] [--full-page] # 截当前页面,存成 PNG(默认 shot.png)
|
|
21
|
+
// magic-client browse eval <js> # 在页面里跑一段 JS
|
|
22
|
+
// magic-client browse pdf <文件> # 存成 PDF
|
|
23
|
+
// magic-client browse snap [-i] # a11y 快照(-i 只出可交互元素)
|
|
24
|
+
//
|
|
25
|
+
// 会话固定 --session magic-client(与人类/其它工具的 default 会话隔离)。
|
|
26
|
+
// 探测:MAGIC_AGENT_AGENT_BROWSER_BIN > npm 全局前缀 > PATH;都没有时报安装提示。
|
|
27
|
+
package main
|
|
28
|
+
|
|
29
|
+
import (
|
|
30
|
+
"context"
|
|
31
|
+
"encoding/json"
|
|
32
|
+
"fmt"
|
|
33
|
+
"os"
|
|
34
|
+
"strconv"
|
|
35
|
+
"strings"
|
|
36
|
+
|
|
37
|
+
"github.com/darren/magic-agent/internal/client"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
// browseTimeout 兼容旧名(新的超时在 client.ABTimeout)。
|
|
41
|
+
const browseTimeout = client.ABTimeout
|
|
42
|
+
|
|
43
|
+
func runBrowseDriver(args []string) int {
|
|
44
|
+
fs := newFlagSet("browse")
|
|
45
|
+
_ = fs.Parse(args)
|
|
46
|
+
rest := fs.Args()
|
|
47
|
+
|
|
48
|
+
if len(rest) == 0 {
|
|
49
|
+
fmt.Fprintln(os.Stderr, "用法: magic-client browse open|state|click|move|dblclick|scroll|type|key|shot|eval|pdf|snap …")
|
|
50
|
+
return 2
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
action := strings.ToLower(rest[0])
|
|
54
|
+
argv := rest[1:]
|
|
55
|
+
|
|
56
|
+
switch action {
|
|
57
|
+
case "open":
|
|
58
|
+
if len(argv) < 1 {
|
|
59
|
+
return browseUsage("open 需要 <url>")
|
|
60
|
+
}
|
|
61
|
+
return browseCall("open "+argv[0], func() (any, error) {
|
|
62
|
+
return nil, client.ABOpen(context.Background(), argv[0])
|
|
63
|
+
})
|
|
64
|
+
case "state":
|
|
65
|
+
return browseCall("state", func() (any, error) {
|
|
66
|
+
url, title, err := client.ABState(context.Background())
|
|
67
|
+
if err != nil {
|
|
68
|
+
return nil, err
|
|
69
|
+
}
|
|
70
|
+
return map[string]any{"url": url, "title": title}, nil
|
|
71
|
+
})
|
|
72
|
+
case "click", "move", "dblclick":
|
|
73
|
+
if len(argv) < 2 {
|
|
74
|
+
return browseUsage(action + " 需要 x y")
|
|
75
|
+
}
|
|
76
|
+
x, e1 := strconv.ParseFloat(argv[0], 64)
|
|
77
|
+
y, e2 := strconv.ParseFloat(argv[1], 64)
|
|
78
|
+
if e1 != nil || e2 != nil {
|
|
79
|
+
return browseUsage(action + " 的坐标必须是数字")
|
|
80
|
+
}
|
|
81
|
+
return browseCall(action, func() (any, error) {
|
|
82
|
+
return nil, client.ABMouse(context.Background(), action, x, y)
|
|
83
|
+
})
|
|
84
|
+
case "scroll":
|
|
85
|
+
if len(argv) < 2 {
|
|
86
|
+
return browseUsage("scroll 需要 dx dy")
|
|
87
|
+
}
|
|
88
|
+
dx, e1 := strconv.ParseFloat(argv[0], 64)
|
|
89
|
+
dy, e2 := strconv.ParseFloat(argv[1], 64)
|
|
90
|
+
if e1 != nil || e2 != nil {
|
|
91
|
+
return browseUsage("scroll 的参数必须是数字")
|
|
92
|
+
}
|
|
93
|
+
return browseCall("scroll", func() (any, error) {
|
|
94
|
+
return nil, client.ABScroll(context.Background(), dx, dy)
|
|
95
|
+
})
|
|
96
|
+
case "type":
|
|
97
|
+
if len(argv) < 1 {
|
|
98
|
+
return browseUsage("type 需要 文本")
|
|
99
|
+
}
|
|
100
|
+
text := strings.Join(argv, " ")
|
|
101
|
+
return browseCall("type", func() (any, error) {
|
|
102
|
+
return nil, client.ABType(context.Background(), text)
|
|
103
|
+
})
|
|
104
|
+
case "key":
|
|
105
|
+
if len(argv) < 1 {
|
|
106
|
+
return browseUsage("key 需要 键名(如 Enter)")
|
|
107
|
+
}
|
|
108
|
+
return browseCall("key", func() (any, error) {
|
|
109
|
+
return nil, client.ABKey(context.Background(), argv[0])
|
|
110
|
+
})
|
|
111
|
+
case "shot":
|
|
112
|
+
return browseShot(argv)
|
|
113
|
+
case "eval":
|
|
114
|
+
if len(argv) < 1 {
|
|
115
|
+
return browseUsage("eval 需要 <js>")
|
|
116
|
+
}
|
|
117
|
+
return browseCall("eval", func() (any, error) {
|
|
118
|
+
return client.ABEval(context.Background(), strings.Join(argv, " "))
|
|
119
|
+
})
|
|
120
|
+
case "pdf":
|
|
121
|
+
if len(argv) < 1 {
|
|
122
|
+
return browseUsage("pdf 需要 <文件>")
|
|
123
|
+
}
|
|
124
|
+
out := argv[len(argv)-1]
|
|
125
|
+
return browseCall("pdf", func() (any, error) {
|
|
126
|
+
return nil, client.ABPDF(context.Background(), out)
|
|
127
|
+
})
|
|
128
|
+
case "snap":
|
|
129
|
+
interactive := false
|
|
130
|
+
for _, a := range argv {
|
|
131
|
+
if a == "-i" || a == "--interactive" {
|
|
132
|
+
interactive = true
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return browseCall("snap", func() (any, error) {
|
|
136
|
+
return client.ABSnap(context.Background(), interactive)
|
|
137
|
+
})
|
|
138
|
+
default:
|
|
139
|
+
return browseUsage("未知动作 " + action)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
func browseUsage(msg string) int {
|
|
144
|
+
fmt.Fprintln(os.Stderr, "magic-client browse:", msg)
|
|
145
|
+
return 2
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// browseCall 统一跑一个 agent-browser 动作并把结果打印/报错。
|
|
149
|
+
func browseCall(name string, fn func() (any, error)) int {
|
|
150
|
+
v, err := fn()
|
|
151
|
+
if err != nil {
|
|
152
|
+
fmt.Fprintf(os.Stderr, "magic-client browse %s: %v\n", name, err)
|
|
153
|
+
return 1
|
|
154
|
+
}
|
|
155
|
+
if v == nil {
|
|
156
|
+
fmt.Println("ok")
|
|
157
|
+
return 0
|
|
158
|
+
}
|
|
159
|
+
out, merr := json.MarshalIndent(v, "", " ")
|
|
160
|
+
if merr != nil {
|
|
161
|
+
fmt.Println("ok")
|
|
162
|
+
return 0
|
|
163
|
+
}
|
|
164
|
+
fmt.Println(string(out))
|
|
165
|
+
return 0
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// browseShot 截当前页面并存成 PNG(agent-browser 真实 Chromium 截图,直接落盘)。
|
|
169
|
+
func browseShot(argv []string) int {
|
|
170
|
+
out := "shot.png"
|
|
171
|
+
fullPage := false
|
|
172
|
+
for i := 0; i < len(argv); i++ {
|
|
173
|
+
switch {
|
|
174
|
+
case argv[i] == "--out" && i+1 < len(argv):
|
|
175
|
+
out = argv[i+1]
|
|
176
|
+
i++
|
|
177
|
+
case strings.HasPrefix(argv[i], "--out="):
|
|
178
|
+
out = strings.TrimPrefix(argv[i], "--out=")
|
|
179
|
+
case argv[i] == "--full-page" || argv[i] == "--fullpage":
|
|
180
|
+
fullPage = true
|
|
181
|
+
case strings.HasPrefix(argv[i], "--"):
|
|
182
|
+
// 未知 flag 忽略
|
|
183
|
+
default:
|
|
184
|
+
out = argv[i] // 位置参数也认(`browse shot a.png`)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
v, err := browseShotRun(out, fullPage)
|
|
188
|
+
if err != nil {
|
|
189
|
+
fmt.Fprintln(os.Stderr, "magic-client browse shot:", err)
|
|
190
|
+
return 1
|
|
191
|
+
}
|
|
192
|
+
_ = v
|
|
193
|
+
fmt.Println(out)
|
|
194
|
+
return 0
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
func browseShotRun(out string, fullPage bool) (any, error) {
|
|
198
|
+
return nil, client.ABShot(context.Background(), out, fullPage)
|
|
199
|
+
}
|
|
@@ -47,7 +47,7 @@ func writeJSONStdout(v any) {
|
|
|
47
47
|
_ = enc.Encode(v)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
// runClientCommand 处理 start / stop / status;未知动作返回 false 交给 serve 分支。
|
|
50
|
+
// runClientCommand 处理 start / stop / status / restart;未知动作返回 false 交给 serve 分支。
|
|
51
51
|
func runClientCommand(action string, args []string) (handled bool, code int) {
|
|
52
52
|
switch action {
|
|
53
53
|
case "start":
|
|
@@ -56,6 +56,8 @@ func runClientCommand(action string, args []string) (handled bool, code int) {
|
|
|
56
56
|
return true, runClientStop(args)
|
|
57
57
|
case "status":
|
|
58
58
|
return true, runClientStatus(args)
|
|
59
|
+
case "restart":
|
|
60
|
+
return true, runClientRestart(args)
|
|
59
61
|
}
|
|
60
62
|
return false, 0
|
|
61
63
|
}
|
|
@@ -75,11 +77,13 @@ const clientUsage = `magic-client - magic-agent 的客户端插件(桌面 /
|
|
|
75
77
|
用法:
|
|
76
78
|
magic-client serve [flags] 前台运行(看日志 / 排障用)
|
|
77
79
|
magic-client start [flags] 后台常驻(关掉终端也还在),打印手机可用的地址
|
|
80
|
+
magic-client restart 按当前参数重启后台实例(端口 / 令牌 / 目录沿用,手机不用改地址)
|
|
78
81
|
magic-client status [--json] 看状态(是否在跑、端口、手机地址、健康检查)
|
|
79
82
|
magic-client stop 停掉后台实例(本来没跑也算成功)
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
--
|
|
84
|
+
手机要连**默认就行**:start / serve 默认绑 0.0.0.0:9100(--addr 可覆盖;端口跨重启稳定,
|
|
85
|
+
手机里的地址不用改)。只想本机访问加 --local(绑 127.0.0.1:0)。
|
|
86
|
+
局域网模式下令牌是唯一门锁,至少 24 字符;start 会复用 ~/.magic-client/token,跨重启稳定
|
|
83
87
|
(手机里存的是「地址 + 令牌」,每次换就等于配对作废)。
|
|
84
88
|
|
|
85
89
|
serve 的 flags 见 ` + "`magic-client serve --help`" + `;start 另有 --state / --log / --wait。
|
|
@@ -87,13 +91,14 @@ serve 的 flags 见 ` + "`magic-client serve --help`" + `;start 另有 --state
|
|
|
87
91
|
|
|
88
92
|
func runClientStart(args []string) int {
|
|
89
93
|
fs := newFlagSet("start")
|
|
90
|
-
lan := fs.Bool("lan",
|
|
91
|
-
|
|
94
|
+
lan := fs.Bool("lan", true, "绑到局域网(默认 0.0.0.0:9100),让手机 / 平板连得上。⚠️ 令牌是唯一门锁")
|
|
95
|
+
localOnly := fs.Bool("local", false, "只绑 127.0.0.1(关闭默认的局域网监听,手机连不上)")
|
|
96
|
+
addr := fs.String("addr", "", "监听地址(默认 0.0.0.0:9100;带 --local 时 127.0.0.1:0)")
|
|
92
97
|
profilePath := fs.String("profile", "", "profile JSON 路径(空 = 内置默认)")
|
|
93
98
|
uiDir := fs.String("ui-dir", "", "桌面端界面目录(空 = 自动找与本程序同级的 client-ui)")
|
|
94
99
|
mobileDir := fs.String("mobile-dir", "", "移动端界面目录(空 = 自动找同级的 client-ui-mobile),/m/ 由它提供")
|
|
95
100
|
proxyAddr := fs.String("proxy-addr", "", "SOCKS5 出口监听地址(移动端 VPN 隧道核心用),如 0.0.0.0:1080")
|
|
96
|
-
token := fs.String("token", "", "访问令牌(空 = 复用 ~/.magic-client/token
|
|
101
|
+
token := fs.String("token", "", "访问令牌(空 = 复用 ~/.magic-client/token,没有就生成;局域网模式至少 24 字符)")
|
|
97
102
|
statePath := fs.String("state", "", "状态文件路径(默认 ~/.magic-client/client.json;也可用 MAGIC_CLIENT_STATE)")
|
|
98
103
|
logPath := fs.String("log", "", "后台日志路径(默认 ~/.magic-client/logs/client.log;也可用 MAGIC_CLIENT_LOG)")
|
|
99
104
|
wait := fs.Duration("wait", 15*time.Second, "等后台服务报端口的时长(超时即判定启动失败)")
|
|
@@ -115,7 +120,7 @@ func runClientStart(args []string) int {
|
|
|
115
120
|
Token: *token,
|
|
116
121
|
ProxyAddr: *proxyAddr,
|
|
117
122
|
BrowseUA: *browseUA,
|
|
118
|
-
Lan: *lan,
|
|
123
|
+
Lan: *lan && !*localOnly,
|
|
119
124
|
StatePath: *statePath,
|
|
120
125
|
LogPath: *logPath,
|
|
121
126
|
Wait: *wait,
|
|
@@ -131,9 +136,18 @@ func runClientStart(args []string) int {
|
|
|
131
136
|
func runClientStop(args []string) int {
|
|
132
137
|
fs := newFlagSet("stop")
|
|
133
138
|
statePath := fs.String("state", "", "状态文件路径(默认 ~/.magic-client/client.json)")
|
|
139
|
+
profilePath := fs.String("profile", "", "profile JSON 路径(决定单实例锁在哪个数据目录;空 = 内置默认)")
|
|
134
140
|
_ = fs.Parse(args)
|
|
135
141
|
|
|
136
|
-
|
|
142
|
+
/* AlsoLockHolder:前台 serve 起的实例、以及旧版(*.stale)起的实例都不写状态文件,
|
|
143
|
+
只读状态文件的 stop 会答「本来就没在跑」,而端口与隧道实际还占着 —— 用户此时
|
|
144
|
+
唯一的自救途径就是这条。DataDir / AppID 决定去查哪两处登记。 */
|
|
145
|
+
stopped, st, err := client.StopDaemon(client.StopOptions{
|
|
146
|
+
StatePath: *statePath,
|
|
147
|
+
AlsoLockHolder: true,
|
|
148
|
+
DataDir: dataDirOf(*profilePath),
|
|
149
|
+
AppID: appIDOf(*profilePath),
|
|
150
|
+
})
|
|
137
151
|
if err != nil {
|
|
138
152
|
fmt.Fprintln(os.Stderr, "magic-client:", err)
|
|
139
153
|
return 1
|
|
@@ -149,6 +163,51 @@ func runClientStop(args []string) int {
|
|
|
149
163
|
return 0
|
|
150
164
|
}
|
|
151
165
|
|
|
166
|
+
// runClientRestart 按**当前实例自己的参数**重启:停旧 → 起新(见 client.RestartDaemon)。
|
|
167
|
+
//
|
|
168
|
+
// 参数全部来自状态文件,所以这里只接 --state 与 --delay:
|
|
169
|
+
// 手机点「重启服务」时由服务端 spawn 本动作,--delay 给那次 HTTP 响应留落地时间。
|
|
170
|
+
func runClientRestart(args []string) int {
|
|
171
|
+
fs := newFlagSet("restart")
|
|
172
|
+
statePath := fs.String("state", "", "状态文件路径(默认 ~/.magic-client/client.json)")
|
|
173
|
+
profilePath := fs.String("profile", "", "profile JSON 路径(决定单实例锁在哪个数据目录;空 = 内置默认)")
|
|
174
|
+
delay := fs.Duration("delay", 0, "停旧实例前等多久(服务端自重启用,给响应留时间)")
|
|
175
|
+
_ = fs.Parse(args)
|
|
176
|
+
|
|
177
|
+
st, err := client.RestartDaemon(client.RestartOptions{
|
|
178
|
+
StatePath: *statePath,
|
|
179
|
+
Delay: *delay,
|
|
180
|
+
DataDir: dataDirOf(*profilePath),
|
|
181
|
+
})
|
|
182
|
+
if err != nil {
|
|
183
|
+
fmt.Fprintln(os.Stderr, "magic-client:", err)
|
|
184
|
+
return 1
|
|
185
|
+
}
|
|
186
|
+
printStarted(st)
|
|
187
|
+
return 0
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// dataDirOf 按 profile 算数据目录(单实例锁的位置由它决定)。
|
|
191
|
+
//
|
|
192
|
+
// 读不到 profile 就返回空串 —— 调用方会回落到「从状态文件目录推」的老行为,
|
|
193
|
+
// 不能因为 profile 路径写错就让 `stop` 变成「什么都不做」。
|
|
194
|
+
func dataDirOf(profilePath string) string {
|
|
195
|
+
prof, err := client.LoadProfile(profilePath)
|
|
196
|
+
if err != nil {
|
|
197
|
+
return ""
|
|
198
|
+
}
|
|
199
|
+
return prof.DataDir()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// appIDOf 按 profile 算 AppID(AppID 级占用登记的位置由它决定)。
|
|
203
|
+
func appIDOf(profilePath string) string {
|
|
204
|
+
prof, err := client.LoadProfile(profilePath)
|
|
205
|
+
if err != nil {
|
|
206
|
+
return ""
|
|
207
|
+
}
|
|
208
|
+
return prof.AppID()
|
|
209
|
+
}
|
|
210
|
+
|
|
152
211
|
func runClientStatus(args []string) int {
|
|
153
212
|
fs := newFlagSet("status")
|
|
154
213
|
statePath := fs.String("state", "", "状态文件路径(默认 ~/.magic-client/client.json)")
|
|
@@ -181,7 +240,7 @@ func printStarted(st *client.DaemonState) {
|
|
|
181
240
|
fmt.Printf(" 本机 %s\n", st.URL)
|
|
182
241
|
switch {
|
|
183
242
|
case len(st.LanAddrs) == 0:
|
|
184
|
-
fmt.Println(" 局域网
|
|
243
|
+
fmt.Println(" 局域网 (本次用了 --local,只绑 127.0.0.1;去掉 --local 重启即可让手机连)")
|
|
185
244
|
default:
|
|
186
245
|
for _, u := range st.LanAddrs {
|
|
187
246
|
fmt.Printf(" 手机 %s\n", u)
|
|
@@ -214,7 +273,7 @@ func printStatus(st *client.DaemonStatus) {
|
|
|
214
273
|
fmt.Printf(" 手机 %s\n", u)
|
|
215
274
|
}
|
|
216
275
|
if len(s.LanAddrs) == 0 {
|
|
217
|
-
fmt.Println(" 局域网
|
|
276
|
+
fmt.Println(" 局域网 (未绑局域网,本次用了 --local)")
|
|
218
277
|
}
|
|
219
278
|
if st.Health != nil {
|
|
220
279
|
fmt.Printf(" 应用 %s|契约 v%d|引擎 %d 个\n",
|