martty 0.2.11

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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +118 -0
  3. package/bin/dsh-tui.js +68 -0
  4. package/cordis.patch.yml +30 -0
  5. package/creator/cordis.patch.yml +6 -0
  6. package/creator/package.json +10 -0
  7. package/lib/acp-client-events.js +65 -0
  8. package/lib/acp-client.js +114 -0
  9. package/lib/acp-host.js +24 -0
  10. package/lib/acp-session-config.js +376 -0
  11. package/lib/acp-session-plan.js +196 -0
  12. package/lib/acp-session-stats.js +239 -0
  13. package/lib/agent.js +64 -0
  14. package/lib/boot.js +119 -0
  15. package/lib/client-process.js +11 -0
  16. package/lib/client-run.js +379 -0
  17. package/lib/cordis-protocol.js +51 -0
  18. package/lib/creator-overlay.js +77 -0
  19. package/lib/demo-skin.js +79 -0
  20. package/lib/ember.js +20 -0
  21. package/lib/index.js +226 -0
  22. package/lib/inspect.js +971 -0
  23. package/lib/jsonrpc-line-transport.js +155 -0
  24. package/lib/mux.js +281 -0
  25. package/lib/palettes/default.json +44 -0
  26. package/lib/palettes/ember.json +44 -0
  27. package/lib/plan-view.js +92 -0
  28. package/lib/profile-acp-client.js +11 -0
  29. package/lib/right-demo.js +55 -0
  30. package/lib/runner.js +94 -0
  31. package/lib/spawn-tui.js +179 -0
  32. package/lib/stats-view.js +90 -0
  33. package/lib/tui-commands.js +144 -0
  34. package/lib/tui-overlay.js +252 -0
  35. package/lib/tui-slots.js +351 -0
  36. package/lib/tui-theme.js +463 -0
  37. package/package.json +83 -0
  38. package/skills/tui-plugin-development/SKILL.md +172 -0
  39. package/vendor/darwin-arm64/dsh-tui +0 -0
  40. package/vendor/darwin-x64/dsh-tui +0 -0
  41. package/vendor/linux-arm64/dsh-tui +0 -0
  42. package/vendor/linux-x64/dsh-tui +0 -0
  43. package/vendor/win32-x64/dsh-tui.exe +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenMA contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/openma-ai/Martty/main/assets/tui-whale.svg" width="424" alt="DeepSeek Harness TUI whale" />
