- 
                Notifications
    You must be signed in to change notification settings 
- Fork 508
more terminal integration for wave ai #2470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5dce5b0
              93a508e
              f3a3214
              89bee53
              543846a
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
|  | @@ -5,7 +5,7 @@ import { useLongClick } from "@/app/hook/useLongClick"; | |
| import { makeIconClass } from "@/util/util"; | ||
| import clsx from "clsx"; | ||
| import { atom, useAtom } from "jotai"; | ||
| import { forwardRef, memo, useMemo, useRef } from "react"; | ||
| import { CSSProperties, forwardRef, memo, useMemo, useRef } from "react"; | ||
| import "./iconbutton.scss"; | ||
|  | ||
| type IconButtonProps = { decl: IconButtonDecl; className?: string }; | ||
|  | @@ -15,6 +15,10 @@ export const IconButton = memo( | |
| const spin = decl.iconSpin ?? false; | ||
| useLongClick(ref, decl.click, decl.longClick, decl.disabled); | ||
| const disabled = decl.disabled ?? false; | ||
| const styleVal: CSSProperties = {}; | ||
| if (decl.iconColor) { | ||
| styleVal.color = decl.iconColor; | ||
| } | ||
| 
      Comment on lines
    
      +18
     to 
      +21
    
   There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major 🧩 Analysis chainInconsistency: ToggleIconButton still uses the old styling pattern. The conditional color styling here differs from  
 While functionally similar in most cases, this inconsistency could lead to subtle styling differences if CSS specificity or inheritance rules interact differently with the two patterns. Consider applying the same conditional styling pattern to  +        const styleVal: CSSProperties = {};
+        if (decl.iconColor) {
+            styleVal.color = decl.iconColor;
+        }
         return (
             <button
                 ref={ref}
                 className={clsx("wave-iconbutton", "toggle", className, decl.className, {
                     active,
                     disabled,
                     "no-action": decl.noAction,
                 })}
                 title={title}
                 aria-label={title}
-                style={{ color: decl.iconColor ?? "inherit" }}
+                style={styleVal}
                 onClick={() => setActive(!active)}
                 disabled={disabled}
             >Also verify that removing the explicit  Also applies to: 31-31 🏁 Script executed: #!/bin/bash
# Examine the IconButton and ToggleIconButton components
cat frontend/app/element/iconbutton.tsxLength of output: 2677 Verified: ToggleIconButton inconsistency is real and worth addressing. The review comment accurately identifies an inconsistency. When  
 While functionally equivalent in most cases, the inconsistent patterns within the same file warrant consistency. Applying the same conditional styleVal pattern to ToggleIconButton is a reasonable refactoring suggestion to improve maintainability. 🤖 Prompt for AI Agents | ||
| return ( | ||
| <button | ||
| ref={ref} | ||
|  | @@ -24,7 +28,7 @@ export const IconButton = memo( | |
| })} | ||
| title={decl.title} | ||
| aria-label={decl.title} | ||
| style={{ color: decl.iconColor ?? "inherit" }} | ||
| style={styleVal} | ||
| disabled={disabled} | ||
| > | ||
| {typeof decl.icon === "string" ? <i className={makeIconClass(decl.icon, true, { spin })} /> : decl.icon} | ||
|  | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| // Copyright 2025, Command Line Inc. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|  | ||
| // Always block (TUIs / pagers / multiplexers / known interactive UIs) | ||
| const ALWAYS_BLOCK = [ | ||
| // multiplexers | ||
| "tmux", "screen", "byobu", "dtach", "abduco", "tmate", | ||
| // editors/pagers | ||
| "vim", "nvim", "emacs", "nano", "less", "more", "man", "most", "view", | ||
| // TUIs / tools | ||
| "htop", "top", "btop", "fzf", "ranger", "mc", "nnn", "k9s", "nmtui", "alsamixer", | ||
| "tig", "gdb", "lldb", | ||
| // mail/irc | ||
| "mutt", "neomutt", "alpine", "weechat", "irssi", | ||
| // dialog UIs | ||
| "dialog", "whiptail", | ||
| // DB shells | ||
| "psql", "mysql", "sqlite3", "mongo", "redis-cli", | ||
| ]; | ||
|  | ||
| // Bare REPLs only block when no args | ||
| const BARE_REPLS = [ | ||
| "python", "python3", "python2", "node", "ruby", "perl", "php", "lua", "ipython", "bpython", "irb", | ||
| ]; | ||
|  | ||
| // Shells: block only if interactive/new shell | ||
| const SHELLS = [ | ||
| "bash", "sh", "zsh", "fish", "ksh", "mksh", "dash", "ash", "tcsh", "csh", | ||
| "xonsh", "elvish", "nu", "nushell", "pwsh", "powershell", "cmd", | ||
| ]; | ||
|  | ||
| // Wrappers to skip | ||
| const WRAPPERS = [ | ||
| "sudo", "doas", "pkexec", "rlwrap", "env", "time", "nice", "nohup", | ||
| "chrt", "stdbuf", "script", "scriptreplay", "sshpass", | ||
| ]; | ||
|  | ||
| function looksInteractiveShellArgs(args: string[]): boolean { | ||
| return ( | ||
| args.length === 0 || | ||
| args.includes("-i") || | ||
| args.includes("--login") || | ||
| args.includes("-l") || | ||
| args.includes("-s") | ||
| ); | ||
| } | ||
|  | ||
| function isNonInteractiveShellExec(args: string[]): boolean { | ||
| return ( | ||
| args.includes("-c") || | ||
| args.some((a) => a === "-Command" || a.startsWith("-Command")) || | ||
| args.some((a) => a.endsWith(".sh") || a.includes("/")) | ||
| ); | ||
| } | ||
|  | ||
| function isAttachLike(cmd: string, args: string[]): boolean { | ||
| if (cmd === "docker" || cmd === "podman") { | ||
| if (args[0] === "attach") return true; | ||
| if (args[0] === "exec") return args.some((a) => a === "-it" || a === "-i" || a === "-t"); | ||
| } | ||
| if (cmd === "kubectl" || cmd === "k3s" || cmd === "oc") { | ||
| if (args[0] === "attach") return true; | ||
| if (args[0] === "exec") return args.some((a) => a === "-it" || a === "-i" || a === "-t"); | ||
| } | ||
| if (cmd === "lxc" && args[0] === "exec") return args.some((a) => a === "-t" || a === "-T"); | ||
| return false; | ||
| } | ||
|  | ||
| function isSshInteractive(args: string[]): boolean { | ||
| const hasForcedTty = args.includes("-t") || args.includes("-tt"); | ||
| const hasRemoteCmd = args.some((a) => !a.startsWith("-") && a.includes(" ")); | ||
| return hasForcedTty || !hasRemoteCmd; | ||
| } | ||
| 
      Comment on lines
    
      +69
     to 
      +73
    
   There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SSH remote-command detection misclassifies non-interactive invocations. 
 -function isSshInteractive(args: string[]): boolean {
-    const hasForcedTty = args.includes("-t") || args.includes("-tt");
-    const hasRemoteCmd = args.some((a) => !a.startsWith("-") && a.includes(" "));
-    return hasForcedTty || !hasRemoteCmd;
-}
+function isSshInteractive(args: string[]): boolean {
+    const hasForcedTty = args.includes("-t") || args.includes("-tt");
+    // find first non-option token (host)
+    const hostIdx = args.findIndex((a) => !a.startsWith("-"));
+    if (hostIdx === -1) {
+        // e.g., `ssh -V` → non-interactive unless TTY forced
+        return hasForcedTty;
+    }
+    // any tokens after host imply a remote command
+    const hasRemoteCmd = args.length > hostIdx + 1;
+    return hasForcedTty || !hasRemoteCmd;
+}🤖 Prompt for AI Agents | ||
|  | ||
| export function getBlockingCommand(lastCommand: string | null, inAltBuffer: boolean): string | null { | ||
| if (!lastCommand) return null; | ||
|  | ||
| let words = lastCommand.trim().split(/\s+/); | ||
| if (words.length === 0) return null; | ||
|  | ||
| while (words.length && WRAPPERS.includes(words[0])) { | ||
| words.shift(); | ||
| } | ||
| if (!words.length) return null; | ||
|  | ||
| const first = words[0].split("/").pop()!; | ||
| const args = words.slice(1); | ||
|  | ||
| if (inAltBuffer) return first; | ||
|  | ||
| if (ALWAYS_BLOCK.includes(first)) return first; | ||
|  | ||
| if (isAttachLike(first, args)) return first; | ||
|  | ||
| if (first === "ssh" || first === "mosh" || first === "telnet" || first === "rlogin") { | ||
| if (isSshInteractive(args)) return first; | ||
| return null; | ||
| } | ||
|  | ||
| if (first === "su" || first === "machinectl" || first === "chroot" || first === "nsenter" || first === "lxc") { | ||
| if (!args.length || SHELLS.includes(args[args.length - 1]?.split("/").pop() || "")) return first; | ||
| return null; | ||
| } | ||
|  | ||
| if (SHELLS.includes(first)) { | ||
| if (looksInteractiveShellArgs(args)) return first; | ||
| if (isNonInteractiveShellExec(args)) return null; | ||
| return null; | ||
| } | ||
|  | ||
| if (BARE_REPLS.includes(first)) { | ||
| if (args.length === 0) return first; | ||
| return null; | ||
| } | ||
|  | ||
| return null; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Fix TermViewModel import path (build blocker)
Other modules import from "@/app/view/term/term-model"; this file uses "@/view/term/term-model". Likely breaks module resolution.
To verify consistency:
🏁 Script executed:
Length of output: 87
🏁 Script executed:
Length of output: 36
🏁 Script executed:
Length of output: 387
🏁 Script executed:
Length of output: 164
Fix TermViewModel import path (build blocker)
The import uses an incorrect path that doesn't match the file location or other modules in the codebase. The actual file is at
frontend/app/view/term/term-model.ts, which corresponds to the alias@/app/view/term/term-model. Three other files (term-wsh.tsx, termtheme.ts, and term.tsx) correctly import from this path.📝 Committable suggestion
🤖 Prompt for AI Agents