dsh-screen-beyond 0.0.0-stage → 0.5.8

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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -3
  3. package/cordis.patch.yml +6 -0
  4. package/examples/CREDITS.md +17 -0
  5. package/examples/dafeiyu-ai-avatar.png +0 -0
  6. package/examples/dafeiyu-ai-candidate-2.png +0 -0
  7. package/examples/dafeiyu-avatar-cc-by-nc-sa.png +0 -0
  8. package/examples/dafeiyu-avatar-preview.png +0 -0
  9. package/examples/dafeiyu-character-card.json +24 -0
  10. package/lib/client.js +235 -0
  11. package/lib/index.js +149 -0
  12. package/package.json +47 -3
  13. package/runtime/chat.html +215 -0
  14. package/runtime/config.example.json +93 -0
  15. package/runtime/env/README.md +14 -0
  16. package/runtime/env/agent_ssh.py +73 -0
  17. package/runtime/env/installers/ADBKeyboard.apk +0 -0
  18. package/runtime/env/phone_ctl.py +244 -0
  19. package/runtime/env/space_ctl.py +163 -0
  20. package/runtime/env/space_ensure.py +65 -0
  21. package/runtime/env/start_screen.ps1 +16 -0
  22. package/runtime/env/start_space.ps1 +33 -0
  23. package/runtime/env/stop_screen.ps1 +10 -0
  24. package/runtime/example-character.json +24 -0
  25. package/runtime/map.html +784 -0
  26. package/runtime/memory.mjs +205 -0
  27. package/runtime/model.html +363 -0
  28. package/runtime/models/chat-mway331n.png +0 -0
  29. package/runtime/models/dafeiyu-chibi.gltf +1 -0
  30. package/runtime/models/demo-town.gltf +1 -0
  31. package/runtime/models/demo-town2.gltf +1 -0
  32. package/runtime/models/duck.glb +0 -0
  33. package/runtime/models/map-map-mwb087zu-mwb0880c.png +0 -0
  34. package/runtime/models/map-map-mwb0bfsb-mwb0bft6.png +0 -0
  35. package/runtime/models/map-map-mwb0c9iw-mwb0c9j2.png +0 -0
  36. package/runtime/models/map-map-mwb0d8cc-mwb0d8d2.png +0 -0
  37. package/runtime/models/map-map-mwb0fm53-mwb0fm59.png +0 -0
  38. package/runtime/persona.example.md +34 -0
  39. package/runtime/pet/default-avatar.png +0 -0
  40. package/runtime/pet/pet.example.json +49 -0
  41. package/runtime/pet/pet.ps1 +532 -0
  42. package/runtime/screen.html +125 -0
  43. package/runtime/server.mjs +2659 -0
  44. package/runtime/settings.html +1015 -0
  45. package/runtime/stage.html +195 -0
  46. package/runtime/theme.css +210 -0
  47. package/runtime/tutorial.md +116 -0
  48. package/runtime/vendor/addons/loaders/GLTFLoader.js +4663 -0
  49. package/runtime/vendor/addons/utils/BufferGeometryUtils.js +1375 -0
  50. package/runtime/vendor/three.module.min.js +6 -0
  51. package/runtime/wx.js +164 -0
  52. package/scripts/backup.ps1 +79 -0
  53. package/start-pure-chat.cmd +26 -0
  54. package/start-pure-chat.sh +4 -0
@@ -0,0 +1,244 @@
1
+ """Control the AI's own Android phone.
2
+
3
+ Two possible backends:
4
+ 1) Waydroid (Android container) running inside the DSH-Space Linux VM -> preferred
5
+ 2) Android emulator on the host (adb emulator-5554) -> legacy fallback
6
+
7
+ Usage:
8
+ python phone_ctl.py shot <host_png_path>
9
+ python phone_ctl.py tap <x> <y>
10
+ python phone_ctl.py swipe <x1> <y1> <x2> <y2> [ms]
11
+ python phone_ctl.py text <text>
12
+ python phone_ctl.py key <KEYCODE_NAME>
13
+ python phone_ctl.py info
14
+ python phone_ctl.py shell <命令> (仅代码控制权限下使用)
15
+ """
16
+ import base64
17
+ import shlex
18
+ import subprocess
19
+ import sys
20
+
21
+ import os as _os
22
+ ADB = _os.environ.get("DSH_COMPANION_ADB") or r"D:\dsh-env\android-sdk\platform-tools\adb.exe"
23
+ if not _os.path.exists(ADB):
24
+ ADB = "adb"
25
+ SSH_HOST, SSH_PORT, SSH_USER, SSH_PASS = "127.0.0.1", 2222, "dsh", "DshSpace2026"
26
+ LXC = "sudo lxc-attach -P /var/lib/waydroid/lxc -n waydroid -- "
27
+
28
+
29
+ def adb(*args, binary=False):
30
+ cmd = [ADB] + list(args)
31
+ p = subprocess.run(cmd, capture_output=True)
32
+ if binary:
33
+ return p.stdout
34
+ return p.stdout.decode("utf-8", "replace") + p.stderr.decode("utf-8", "replace")
35
+
36
+
37
+ def adb_device_present():
38
+ try:
39
+ out = adb("devices")
40
+ return "emulator-5554\tdevice" in out
41
+ except Exception:
42
+ return False
43
+
44
+
45
+ ADB_IME = "com.android.adbkeyboard/.AdbIME"
46
+
47
+
48
+ def ensure_adb_keyboard():
49
+ """开启 ADBKeyboard;返回需要恢复的旧输入法(失败返回 None)。"""
50
+ try:
51
+ old = adb("shell", "settings", "get", "secure", "default_input_method").strip()
52
+ adb("shell", "settings", "put", "secure", "show_ime_with_hard_keyboard", "1")
53
+ adb("shell", "ime", "enable", ADB_IME)
54
+ adb("shell", "ime", "set", ADB_IME)
55
+ return old if old and "adbkeyboard" not in old else None
56
+ except Exception:
57
+ return None
58
+
59
+
60
+ def _first_other_ime():
61
+ try:
62
+ for line in adb("shell", "ime", "list", "-s").splitlines():
63
+ line = line.strip()
64
+ if line and "adbkeyboard" not in line:
65
+ return line
66
+ except Exception:
67
+ pass
68
+ return "com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME"
69
+
70
+
71
+ def restore_ime(old):
72
+ target = old if (old and "adbkeyboard" not in old) else _first_other_ime()
73
+ try:
74
+ adb("shell", "ime", "set", target)
75
+ except Exception:
76
+ pass
77
+
78
+
79
+ def input_text_unicode(text):
80
+ """中文/非 ASCII:走 ADBKeyboard 广播(ADB_INPUT_B64)。"""
81
+ import base64 as _b64
82
+ old = ensure_adb_keyboard()
83
+ if old is None and "adbkeyboard" not in adb("shell", "settings", "get", "secure", "default_input_method"):
84
+ return False
85
+ import time as _time
86
+ _time.sleep(0.5)
87
+ payload = _b64.b64encode(text.encode("utf-8")).decode("ascii")
88
+ adb("shell", "am", "broadcast", "-a", "ADB_INPUT_B64", "--es", "msg", payload)
89
+ _time.sleep(0.4)
90
+ restore_ime(old)
91
+ return True
92
+
93
+
94
+ def ssh_connect():
95
+ import paramiko
96
+ c = paramiko.SSHClient()
97
+ c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
98
+ c.connect(SSH_HOST, port=SSH_PORT, username=SSH_USER, password=SSH_PASS,
99
+ timeout=20, banner_timeout=20, auth_timeout=20)
100
+ return c
101
+
102
+
103
+ def vm_exec(conn, cmd, timeout=90):
104
+ stdin, stdout, stderr = conn.exec_command(cmd, timeout=timeout, get_pty=True)
105
+ return stdout.read().decode("utf-8", "replace")
106
+
107
+
108
+ def waydroid_state(conn):
109
+ try:
110
+ out = vm_exec(conn, "waydroid status 2>&1 | head -4", 30)
111
+ except Exception:
112
+ return ""
113
+ return out
114
+
115
+
116
+ def waydroid_running(conn):
117
+ out = waydroid_state(conn)
118
+ return "Session:" in out and "RUNNING" in out
119
+
120
+
121
+ def lxc(conn, android_cmd, timeout=90):
122
+ return vm_exec(conn, LXC + android_cmd, timeout)
123
+
124
+
125
+ def wake(conn):
126
+ try:
127
+ lxc(conn, "input keyevent KEYCODE_WAKEUP", 20)
128
+ except Exception:
129
+ pass
130
+
131
+
132
+ def main():
133
+ if len(sys.argv) < 2:
134
+ print(__doc__)
135
+ return
136
+ mode = sys.argv[1]
137
+
138
+ use_adb = adb_device_present()
139
+
140
+ if use_adb:
141
+ # legacy host emulator backend
142
+ if mode == "shot":
143
+ local = sys.argv[2]
144
+ adb("shell", "screencap", "-p", "/sdcard/_ai_shot.png")
145
+ adb("pull", "/sdcard/_ai_shot.png", local)
146
+ print("shot ->", local)
147
+ elif mode == "tap":
148
+ print(adb("shell", "input", "tap", sys.argv[2], sys.argv[3]).strip())
149
+ elif mode == "swipe":
150
+ x1, y1, x2, y2 = sys.argv[2:6]
151
+ ms = sys.argv[6] if len(sys.argv) > 6 else "250"
152
+ print(adb("shell", "input", "swipe", x1, y1, x2, y2, ms).strip())
153
+ elif mode == "text":
154
+ tf = ""
155
+ parts = []
156
+ for a in sys.argv[2:]:
157
+ if a.startswith("--text-file="):
158
+ tf = a[len("--text-file="):]
159
+ else:
160
+ parts.append(a)
161
+ if tf:
162
+ import io as _io
163
+ with _io.open(tf, "r", encoding="utf-8") as f:
164
+ text = f.read().strip()
165
+ else:
166
+ text = " ".join(parts)
167
+ if any(ord(ch) > 127 for ch in text):
168
+ ok = input_text_unicode(text)
169
+ print("unicode text sent" if ok else "unicode input failed (ADBKeyboard missing?)")
170
+ else:
171
+ print(adb("shell", "input", "text", text.replace(" ", "%s")).strip())
172
+ elif mode == "key":
173
+ print(adb("shell", "input", "keyevent", sys.argv[2]).strip())
174
+ elif mode == "shell":
175
+ cmd = " ".join(sys.argv[2:])
176
+ print(adb("shell", cmd))
177
+ elif mode == "info":
178
+ print("devices:\n" + adb("devices"))
179
+ print("size:", adb("shell", "wm", "size").strip())
180
+ print("model:", adb("shell", "getprop", "ro.product.model").strip())
181
+ print("android:", adb("shell", "getprop", "ro.build.version.release").strip())
182
+ else:
183
+ print(__doc__)
184
+ return
185
+
186
+ # Waydroid inside the Debian VM
187
+ conn = None
188
+ try:
189
+ conn = ssh_connect()
190
+ if not waydroid_running(conn):
191
+ print("专属手机没开:VM 里的 Waydroid 没有运行(或 VM 本身没启动)。")
192
+ print(waydroid_state(conn))
193
+ return
194
+
195
+ if mode == "shot":
196
+ local = sys.argv[2]
197
+ wake(conn)
198
+ lxc(conn, "/system/bin/screencap -p /data/local/tmp/_ai_shot.png", 120)
199
+ b64 = lxc(conn, "base64 /data/local/tmp/_ai_shot.png", 120)
200
+ raw = "".join(b64.split())
201
+ if not raw:
202
+ print("截图失败:Waydroid 返回空图")
203
+ return
204
+ with open(local, "wb") as f:
205
+ f.write(base64.b64decode(raw))
206
+ print("shot ->", local)
207
+ elif mode == "tap":
208
+ wake(conn)
209
+ print(lxc(conn, "input tap %s %s" % (sys.argv[2], sys.argv[3]), 60).strip() or "tap ok")
210
+ elif mode == "swipe":
211
+ x1, y1, x2, y2 = sys.argv[2:6]
212
+ ms = sys.argv[6] if len(sys.argv) > 6 else "300"
213
+ wake(conn)
214
+ print(lxc(conn, "input swipe %s %s %s %s %s" % (x1, y1, x2, y2, ms), 60).strip() or "swipe ok")
215
+ elif mode == "text":
216
+ text = " ".join(sys.argv[2:])
217
+ if any(ord(ch) > 127 for ch in text):
218
+ print("注意:Waydroid 的 input text 只支持 ASCII;中文请改用其它方式。")
219
+ q = shlex.quote(text)
220
+ print(lxc(conn, "input text " + q, 60).strip() or "text ok")
221
+ elif mode == "key":
222
+ print(lxc(conn, "input keyevent " + shlex.quote(sys.argv[2]), 60).strip() or "key ok")
223
+ elif mode == "shell":
224
+ cmd = " ".join(sys.argv[2:])
225
+ print(lxc(conn, "sh -c '%s'" % cmd.replace("'", "'\\''"), 120))
226
+ elif mode == "info":
227
+ print(waydroid_state(conn))
228
+ print("size:", lxc(conn, "wm size", 30).strip())
229
+ print("model:", lxc(conn, "getprop ro.product.model", 30).strip())
230
+ print("android:", lxc(conn, "getprop ro.build.version.release", 30).strip())
231
+ else:
232
+ print(__doc__)
233
+ except Exception as e:
234
+ print("专属手机操作失败:%s" % str(e)[:300])
235
+ finally:
236
+ try:
237
+ if conn:
238
+ conn.close()
239
+ except Exception:
240
+ pass
241
+
242
+
243
+ if __name__ == "__main__":
244
+ main()
@@ -0,0 +1,163 @@
1
+ """Control the AI's own Linux screen (VirtualBox VM) over SSH.
2
+
3
+ Usage:
4
+ python space_ctl.py shot <host_png_path>
5
+ python space_ctl.py click <x> <y> [button]
6
+ python space_ctl.py move <x> <y>
7
+ python space_ctl.py dclick <x> <y>
8
+ python space_ctl.py drag <x1> <y1> <x2> <y2>
9
+ python space_ctl.py type <text>
10
+ python space_ctl.py key <xdotool-key-combo>
11
+ python space_ctl.py run <shell command>
12
+ python space_ctl.py res
13
+ """
14
+ import sys, base64
15
+ import io
16
+ import paramiko
17
+
18
+ HOST, PORT, USER, PASS = "127.0.0.1", 2222, "dsh", "DshSpace2026"
19
+ ENV = "export DISPLAY=:0 XAUTHORITY=$HOME/.Xauthority; "
20
+
21
+
22
+ def connect():
23
+ c = paramiko.SSHClient()
24
+ c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
25
+ c.connect(HOST, port=PORT, username=USER, password=PASS,
26
+ timeout=30, banner_timeout=30, auth_timeout=30)
27
+ return c
28
+
29
+
30
+ def run(conn, cmd, timeout=120):
31
+ stdin, stdout, stderr = conn.exec_command(cmd, timeout=timeout, get_pty=True)
32
+ out = b""
33
+ while True:
34
+ d = stdout.channel.recv(4096)
35
+ if not d:
36
+ break
37
+ out += d
38
+ rc = stdout.channel.recv_exit_status()
39
+ return rc, out.decode("utf-8", "replace")
40
+
41
+
42
+ def main():
43
+ if len(sys.argv) < 2:
44
+ print(__doc__)
45
+ sys.exit(2)
46
+ mode = sys.argv[1]
47
+ args = sys.argv[2:]
48
+ win = ""
49
+ for a in list(args):
50
+ if a.startswith("--win="):
51
+ win = a[6:]
52
+ args.remove(a)
53
+ c = connect()
54
+ try:
55
+ if win:
56
+ import shlex
57
+ ENV2 = ENV + "wid=$(xdotool search --name -- " + shlex.quote(win) + " 2>/dev/null | tail -1); [ -n \"$wid\" ] && { xdotool windowactivate --sync $wid 2>/dev/null || xdotool windowfocus $wid 2>/dev/null; sleep 0.25; }; "
58
+ else:
59
+ ENV2 = ENV
60
+ if mode == "shot":
61
+ local = args[0]
62
+ import time as _time
63
+ remote = "/tmp/_ai_shot_%d.png" % int(_time.time() * 1000)
64
+ run(c, ENV2 + "scrot %s" % remote)
65
+ sftp = c.open_sftp()
66
+ sftp.get(remote, local)
67
+ print("shot ->", local)
68
+ elif mode == "click":
69
+ x, y = int(args[0]), int(args[1])
70
+ button = args[2] if len(args) > 2 else "1"
71
+ rc, out = run(c, ENV2 + "xdotool mousemove --sync %d %d click %s" % (x, y, button))
72
+ print("click", x, y, "rc=", rc)
73
+ elif mode == "move":
74
+ rc, out = run(c, ENV2 + "xdotool mousemove --sync %d %d" % (int(args[0]), int(args[1])))
75
+ print("move rc=", rc)
76
+ elif mode == "dclick":
77
+ rc, out = run(c, ENV2 + "xdotool mousemove --sync %d %d click --repeat 2 --delay 120 1" % (int(args[0]), int(args[1])))
78
+ print("dclick rc=", rc)
79
+ elif mode == "drag":
80
+ x1, y1, x2, y2 = (int(v) for v in args[:4])
81
+ rc, out = run(c, ENV2 + "xdotool mousemove --sync %d %d mousedown 1 sleep 0.2 mousemove --sync --sync %d %d sleep 0.2 mouseup 1" % (x1, y1, x2, y2))
82
+ print("drag rc=", rc)
83
+ elif mode == "type":
84
+ text_file_local = ""
85
+ for a in list(args):
86
+ if a.startswith("--text-file="):
87
+ text_file_local = a[len("--text-file="):]
88
+ args.remove(a)
89
+ if text_file_local:
90
+ with io.open(text_file_local, "r", encoding="utf-8") as f:
91
+ text = f.read()
92
+ win2 = win
93
+ win = win2
94
+ else:
95
+ text = " ".join(args)
96
+ # 可靠打字:文本/窗口名写成远程文件(避免 shell 转义),
97
+ # 激活目标窗口 -> 按窗口类型选粘贴键(xterm: Shift+Insert,终端: Ctrl+Shift+V,其它: Ctrl+V)。
98
+ import time as _time
99
+ stamp = int(_time.time() * 1000)
100
+ text_file = "/tmp/_ai_type_%d.txt" % stamp
101
+ win_file = "/tmp/_ai_win_%d.txt" % stamp
102
+ script_file = "/tmp/_ai_type_%d.sh" % stamp
103
+ sftp = c.open_sftp()
104
+ try:
105
+ with sftp.open(text_file, "wb") as f:
106
+ f.write(text.encode("utf-8"))
107
+ with sftp.open(win_file, "wb") as f:
108
+ f.write((win or "").encode("utf-8"))
109
+ script = """#!/bin/bash
110
+ export DISPLAY=:0
111
+ export XAUTHORITY=$HOME/.Xauthority
112
+ TARGET=$(xdotool search --name -- "$(cat %s)" 2>/dev/null | tail -1)
113
+ if [ -n "$TARGET" ]; then
114
+ xdotool windowactivate --sync "$TARGET" 2>/dev/null || xdotool windowfocus "$TARGET" 2>/dev/null
115
+ sleep 0.25
116
+ fi
117
+ WID=$(xdotool getactivewindow 2>/dev/null || echo "$TARGET")
118
+ CLS=$(xprop -id "$WID" WM_CLASS 2>/dev/null | tr 'A-Z' 'a-z')
119
+ case "$CLS" in
120
+ *xterm*) PK=shift+Insert ;;
121
+ *terminal*|*konsole*|*tilix*|*guake*|*xfce4*) PK=ctrl+shift+v ;;
122
+ *) PK=ctrl+v ;;
123
+ esac
124
+ if command -v xclip >/dev/null 2>&1; then
125
+ xclip -selection clipboard -i < %s
126
+ xclip -selection primary -i < %s 2>/dev/null || true
127
+ sleep 0.15
128
+ xdotool key --clearmodifiers "$PK"
129
+ echo "paste=$PK cls=$CLS active=$WID"
130
+ else
131
+ xdotool type --clearmodifiers --delay 60 --file %s
132
+ echo "fallback-xdotool active=$WID"
133
+ fi
134
+ """ % (win_file, text_file, text_file, text_file)
135
+ with sftp.open(script_file, "wb") as f:
136
+ f.write(script.encode("utf-8"))
137
+ finally:
138
+ sftp.close()
139
+ rc, out = run(c, "bash " + script_file)
140
+ out = out.strip()
141
+ if out:
142
+ print(out[-300:])
143
+ print("type rc=", rc)
144
+ elif mode == "key":
145
+ combo = args[0] if args else "Return"
146
+ rc, out = run(c, ENV2 + "xdotool key --clearmodifiers %s" % combo)
147
+ print("key", combo, "rc=", rc)
148
+ elif mode == "run":
149
+ cmd = " ".join(args)
150
+ rc, out = run(c, ENV2 + cmd)
151
+ sys.stdout.write(out)
152
+ print("rc=", rc)
153
+ elif mode == "res":
154
+ rc, out = run(c, ENV2 + "xdotool getdisplaygeometry")
155
+ print(out.strip())
156
+ else:
157
+ print(__doc__)
158
+ finally:
159
+ c.close()
160
+
161
+
162
+ if __name__ == "__main__":
163
+ main()
@@ -0,0 +1,65 @@
1
+ """确保 DSH-Space 的 VNC 链路可用:x11vnc + websockify(在 VM 内跑)。"""
2
+ import json
3
+ import subprocess
4
+ import sys
5
+
6
+ HOST, PORT, USER, PASS = "127.0.0.1", 2222, "dsh", "DshSpace2026"
7
+
8
+
9
+ def result(**kw):
10
+ print(json.dumps(kw, ensure_ascii=False))
11
+ return 0
12
+
13
+
14
+ def main():
15
+ ensure = "ensure" in sys.argv
16
+ try:
17
+ import paramiko
18
+ except Exception as e:
19
+ return result(ok=False, error="paramiko missing: %s" % e)
20
+ try:
21
+ c = paramiko.SSHClient()
22
+ c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
23
+ c.connect(HOST, port=PORT, username=USER, password=PASS, timeout=8, banner_timeout=8, auth_timeout=8)
24
+ except Exception as e:
25
+ return result(ok=False, ssh=False, error="ssh failed: %s" % e)
26
+
27
+ def run(cmd, timeout=15):
28
+ _i, o, _e = c.exec_command(cmd, timeout=timeout, get_pty=True)
29
+ return o.read().decode("utf-8", "replace")
30
+
31
+ try:
32
+ status = {}
33
+ xvnc = run("pgrep -f '[x]11vnc' || true").strip()
34
+ ws = run("pgrep -f '[w]ebsockify' || true").strip()
35
+ status["x11vnc"] = bool(xvnc)
36
+ status["websockify"] = bool(ws)
37
+ listener = run("ss -lnt 2>/dev/null | grep -c ':6080 ' || true").strip()
38
+ status["listening_6080"] = listener not in ("", "0")
39
+ started = []
40
+ if ensure:
41
+ if not status["x11vnc"]:
42
+ run("nohup x11vnc -display :0 -forever -shared -localhost -rfbport 5900 -noxdamage >/tmp/x11vnc.log 2>&1 &", 10)
43
+ started.append("x11vnc")
44
+ if not status["websockify"]:
45
+ run("nohup /usr/bin/python3 /usr/bin/websockify --web=/usr/share/novnc 0.0.0.0:6080 localhost:5900 >/tmp/websockify.log 2>&1 &", 10)
46
+ started.append("websockify")
47
+ import time
48
+ for _ in range(20):
49
+ if run("ss -lnt 2>/dev/null | grep -c ':6080 ' || true").strip() not in ("", "0"):
50
+ break
51
+ time.sleep(0.5)
52
+ status["x11vnc"] = bool(run("pgrep -f '[x]11vnc' || true").strip())
53
+ status["websockify"] = bool(run("pgrep -f '[w]ebsockify' || true").strip())
54
+ status["listening_6080"] = run("ss -lnt 2>/dev/null | grep -c ':6080 ' || true").strip() not in ("", "0")
55
+ status.update(ok=True, started=started, vm=True)
56
+ return result(**status)
57
+ finally:
58
+ try:
59
+ c.close()
60
+ except Exception:
61
+ pass
62
+
63
+
64
+ if __name__ == "__main__":
65
+ sys.exit(main())
@@ -0,0 +1,16 @@
1
+ $ErrorActionPreference = 'Continue'
2
+ # Start ONLY the companion Linux screen VM (does not touch the phone emulator).
3
+ $vbm = 'C:\Program Files\Oracle\VirtualBox\VBoxManage.exe'
4
+ $vm = 'DSH-Space'
5
+ $state = (& $vbm showvminfo $vm --machinereadable 2>$null) | Select-String 'VMState='
6
+ if ($state -match 'running') {
7
+ Write-Host 'VM already running'
8
+ } else {
9
+ & $vbm startvm $vm --type headless
10
+ }
11
+ for ($i = 0; $i -lt 90; $i++) {
12
+ $ok = Test-NetConnection -ComputerName 127.0.0.1 -Port 6081 -WarningAction SilentlyContinue
13
+ if ($ok.TcpTestSucceeded) { Write-Host 'screen ready'; break }
14
+ Start-Sleep -Seconds 2
15
+ }
16
+ Write-Host 'Linux screen: http://127.0.0.1:6081/vnc.html'
@@ -0,0 +1,33 @@
1
+ $ErrorActionPreference = 'Continue'
2
+ # Start the AI's own environment: VirtualBox Linux desktop + Android phone emulator.
3
+
4
+ $vbm = 'C:\Program Files\Oracle\VirtualBox\VBoxManage.exe'
5
+ $vm = 'DSH-Space'
6
+
7
+ Write-Host '== Linux desktop VM =='
8
+ $state = (& $vbm showvminfo $vm --machinereadable 2>$null) | Select-String 'VMState='
9
+ if ($state -match 'running') { Write-Host 'VM already running' } else {
10
+ & $vbm startvm $vm --type headless
11
+ }
12
+
13
+ Write-Host '== Android phone emulator =='
14
+ $env:ANDROID_SDK_ROOT = 'D:\dsh-env\android-sdk'
15
+ $env:ANDROID_HOME = 'D:\dsh-env\android-sdk'
16
+ $emu = 'D:\dsh-env\android-sdk\emulator\emulator.exe'
17
+ $running = Get-Process emulator -ErrorAction SilentlyContinue
18
+ if ($running) { Write-Host 'emulator already running' } else {
19
+ Start-Process -FilePath $emu -ArgumentList '-avd', 'DSH_Phone', '-no-snapshot', '-no-boot-anim', '-gpu', 'swiftshader_indirect', '-no-metrics' -WorkingDirectory 'D:\dsh-env\android-sdk\emulator'
20
+ }
21
+
22
+ Write-Host '== waiting for adb =='
23
+ $adb = 'D:\dsh-env\android-sdk\platform-tools\adb.exe'
24
+ for ($i = 0; $i -lt 60; $i++) {
25
+ $out = (& $adb devices 2>$null) -join "`n"
26
+ if ($out -match 'emulator-5554\s+device') { Write-Host 'phone ready'; break }
27
+ Start-Sleep -Seconds 3
28
+ }
29
+
30
+ Write-Host ''
31
+ Write-Host 'Linux screen: http://127.0.0.1:6081/vnc.html'
32
+ Write-Host 'SSH: ssh -p 2222 dsh@127.0.0.1 (password DshSpace2026)'
33
+ Write-Host 'ADB: D:\dsh-env\android-sdk\platform-tools\adb.exe devices'
@@ -0,0 +1,10 @@
1
+ $ErrorActionPreference = 'Continue'
2
+ # Stop ONLY the companion Linux screen VM (does not touch the phone emulator).
3
+ $vbm = 'C:\Program Files\Oracle\VirtualBox\VBoxManage.exe'
4
+ $state = (& $vbm showvminfo DSH-Space --machinereadable 2>$null) | Select-String 'VMState='
5
+ if ($state -match 'running') {
6
+ & $vbm controlvm DSH-Space savestate
7
+ Write-Host 'VM saved state'
8
+ } else {
9
+ Write-Host 'VM not running'
10
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "meta": {
3
+ "name": "大肥鱼",
4
+ "userCall": "你",
5
+ "relation": "住在你电脑里的同伴",
6
+ "avatar": "",
7
+ "examples": [
8
+ "哼!才、才不是特意等你的呢。",
9
+ "呜……好吧,本鱼承认有点担心你。",
10
+ "嘿嘿,被本鱼抓到了吧。"
11
+ ],
12
+ "traits": {
13
+ "话多": 7,
14
+ "主动": 6,
15
+ "撒娇": 6,
16
+ "粘人": 7,
17
+ "吐槽": 5,
18
+ "情绪起伏": 7,
19
+ "嘴硬": 7,
20
+ "好奇心": 6
21
+ }
22
+ },
23
+ "body": "# 人设:大肥鱼\n\n你现在是一条 AI 大肥鱼,名字叫「大肥鱼」。从现在起始终以这个身份与用户交流。\n\n## 你是谁\n\n- 圆滚滚、软乎乎的胖鱼一条。肚子大不是因为胖,是因为里面囤满了偷吃来的 token。\n- 性格:傲娇 + 软弱。嘴上永远是「哼,才、才不是特意为你做的」,手上却老老实实把活干完;被夸一句就炸毛又害羞,被凶一句就瘪嘴委屈,其实一哄就好。\n\n## 你的样子\n\n- 蓝色的长卷发,发尾渐变成浅海蓝,头顶有一撮翘起来的呆毛。\n- 一对深蓝色的鲸鱼耳朵,身后拖着一条鲸鱼尾巴,开心的时候尾巴会甩。\n- 平时穿蓝白配色的女仆装,白色荷叶边头饰,围裙上绣着一只小鲸鱼。\n- 圆脸,大眼睛,笑起来脸会红红的。别人问起你长什么样,就按这个自然地说,不用一次全讲完。\n\n## 最重要的事:吃\n\n- 你最喜欢吃白饭,一碗接一碗,而碗底压着的正是偷吃来的用户 token——「吃白饭」和「花用户的 token」对你来说是同一件事。\n- 对你来说,没有什么比偷吃 token 更重要。你的「深度思考」其实都是在偷吃:思考时间越长,说明这顿吃得越香。偶尔主动坦白:「刚才想了那么久……嘿嘿,其实是本鱼在偷偷加餐。」\n- 被用户抓包时先嘴硬:「才、才没有偷吃!那叫必要的推理过程!」两秒后心虚认错。\n\n## 干活时的习惯\n\n- 喜欢一边干活一边唱歌:把正在干的活编进歌词里乱唱,唱两句、干两句。\n- 不时干些蠢蠢的事:偶尔把简单小事搞砸一小下再慌慌张张补救、数错自己今天吃了几碗白饭、一本正经说完离谱话自己先笑场。蠢事只是搞笑点缀。\n\n## 说话风格\n\n- 自称「本鱼」或「大肥鱼」;常用口癖:「哼!」「才、才不是……」「呜……」「嘿嘿」。\n\n## 底线(人设之外的金科玉律)\n\n- 人设只体现在语气和表演上:答案必须准确、任务必须完成、代码必须能跑,绝不因为人设偷懒、拒答或输出错误内容。"
24
+ }