@mobius-os/mobius 0.3.4 → 0.3.5
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/package.json +1 -1
- package/src/aimux.ts +23 -2
- package/src/components/Chat.tsx +5 -3
- package/src/components/PrepScreen.tsx +1 -1
- package/src/components/primitives.tsx +7 -2
- package/src/main.tsx +2 -2
package/package.json
CHANGED
package/src/aimux.ts
CHANGED
|
@@ -209,6 +209,27 @@ export function tuiAimuxIdentifier(): string {
|
|
|
209
209
|
return `tui-${host || 'pc'}`
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Build the reverse-connect command in one place. The TUI can launch AIMUX
|
|
214
|
+
* through either a venv executable or bundled Python; both paths must request
|
|
215
|
+
* hidden Windows shells or every remote command flashes a console and steals
|
|
216
|
+
* keyboard focus from the TUI.
|
|
217
|
+
*/
|
|
218
|
+
export function reverseConnectArgs(
|
|
219
|
+
server: string,
|
|
220
|
+
identifier: string,
|
|
221
|
+
token: string,
|
|
222
|
+
platform: NodeJS.Platform = process.platform,
|
|
223
|
+
): string[] {
|
|
224
|
+
return [
|
|
225
|
+
'reverse', 'connect', `${server.replace(/\/$/, '')}/aimux_bridge`,
|
|
226
|
+
'--identifier', identifier,
|
|
227
|
+
'--token', token,
|
|
228
|
+
'--replace',
|
|
229
|
+
...(platform === 'win32' ? ['--silent-shell'] : []),
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
|
|
212
233
|
export async function probeAimuxBridgeConnection(
|
|
213
234
|
server: string,
|
|
214
235
|
token: string,
|
|
@@ -261,7 +282,7 @@ export class AimuxSupervisor {
|
|
|
261
282
|
onStatus({ state: 'starting', phase: 'connecting', detail: '正在连接 Mobius AIMUX bridge…', identifier, attempt: this.reconnectAttempt })
|
|
262
283
|
const child = this.opts.spawnProcess?.() ?? spawn(
|
|
263
284
|
aimuxExe(),
|
|
264
|
-
|
|
285
|
+
reverseConnectArgs(server, identifier, token),
|
|
265
286
|
{ windowsHide: true },
|
|
266
287
|
)
|
|
267
288
|
this.child = child
|
|
@@ -402,7 +423,7 @@ export async function startAimuxConnection(opts: { server: string; token: string
|
|
|
402
423
|
const launcher = ready.launcher
|
|
403
424
|
supervisor = new AimuxSupervisor({
|
|
404
425
|
server: opts.server, token: opts.token, identifier, onStatus,
|
|
405
|
-
spawnProcess: () => spawnLauncher(launcher,
|
|
426
|
+
spawnProcess: () => spawnLauncher(launcher, reverseConnectArgs(opts.server, identifier, opts.token)),
|
|
406
427
|
})
|
|
407
428
|
supervisor.start()
|
|
408
429
|
})().finally(() => { installing = null })
|
package/src/components/Chat.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import type { ReadyState } from './PrepScreen.js'
|
|
|
17
17
|
import type { AnyEntry } from '../types.js'
|
|
18
18
|
import type { AimuxStatus } from '../aimux.js'
|
|
19
19
|
import { AimuxStatusLine, aimuxStatusText } from './AimuxStatus.js'
|
|
20
|
+
import { isEscapeKeypress } from './primitives.js'
|
|
20
21
|
|
|
21
22
|
interface ChatProps {
|
|
22
23
|
client: MobiusClient
|
|
@@ -423,7 +424,8 @@ function Composer({ onSubmit, onStop, onQuit, typing, commands }: ComposerProps)
|
|
|
423
424
|
}
|
|
424
425
|
|
|
425
426
|
useInput((input, key) => {
|
|
426
|
-
|
|
427
|
+
const escape = isEscapeKeypress(input, key)
|
|
428
|
+
if (typing && escape) { void onStop(); return }
|
|
427
429
|
|
|
428
430
|
if (popupOpen) {
|
|
429
431
|
if (key.upArrow) { setPopupIdx(i => (i <= 0 ? filtered.length - 1 : i - 1)); return }
|
|
@@ -432,7 +434,7 @@ function Composer({ onSubmit, onStop, onQuit, typing, commands }: ComposerProps)
|
|
|
432
434
|
const pick = filtered[popupIdx >= 0 ? popupIdx : 0]
|
|
433
435
|
if (pick) { edit(`${pick.cmd} `, pick.cmd.length + 1); setPopupDismissed(true); return }
|
|
434
436
|
}
|
|
435
|
-
if (
|
|
437
|
+
if (escape) { setPopupDismissed(true); return }
|
|
436
438
|
}
|
|
437
439
|
|
|
438
440
|
if (key.return) {
|
|
@@ -476,7 +478,7 @@ function Composer({ onSubmit, onStop, onQuit, typing, commands }: ComposerProps)
|
|
|
476
478
|
if (key.ctrl && input === 'u') { edit('', 0); return }
|
|
477
479
|
if (key.ctrl && input === 'k') { edit(value.slice(0, cursor), cursor); return }
|
|
478
480
|
if (key.ctrl && input === 'j') { edit(value.slice(0, cursor) + '\n' + value.slice(cursor), cursor + 1); return }
|
|
479
|
-
if (key.ctrl || key.meta ||
|
|
481
|
+
if (key.ctrl || key.meta || escape || !input) return
|
|
480
482
|
edit(value.slice(0, cursor) + input + value.slice(cursor), cursor + input.length)
|
|
481
483
|
})
|
|
482
484
|
|
|
@@ -293,7 +293,7 @@ function IssuePicker({ issues, onPick, onCreate }: {
|
|
|
293
293
|
<Text bold color="cyan">创建新任务</Text>
|
|
294
294
|
<Text color="gray">输入任务名称(不使用 git worktree)</Text>
|
|
295
295
|
<TextInput value={name} onChange={setName} focused placeholder="命令行任务"
|
|
296
|
-
onSubmit={() => onCreate(name)} />
|
|
296
|
+
onSubmit={() => onCreate(name)} onEscape={() => setMode('list')} />
|
|
297
297
|
<Text color="gray">回车创建 · Esc 返回</Text>
|
|
298
298
|
</Box>
|
|
299
299
|
)
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
import React, { useEffect, useRef, useState } from 'react'
|
|
6
6
|
import { Box, Text, useInput, useStdout } from 'ink'
|
|
7
7
|
|
|
8
|
+
/** Windows Terminal/ConPTY may expose Esc as a named key, a raw byte, or Ctrl+[. */
|
|
9
|
+
export function isEscapeKeypress(input: string, key: { escape?: boolean; ctrl?: boolean }): boolean {
|
|
10
|
+
return key.escape === true || input === '\x1b' || (key.ctrl === true && input === '[')
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
// ─── TextInput ───────────────────────────────────────────────────────────────
|
|
9
14
|
export interface TextInputProps {
|
|
10
15
|
value: string
|
|
@@ -46,7 +51,7 @@ export function TextInput(props: TextInputProps) {
|
|
|
46
51
|
if (key.return) { props.onSubmit?.(); return }
|
|
47
52
|
if (key.upArrow) { props.onArrowUp?.(); return }
|
|
48
53
|
if (key.downArrow) { props.onArrowDown?.(); return }
|
|
49
|
-
if (
|
|
54
|
+
if (isEscapeKeypress(input, key)) { props.onEscape?.(); return }
|
|
50
55
|
if (key.tab) { props.onTab?.(); return }
|
|
51
56
|
// Ink labels the \x7f that virtually every terminal's Backspace key emits
|
|
52
57
|
// as `key.delete` (see its parse-keypress.js TODO). Treat either signal as
|
|
@@ -175,7 +180,7 @@ export function Select(props: SelectProps) {
|
|
|
175
180
|
if (key.return) { props.onConfirm?.(Array.from(selectedSet)); return }
|
|
176
181
|
if (input === ' ') { props.onToggle?.(items[active].value); return }
|
|
177
182
|
}
|
|
178
|
-
if (key
|
|
183
|
+
if (isEscapeKeypress(input, key)) { props.onBack?.(); return }
|
|
179
184
|
}, { isActive: props.focused !== false })
|
|
180
185
|
|
|
181
186
|
// viewport: keep the active item on screen. Without this a long list renders
|
package/src/main.tsx
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Run: npx tsx src/main.tsx (or `npm start`)
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Ink owns Ctrl+C globally so Windows users can always exit, including from
|
|
7
|
+
* setup screens that do not mount the chat composer.
|
|
8
8
|
*/
|
|
9
9
|
import React from 'react'
|
|
10
10
|
import { render } from 'ink'
|