3
+ </p>
4
+
5
+ <h1 align="center">DeepSeek Harness TUI</h1>
6
+
7
+ <p align="center">
8
+ Terminal-native ACP client UI with a Cordis client plugin tree.
9
+ </p>
10
+
11
+ ---
12
+
13
+ Native binaries are packaged for macOS arm64, macOS x64, Linux x64, and Windows
14
+ x64. Requires Node.js 18+.
15
+
16
+ ## Recommended
17
+
18
+ ```sh
19
+ npm install --global @deepseek-ai/dsh
20
+ dsh plugin --profile tui add martty@latest
21
+ dsh --profile tui
22
+ ```
23
+
24
+ The profile command is the recommended install and upgrade path. It creates a
25
+ missing profile and installs the TUI plus its ACP dependency; no global
26
+ `dsh-tui` installation is required.
27
+
28
+ The profile Host mounts the ACP plugin on Base, then starts a separate TUI
29
+ Client process over standard ACP stdin/stdout. For standalone use, run
30
+ `dsh-tui` and use `--agent <cmd>` plus repeated `--agent-arg <arg>` for another
31
+ ACP server. The Node Client process owns a Cordis tree and starts the Rust painter. A sibling
32
+ `tui-cordis-client-runner` publishes TUI Client capabilities and evaluates
33
+ approved `code.client` packages from `dsh-tool-cordis` against that client tree.
34
+
35
+ ACP is a runtime dependency of this package. On a profile that already carries
36
+ a different version through the standard ACP bundle, the package manager may
37
+ retain both copies, but the TUI bundle replaces that surface's rows and mounts
38
+ only the plugin resolved from TUI's own dependency graph. The supported profile
39
+ shape never runs two ACP surfaces for one TUI.
40
+
41
+ The package carries ACP as a runtime dependency and exports its Creator Host
42
+ overlay internally. The profile bundle mounts both on the Host Base tree;
43
+ neither enters the Client tree. Creator adds TUI plugin guidance to the
44
+ existing `cordis` preset and does not use ACP for skill registration.
45
+
46
+ Node and Rust use inherited pipes on Unix and an authenticated loopback TCP
47
+ socket on Windows. This compositor channel carries theme/render data only;
48
+ agent traffic remains ACP.
49
+
50
+ ## Demo
51
+
52
+ ```sh
53
+ dsh-tui --demo
54
+ dsh-tui --demo-skin
55
+ ```
56
+
57
+ `dsh-tui` is the primary command. `dsb` is a compatibility alias.
58
+
59
+ ## Highlights
60
+
61
+ - Streamed reasoning, replies, tool activity, subagents, run state, and
62
+ token/cache metrics in one terminal timeline.
63
+ - Agent-advertised models, compositions, permissions, authentication methods,
64
+ and invocable skills in one searchable slash menu.
65
+ - Up to eight editable image chips per prompt, with clipboard/file staging and
66
+ metadata previews on kitty-capable terminals.
67
+ - Terminal-aware Markdown for headings, lists, quotes, code, emphasis, links,
68
+ and mixed CJK/Latin text.
69
+ - Durable sessions, queued follow-ups, immediate steering, readline editing,
70
+ platform-native modifier bindings, mouse selection, and inline-expanded tools.
71
+ - Dark/light themes, clipboard routing for local, tmux, and SSH sessions, plus
72
+ the optional `/liang` pixel companion.
73
+ - A root `chrome.right` plugin rail for validated TuiNode trees, with live
74
+ update/unload and Client inspect support for Creator-authored plugins.
75
+ - Lifecycle-owned local commands and native slider overlays, plus transactions
76
+ over the current Session's standard ACP `configOptions`.
77
+ - Dynamic `code.host` + `code.client` Packages with inspect/run lifecycle and
78
+ package-private Host/Client RPC over a negotiated `_dsh/cordis/*` ACP
79
+ extension. Ordinary ACP agents remain usable when they do not advertise it.
80
+
81
+ ## Plugin surface
82
+
83
+ TUI extensions are ordinary Cordis plugins on the Node Client tree. A single
84
+ plugin may register a theme, `chrome.right` nodes, slash commands, overlays,
85
+ timers, Session config transactions, and Host/Client RPC; all contributions
86
+ leave together when its fiber stops or `/theme` replaces the selected Theme
87
+ Plugin. Plugins submit semantic data and never receive the TTY, Ratatui,
88
+ absolute coordinates, compositor fds, or private transport methods.
89
+
90
+ The versioned contract and examples live in the repository's
91
+ [plugin API](https://github.com/openma-ai/Martty/blob/main/docs/plugins.en.md).
92
+
93
+ The demo needs no API key or agent. Run `dsh-tui --help` for agent, model,
94
+ credential, session, theme, and demo options.
95
+
96
+ ## Supported platforms
97
+
98
+ | Node platform key | Binary |
99
+ |---|---|
100
+ | `darwin-arm64` | `vendor/darwin-arm64/dsh-tui` |
101
+ | `darwin-x64` | `vendor/darwin-x64/dsh-tui` |
102
+ | `linux-x64` | `vendor/linux-x64/dsh-tui` |
103
+ | `linux-arm64` | `vendor/linux-arm64/dsh-tui` |
104
+ | `win32-x64` | `vendor/win32-x64/dsh-tui.exe` |
105
+
106
+ If installation succeeds but launch reports `no native binary for ...`, confirm
107
+ that you installed the latest version and that your platform appears above.
108
+
109
+ ## Uninstall
110
+
111
+ ```sh
112
+ npm uninstall --global martty
113
+ ```
114
+
115
+ Source, screenshots, development commands, and architecture notes live in the
116
+ [GitHub repository](https://github.com/openma-ai/Martty).
117
+
118
+ [MIT](https://github.com/openma-ai/Martty/blob/main/LICENSE).
package/bin/dsh-tui.js ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import { spawnSync } from 'node:child_process'
6
+ import { fileURLToPath } from 'node:url'
7
+ import { bootClient, parseClientArgv, painterArgs } from '../lib/boot.js'
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
10
+ const platformKey = process.platform + '-' + process.arch
11
+ const vendorDir = path.join(__dirname, '..', 'vendor')
12
+ const binaryName = process.platform === 'win32' ? 'dsh-tui.exe' : 'dsh-tui'
13
+ const packaged = path.join(vendorDir, platformKey, binaryName)
14
+ const envBin = process.env.DSH_TUI_BIN
15
+ const binaryPath = typeof envBin === 'string' && envBin.length > 0 && fs.existsSync(envBin)
16
+ ? envBin
17
+ : packaged
18
+
19
+ const argv = process.argv.slice(2)
20
+ const wantDemoSkin = argv.includes('--demo-skin')
21
+ const wantDemo = argv.includes('--demo')
22
+ const wantHelp = argv.includes('-h') || argv.includes('--help')
23
+ const wantVersion = argv.includes('-V') || argv.includes('--version')
24
+
25
+ if (!fs.existsSync(binaryPath)) {
26
+ let available = []
27
+ try {
28
+ available = fs
29
+ .readdirSync(vendorDir, { withFileTypes: true })
30
+ .filter((e) => e.isDirectory())
31
+ .map((e) => e.name)
32
+ } catch {
33
+ // vendor dir missing entirely
34
+ }
35
+ console.error('dsh-tui: no bundled binary for this platform (' + platformKey + ').')
36
+ if (available.length > 0) {
37
+ console.error('Bundled platforms: ' + available.join(', '))
38
+ } else {
39
+ console.error('No platform binaries are bundled in this install.')
40
+ }
41
+ console.error('Hint: run scripts/build-npm.sh on this machine to build and package a native binary.')
42
+ process.exit(1)
43
+ }
44
+
45
+ function exitFromSpawn(result) {
46
+ process.exit(
47
+ result.status ??
48
+ (result.signal === 'SIGINT' ? 130 : result.signal === 'SIGTERM' ? 143 : 1),
49
+ )
50
+ }
51
+
52
+ if (wantHelp || wantVersion || wantDemo) {
53
+ exitFromSpawn(spawnSync(binaryPath, argv, { stdio: 'inherit' }))
54
+ }
55
+
56
+ if (wantDemoSkin) {
57
+ process.env.DSH_TUI_BIN ??= binaryPath
58
+ const demoSkinPath = path.join(__dirname, '..', 'lib', 'demo-skin.js')
59
+ const args = argv.filter((arg) => arg !== '--demo-skin')
60
+ exitFromSpawn(spawnSync(process.execPath, [demoSkinPath, ...args], {
61
+ stdio: 'inherit',
62
+ env: process.env,
63
+ }))
64
+ }
65
+
66
+ process.env.DSH_TUI_BIN ??= binaryPath
67
+ const parsed = parseClientArgv(argv)
68
+ await bootClient({ agent: parsed.agent, extraArgs: painterArgs(parsed) })
@@ -0,0 +1,30 @@
1
+ # Host-side entries for the TUI profile surface. The ACP plugin mounts on the
2
+ # existing Base Cordis tree. The runner starts a separate TUI Client process;
3
+ # standard stdin/stdout carries ACP between them. Client plugins never mount
4
+ # beside the Harness.
5
+
6
+ # If this profile already carries an ACP surface bundle, TUI replaces its
7
+ # provider/stdio rows with the same ACP plugin resolved through TUI's declared
8
+ # dependency. The profile transport is replaced because its stdio belongs to
9
+ # the TUI launcher, not an external ACP client. Missing ids are harmless.
10
+ - id: acp-plugin
11
+ disabled: true
12
+
13
+ - id: acp-server
14
+ disabled: true
15
+
16
+ - id: acp-bridge
17
+ disabled: true
18
+
19
+ - id: tui-creator-overlay
20
+ disabled: true
21
+
22
+ - insert:
23
+ - id: tui-acp-host
24
+ name: 'martty/acp-host'
25
+
26
+ - id: tui-host-creator
27
+ name: 'martty/creator-overlay'
28
+
29
+ - id: tui-runner
30
+ name: 'martty/runner'
@@ -0,0 +1,6 @@
1
+ # Internal bundle used by standalone dsh-tui when it spawns dsh-acp. This is
2
+ # part of martty, not a separately published package.
3
+
4
+ - insert:
5
+ - id: tui-creator-overlay
6
+ name: deepseek-harness-tui-creator-overlay
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "deepseek-harness-tui-creator-overlay",
3
+ "private": true,
4
+ "main": "../lib/creator-overlay.js",
5
+ "dsh": {
6
+ "bundle": {
7
+ "patch": "./cordis.patch.yml"
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,65 @@
1
+ /** Effect-scoped fan-out for standard ACP messages observed by Client plugins. */
2
+
3
+ import { Service } from '@deepseek-ai/cordis'
4
+
5
+ export const name = 'acp-client-events'
6
+ export const inject = []
7
+
8
+ class AcpClientEventsService extends Service {
9
+ constructor(ctx, core) {
10
+ super(ctx, 'acpClientEvents')
11
+ this.core = core
12
+ }
13
+
14
+ register(observer) { return this.core.register(this.ctx, observer) }
15
+ observeClient(message) { return this.core.observeClient(message) }
16
+ observeAgent(message) { return this.core.observeAgent(message) }
17
+ }
18
+
19
+ export function installAcpClientEvents(ctx) {
20
+ const observers = new Set()
21
+
22
+ function register(effectCtx, observer) {
23
+ if (observer === null || typeof observer !== 'object'
24
+ || (typeof observer.observeClient !== 'function'
25
+ && typeof observer.observeAgent !== 'function')) {
26
+ throw new Error('acpClientEvents.register: observeClient or observeAgent is required')
27
+ }
28
+ const setup = () => {
29
+ observers.add(observer)
30
+ return () => observers.delete(observer)
31
+ }
32
+ const release = typeof effectCtx?.effect === 'function'
33
+ ? effectCtx.effect(setup, 'acpClientEvents.register')
34
+ : setup()
35
+ let disposed = false
36
+ return () => {
37
+ if (disposed) return
38
+ disposed = true
39
+ return release?.()
40
+ }
41
+ }
42
+
43
+ function observeClient(message) {
44
+ for (const observer of [...observers]) observer.observeClient?.(message)
45
+ }
46
+
47
+ function observeAgent(message) {
48
+ for (const observer of [...observers]) observer.observeAgent?.(message)
49
+ }
50
+
51
+ const core = { register, observeClient, observeAgent }
52
+ const service = typeof ctx.provide === 'function'
53
+ ? new AcpClientEventsService(ctx, core)
54
+ : {
55
+ register(observer) { return register(ctx, observer) },
56
+ observeClient,
57
+ observeAgent,
58
+ }
59
+ if (typeof ctx.provide !== 'function') ctx.acpClientEvents = service
60
+ return service
61
+ }
62
+
63
+ export function apply(ctx) {
64
+ installAcpClientEvents(ctx)
65
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Cordis plugin: attach to any ACP agent by spawn or an existing stream.
3
+ *
4
+ * Provides `ctx.acpClient` plus the standard-ACP-backed
5
+ * `ctx.acpSessionConfig`. Does not import a harness, dsh, or dsh-acp.
6
+ * Switching agents is `{ command, args }` (or `config.stream`).
7
+ */
8
+
9
+ import { spawn } from 'node:child_process'
10
+ import { installAcpClientEvents } from './acp-client-events.js'
11
+ import { installAcpSessionConfig } from './acp-session-config.js'
12
+ import { installAcpSessionPlan } from './acp-session-plan.js'
13
+ import { installAcpSessionStats } from './acp-session-stats.js'
14
+
15
+ export { installAcpSessionConfig } from './acp-session-config.js'
16
+ export { installAcpSessionPlan } from './acp-session-plan.js'
17
+ export { installAcpSessionStats } from './acp-session-stats.js'
18
+ export { installAcpClientEvents } from './acp-client-events.js'
19
+
20
+ export const name = 'acp-client'
21
+ export const inject = []
22
+
23
+ /** @type {null | { command: string, args: string[], child: import('node:child_process').ChildProcess, stdin: import('node:stream').Writable, stdout: import('node:stream').Readable, kind: 'spawn' }} */
24
+ let liveAgent = null
25
+
26
+ /**
27
+ * @typedef {{ command: string, args?: string[], env?: Record<string, string> }} AgentSpec
28
+ */
29
+
30
+ /**
31
+ * @param {unknown} config
32
+ * @returns {AgentSpec}
33
+ */
34
+ export function resolveAgent(config = {}) {
35
+ const fromConfig = config && typeof config === 'object' ? config.agent : undefined
36
+ if (fromConfig && typeof fromConfig === 'object' && typeof fromConfig.command === 'string') {
37
+ return {
38
+ command: fromConfig.command,
39
+ args: Array.isArray(fromConfig.args) ? fromConfig.args.map(String) : [],
40
+ ...(fromConfig.env !== undefined ? { env: fromConfig.env } : {}),
41
+ }
42
+ }
43
+ const envCmd = process.env.DSH_TUI_AGENT
44
+ if (typeof envCmd === 'string' && envCmd.trim().length > 0) {
45
+ const tokens = envCmd.trim().split(/\s+/)
46
+ return { command: tokens[0], args: tokens.slice(1) }
47
+ }
48
+ return { command: 'dsh-acp', args: [] }
49
+ }
50
+
51
+ /**
52
+ * @param {object} ctx
53
+ * @param {object} [config]
54
+ */
55
+ export function apply(ctx, config = {}) {
56
+ const events = installAcpClientEvents(ctx)
57
+ const sessionConfig = installAcpSessionConfig(ctx)
58
+ const sessionPlan = installAcpSessionPlan(ctx)
59
+ const sessionStats = installAcpSessionStats(ctx)
60
+ events.register(sessionConfig)
61
+ events.register(sessionPlan)
62
+ events.register(sessionStats)
63
+ if (config.stream !== undefined && config.stream !== null) {
64
+ const service = {
65
+ kind: 'stream',
66
+ stdin: config.stream.stdin,
67
+ stdout: config.stream.stdout,
68
+ child: config.stream.child,
69
+ }
70
+ provide(ctx, service)
71
+ return
72
+ }
73
+ const agent = resolveAgent(config)
74
+ if (
75
+ liveAgent !== null
76
+ && liveAgent.child.exitCode === null
77
+ && liveAgent.command === agent.command
78
+ && JSON.stringify(liveAgent.args) === JSON.stringify(agent.args ?? [])
79
+ ) {
80
+ provide(ctx, liveAgent)
81
+ return
82
+ }
83
+ const child = spawn(agent.command, agent.args ?? [], {
84
+ stdio: ['pipe', 'pipe', 'inherit'],
85
+ env: { ...process.env, ...(agent.env ?? {}) },
86
+ })
87
+ child.stdin.on('error', () => {})
88
+ child.stdout.on('error', () => {})
89
+ const service = {
90
+ kind: 'spawn',
91
+ command: agent.command,
92
+ args: agent.args ?? [],
93
+ stdin: child.stdin,
94
+ stdout: child.stdout,
95
+ child,
96
+ }
97
+ liveAgent = service
98
+ provide(ctx, service)
99
+ process.once('exit', () => {
100
+ try {
101
+ child.kill('SIGTERM')
102
+ } catch {
103
+ // already gone
104
+ }
105
+ })
106
+ }
107
+
108
+ function provide(ctx, service) {
109
+ if (typeof ctx.provide === 'function') {
110
+ ctx.provide('acpClient', service)
111
+ } else {
112
+ ctx.acpClient = service
113
+ }
114
+ }
@@ -0,0 +1,24 @@
1
+ /** Resolve the embeddable ACP plugin from TUI's own dependency graph. */
2
+
3
+ import { createRequire } from 'node:module'
4
+
5
+ export const name = 'dsh-tui-acp-host'
6
+ export const inject = ['loader']
7
+
8
+ const requireFromTui = createRequire(import.meta.url)
9
+
10
+ function ownAcpPlugin() {
11
+ const specifier = '@openma/deepseek-harness-acp/plugin'
12
+ try {
13
+ return requireFromTui.resolve(specifier)
14
+ } catch {
15
+ // A source-linked package may rely on the active profile's installation.
16
+ return specifier
17
+ }
18
+ }
19
+
20
+ export async function apply(ctx, config) {
21
+ const exports = await ctx.loader.import(ownAcpPlugin())
22
+ const plugin = ctx.loader.unwrapExports(exports)
23
+ await ctx.plugin(plugin, config)
24
+ }