mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
Merge branch 'feat/ink-refactor' of github.com:NousResearch/hermes-agent into feat/ink-refactor
This commit is contained in:
commit
acbf1794f2
5 changed files with 380 additions and 146 deletions
|
|
@ -1824,6 +1824,109 @@ def _(rid, params: dict) -> dict:
|
||||||
return _ok(rid, {"plugins": []})
|
return _ok(rid, {"plugins": []})
|
||||||
|
|
||||||
|
|
||||||
|
@method("config.show")
|
||||||
|
def _(rid, params: dict) -> dict:
|
||||||
|
try:
|
||||||
|
cfg = _load_cfg()
|
||||||
|
model = _resolve_model()
|
||||||
|
api_key = os.environ.get("HERMES_API_KEY", "") or cfg.get("api_key", "")
|
||||||
|
masked = f"****{api_key[-4:]}" if len(api_key) > 4 else "(not set)"
|
||||||
|
base_url = os.environ.get("HERMES_BASE_URL", "") or cfg.get("base_url", "")
|
||||||
|
|
||||||
|
sections = [{
|
||||||
|
"title": "Model",
|
||||||
|
"rows": [
|
||||||
|
["Model", model],
|
||||||
|
["Base URL", base_url or "(default)"],
|
||||||
|
["API Key", masked],
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
"title": "Agent",
|
||||||
|
"rows": [
|
||||||
|
["Max Turns", str(cfg.get("max_turns", 25))],
|
||||||
|
["Toolsets", ", ".join(cfg.get("enabled_toolsets", [])) or "all"],
|
||||||
|
["Verbose", str(cfg.get("verbose", False))],
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
"title": "Environment",
|
||||||
|
"rows": [
|
||||||
|
["Working Dir", os.getcwd()],
|
||||||
|
["Config File", str(_hermes_home / "config.yaml")],
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
return _ok(rid, {"sections": sections})
|
||||||
|
except Exception as e:
|
||||||
|
return _err(rid, 5030, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@method("tools.list")
|
||||||
|
def _(rid, params: dict) -> dict:
|
||||||
|
try:
|
||||||
|
from toolsets import get_all_toolsets, get_toolset_info
|
||||||
|
session = _sessions.get(params.get("session_id", ""))
|
||||||
|
enabled = set()
|
||||||
|
if session:
|
||||||
|
enabled = set(getattr(session["agent"], "enabled_toolsets", []) or [])
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for name in sorted(get_all_toolsets().keys()):
|
||||||
|
info = get_toolset_info(name)
|
||||||
|
if not info:
|
||||||
|
continue
|
||||||
|
items.append({
|
||||||
|
"name": name,
|
||||||
|
"description": info["description"],
|
||||||
|
"tool_count": info["tool_count"],
|
||||||
|
"enabled": name in enabled if enabled else True,
|
||||||
|
"tools": info["resolved_tools"],
|
||||||
|
})
|
||||||
|
return _ok(rid, {"toolsets": items})
|
||||||
|
except Exception as e:
|
||||||
|
return _err(rid, 5031, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@method("toolsets.list")
|
||||||
|
def _(rid, params: dict) -> dict:
|
||||||
|
try:
|
||||||
|
from toolsets import get_all_toolsets, get_toolset_info
|
||||||
|
session = _sessions.get(params.get("session_id", ""))
|
||||||
|
enabled = set()
|
||||||
|
if session:
|
||||||
|
enabled = set(getattr(session["agent"], "enabled_toolsets", []) or [])
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for name in sorted(get_all_toolsets().keys()):
|
||||||
|
info = get_toolset_info(name)
|
||||||
|
if not info:
|
||||||
|
continue
|
||||||
|
items.append({
|
||||||
|
"name": name,
|
||||||
|
"description": info["description"],
|
||||||
|
"tool_count": info["tool_count"],
|
||||||
|
"enabled": name in enabled if enabled else True,
|
||||||
|
})
|
||||||
|
return _ok(rid, {"toolsets": items})
|
||||||
|
except Exception as e:
|
||||||
|
return _err(rid, 5032, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@method("agents.list")
|
||||||
|
def _(rid, params: dict) -> dict:
|
||||||
|
try:
|
||||||
|
from tools.process_registry import ProcessRegistry
|
||||||
|
procs = ProcessRegistry().list_sessions()
|
||||||
|
return _ok(rid, {
|
||||||
|
"processes": [{
|
||||||
|
"session_id": p["session_id"],
|
||||||
|
"command": p["command"][:80],
|
||||||
|
"status": p["status"],
|
||||||
|
"uptime": p["uptime_seconds"],
|
||||||
|
} for p in procs]
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
return _err(rid, 5033, str(e))
|
||||||
|
|
||||||
|
|
||||||
@method("cron.manage")
|
@method("cron.manage")
|
||||||
def _(rid, params: dict) -> dict:
|
def _(rid, params: dict) -> dict:
|
||||||
action, jid = params.get("action", "list"), params.get("name", "")
|
action, jid = params.get("action", "list"), params.get("name", "")
|
||||||
|
|
|
||||||
38
ui-tui/package-lock.json
generated
38
ui-tui/package-lock.json
generated
|
|
@ -88,6 +88,7 @@
|
||||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.0",
|
||||||
|
|
@ -317,29 +318,6 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emnapi/core": {
|
|
||||||
"version": "1.9.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
|
|
||||||
"integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/wasi-threads": "1.2.1",
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@emnapi/runtime": {
|
|
||||||
"version": "1.9.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
|
|
||||||
"integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@emnapi/wasi-threads": {
|
"node_modules/@emnapi/wasi-threads": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||||
|
|
@ -1464,6 +1442,7 @@
|
||||||
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.19.0"
|
"undici-types": "~7.19.0"
|
||||||
}
|
}
|
||||||
|
|
@ -1474,6 +1453,7 @@
|
||||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
|
|
@ -1484,6 +1464,7 @@
|
||||||
"integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==",
|
"integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.12.2",
|
"@eslint-community/regexpp": "^4.12.2",
|
||||||
"@typescript-eslint/scope-manager": "8.58.1",
|
"@typescript-eslint/scope-manager": "8.58.1",
|
||||||
|
|
@ -1513,6 +1494,7 @@
|
||||||
"integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==",
|
"integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.58.1",
|
"@typescript-eslint/scope-manager": "8.58.1",
|
||||||
"@typescript-eslint/types": "8.58.1",
|
"@typescript-eslint/types": "8.58.1",
|
||||||
|
|
@ -1830,6 +1812,7 @@
|
||||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
|
|
@ -2165,6 +2148,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.10.12",
|
"baseline-browser-mapping": "^2.10.12",
|
||||||
"caniuse-lite": "^1.0.30001782",
|
"caniuse-lite": "^1.0.30001782",
|
||||||
|
|
@ -2850,6 +2834,7 @@
|
||||||
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
|
|
@ -3745,6 +3730,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/ink-text-input/-/ink-text-input-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ink-text-input/-/ink-text-input-6.0.0.tgz",
|
||||||
"integrity": "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw==",
|
"integrity": "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"type-fest": "^4.18.2"
|
"type-fest": "^4.18.2"
|
||||||
|
|
@ -5085,6 +5071,7 @@
|
||||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|
@ -5184,6 +5171,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
|
||||||
"integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
|
"integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
|
@ -5956,6 +5944,7 @@
|
||||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "~0.27.0",
|
"esbuild": "~0.27.0",
|
||||||
"get-tsconfig": "^4.7.5"
|
"get-tsconfig": "^4.7.5"
|
||||||
|
|
@ -6082,6 +6071,7 @@
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|
@ -6191,6 +6181,7 @@
|
||||||
"integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
|
"integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lightningcss": "^1.32.0",
|
"lightningcss": "^1.32.0",
|
||||||
"picomatch": "^4.0.4",
|
"picomatch": "^4.0.4",
|
||||||
|
|
@ -6599,6 +6590,7 @@
|
||||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { join } from 'node:path'
|
||||||
import { Box, Text, useApp, useInput, useStdout } from '@hermes/ink'
|
import { Box, Text, useApp, useInput, useStdout } from '@hermes/ink'
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
import { Banner, SessionPanel } from './components/branding.js'
|
import { Banner, Panel, SessionPanel } from './components/branding.js'
|
||||||
import { MaskedPrompt } from './components/maskedPrompt.js'
|
import { MaskedPrompt } from './components/maskedPrompt.js'
|
||||||
import { MessageLine } from './components/messageLine.js'
|
import { MessageLine } from './components/messageLine.js'
|
||||||
import { ApprovalPrompt, ClarifyPrompt } from './components/prompts.js'
|
import { ApprovalPrompt, ClarifyPrompt } from './components/prompts.js'
|
||||||
|
|
@ -36,6 +36,7 @@ import type {
|
||||||
ApprovalReq,
|
ApprovalReq,
|
||||||
ClarifyReq,
|
ClarifyReq,
|
||||||
Msg,
|
Msg,
|
||||||
|
PanelSection,
|
||||||
PasteMode,
|
PasteMode,
|
||||||
PendingPaste,
|
PendingPaste,
|
||||||
SecretReq,
|
SecretReq,
|
||||||
|
|
@ -343,7 +344,7 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
const [turnTrail, setTurnTrail] = useState<string[]>([])
|
const [turnTrail, setTurnTrail] = useState<string[]>([])
|
||||||
const [bgTasks, setBgTasks] = useState<Set<string>>(new Set())
|
const [bgTasks, setBgTasks] = useState<Set<string>>(new Set())
|
||||||
const [catalog, setCatalog] = useState<SlashCatalog | null>(null)
|
const [catalog, setCatalog] = useState<SlashCatalog | null>(null)
|
||||||
const [pager, setPager] = useState<{ lines: string[]; offset: number } | null>(null)
|
const [pager, setPager] = useState<{ lines: string[]; offset: number; title?: string } | null>(null)
|
||||||
const [voiceEnabled, setVoiceEnabled] = useState(false)
|
const [voiceEnabled, setVoiceEnabled] = useState(false)
|
||||||
const [voiceRecording, setVoiceRecording] = useState(false)
|
const [voiceRecording, setVoiceRecording] = useState(false)
|
||||||
const [voiceProcessing, setVoiceProcessing] = useState(false)
|
const [voiceProcessing, setVoiceProcessing] = useState(false)
|
||||||
|
|
@ -426,11 +427,18 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
|
|
||||||
const sys = useCallback((text: string) => appendMessage({ role: 'system' as const, text }), [appendMessage])
|
const sys = useCallback((text: string) => appendMessage({ role: 'system' as const, text }), [appendMessage])
|
||||||
|
|
||||||
const page = useCallback((text: string) => {
|
const page = useCallback((text: string, title?: string) => {
|
||||||
const lines = text.split('\n')
|
const lines = text.split('\n')
|
||||||
setPager({ lines, offset: 0 })
|
setPager({ lines, offset: 0, title })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const panel = useCallback(
|
||||||
|
(title: string, sections: PanelSection[]) => {
|
||||||
|
appendMessage({ role: 'system', text: '', kind: 'panel', panelData: { title, sections } })
|
||||||
|
},
|
||||||
|
[appendMessage]
|
||||||
|
)
|
||||||
|
|
||||||
const pushActivity = useCallback((text: string, tone: ActivityItem['tone'] = 'info', replaceLabel?: string) => {
|
const pushActivity = useCallback((text: string, tone: ActivityItem['tone'] = 'info', replaceLabel?: string) => {
|
||||||
setActivity(prev => {
|
setActivity(prev => {
|
||||||
const base = replaceLabel ? prev.filter(a => !sameToolTrailGroup(replaceLabel, a.text)) : prev
|
const base = replaceLabel ? prev.filter(a => !sameToolTrailGroup(replaceLabel, a.text)) : prev
|
||||||
|
|
@ -1504,37 +1512,18 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'help': {
|
case 'help': {
|
||||||
const cats = catalog?.categories ?? []
|
const sections: PanelSection[] = (catalog?.categories ?? []).map(({ name: catName, pairs }) => ({
|
||||||
const skills = catalog?.skillCount ?? 0
|
title: catName,
|
||||||
const lines: string[] = []
|
rows: pairs
|
||||||
|
}))
|
||||||
|
|
||||||
for (const { name: catName, pairs } of cats) {
|
if (catalog?.skillCount) {
|
||||||
if (lines.length) {
|
sections.push({ text: `${catalog.skillCount} skill commands available — /skills to browse` })
|
||||||
lines.push('')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push(` ${catName}:`)
|
sections.push({ title: 'Hotkeys', rows: HOTKEYS })
|
||||||
|
|
||||||
for (const [c, d] of pairs) {
|
panel('Commands', sections)
|
||||||
lines.push(` ${c.padEnd(18)} ${d}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lines.length) {
|
|
||||||
lines.push(' (no commands loaded)')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skills > 0) {
|
|
||||||
lines.push('', ` ${skills} skill commands available — /skills to browse`)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.push('', ' Hotkeys:')
|
|
||||||
|
|
||||||
for (const [k, d] of HOTKEYS) {
|
|
||||||
lines.push(` ${k.padEnd(14)} ${d}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
sys(lines.join('\n'))
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -1598,16 +1587,16 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg === 'list') {
|
if (arg === 'list') {
|
||||||
sys(
|
if (!pastes.length) {
|
||||||
pastes.length
|
sys('no text pastes')
|
||||||
? pastes
|
} else {
|
||||||
.map(
|
panel('Paste Shelf', [{
|
||||||
p =>
|
rows: pastes.map(p => [
|
||||||
`#${p.id} ${p.mode} · ${p.lineCount}L · ${p.kind} · ${compactPreview(p.text, 60) || '(empty)'}`
|
`#${p.id} ${p.mode}`,
|
||||||
)
|
`${p.lineCount}L · ${p.kind} · ${compactPreview(p.text, 60) || '(empty)'}`
|
||||||
.join('\n')
|
] as [string, string])
|
||||||
: 'no text pastes'
|
}])
|
||||||
)
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -1660,10 +1649,12 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case 'logs':
|
case 'logs': {
|
||||||
sys(gw.getLogTail(Math.min(80, Math.max(1, parseInt(arg, 10) || 20))) || 'no gateway logs')
|
const logText = gw.getLogTail(Math.min(80, Math.max(1, parseInt(arg, 10) || 20)))
|
||||||
|
logText ? page(logText, 'Logs') : sys('no gateway logs')
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
case 'statusbar':
|
case 'statusbar':
|
||||||
|
|
||||||
|
|
@ -1769,7 +1760,9 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
|
|
||||||
case 'model':
|
case 'model':
|
||||||
if (!arg) {
|
if (!arg) {
|
||||||
rpc('config.get', { key: 'provider' }).then((r: any) => sys(`${r.model} (${r.provider})`))
|
rpc('config.get', { key: 'provider' }).then((r: any) =>
|
||||||
|
panel('Model', [{ rows: [['Model', r.model], ['Provider', r.provider]] }])
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
rpc('config.set', { session_id: sid, key: 'model', value: arg.replace('--global', '').trim() }).then(
|
rpc('config.set', { session_id: sid, key: 'model', value: arg.replace('--global', '').trim() }).then(
|
||||||
(r: any) => {
|
(r: any) => {
|
||||||
|
|
@ -1798,7 +1791,7 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
|
|
||||||
case 'provider':
|
case 'provider':
|
||||||
gw.request('slash.exec', { command: 'provider', session_id: sid })
|
gw.request('slash.exec', { command: 'provider', session_id: sid })
|
||||||
.then((r: any) => page(r?.output || '(no output)'))
|
.then((r: any) => page(r?.output || '(no output)', 'Provider'))
|
||||||
.catch(() => sys('provider command failed'))
|
.catch(() => sys('provider command failed'))
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
@ -1840,7 +1833,7 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
gw.request('slash.exec', { command: 'personality', session_id: sid })
|
gw.request('slash.exec', { command: 'personality', session_id: sid })
|
||||||
.then((r: any) => sys(r?.output || '(no output)'))
|
.then((r: any) => panel('Personality', [{ text: r?.output || '(no output)' }]))
|
||||||
.catch(() => sys('personality command failed'))
|
.catch(() => sys('personality command failed'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1900,30 +1893,30 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const f = (v: number) => (v ?? 0).toLocaleString()
|
const f = (v: number) => (v ?? 0).toLocaleString()
|
||||||
const ln = (k: string, v: string) => ` ${k.padEnd(26)}${v.padStart(10)}`
|
|
||||||
const hr = ` ${'─'.repeat(36)}`
|
|
||||||
|
|
||||||
const cost =
|
const cost =
|
||||||
r.cost_usd != null ? `${r.cost_status === 'estimated' ? '~' : ''}$${r.cost_usd.toFixed(4)}` : null
|
r.cost_usd != null ? `${r.cost_status === 'estimated' ? '~' : ''}$${r.cost_usd.toFixed(4)}` : null
|
||||||
|
|
||||||
sys(
|
const rows: [string, string][] = [
|
||||||
[
|
['Model', r.model ?? ''],
|
||||||
hr,
|
['Input tokens', f(r.input)],
|
||||||
ln('Model:', r.model ?? ''),
|
['Cache read tokens', f(r.cache_read)],
|
||||||
ln('Input tokens:', f(r.input)),
|
['Cache write tokens', f(r.cache_write)],
|
||||||
ln('Cache read tokens:', f(r.cache_read)),
|
['Output tokens', f(r.output)],
|
||||||
ln('Cache write tokens:', f(r.cache_write)),
|
['Total tokens', f(r.total)],
|
||||||
ln('Output tokens:', f(r.output)),
|
['API calls', f(r.calls)]
|
||||||
ln('Total tokens:', f(r.total)),
|
|
||||||
ln('API calls:', f(r.calls)),
|
|
||||||
cost && ln('Cost:', cost),
|
|
||||||
hr,
|
|
||||||
r.context_max && ` Context: ${f(r.context_used)} / ${f(r.context_max)} (${r.context_percent}%)`,
|
|
||||||
r.compressions && ` Compressions: ${r.compressions}`
|
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
|
||||||
.join('\n')
|
if (cost) rows.push(['Cost', cost])
|
||||||
)
|
|
||||||
|
const sections: PanelSection[] = [{ rows }]
|
||||||
|
|
||||||
|
if (r.context_max) {
|
||||||
|
sections.push({ text: `Context: ${f(r.context_used)} / ${f(r.context_max)} (${r.context_percent}%)` })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.compressions) sections.push({ text: `Compressions: ${r.compressions}` })
|
||||||
|
|
||||||
|
panel('Usage', sections)
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
@ -1939,7 +1932,16 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case 'profile':
|
case 'profile':
|
||||||
rpc('config.get', { key: 'profile' }).then((r: any) => sys(r.display || r.home))
|
rpc('config.get', { key: 'profile' }).then((r: any) => {
|
||||||
|
const text = r.display || r.home
|
||||||
|
const lines = text.split('\n').filter(Boolean)
|
||||||
|
|
||||||
|
if (lines.length <= 2) {
|
||||||
|
panel('Profile', [{ text }])
|
||||||
|
} else {
|
||||||
|
page(text, 'Profile')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
@ -1957,7 +1959,13 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
|
|
||||||
case 'insights':
|
case 'insights':
|
||||||
rpc('insights.get', { days: parseInt(arg) || 30 }).then((r: any) =>
|
rpc('insights.get', { days: parseInt(arg) || 30 }).then((r: any) =>
|
||||||
sys(`${r.days}d: ${r.sessions} sessions, ${r.messages} messages`)
|
panel('Insights', [{
|
||||||
|
rows: [
|
||||||
|
['Period', `${r.days} days`],
|
||||||
|
['Sessions', `${r.sessions}`],
|
||||||
|
['Messages', `${r.messages}`]
|
||||||
|
]
|
||||||
|
}])
|
||||||
)
|
)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
@ -1970,7 +1978,12 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
return sys('no checkpoints')
|
return sys('no checkpoints')
|
||||||
}
|
}
|
||||||
|
|
||||||
sys(r.checkpoints.map((c: any, i: number) => ` ${i + 1} ${c.hash?.slice(0, 8)} ${c.message}`).join('\n'))
|
panel('Checkpoints', [{
|
||||||
|
rows: r.checkpoints.map((c: any, i: number) => [
|
||||||
|
`${i + 1} ${c.hash?.slice(0, 8)}`,
|
||||||
|
c.message
|
||||||
|
] as [string, string])
|
||||||
|
}])
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const hash = sub === 'restore' || sub === 'diff' ? rArgs[0] : sub
|
const hash = sub === 'restore' || sub === 'diff' ? rArgs[0] : sub
|
||||||
|
|
@ -2003,7 +2016,9 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
return sys('no plugins')
|
return sys('no plugins')
|
||||||
}
|
}
|
||||||
|
|
||||||
sys(r.plugins.map((p: any) => ` ${p.name} v${p.version}${p.enabled ? '' : ' (disabled)'}`).join('\n'))
|
panel('Plugins', [{
|
||||||
|
items: r.plugins.map((p: any) => `${p.name} v${p.version}${p.enabled ? '' : ' (disabled)'}`)
|
||||||
|
}])
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
@ -2018,43 +2033,31 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
return sys('no skills installed')
|
return sys('no skills installed')
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines: string[] = []
|
panel('Installed Skills', Object.entries(sk).map(([cat, names]) => ({
|
||||||
|
title: cat,
|
||||||
for (const [cat, names] of Object.entries(sk)) {
|
items: names as string[]
|
||||||
lines.push(` ${cat}: ${(names as string[]).join(', ')}`)
|
})))
|
||||||
}
|
|
||||||
|
|
||||||
sys(lines.join('\n'))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sub === 'browse') {
|
if (sub === 'browse') {
|
||||||
const page = parseInt(sArgs[0] ?? '1', 10) || 1
|
const pg = parseInt(sArgs[0] ?? '1', 10) || 1
|
||||||
rpc('skills.manage', { action: 'browse', page }).then((r: any) => {
|
rpc('skills.manage', { action: 'browse', page: pg }).then((r: any) => {
|
||||||
if (!r.items?.length) {
|
if (!r.items?.length) return sys('no skills found in the hub')
|
||||||
return sys('no skills found in the hub')
|
|
||||||
}
|
|
||||||
|
|
||||||
const lines = [
|
const sections: PanelSection[] = [{
|
||||||
` Skills Hub (page ${r.page}/${r.total_pages}, ${r.total} total)`,
|
rows: r.items.map((s: any) => [
|
||||||
'',
|
s.name ?? '',
|
||||||
...r.items.map(
|
(s.description ?? '').slice(0, 60) + (s.description?.length > 60 ? '…' : '')
|
||||||
(s: any) =>
|
] as [string, string])
|
||||||
` ${(s.name ?? '').padEnd(28)} ${(s.description ?? '').slice(0, 60)}${s.description?.length > 60 ? '…' : ''}`
|
}]
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
if (r.page < r.total_pages) {
|
if (r.page < r.total_pages) sections.push({ text: `/skills browse ${r.page + 1} → next page` })
|
||||||
lines.push('', ` /skills browse ${r.page + 1} → next page`)
|
if (r.page > 1) sections.push({ text: `/skills browse ${r.page - 1} → prev page` })
|
||||||
}
|
|
||||||
|
|
||||||
if (r.page > 1) {
|
panel(`Skills Hub (page ${r.page}/${r.total_pages}, ${r.total} total)`, sections)
|
||||||
lines.push(` /skills browse ${r.page - 1} → prev page`)
|
|
||||||
}
|
|
||||||
|
|
||||||
sys(lines.join('\n'))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
@ -2067,6 +2070,94 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'agents':
|
||||||
|
|
||||||
|
case 'tasks':
|
||||||
|
rpc('agents.list', {}).then((r: any) => {
|
||||||
|
const procs = r.processes ?? []
|
||||||
|
const running = procs.filter((p: any) => p.status === 'running')
|
||||||
|
const finished = procs.filter((p: any) => p.status !== 'running')
|
||||||
|
const sections: PanelSection[] = []
|
||||||
|
|
||||||
|
if (running.length) {
|
||||||
|
sections.push({
|
||||||
|
title: `Running (${running.length})`,
|
||||||
|
rows: running.map((p: any) => [p.session_id.slice(0, 8), p.command])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finished.length) {
|
||||||
|
sections.push({
|
||||||
|
title: `Finished (${finished.length})`,
|
||||||
|
rows: finished.map((p: any) => [p.session_id.slice(0, 8), p.command])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sections.length) sections.push({ text: 'No active processes' })
|
||||||
|
|
||||||
|
panel('Agents', sections)
|
||||||
|
}).catch(() => sys('agents command failed'))
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
case 'cron':
|
||||||
|
if (!arg || arg === 'list') {
|
||||||
|
rpc('cron.manage', { action: 'list' }).then((r: any) => {
|
||||||
|
const jobs = r.jobs ?? []
|
||||||
|
|
||||||
|
if (!jobs.length) return sys('no scheduled jobs')
|
||||||
|
|
||||||
|
panel('Cron', [{
|
||||||
|
rows: jobs.map((j: any) => [
|
||||||
|
j.name || j.job_id?.slice(0, 12),
|
||||||
|
`${j.schedule} · ${j.state ?? 'active'}`
|
||||||
|
] as [string, string])
|
||||||
|
}])
|
||||||
|
}).catch(() => sys('cron command failed'))
|
||||||
|
} else {
|
||||||
|
gw.request('slash.exec', { command: cmd.slice(1), session_id: sid })
|
||||||
|
.then((r: any) => sys(r?.output || '(no output)'))
|
||||||
|
.catch(() => sys('cron command failed'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
case 'config':
|
||||||
|
rpc('config.show', {}).then((r: any) => {
|
||||||
|
panel('Config', (r.sections ?? []).map((s: any) => ({
|
||||||
|
title: s.title,
|
||||||
|
rows: s.rows
|
||||||
|
})))
|
||||||
|
}).catch(() => sys('config command failed'))
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
case 'tools':
|
||||||
|
rpc('tools.list', { session_id: sid }).then((r: any) => {
|
||||||
|
if (!r.toolsets?.length) return sys('no tools')
|
||||||
|
|
||||||
|
panel('Tools', r.toolsets.map((ts: any) => ({
|
||||||
|
title: `${ts.enabled ? '*' : ' '} ${ts.name} [${ts.tool_count} tools]`,
|
||||||
|
items: ts.tools
|
||||||
|
})))
|
||||||
|
}).catch(() => sys('tools command failed'))
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
case 'toolsets':
|
||||||
|
rpc('toolsets.list', { session_id: sid }).then((r: any) => {
|
||||||
|
if (!r.toolsets?.length) return sys('no toolsets')
|
||||||
|
|
||||||
|
panel('Toolsets', [{
|
||||||
|
rows: r.toolsets.map((ts: any) => [
|
||||||
|
`${ts.enabled ? '(*)' : ' '} ${ts.name}`,
|
||||||
|
`[${ts.tool_count}] ${ts.description}`
|
||||||
|
] as [string, string])
|
||||||
|
}])
|
||||||
|
}).catch(() => sys('toolsets command failed'))
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
default:
|
default:
|
||||||
gw.request('slash.exec', { command: cmd.slice(1), session_id: sid })
|
gw.request('slash.exec', { command: cmd.slice(1), session_id: sid })
|
||||||
.then((r: any) => sys(r?.output || `/${name}: no output`))
|
.then((r: any) => sys(r?.output || `/${name}: no output`))
|
||||||
|
|
@ -2090,22 +2181,7 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[catalog, compact, gw, lastUserMsg, messages, newSession, page, panel, pastes, pushActivity, rpc, send, sid, statusBar, sys]
|
||||||
catalog,
|
|
||||||
compact,
|
|
||||||
gw,
|
|
||||||
lastUserMsg,
|
|
||||||
messages,
|
|
||||||
newSession,
|
|
||||||
page,
|
|
||||||
pastes,
|
|
||||||
pushActivity,
|
|
||||||
rpc,
|
|
||||||
send,
|
|
||||||
sid,
|
|
||||||
statusBar,
|
|
||||||
sys
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
slashRef.current = slash
|
slashRef.current = slash
|
||||||
|
|
@ -2199,6 +2275,8 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
<Banner t={theme} />
|
<Banner t={theme} />
|
||||||
<SessionPanel info={m.info} sid={sid} t={theme} />
|
<SessionPanel info={m.info} sid={sid} t={theme} />
|
||||||
</Box>
|
</Box>
|
||||||
|
) : m.kind === 'panel' && m.panelData ? (
|
||||||
|
<Panel sections={m.panelData.sections} t={theme} title={m.panelData.title} />
|
||||||
) : (
|
) : (
|
||||||
<MessageLine cols={cols} compact={compact} msg={m} t={theme} />
|
<MessageLine cols={cols} compact={compact} msg={m} t={theme} />
|
||||||
)}
|
)}
|
||||||
|
|
@ -2321,17 +2399,27 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{pager && (
|
{pager && (
|
||||||
<Box flexDirection="column">
|
<Box borderColor={theme.color.bronze} borderStyle="round" flexDirection="column" paddingX={2} paddingY={1}>
|
||||||
|
{pager.title && (
|
||||||
|
<Box justifyContent="center" marginBottom={1}>
|
||||||
|
<Text bold color={theme.color.gold}>
|
||||||
|
{pager.title}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
{pager.lines.slice(pager.offset, pager.offset + pagerPageSize).map((line, i) => (
|
{pager.lines.slice(pager.offset, pager.offset + pagerPageSize).map((line, i) => (
|
||||||
<Text key={i}>{line}</Text>
|
<Text key={i}>{line}</Text>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
<Box marginTop={1}>
|
||||||
<Text color={theme.color.dim}>
|
<Text color={theme.color.dim}>
|
||||||
{pager.offset + pagerPageSize < pager.lines.length
|
{pager.offset + pagerPageSize < pager.lines.length
|
||||||
? `── Enter/Space for more · q to close (${Math.min(pager.offset + pagerPageSize, pager.lines.length)}/${pager.lines.length}) ──`
|
? `Enter/Space for more · q to close (${Math.min(pager.offset + pagerPageSize, pager.lines.length)}/${pager.lines.length})`
|
||||||
: `── end · q to close (${pager.lines.length} lines) ──`}
|
: `end · q to close (${pager.lines.length} lines)`}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isBlocked && (
|
{!isBlocked && (
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Box, Text, useStdout } from '@hermes/ink'
|
||||||
import { artWidth, caduceus, CADUCEUS_WIDTH, logo, LOGO_WIDTH } from '../banner.js'
|
import { artWidth, caduceus, CADUCEUS_WIDTH, logo, LOGO_WIDTH } from '../banner.js'
|
||||||
import { flat } from '../lib/text.js'
|
import { flat } from '../lib/text.js'
|
||||||
import type { Theme } from '../theme.js'
|
import type { Theme } from '../theme.js'
|
||||||
import type { SessionInfo } from '../types.js'
|
import type { PanelSection, SessionInfo } from '../types.js'
|
||||||
|
|
||||||
export function ArtLines({ lines }: { lines: [string, string][] }) {
|
export function ArtLines({ lines }: { lines: [string, string][] }) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -142,3 +142,41 @@ export function SessionPanel({ info, sid, t }: { info: SessionInfo; sid?: string
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Panel({ sections, t, title }: { sections: PanelSection[]; t: Theme; title: string }) {
|
||||||
|
return (
|
||||||
|
<Box borderColor={t.color.bronze} borderStyle="round" flexDirection="column" paddingX={2} paddingY={1}>
|
||||||
|
<Box justifyContent="center" marginBottom={1}>
|
||||||
|
<Text bold color={t.color.gold}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{sections.map((sec, si) => (
|
||||||
|
<Box flexDirection="column" key={si} marginTop={si > 0 ? 1 : 0}>
|
||||||
|
{sec.title && (
|
||||||
|
<Text bold color={t.color.amber}>
|
||||||
|
{sec.title}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{sec.rows?.map(([k, v], ri) => (
|
||||||
|
<Text key={ri} wrap="truncate">
|
||||||
|
<Text color={t.color.dim}>{k.padEnd(20)}</Text>
|
||||||
|
<Text color={t.color.cornsilk}>{v}</Text>
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{sec.items?.map((item, ii) => (
|
||||||
|
<Text color={t.color.cornsilk} key={ii} wrap="truncate">
|
||||||
|
{item}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{sec.text && <Text color={t.color.dim}>{sec.text}</Text>}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,9 @@ export interface ClarifyReq {
|
||||||
export interface Msg {
|
export interface Msg {
|
||||||
role: Role
|
role: Role
|
||||||
text: string
|
text: string
|
||||||
kind?: 'intro' | 'slash'
|
kind?: 'intro' | 'panel' | 'slash'
|
||||||
info?: SessionInfo
|
info?: SessionInfo
|
||||||
|
panelData?: PanelData
|
||||||
thinking?: string
|
thinking?: string
|
||||||
tools?: string[]
|
tools?: string[]
|
||||||
}
|
}
|
||||||
|
|
@ -63,6 +64,18 @@ export interface SecretReq {
|
||||||
requestId: string
|
requestId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PanelData {
|
||||||
|
sections: PanelSection[]
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PanelSection {
|
||||||
|
items?: string[]
|
||||||
|
rows?: [string, string][]
|
||||||
|
text?: string
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
|
||||||
export type PasteKind = 'code' | 'log' | 'text'
|
export type PasteKind = 'code' | 'log' | 'text'
|
||||||
export type PasteMode = 'attach' | 'excerpt' | 'inline'
|
export type PasteMode = 'attach' | 'excerpt' | 'inline'
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue