@yeshwanthyk/coding-agent 0.2.2
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/CHANGELOG.md +40 -0
- package/README.md +372 -0
- package/package.json +42 -0
- package/src/adapters/acp/index.ts +305 -0
- package/src/adapters/acp/protocol.ts +191 -0
- package/src/adapters/acp/session.ts +289 -0
- package/src/adapters/acp/updates.ts +96 -0
- package/src/adapters/cli/headless.ts +112 -0
- package/src/adapters/cli/validate.ts +50 -0
- package/src/adapters/tui/app.tsx +39 -0
- package/src/agent-events.ts +671 -0
- package/src/args.ts +102 -0
- package/src/autocomplete-commands.ts +102 -0
- package/src/commands.ts +23 -0
- package/src/compact-handler.ts +272 -0
- package/src/components/Footer.tsx +49 -0
- package/src/components/Header.tsx +218 -0
- package/src/components/MessageList.tsx +380 -0
- package/src/config.ts +1 -0
- package/src/domain/commands/builtin/clear.ts +14 -0
- package/src/domain/commands/builtin/compact.ts +96 -0
- package/src/domain/commands/builtin/conceal.ts +9 -0
- package/src/domain/commands/builtin/diffwrap.ts +9 -0
- package/src/domain/commands/builtin/editor.ts +24 -0
- package/src/domain/commands/builtin/exit.ts +14 -0
- package/src/domain/commands/builtin/followup.ts +24 -0
- package/src/domain/commands/builtin/index.ts +29 -0
- package/src/domain/commands/builtin/login.ts +118 -0
- package/src/domain/commands/builtin/model.ts +66 -0
- package/src/domain/commands/builtin/status.ts +32 -0
- package/src/domain/commands/builtin/steer.ts +24 -0
- package/src/domain/commands/builtin/theme.ts +23 -0
- package/src/domain/commands/builtin/thinking.ts +16 -0
- package/src/domain/commands/helpers.ts +41 -0
- package/src/domain/commands/registry.ts +42 -0
- package/src/domain/commands/types.ts +69 -0
- package/src/domain/messaging/content.ts +117 -0
- package/src/editor.ts +103 -0
- package/src/extensibility/schema.ts +1 -0
- package/src/extensibility/validation.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAgentEvents.ts +28 -0
- package/src/hooks/useEditorBridge.ts +101 -0
- package/src/hooks/useGitStatus.ts +28 -0
- package/src/hooks/usePromptQueue.ts +7 -0
- package/src/hooks/useSessionController.ts +5 -0
- package/src/hooks/useSpinner.ts +28 -0
- package/src/hooks/useToastManager.ts +26 -0
- package/src/index.ts +188 -0
- package/src/keyboard-handler.ts +134 -0
- package/src/profiler.ts +40 -0
- package/src/runtime/context.tsx +16 -0
- package/src/runtime/factory.ts +63 -0
- package/src/runtime/git/git-info.ts +25 -0
- package/src/runtime/session/session-controller.ts +208 -0
- package/src/session-manager.ts +1 -0
- package/src/session-picker.tsx +134 -0
- package/src/shell-runner.ts +134 -0
- package/src/syntax-highlighting.ts +114 -0
- package/src/theme-names.ts +37 -0
- package/src/tool-ui-contracts.ts +77 -0
- package/src/tui-open-rendering.tsx +565 -0
- package/src/types.ts +89 -0
- package/src/ui/app-shell/TuiApp.tsx +586 -0
- package/src/ui/clipboard/osc52.ts +18 -0
- package/src/ui/components/modals/ConfirmModal.tsx +52 -0
- package/src/ui/components/modals/EditorModal.tsx +39 -0
- package/src/ui/components/modals/InputModal.tsx +30 -0
- package/src/ui/components/modals/ModalContainer.tsx +67 -0
- package/src/ui/components/modals/SelectModal.tsx +48 -0
- package/src/ui/components/modals/index.ts +4 -0
- package/src/ui/features/composer/Composer.tsx +73 -0
- package/src/ui/features/composer/SlashCommandHandler.ts +58 -0
- package/src/ui/features/composer/keyboard.ts +3 -0
- package/src/ui/features/main-view/MainView.tsx +367 -0
- package/src/ui/features/message-pane/MessagePane.tsx +34 -0
- package/src/ui/hooks/useModals.ts +74 -0
- package/src/ui/state/app-store.ts +67 -0
- package/src/utils.ts +14 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell command runner for TUI ! prefix commands.
|
|
3
|
+
* Executes commands and returns truncated output.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn } from "node:child_process"
|
|
7
|
+
import { createWriteStream, mkdirSync } from "node:fs"
|
|
8
|
+
import { tmpdir } from "node:os"
|
|
9
|
+
import { join } from "node:path"
|
|
10
|
+
import {
|
|
11
|
+
getShellConfig,
|
|
12
|
+
killProcessTree,
|
|
13
|
+
truncateTail,
|
|
14
|
+
DEFAULT_MAX_LINES,
|
|
15
|
+
DEFAULT_MAX_BYTES,
|
|
16
|
+
type TruncationResult,
|
|
17
|
+
} from "@yeshwanthyk/base-tools"
|
|
18
|
+
|
|
19
|
+
export interface ShellResult {
|
|
20
|
+
output: string
|
|
21
|
+
exitCode: number | null
|
|
22
|
+
truncated: boolean
|
|
23
|
+
truncation?: TruncationResult
|
|
24
|
+
tempFilePath?: string
|
|
25
|
+
cancelled: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getTempFilePath(): string {
|
|
29
|
+
const dir = join(tmpdir(), "marvin-shell")
|
|
30
|
+
mkdirSync(dir, { recursive: true })
|
|
31
|
+
return join(dir, `output-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.txt`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Execute a shell command and return the result with truncation applied.
|
|
36
|
+
*/
|
|
37
|
+
export function runShellCommand(
|
|
38
|
+
command: string,
|
|
39
|
+
options?: { signal?: AbortSignal; timeout?: number }
|
|
40
|
+
): Promise<ShellResult> {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
const { shell, args } = getShellConfig()
|
|
43
|
+
const child = spawn(shell, [...args, command], {
|
|
44
|
+
detached: true,
|
|
45
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const chunks: Buffer[] = []
|
|
49
|
+
let totalBytes = 0
|
|
50
|
+
let tempFilePath: string | undefined
|
|
51
|
+
let tempFileStream: ReturnType<typeof createWriteStream> | undefined
|
|
52
|
+
let cancelled = false
|
|
53
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
54
|
+
|
|
55
|
+
// Timeout handling
|
|
56
|
+
if (options?.timeout) {
|
|
57
|
+
timeoutId = setTimeout(() => {
|
|
58
|
+
cancelled = true
|
|
59
|
+
if (child.pid) killProcessTree(child.pid)
|
|
60
|
+
}, options.timeout)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Abort signal handling
|
|
64
|
+
if (options?.signal) {
|
|
65
|
+
options.signal.addEventListener("abort", () => {
|
|
66
|
+
cancelled = true
|
|
67
|
+
if (child.pid) killProcessTree(child.pid)
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const handleData = (data: Buffer) => {
|
|
72
|
+
totalBytes += data.length
|
|
73
|
+
|
|
74
|
+
// Start temp file if exceeding threshold
|
|
75
|
+
if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
|
|
76
|
+
tempFilePath = getTempFilePath()
|
|
77
|
+
tempFileStream = createWriteStream(tempFilePath)
|
|
78
|
+
// Write existing chunks to temp file
|
|
79
|
+
for (const chunk of chunks) {
|
|
80
|
+
tempFileStream.write(chunk)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Write to temp file if active
|
|
85
|
+
if (tempFileStream) {
|
|
86
|
+
tempFileStream.write(data)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Keep rolling buffer (2x max for truncation headroom)
|
|
90
|
+
chunks.push(data)
|
|
91
|
+
let chunksBytes = chunks.reduce((sum, c) => sum + c.length, 0)
|
|
92
|
+
const maxChunksBytes = DEFAULT_MAX_BYTES * 2
|
|
93
|
+
while (chunksBytes > maxChunksBytes && chunks.length > 1) {
|
|
94
|
+
const removed = chunks.shift()!
|
|
95
|
+
chunksBytes -= removed.length
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
child.stdout?.on("data", handleData)
|
|
100
|
+
child.stderr?.on("data", handleData)
|
|
101
|
+
|
|
102
|
+
child.on("close", (code) => {
|
|
103
|
+
if (timeoutId) clearTimeout(timeoutId)
|
|
104
|
+
if (tempFileStream) tempFileStream.end()
|
|
105
|
+
|
|
106
|
+
const fullOutput = Buffer.concat(chunks).toString("utf-8")
|
|
107
|
+
const truncation = truncateTail(fullOutput, {
|
|
108
|
+
maxLines: DEFAULT_MAX_LINES,
|
|
109
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
resolve({
|
|
113
|
+
output: truncation.content,
|
|
114
|
+
exitCode: code,
|
|
115
|
+
truncated: truncation.truncated,
|
|
116
|
+
truncation: truncation.truncated ? truncation : undefined,
|
|
117
|
+
tempFilePath: truncation.truncated ? tempFilePath : undefined,
|
|
118
|
+
cancelled,
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
child.on("error", (err) => {
|
|
123
|
+
if (timeoutId) clearTimeout(timeoutId)
|
|
124
|
+
if (tempFileStream) tempFileStream.end()
|
|
125
|
+
|
|
126
|
+
resolve({
|
|
127
|
+
output: `Error: ${err.message}`,
|
|
128
|
+
exitCode: null,
|
|
129
|
+
truncated: false,
|
|
130
|
+
cancelled,
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { highlight } from "cli-highlight";
|
|
3
|
+
|
|
4
|
+
type CliHighlightTheme = Record<string, (s: string) => string>;
|
|
5
|
+
|
|
6
|
+
const cliHighlightTheme: CliHighlightTheme = {
|
|
7
|
+
keyword: (s: string) => chalk.hex("#569CD6")(s),
|
|
8
|
+
built_in: (s: string) => chalk.hex("#4EC9B0")(s),
|
|
9
|
+
literal: (s: string) => chalk.hex("#B5CEA8")(s),
|
|
10
|
+
number: (s: string) => chalk.hex("#B5CEA8")(s),
|
|
11
|
+
string: (s: string) => chalk.hex("#CE9178")(s),
|
|
12
|
+
comment: (s: string) => chalk.hex("#6A9955")(s),
|
|
13
|
+
function: (s: string) => chalk.hex("#DCDCAA")(s),
|
|
14
|
+
title: (s: string) => chalk.hex("#DCDCAA")(s),
|
|
15
|
+
class: (s: string) => chalk.hex("#4EC9B0")(s),
|
|
16
|
+
type: (s: string) => chalk.hex("#4EC9B0")(s),
|
|
17
|
+
attr: (s: string) => chalk.hex("#9CDCFE")(s),
|
|
18
|
+
variable: (s: string) => chalk.hex("#9CDCFE")(s),
|
|
19
|
+
params: (s: string) => chalk.hex("#9CDCFE")(s),
|
|
20
|
+
operator: (s: string) => chalk.hex("#D4D4D4")(s),
|
|
21
|
+
punctuation: (s: string) => chalk.hex("#D4D4D4")(s),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function replaceTabs(text: string): string {
|
|
25
|
+
return text.replace(/\t/g, " ");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function highlightCode(code: string, lang?: string): string[] {
|
|
29
|
+
const opts = {
|
|
30
|
+
language: lang,
|
|
31
|
+
ignoreIllegals: true,
|
|
32
|
+
theme: cliHighlightTheme,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return highlight(code, opts).split("\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getLanguageFromPath(filePath: string): string | undefined {
|
|
39
|
+
const trimmed = (filePath || "").trim();
|
|
40
|
+
if (!trimmed) return undefined;
|
|
41
|
+
|
|
42
|
+
const base = trimmed.split(/[\\/]/).pop()?.toLowerCase();
|
|
43
|
+
if (!base) return undefined;
|
|
44
|
+
|
|
45
|
+
if (base === "dockerfile") return "dockerfile";
|
|
46
|
+
if (base === "makefile") return "makefile";
|
|
47
|
+
|
|
48
|
+
const ext = base.includes(".") ? base.split(".").pop()?.toLowerCase() : undefined;
|
|
49
|
+
if (!ext) return undefined;
|
|
50
|
+
|
|
51
|
+
const extToLang: Record<string, string> = {
|
|
52
|
+
ts: "typescript",
|
|
53
|
+
tsx: "typescript",
|
|
54
|
+
js: "javascript",
|
|
55
|
+
jsx: "javascript",
|
|
56
|
+
mjs: "javascript",
|
|
57
|
+
cjs: "javascript",
|
|
58
|
+
py: "python",
|
|
59
|
+
rb: "ruby",
|
|
60
|
+
rs: "rust",
|
|
61
|
+
go: "go",
|
|
62
|
+
java: "java",
|
|
63
|
+
kt: "kotlin",
|
|
64
|
+
swift: "swift",
|
|
65
|
+
c: "c",
|
|
66
|
+
h: "c",
|
|
67
|
+
cpp: "cpp",
|
|
68
|
+
cc: "cpp",
|
|
69
|
+
cxx: "cpp",
|
|
70
|
+
hpp: "cpp",
|
|
71
|
+
cs: "csharp",
|
|
72
|
+
php: "php",
|
|
73
|
+
sh: "bash",
|
|
74
|
+
bash: "bash",
|
|
75
|
+
zsh: "bash",
|
|
76
|
+
fish: "fish",
|
|
77
|
+
ps1: "powershell",
|
|
78
|
+
sql: "sql",
|
|
79
|
+
html: "html",
|
|
80
|
+
htm: "html",
|
|
81
|
+
css: "css",
|
|
82
|
+
scss: "scss",
|
|
83
|
+
sass: "sass",
|
|
84
|
+
less: "less",
|
|
85
|
+
json: "json",
|
|
86
|
+
yaml: "yaml",
|
|
87
|
+
yml: "yaml",
|
|
88
|
+
toml: "toml",
|
|
89
|
+
xml: "xml",
|
|
90
|
+
md: "markdown",
|
|
91
|
+
markdown: "markdown",
|
|
92
|
+
dockerfile: "dockerfile",
|
|
93
|
+
makefile: "makefile",
|
|
94
|
+
cmake: "cmake",
|
|
95
|
+
lua: "lua",
|
|
96
|
+
pl: "perl",
|
|
97
|
+
perl: "perl",
|
|
98
|
+
r: "r",
|
|
99
|
+
scala: "scala",
|
|
100
|
+
clj: "clojure",
|
|
101
|
+
ex: "elixir",
|
|
102
|
+
exs: "elixir",
|
|
103
|
+
erl: "erlang",
|
|
104
|
+
hs: "haskell",
|
|
105
|
+
ml: "ocaml",
|
|
106
|
+
vim: "vim",
|
|
107
|
+
graphql: "graphql",
|
|
108
|
+
proto: "protobuf",
|
|
109
|
+
tf: "hcl",
|
|
110
|
+
hcl: "hcl",
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return extToLang[ext];
|
|
114
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for available theme names.
|
|
3
|
+
* "marvin" is the built-in default, other names match BUILTIN_THEMES keys in @yeshwanthyk/open-tui.
|
|
4
|
+
* Verified by test in autocomplete-commands.test.ts.
|
|
5
|
+
*/
|
|
6
|
+
export const THEME_NAMES = [
|
|
7
|
+
"marvin",
|
|
8
|
+
"aura",
|
|
9
|
+
"ayu",
|
|
10
|
+
"catppuccin",
|
|
11
|
+
"catppuccin-macchiato",
|
|
12
|
+
"cobalt2",
|
|
13
|
+
"dracula",
|
|
14
|
+
"everforest",
|
|
15
|
+
"flexoki",
|
|
16
|
+
"github",
|
|
17
|
+
"gruvbox",
|
|
18
|
+
"kanagawa",
|
|
19
|
+
"lucent-orng",
|
|
20
|
+
"material",
|
|
21
|
+
"matrix",
|
|
22
|
+
"mercury",
|
|
23
|
+
"monokai",
|
|
24
|
+
"nightowl",
|
|
25
|
+
"nord",
|
|
26
|
+
"one-dark",
|
|
27
|
+
"opencode",
|
|
28
|
+
"orng",
|
|
29
|
+
"palenight",
|
|
30
|
+
"rosepine",
|
|
31
|
+
"solarized",
|
|
32
|
+
"synthwave84",
|
|
33
|
+
"tokyonight",
|
|
34
|
+
"vercel",
|
|
35
|
+
"vesper",
|
|
36
|
+
"zenburn",
|
|
37
|
+
]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export type DelegationMode = "single" | "parallel" | "chain"
|
|
2
|
+
export type DelegationStatus = "pending" | "running" | "ok" | "error"
|
|
3
|
+
|
|
4
|
+
export interface AgentDelegationUiItem {
|
|
5
|
+
id: string
|
|
6
|
+
agent: string
|
|
7
|
+
task: string
|
|
8
|
+
status: DelegationStatus
|
|
9
|
+
preview?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface AgentDelegationUi {
|
|
13
|
+
kind: "agent_delegation"
|
|
14
|
+
mode: DelegationMode
|
|
15
|
+
items: AgentDelegationUiItem[]
|
|
16
|
+
activeId?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AgentDelegationArgs {
|
|
20
|
+
agent?: string
|
|
21
|
+
task?: string
|
|
22
|
+
tasks?: Array<{ agent: string; task: string }>
|
|
23
|
+
chain?: Array<{ agent: string; task: string }>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null
|
|
27
|
+
|
|
28
|
+
export function getAgentDelegationArgs(args: unknown): AgentDelegationArgs | null {
|
|
29
|
+
if (!isRecord(args)) return null
|
|
30
|
+
const agent = typeof args.agent === "string" ? args.agent : undefined
|
|
31
|
+
const task = typeof args.task === "string" ? args.task : undefined
|
|
32
|
+
const tasks = Array.isArray(args.tasks) ? args.tasks : undefined
|
|
33
|
+
const chain = Array.isArray(args.chain) ? args.chain : undefined
|
|
34
|
+
|
|
35
|
+
const isTaskItem = (v: unknown): v is { agent: string; task: string } =>
|
|
36
|
+
isRecord(v) && typeof v.agent === "string" && typeof v.task === "string"
|
|
37
|
+
|
|
38
|
+
const normTasks = tasks?.every(isTaskItem) ? tasks : undefined
|
|
39
|
+
const normChain = chain?.every(isTaskItem) ? chain : undefined
|
|
40
|
+
|
|
41
|
+
if (normChain || normTasks || (agent && task)) return { agent, task, tasks: normTasks, chain: normChain }
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getAgentDelegationUi(details: unknown): AgentDelegationUi | null {
|
|
46
|
+
if (!isRecord(details)) return null
|
|
47
|
+
if (!isRecord(details.ui)) return null
|
|
48
|
+
if (details.ui.kind !== "agent_delegation") return null
|
|
49
|
+
|
|
50
|
+
const ui = details.ui as Record<string, unknown>
|
|
51
|
+
const mode = ui.mode
|
|
52
|
+
if (mode !== "single" && mode !== "parallel" && mode !== "chain") return null
|
|
53
|
+
if (!Array.isArray(ui.items)) return null
|
|
54
|
+
|
|
55
|
+
const isStatus = (s: unknown): s is DelegationStatus =>
|
|
56
|
+
s === "pending" || s === "running" || s === "ok" || s === "error"
|
|
57
|
+
const items: AgentDelegationUiItem[] = []
|
|
58
|
+
for (const raw of ui.items) {
|
|
59
|
+
if (!isRecord(raw)) return null
|
|
60
|
+
if (typeof raw.id !== "string" || typeof raw.agent !== "string" || typeof raw.task !== "string") return null
|
|
61
|
+
if (!isStatus(raw.status)) return null
|
|
62
|
+
items.push({
|
|
63
|
+
id: raw.id,
|
|
64
|
+
agent: raw.agent,
|
|
65
|
+
task: raw.task,
|
|
66
|
+
status: raw.status,
|
|
67
|
+
preview: typeof raw.preview === "string" ? raw.preview : undefined,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
kind: "agent_delegation",
|
|
73
|
+
mode,
|
|
74
|
+
items,
|
|
75
|
+
activeId: typeof ui.activeId === "string" ? ui.activeId : undefined,
|
|
76
|
+
}
|
|
77
|
+
}
|