@mobius-os/mobius 0.3.27 → 0.3.34
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/README.md +7 -0
- package/install.ps1 +265 -0
- package/package.json +24 -7
- package/scripts/build-python-bundles.sh +129 -0
- package/src/App.tsx +2 -2
- package/src/components/Chat.tsx +158 -341
- package/src/components/ConfigFlow.tsx +2 -0
- package/src/components/primitives.tsx +134 -7
- package/src/lib/entry-view.ts +10 -5
- package/src/lib/screen-text.ts +68 -57
- package/src/lib/transcript-viewport.ts +162 -0
- package/src/lib/windows-input.ts +186 -0
- package/src/main.tsx +5 -1
- package/src/version.ts +21 -0
- package/tests/aimux.test.tsx +210 -0
- package/tests/flow.test.tsx +211 -0
- package/tests/integration.test.ts +114 -0
- package/tests/preview.tsx +104 -0
- package/tests/reconnect.test.tsx +170 -0
- package/tests/resume.test.tsx +73 -0
- package/tests/screen.test.tsx +118 -0
- package/tests/scroll.test.tsx +253 -0
- package/tests/selection.test.tsx +219 -0
- package/tests/ui.test.tsx +901 -0
- package/tests/viewport.test.ts +83 -0
- package/tsconfig.json +19 -0
- package/uninstall.ps1 +144 -0
package/README.md
CHANGED
|
@@ -130,6 +130,13 @@ npm test # all three
|
|
|
130
130
|
|
|
131
131
|
## Build an installable package
|
|
132
132
|
|
|
133
|
+
The TUI release version has one source of truth: `mobius/tui/package.json`.
|
|
134
|
+
The welcome screen, npm package metadata, artifact filename, and download
|
|
135
|
+
manifest all read that value. `package-lock.json` mirrors it as generated npm
|
|
136
|
+
metadata; do not edit it as a separate release setting. The AIMUX bundle
|
|
137
|
+
version (`BUNDLE_VER`) is an independent Python runtime cache version and is
|
|
138
|
+
not the TUI version.
|
|
139
|
+
|
|
133
140
|
From the repository root:
|
|
134
141
|
|
|
135
142
|
```bash
|
package/install.ps1
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Mobius TUI Windows 便携安装 (自带 portable Node, 无需 admin, 不依赖 npm.ps1)
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
下载 portable Node win-x64 zip 解压到 ~/.mobius/node-portable/ → 用 node.exe 直跑 npm-cli.js
|
|
7
|
+
装 @mobius-os/mobius 到 ~/.mobius/npm-global/ → 自建 mobius.cmd 启动器(绝对路径指向便携 node)
|
|
8
|
+
→ 加 ~/.mobius/bin 到用户 PATH (无需 admin)。
|
|
9
|
+
npm 安装和 mobius 启动均直接使用 node.exe/tsx;安装完成后同时注册两个
|
|
10
|
+
Explorer 右键入口:“在 Mobius 中打开”(文件夹本身 + 文件夹空白处)。
|
|
11
|
+
.EXAMPLE
|
|
12
|
+
irm https://serve.nutshellai.cn/publish/auto/mobiustui/install-v15.ps1 | iex
|
|
13
|
+
#>
|
|
14
|
+
$ErrorActionPreference = 'Stop'
|
|
15
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
16
|
+
|
|
17
|
+
function Step($m){ Write-Host "[*] $m" -ForegroundColor Cyan }
|
|
18
|
+
function Ok($m) { Write-Host "[OK] $m" -ForegroundColor Green }
|
|
19
|
+
function Warn($m){ Write-Host "[!] $m" -ForegroundColor Yellow }
|
|
20
|
+
function Err($m) { Write-Host "[X] $m" -ForegroundColor Red }
|
|
21
|
+
function Fail($m) { throw $m }
|
|
22
|
+
|
|
23
|
+
function Invoke-MobiusInstall {
|
|
24
|
+
Write-Host "=== Mobius TUI Windows 便携安装 (无需 admin) ===" -ForegroundColor White
|
|
25
|
+
|
|
26
|
+
$MHOME = Join-Path $env:USERPROFILE ".mobius"
|
|
27
|
+
$NODE_DIR = Join-Path $MHOME "node-portable"
|
|
28
|
+
$GLOBAL_DIR = Join-Path $MHOME "npm-global"
|
|
29
|
+
$NODE_VER = "v24.18.1"
|
|
30
|
+
$ZIP_URL = "https://serve.nutshellai.cn/publish/auto/mobius-tui/node/node-$NODE_VER-win-x64.zip"
|
|
31
|
+
$NODE_SHA256 = "ec56b84a7551893ab2324ebdfdc4ab974a63b4781162600b68a1293cc3e53765" # node v24.18.1 win-x64
|
|
32
|
+
$nodeExe = Join-Path $NODE_DIR "node.exe"
|
|
33
|
+
$npmCli = Join-Path $NODE_DIR "node_modules\npm\bin\npm-cli.js"
|
|
34
|
+
|
|
35
|
+
New-Item -ItemType Directory -Force -Path $MHOME | Out-Null
|
|
36
|
+
|
|
37
|
+
# --- 1. portable Node ---
|
|
38
|
+
if (Test-Path $nodeExe) {
|
|
39
|
+
Ok "便携 Node 已存在: $NODE_DIR"
|
|
40
|
+
} else {
|
|
41
|
+
Step "下载便携 Node $NODE_VER (~37MB), 可能需 1-2 分钟..."
|
|
42
|
+
$zip = Join-Path $MHOME "node.zip"
|
|
43
|
+
Invoke-WebRequest -Uri $ZIP_URL -OutFile $zip
|
|
44
|
+
Step "校验 sha256..."
|
|
45
|
+
$actual = (Get-FileHash $zip -Algorithm SHA256).Hash.ToLower()
|
|
46
|
+
if ($actual -ne $NODE_SHA256.ToLower()) { Fail "Node zip sha256 不匹配 (下载损坏? 期望 $NODE_SHA256 实际 $actual)" }
|
|
47
|
+
Step "解压..."
|
|
48
|
+
$tmp = Join-Path $MHOME "_extract"
|
|
49
|
+
if (Test-Path $tmp) { Remove-Item $tmp -Recurse -Force }
|
|
50
|
+
Expand-Archive -Path $zip -DestinationPath $tmp -Force
|
|
51
|
+
$inner = Join-Path $tmp "node-$NODE_VER-win-x64"
|
|
52
|
+
if (-not (Test-Path (Join-Path $inner "node.exe"))) { Fail "解压后未找到 node.exe" }
|
|
53
|
+
if (Test-Path $NODE_DIR) { Remove-Item $NODE_DIR -Recurse -Force }
|
|
54
|
+
Move-Item $inner $NODE_DIR -Force
|
|
55
|
+
Remove-Item $tmp -Recurse -Force
|
|
56
|
+
Remove-Item $zip -Force
|
|
57
|
+
Ok "便携 Node 就绪: $NODE_DIR"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# --- 2. npm 装 @mobius-os/mobius (node.exe 直跑 npm-cli.js, 绕过 npm.ps1 的 ExecutionPolicy 拦截) ---
|
|
61
|
+
Step "npm 安装 @mobius-os/mobius@latest (本地 install 到便携目录, 官方 registry)..."
|
|
62
|
+
New-Item -ItemType Directory -Force -Path $GLOBAL_DIR | Out-Null
|
|
63
|
+
$oldNodeModules = Join-Path $GLOBAL_DIR "node_modules"
|
|
64
|
+
$oldPackageJson = Join-Path $GLOBAL_DIR "package.json"
|
|
65
|
+
for ($attempt = 1; $attempt -le 3 -and (Test-Path $oldNodeModules); $attempt++) {
|
|
66
|
+
Remove-Item $oldNodeModules -Recurse -Force -ErrorAction SilentlyContinue
|
|
67
|
+
if (Test-Path $oldNodeModules) { Start-Sleep -Milliseconds (300 * $attempt) }
|
|
68
|
+
}
|
|
69
|
+
if (Test-Path $oldNodeModules) {
|
|
70
|
+
Warn "请关闭正在运行的 mobius 终端后重试: $oldNodeModules"
|
|
71
|
+
Fail "无法清理旧 node_modules,可能仍有 Mobius/Node 进程占用文件"
|
|
72
|
+
}
|
|
73
|
+
Remove-Item $oldPackageJson -Force -ErrorAction SilentlyContinue
|
|
74
|
+
$npmLog = Join-Path $MHOME "npm-install.log"
|
|
75
|
+
$npmStdout = Join-Path $MHOME "npm-install.stdout.log"
|
|
76
|
+
$npmStderr = Join-Path $MHOME "npm-install.stderr.log"
|
|
77
|
+
$previousPath = $env:Path
|
|
78
|
+
# npm lifecycle scripts use cmd.exe and resolve `node` from PATH.
|
|
79
|
+
$env:Path = "$NODE_DIR;$env:Path"
|
|
80
|
+
Push-Location $GLOBAL_DIR
|
|
81
|
+
$previousErrorActionPreference = $ErrorActionPreference
|
|
82
|
+
$ErrorActionPreference = "Continue"
|
|
83
|
+
try {
|
|
84
|
+
# PowerShell 5.1 会把 npm 的 stderr warning 包装成 NativeCommandError。
|
|
85
|
+
# 合并输出并只依据原生进程退出码判定成败,避免 warning 提前终止脚本。
|
|
86
|
+
& $nodeExe $npmCli init -y 2>&1 | Out-Null
|
|
87
|
+
$initExitCode = $LASTEXITCODE
|
|
88
|
+
if ($initExitCode -ne 0) { throw "npm 初始化失败 (退出码 $initExitCode)" }
|
|
89
|
+
|
|
90
|
+
# npm 11 默认拦截 esbuild postinstall,安装前明确允许该脚本。旧 npm 会忽略该字段。
|
|
91
|
+
& $nodeExe $npmCli pkg set "allowScripts.esbuild=true" --json 2>&1 | Out-Null
|
|
92
|
+
$configExitCode = $LASTEXITCODE
|
|
93
|
+
if ($configExitCode -ne 0) { throw "配置 esbuild 安装脚本授权失败 (退出码 $configExitCode)" }
|
|
94
|
+
|
|
95
|
+
function Invoke-NpmInstallAttempt([string]$registry, [int]$timeoutSeconds) {
|
|
96
|
+
Remove-Item $npmStdout, $npmStderr -Force -ErrorAction SilentlyContinue
|
|
97
|
+
# Do not call System.Diagnostics.Process instance methods here.
|
|
98
|
+
# Windows PowerShell ConstrainedLanguage blocks those methods even
|
|
99
|
+
# though Start-Process itself is allowed. A tiny cmd wrapper records
|
|
100
|
+
# %ERRORLEVEL%; Wait-Process and taskkill remain CLM-safe.
|
|
101
|
+
$attemptCmd = Join-Path $GLOBAL_DIR 'npm-install-attempt.cmd'
|
|
102
|
+
$exitCodeFile = Join-Path $GLOBAL_DIR 'npm-install.exitcode'
|
|
103
|
+
Remove-Item $exitCodeFile -Force -ErrorAction SilentlyContinue
|
|
104
|
+
$attemptLines = @(
|
|
105
|
+
'@echo off',
|
|
106
|
+
'setlocal',
|
|
107
|
+
('set "PATH={0};%PATH%"' -f $NODE_DIR),
|
|
108
|
+
('"{0}" "{1}" install "@mobius-os/mobius@latest" --registry "{2}" --loglevel warn >"{3}" 2>"{4}"' -f $nodeExe, $npmCli, $registry, $npmStdout, $npmStderr),
|
|
109
|
+
'set "EXIT_CODE=%ERRORLEVEL%"',
|
|
110
|
+
('>"{0}" echo %EXIT_CODE%' -f $exitCodeFile),
|
|
111
|
+
'exit /b %EXIT_CODE%'
|
|
112
|
+
)
|
|
113
|
+
Set-Content -Path $attemptCmd -Value $attemptLines -Encoding ASCII
|
|
114
|
+
$proc = Start-Process -FilePath $env:ComSpec -ArgumentList @('/d', '/s', '/c', ('"{0}"' -f $attemptCmd)) `
|
|
115
|
+
-WorkingDirectory $GLOBAL_DIR -PassThru -WindowStyle Hidden
|
|
116
|
+
Wait-Process -Id $proc.Id -Timeout $timeoutSeconds -ErrorAction SilentlyContinue | Out-Null
|
|
117
|
+
# Allow cmd a short moment to flush the marker after it exits, without
|
|
118
|
+
# invoking any restricted Process instance methods.
|
|
119
|
+
for ($markerAttempt = 1; $markerAttempt -le 20 -and -not (Test-Path $exitCodeFile); $markerAttempt++) {
|
|
120
|
+
Start-Sleep -Milliseconds 100
|
|
121
|
+
}
|
|
122
|
+
if (-not (Test-Path $exitCodeFile)) {
|
|
123
|
+
Warn "npm 官方源安装超过 $timeoutSeconds 秒,正在终止并切换镜像源..."
|
|
124
|
+
& $env:ComSpec /d /s /c "taskkill /PID $($proc.Id) /T /F" 2>&1 | Out-Null
|
|
125
|
+
Start-Sleep -Milliseconds 300
|
|
126
|
+
Remove-Item $attemptCmd -Force -ErrorAction SilentlyContinue
|
|
127
|
+
return @{ TimedOut = $true; ExitCode = $null }
|
|
128
|
+
}
|
|
129
|
+
$exitText = Get-Content -Path $exitCodeFile -Raw -ErrorAction SilentlyContinue
|
|
130
|
+
$exitCode = $exitText -as [int]
|
|
131
|
+
if ($null -eq $exitCode) { $exitCode = 1 }
|
|
132
|
+
Remove-Item $attemptCmd, $exitCodeFile -Force -ErrorAction SilentlyContinue
|
|
133
|
+
return @{ TimedOut = $false; ExitCode = $exitCode }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function Save-NpmAttemptLog([string]$label) {
|
|
137
|
+
Add-Content -Path $npmLog -Value "`n===== $label ====="
|
|
138
|
+
if (Test-Path $npmStdout) { Get-Content $npmStdout | Add-Content -Path $npmLog }
|
|
139
|
+
if (Test-Path $npmStderr) { Get-Content $npmStderr | Add-Content -Path $npmLog }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
Remove-Item $npmLog -Force -ErrorAction SilentlyContinue
|
|
143
|
+
$official = Invoke-NpmInstallAttempt "https://registry.npmjs.org/" 10
|
|
144
|
+
Save-NpmAttemptLog "official registry.npmjs.org"
|
|
145
|
+
$npmSucceeded = (-not $official.TimedOut -and $official.ExitCode -eq 0)
|
|
146
|
+
if (-not $npmSucceeded) {
|
|
147
|
+
if ($official.TimedOut) {
|
|
148
|
+
Warn "官方源超时,切换 npmmirror 镜像源重试..."
|
|
149
|
+
} else {
|
|
150
|
+
Warn "官方源安装失败 (退出码 $($official.ExitCode)),切换 npmmirror 镜像源重试..."
|
|
151
|
+
}
|
|
152
|
+
# A timed-out npm process can leave a partial tree behind; remove only
|
|
153
|
+
# the package tree and keep the user-level install directory intact.
|
|
154
|
+
if (Test-Path $oldNodeModules) { Remove-Item $oldNodeModules -Recurse -Force -ErrorAction SilentlyContinue }
|
|
155
|
+
$mirror = Invoke-NpmInstallAttempt "https://registry.npmmirror.com/" 120
|
|
156
|
+
Save-NpmAttemptLog "mirror registry.npmmirror.com"
|
|
157
|
+
$npmSucceeded = (-not $mirror.TimedOut -and $mirror.ExitCode -eq 0)
|
|
158
|
+
}
|
|
159
|
+
if (-not $npmSucceeded) {
|
|
160
|
+
Err "npm 安装失败"
|
|
161
|
+
if (Test-Path $npmLog) {
|
|
162
|
+
Write-Host "--- npm 最近日志: $npmLog ---" -ForegroundColor Yellow
|
|
163
|
+
Get-Content $npmLog -Tail 120
|
|
164
|
+
Write-Host "--- npm 日志结束 ---" -ForegroundColor Yellow
|
|
165
|
+
}
|
|
166
|
+
Fail "npm 安装失败,完整日志: $npmLog"
|
|
167
|
+
}
|
|
168
|
+
} finally {
|
|
169
|
+
$ErrorActionPreference = $previousErrorActionPreference
|
|
170
|
+
$env:Path = $previousPath
|
|
171
|
+
Pop-Location
|
|
172
|
+
}
|
|
173
|
+
$tsxCli = Join-Path $GLOBAL_DIR "node_modules\tsx\dist\cli.mjs"
|
|
174
|
+
$esbuildExe = Join-Path $GLOBAL_DIR "node_modules\@esbuild\win32-x64\esbuild.exe"
|
|
175
|
+
if (-not (Test-Path $tsxCli)) { Fail "npm 包缺少 tsx 运行时依赖,日志: $npmLog" }
|
|
176
|
+
if (-not (Test-Path $esbuildExe)) { Fail "esbuild Windows 二进制未正确安装,日志: $npmLog" }
|
|
177
|
+
Ok "mobius 装到: $GLOBAL_DIR"
|
|
178
|
+
|
|
179
|
+
# --- 3. mobius 启动器 (.cmd batch, 用绝对路径指向便携 node, 不依赖系统 PATH/ExecutionPolicy) ---
|
|
180
|
+
$binDir = Join-Path $MHOME "bin"
|
|
181
|
+
$entryJs = Join-Path $GLOBAL_DIR "node_modules\@mobius-os\mobius\bin\mobius-tui.js"
|
|
182
|
+
if (-not (Test-Path $entryJs)) { Fail "未找到 mobius 入口: $entryJs" }
|
|
183
|
+
New-Item -ItemType Directory -Force -Path $binDir | Out-Null
|
|
184
|
+
$mainTsx = Join-Path $GLOBAL_DIR "node_modules\@mobius-os\mobius\src\main.tsx"
|
|
185
|
+
$mobiusCmd = Join-Path $binDir "mobius.cmd"
|
|
186
|
+
@"
|
|
187
|
+
@echo off
|
|
188
|
+
"$nodeExe" "$tsxCli" "$mainTsx" %*
|
|
189
|
+
"@ | Set-Content -Path $mobiusCmd -Encoding ASCII
|
|
190
|
+
Ok "启动器: $mobiusCmd"
|
|
191
|
+
|
|
192
|
+
# --- 4. 用户 PATH (无需 admin) ---
|
|
193
|
+
$userPath = (Get-ItemProperty -Path 'HKCU:\Environment' -Name Path -ErrorAction SilentlyContinue).Path
|
|
194
|
+
if ($userPath -notlike "*$binDir*") {
|
|
195
|
+
$newPath = if ($userPath) { "$userPath;$binDir" } else { $binDir }
|
|
196
|
+
if (Test-Path 'HKCU:\Environment') {
|
|
197
|
+
Set-ItemProperty -Path 'HKCU:\Environment' -Name Path -Value $newPath
|
|
198
|
+
} else {
|
|
199
|
+
New-Item -Path 'HKCU:\Environment' -Force | Out-Null
|
|
200
|
+
New-ItemProperty -Path 'HKCU:\Environment' -Name Path -Value $newPath -PropertyType ExpandString -Force | Out-Null
|
|
201
|
+
}
|
|
202
|
+
$env:Path += ";$binDir"
|
|
203
|
+
Ok "已加入用户 PATH: $binDir (重开 PowerShell 生效)"
|
|
204
|
+
} else {
|
|
205
|
+
Ok "PATH 已含: $binDir"
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# --- 5. Explorer 右键菜单 (当前用户,无需 admin) ----------------------------
|
|
209
|
+
# 两个入口必须同时注册:
|
|
210
|
+
# Directory\shell\Mobius = 右键文件夹本身,目标参数 %1
|
|
211
|
+
# Directory\Background\shell\Mobius = 右键文件夹空白处,目标参数 %V
|
|
212
|
+
# 使用 .cmd 辅助启动器,避免右键动作依赖 PowerShell ExecutionPolicy。
|
|
213
|
+
$openHereCmd = Join-Path $binDir "mobius-open-here.cmd"
|
|
214
|
+
$openHereLines = @(
|
|
215
|
+
'@echo off',
|
|
216
|
+
'setlocal',
|
|
217
|
+
'set "MOBIUS_TARGET=%~1"',
|
|
218
|
+
'if not defined MOBIUS_TARGET set "MOBIUS_TARGET=%CD%"',
|
|
219
|
+
'cd /d "%MOBIUS_TARGET%"',
|
|
220
|
+
'call "%~dp0mobius.cmd"',
|
|
221
|
+
'endlocal'
|
|
222
|
+
)
|
|
223
|
+
Set-Content -Path $openHereCmd -Value $openHereLines -Encoding ASCII
|
|
224
|
+
|
|
225
|
+
$classes = "HKCU:\Software\Classes"
|
|
226
|
+
$folderMenu = Join-Path $classes "Directory\shell\Mobius"
|
|
227
|
+
$folderCommand = Join-Path $folderMenu "command"
|
|
228
|
+
$backgroundMenu = Join-Path $classes "Directory\Background\shell\Mobius"
|
|
229
|
+
$backgroundCommand = Join-Path $backgroundMenu "command"
|
|
230
|
+
|
|
231
|
+
foreach ($key in @($folderCommand, $backgroundCommand)) {
|
|
232
|
+
New-Item -Path $key -Force | Out-Null
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
foreach ($menu in @($folderMenu, $backgroundMenu)) {
|
|
236
|
+
Set-ItemProperty -Path $menu -Name "MUIVerb" -Value "在 Mobius 中打开"
|
|
237
|
+
Set-ItemProperty -Path $menu -Name "Icon" -Value "$env:SystemRoot\System32\cmd.exe,0"
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Explorer command quoting: cmd /c ""helper.cmd" "%1"".
|
|
241
|
+
$folderCommandValue = '"{0}" /d /s /c ""{1}" "%1""' -f $env:ComSpec, $openHereCmd
|
|
242
|
+
$backgroundCommandValue = '"{0}" /d /s /c ""{1}" "%V""' -f $env:ComSpec, $openHereCmd
|
|
243
|
+
Set-Item -Path $folderCommand -Value $folderCommandValue
|
|
244
|
+
Set-Item -Path $backgroundCommand -Value $backgroundCommandValue
|
|
245
|
+
Ok "右键菜单已添加: 在 Mobius 中打开 (文件夹 + 空白处)"
|
|
246
|
+
|
|
247
|
+
Write-Host ""
|
|
248
|
+
Write-Host "=== 完成! 重开 PowerShell 后运行: mobius ===" -ForegroundColor Green
|
|
249
|
+
Write-Host "(或直接运行: $mobiusCmd)" -ForegroundColor Gray
|
|
250
|
+
Write-Host "右键文件夹或文件夹空白处,可选择: 在 Mobius 中打开" -ForegroundColor Gray
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
Invoke-MobiusInstall
|
|
255
|
+
} catch {
|
|
256
|
+
$errorLog = Join-Path $env:TEMP "mobius-install-v15-error.log"
|
|
257
|
+
$errorText = $_ | Format-List * -Force | Out-String
|
|
258
|
+
$errorText | Set-Content -Path $errorLog -Encoding UTF8
|
|
259
|
+
Write-Host ""
|
|
260
|
+
Err "Mobius 安装失败: $($_.Exception.Message)"
|
|
261
|
+
Write-Host "完整错误日志: $errorLog" -ForegroundColor Yellow
|
|
262
|
+
Write-Host $errorText -ForegroundColor DarkYellow
|
|
263
|
+
try { Read-Host "按 Enter 返回 PowerShell(窗口不会自动关闭)" | Out-Null } catch { }
|
|
264
|
+
return
|
|
265
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mobius-os/mobius",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.34",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Mobius terminal client. Reuses Mobius frontend TypeScript types and jsonl entry shapes.",
|
|
6
6
|
"bin": {
|
|
@@ -8,13 +8,21 @@
|
|
|
8
8
|
"mobius-tui": "bin/mobius-tui.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"
|
|
11
|
+
"dev": "tsx src/main.tsx",
|
|
12
|
+
"start": "tsx src/main.tsx",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"test:integration": "tsx tests/integration.test.ts",
|
|
15
|
+
"test:ui": "tsx tests/ui.test.tsx",
|
|
16
|
+
"test:flow": "tsx tests/flow.test.tsx",
|
|
17
|
+
"test:resume": "tsx tests/resume.test.tsx",
|
|
18
|
+
"test:aimux": "tsx tests/aimux.test.tsx",
|
|
19
|
+
"test:reconnect": "tsx tests/reconnect.test.tsx",
|
|
20
|
+
"test:screen": "FORCE_COLOR=1 tsx tests/screen.test.tsx",
|
|
21
|
+
"test:scroll": "tsx tests/scroll.test.tsx",
|
|
22
|
+
"test:viewport": "tsx tests/viewport.test.ts",
|
|
23
|
+
"test:selection": "FORCE_COLOR=1 tsx tests/selection.test.tsx",
|
|
24
|
+
"test": "npm run typecheck && npm run test:ui && npm run test:integration"
|
|
12
25
|
},
|
|
13
|
-
"files": [
|
|
14
|
-
"bin",
|
|
15
|
-
"src",
|
|
16
|
-
"README.md"
|
|
17
|
-
],
|
|
18
26
|
"dependencies": {
|
|
19
27
|
"chalk": "^5.3.0",
|
|
20
28
|
"cli-highlight": "2.1.11",
|
|
@@ -25,7 +33,16 @@
|
|
|
25
33
|
"tsx": "4.19.2",
|
|
26
34
|
"wrap-ansi": "^9.0.0"
|
|
27
35
|
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "18.19.34",
|
|
38
|
+
"@types/react": "18.3.3",
|
|
39
|
+
"ink-testing-library": "4.0.0",
|
|
40
|
+
"typescript": "5.4.5"
|
|
41
|
+
},
|
|
28
42
|
"engines": {
|
|
29
43
|
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
30
47
|
}
|
|
31
48
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 打包 TUI Plan B 用的 "python + aimux" 离线运行时 zip(linux-x64 / win-x64 / mac-x64)。
|
|
3
|
+
#
|
|
4
|
+
# 基础: python-build-standalone (CPython 3.12.7, tag 20241002), 自带完整 ensurepip+pip。
|
|
5
|
+
# aimux 及其依赖 (click/loguru/typer/rich) 全为纯 Python → linux 上一次 pip install 产出的
|
|
6
|
+
# 代码三平台通吃。win/mac 通过 `pip install --target` 把纯 python 轮子跨装进各自 site-packages。
|
|
7
|
+
# 跨装坑: click 在 Windows 依赖 colorama (marker platform_system==Windows), linux 上 pip 会漏,
|
|
8
|
+
# 故 win 目标显式补 colorama。
|
|
9
|
+
#
|
|
10
|
+
# 产物: <DIST>/mobius-python-<arch>-v<BUNDLE_VER>.zip (zip 内根目录为 python/)
|
|
11
|
+
# TUI 端 URL 默认指向 mobius CDN, 可用 MOBIUS_TUI_PYTHON_BUNDLE_URL 覆盖。
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
TAG=20241002
|
|
15
|
+
PYVER=3.12.7
|
|
16
|
+
BUNDLE_VER=2
|
|
17
|
+
AIMUX_VERSION=0.1.21
|
|
18
|
+
PYPI_INDEX=https://pypi.org/simple
|
|
19
|
+
WORK="${WORK:-/home/tianyi/python-bundles}"
|
|
20
|
+
DIST="${DIST:-$WORK/dist}"
|
|
21
|
+
mkdir -p "$WORK" "$DIST"
|
|
22
|
+
|
|
23
|
+
base="https://github.com/astral-sh/python-build-standalone/releases/download/$TAG"
|
|
24
|
+
# install_only_stripped = 去掉静态库 libpython.a / debug 符号, 专为分发瘦身 (运行 Python 应用无影响)
|
|
25
|
+
declare -A URLS=(
|
|
26
|
+
[linux-x64]="$base/cpython-${PYVER}+${TAG}-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
|
27
|
+
[win-x64]="$base/cpython-${PYVER}+${TAG}-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz"
|
|
28
|
+
[mac-x64]="$base/cpython-${PYVER}+${TAG}-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# 运行 `python -m aimux` 用不到的部分: 静态库/构建脚本/idle/tk/ensurepip 内置 wheel/缓存
|
|
32
|
+
prune() { # prune <python-root>
|
|
33
|
+
local root=$1
|
|
34
|
+
rm -rf "$root"/lib/python*/config-* "$root"/lib/python*/test "$root"/lib/python*/idlelib \
|
|
35
|
+
"$root"/lib/python*/tkinter "$root"/lib/python*/turtledemo "$root"/lib/python*/ensurepip/_bundled \
|
|
36
|
+
"$root"/lib/python*/site-packages/pip* "$root"/lib/python*/site-packages/setuptools* "$root"/lib/python*/site-packages/pkg_resources* \
|
|
37
|
+
"$root"/Lib/config "$root"/Lib/test "$root"/Lib/idlelib "$root"/Lib/tkinter "$root"/Lib/turtledemo \
|
|
38
|
+
"$root"/Lib/ensurepip/_bundled \
|
|
39
|
+
2>/dev/null || true
|
|
40
|
+
find "$root" -name '__pycache__' -type d -prune -exec rm -rf {} + 2>/dev/null || true
|
|
41
|
+
find "$root" -name '*.pyc' -delete 2>/dev/null || true
|
|
42
|
+
rm -f "$root"/bin/2to3* "$root"/bin/idle* "$root"/bin/pydoc* "$root"/bin/*-config \
|
|
43
|
+
"$root"/Scripts/2to3* "$root"/Scripts/idle* "$root"/Scripts/pydoc* 2>/dev/null || true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# 也许需要代理拉 github / pypi; 命令行无代理时直接跑, 失败再换 proxychains
|
|
47
|
+
dl() { # dl <url> <out>
|
|
48
|
+
if command -v proxychains4 >/dev/null 2>&1 && [ "${USE_PROXY:-1}" = 1 ]; then
|
|
49
|
+
proxychains4 -q curl -fL "$1" -o "$2"
|
|
50
|
+
else
|
|
51
|
+
curl -fL "$1" -o "$2"
|
|
52
|
+
fi
|
|
53
|
+
}
|
|
54
|
+
uv_install_python() {
|
|
55
|
+
if command -v uv >/dev/null 2>&1; then
|
|
56
|
+
uv pip install --python "$LINUX_PY" --index-url "$PYPI_INDEX" "$@"
|
|
57
|
+
else
|
|
58
|
+
"$LINUX_PY" -m pip install "$@"
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
echo "== 1) 下载并解压 python-build-standalone =="
|
|
63
|
+
for arch in linux-x64 win-x64 mac-x64; do
|
|
64
|
+
if [ -x "$WORK/$arch/python/bin/python3" ] || [ -f "$WORK/$arch/python/python.exe" ]; then
|
|
65
|
+
echo " [$arch] 已存在, 跳过下载"; continue
|
|
66
|
+
fi
|
|
67
|
+
echo " [$arch] 下载 ${URLS[$arch]}"
|
|
68
|
+
dl "${URLS[$arch]}" "$WORK/$arch.tar.gz"
|
|
69
|
+
mkdir -p "$WORK/$arch"
|
|
70
|
+
tar -xzf "$WORK/$arch.tar.gz" -C "$WORK/$arch"
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
LINUX_PY="$WORK/linux-x64/python/bin/python3"
|
|
74
|
+
echo "== 2) linux-x64: 原生 pip install aimux =="
|
|
75
|
+
USE_PROXY=1 uv_install_python --quiet "aimux==$AIMUX_VERSION" colorama || \
|
|
76
|
+
uv_install_python "aimux==$AIMUX_VERSION" colorama
|
|
77
|
+
echo " 验证: $($LINUX_PY -c 'import aimux, click, loguru, typer, rich; print("linux import ok", aimux.__name__)')"
|
|
78
|
+
|
|
79
|
+
echo "== 3) win-x64 / mac-x64: 跨装纯 python aimux 到各自 site-packages =="
|
|
80
|
+
# win: click 需 colorama; mac: 不需要 colorama
|
|
81
|
+
install_target() { # arch site_packages_dir [extra...]
|
|
82
|
+
local arch=$1 sp=$2; shift 2
|
|
83
|
+
echo " [$arch] pip install --target $sp aimux $*"
|
|
84
|
+
rm -rf "$sp"/* 2>/dev/null || true # 重复构建时清旧
|
|
85
|
+
USE_PROXY=1 uv pip install --quiet --index-url "$PYPI_INDEX" --target "$sp" "aimux==$AIMUX_VERSION" "$@" || \
|
|
86
|
+
uv pip install --index-url "$PYPI_INDEX" --target "$sp" "aimux==$AIMUX_VERSION" "$@"
|
|
87
|
+
echo " [$arch] site-packages:"; ls "$sp" | head -20
|
|
88
|
+
}
|
|
89
|
+
install_target win-x64 "$WORK/win-x64/python/Lib/site-packages" colorama win32-setctime
|
|
90
|
+
install_target mac-x64 "$WORK/mac-x64/python/lib/python3.12/site-packages"
|
|
91
|
+
|
|
92
|
+
echo "== 4) 瘦身 (删运行时用不到的 test/idle/tk/ensurepip wheel/缓存) 后打 zip =="
|
|
93
|
+
for arch in linux-x64 win-x64 mac-x64; do prune "$WORK/$arch/python"; done
|
|
94
|
+
# 本机无 zip 命令 → 用 python 造一个等价 -ry 的打包器: external_attr 存 st_mode,
|
|
95
|
+
# 符号链接存为 link-target + S_IFLNK 位, extract-zip 据此还原 symlink 与可执行位。
|
|
96
|
+
zip_py="$(mktemp).py"
|
|
97
|
+
cat > "$zip_py" <<'PYEOF'
|
|
98
|
+
import os, sys, stat, zipfile
|
|
99
|
+
src, out = sys.argv[1], sys.argv[2]
|
|
100
|
+
parent = os.path.dirname(src.rstrip('/'))
|
|
101
|
+
def add(z, full):
|
|
102
|
+
arc = os.path.relpath(full, parent)
|
|
103
|
+
st = os.lstat(full)
|
|
104
|
+
zi = zipfile.ZipInfo(arc, (1980, 1, 1, 0, 0, 0))
|
|
105
|
+
zi.external_attr = (st.st_mode & 0xFFFF) << 16
|
|
106
|
+
zi.create_system = 3 # unix
|
|
107
|
+
if stat.S_ISLNK(st.st_mode):
|
|
108
|
+
z.writestr(zi, os.readlink(full)) # 符号链接: 内容=目标路径
|
|
109
|
+
else:
|
|
110
|
+
with open(full, 'rb') as f: z.writestr(zi, f.read())
|
|
111
|
+
with zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED) as z:
|
|
112
|
+
for dp, dirs, files in os.walk(src):
|
|
113
|
+
for name in list(dirs):
|
|
114
|
+
full = os.path.join(dp, name)
|
|
115
|
+
if os.path.islink(full): # 目录符号链接: 存为链接, 不下钻
|
|
116
|
+
add(z, full); dirs.remove(name)
|
|
117
|
+
for name in files:
|
|
118
|
+
add(z, os.path.join(dp, name))
|
|
119
|
+
print(' ok', out)
|
|
120
|
+
PYEOF
|
|
121
|
+
for arch in linux-x64 win-x64 mac-x64; do
|
|
122
|
+
out="$DIST/mobius-python-$arch-v${BUNDLE_VER}.zip"
|
|
123
|
+
rm -f "$out"
|
|
124
|
+
"$LINUX_PY" "$zip_py" "$WORK/$arch/python" "$out"
|
|
125
|
+
echo " $out $(du -h "$out" | cut -f1)"
|
|
126
|
+
done
|
|
127
|
+
rm -f "$zip_py"
|
|
128
|
+
echo "== 完成. 产物: =="
|
|
129
|
+
ls -lh "$DIST"/mobius-python-*-v${BUNDLE_VER}.zip
|
package/src/App.tsx
CHANGED
|
@@ -21,7 +21,7 @@ import { ResumePicker } from './components/ResumePicker.js'
|
|
|
21
21
|
import { Screen } from './components/Screen.js'
|
|
22
22
|
import { startAimuxConnection, stopAimuxConnection, type AimuxStatus } from './aimux.js'
|
|
23
23
|
import { AimuxStatusLine } from './components/AimuxStatus.js'
|
|
24
|
-
import { useStableInput } from './components/primitives.js'
|
|
24
|
+
import { bufferUnclaimedInput, useStableInput } from './components/primitives.js'
|
|
25
25
|
|
|
26
26
|
type Route = 'boot' | 'login' | 'prep' | 'chat' | 'resume'
|
|
27
27
|
|
|
@@ -30,7 +30,7 @@ export function App() {
|
|
|
30
30
|
// owner, Ink can briefly drop raw mode between an async picker unmount and
|
|
31
31
|
// the next Chat/Select mount, making the first arrows or typed characters
|
|
32
32
|
// appear ignored until a later key causes another render.
|
|
33
|
-
useStableInput(
|
|
33
|
+
useStableInput(bufferUnclaimedInput, { interactive: false })
|
|
34
34
|
const [route, setRoute] = useState<Route>('boot')
|
|
35
35
|
const [bootMsg, setBootMsg] = useState('初始化…')
|
|
36
36
|
const [client, setClient] = useState<MobiusClient | null>(null)
|