@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.
Files changed (79) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +372 -0
  3. package/package.json +42 -0
  4. package/src/adapters/acp/index.ts +305 -0
  5. package/src/adapters/acp/protocol.ts +191 -0
  6. package/src/adapters/acp/session.ts +289 -0
  7. package/src/adapters/acp/updates.ts +96 -0
  8. package/src/adapters/cli/headless.ts +112 -0
  9. package/src/adapters/cli/validate.ts +50 -0
  10. package/src/adapters/tui/app.tsx +39 -0
  11. package/src/agent-events.ts +671 -0
  12. package/src/args.ts +102 -0
  13. package/src/autocomplete-commands.ts +102 -0
  14. package/src/commands.ts +23 -0
  15. package/src/compact-handler.ts +272 -0
  16. package/src/components/Footer.tsx +49 -0
  17. package/src/components/Header.tsx +218 -0
  18. package/src/components/MessageList.tsx +380 -0
  19. package/src/config.ts +1 -0
  20. package/src/domain/commands/builtin/clear.ts +14 -0
  21. package/src/domain/commands/builtin/compact.ts +96 -0
  22. package/src/domain/commands/builtin/conceal.ts +9 -0
  23. package/src/domain/commands/builtin/diffwrap.ts +9 -0
  24. package/src/domain/commands/builtin/editor.ts +24 -0
  25. package/src/domain/commands/builtin/exit.ts +14 -0
  26. package/src/domain/commands/builtin/followup.ts +24 -0
  27. package/src/domain/commands/builtin/index.ts +29 -0
  28. package/src/domain/commands/builtin/login.ts +118 -0
  29. package/src/domain/commands/builtin/model.ts +66 -0
  30. package/src/domain/commands/builtin/status.ts +32 -0
  31. package/src/domain/commands/builtin/steer.ts +24 -0
  32. package/src/domain/commands/builtin/theme.ts +23 -0
  33. package/src/domain/commands/builtin/thinking.ts +16 -0
  34. package/src/domain/commands/helpers.ts +41 -0
  35. package/src/domain/commands/registry.ts +42 -0
  36. package/src/domain/commands/types.ts +69 -0
  37. package/src/domain/messaging/content.ts +117 -0
  38. package/src/editor.ts +103 -0
  39. package/src/extensibility/schema.ts +1 -0
  40. package/src/extensibility/validation.ts +1 -0
  41. package/src/hooks/index.ts +1 -0
  42. package/src/hooks/useAgentEvents.ts +28 -0
  43. package/src/hooks/useEditorBridge.ts +101 -0
  44. package/src/hooks/useGitStatus.ts +28 -0
  45. package/src/hooks/usePromptQueue.ts +7 -0
  46. package/src/hooks/useSessionController.ts +5 -0
  47. package/src/hooks/useSpinner.ts +28 -0
  48. package/src/hooks/useToastManager.ts +26 -0
  49. package/src/index.ts +188 -0
  50. package/src/keyboard-handler.ts +134 -0
  51. package/src/profiler.ts +40 -0
  52. package/src/runtime/context.tsx +16 -0
  53. package/src/runtime/factory.ts +63 -0
  54. package/src/runtime/git/git-info.ts +25 -0
  55. package/src/runtime/session/session-controller.ts +208 -0
  56. package/src/session-manager.ts +1 -0
  57. package/src/session-picker.tsx +134 -0
  58. package/src/shell-runner.ts +134 -0
  59. package/src/syntax-highlighting.ts +114 -0
  60. package/src/theme-names.ts +37 -0
  61. package/src/tool-ui-contracts.ts +77 -0
  62. package/src/tui-open-rendering.tsx +565 -0
  63. package/src/types.ts +89 -0
  64. package/src/ui/app-shell/TuiApp.tsx +586 -0
  65. package/src/ui/clipboard/osc52.ts +18 -0
  66. package/src/ui/components/modals/ConfirmModal.tsx +52 -0
  67. package/src/ui/components/modals/EditorModal.tsx +39 -0
  68. package/src/ui/components/modals/InputModal.tsx +30 -0
  69. package/src/ui/components/modals/ModalContainer.tsx +67 -0
  70. package/src/ui/components/modals/SelectModal.tsx +48 -0
  71. package/src/ui/components/modals/index.ts +4 -0
  72. package/src/ui/features/composer/Composer.tsx +73 -0
  73. package/src/ui/features/composer/SlashCommandHandler.ts +58 -0
  74. package/src/ui/features/composer/keyboard.ts +3 -0
  75. package/src/ui/features/main-view/MainView.tsx +367 -0
  76. package/src/ui/features/message-pane/MessagePane.tsx +34 -0
  77. package/src/ui/hooks/useModals.ts +74 -0
  78. package/src/ui/state/app-store.ts +67 -0
  79. 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
+ }