-
-
Notifications
You must be signed in to change notification settings - Fork 920
Show Claude icon in terminal header while Claude Code is active #3046
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 8 commits
bef3b3c
b24ac2e
a07d08d
b92080c
d15dad6
00c361f
3f30447
7608afc
e2b5ec1
a909188
0a88565
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 |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
|
|
||
| import { isClaudeCodeCommand } from "./osc-handlers"; | ||
|
|
||
| describe("isClaudeCodeCommand", () => { | ||
| it("matches direct Claude Code invocations", () => { | ||
| expect(isClaudeCodeCommand("claude")).toBe(true); | ||
| expect(isClaudeCodeCommand("claude --dangerously-skip-permissions")).toBe(true); | ||
| expect(isClaudeCodeCommand("/usr/local/bin/claude chat")).toBe(true); | ||
| }); | ||
|
|
||
| it("matches Claude Code invocations wrapped with env assignments", () => { | ||
| expect(isClaudeCodeCommand('ANTHROPIC_API_KEY="test" claude')).toBe(true); | ||
| expect(isClaudeCodeCommand("FOO=bar env claude --print")).toBe(true); | ||
| }); | ||
|
|
||
| it("ignores other commands", () => { | ||
| expect(isClaudeCodeCommand("claudes")).toBe(false); | ||
| expect(isClaudeCodeCommand("echo claude")).toBe(false); | ||
| expect(isClaudeCodeCommand("")).toBe(false); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -25,6 +25,8 @@ const Osc52MaxRawLength = 128 * 1024; // includes selector + base64 + whitespace | |||||||||||||||||||||||||||||||||||||
| // See aiprompts/wave-osc-16162.md for full documentation | ||||||||||||||||||||||||||||||||||||||
| export type ShellIntegrationStatus = "ready" | "running-command"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const ClaudeCodeRegex = /(?:^|\/)claude\b/; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| type Osc16162Command = | ||||||||||||||||||||||||||||||||||||||
| | { command: "A"; data: Record<string, never> } | ||||||||||||||||||||||||||||||||||||||
| | { command: "C"; data: { cmd64?: string } } | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -43,41 +45,56 @@ type Osc16162Command = | |||||||||||||||||||||||||||||||||||||
| | { command: "I"; data: { inputempty?: boolean } } | ||||||||||||||||||||||||||||||||||||||
| | { command: "R"; data: Record<string, never> }; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| function normalizeCmd(decodedCmd: string): string { | ||||||||||||||||||||||||||||||||||||||
| let normalizedCmd = decodedCmd.trim(); | ||||||||||||||||||||||||||||||||||||||
| normalizedCmd = normalizedCmd.replace(/^(?:\w+=(?:"[^"]*"|'[^']*'|\S+)\s+)*/, ""); | ||||||||||||||||||||||||||||||||||||||
| normalizedCmd = normalizedCmd.replace(/^env\s+/, ""); | ||||||||||||||||||||||||||||||||||||||
| return normalizedCmd; | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+48
to
+52
Contributor
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.
Line 51 strips 🔧 Suggested fix function normalizeCmd(decodedCmd: string): string {
- let normalizedCmd = decodedCmd.trim();
- normalizedCmd = normalizedCmd.replace(/^(?:\w+=(?:"[^"]*"|'[^']*'|\S+)\s+)*/, "");
- normalizedCmd = normalizedCmd.replace(/^env\s+/, "");
- return normalizedCmd;
+ let normalizedCmd = decodedCmd.trim();
+ while (true) {
+ const prev = normalizedCmd;
+ normalizedCmd = normalizedCmd.replace(/^env\s+/, "");
+ normalizedCmd = normalizedCmd.replace(/^(?:\w+=(?:"[^"]*"|'[^']*'|\S+)\s+)*/, "");
+ normalizedCmd = normalizedCmd.trimStart();
+ if (normalizedCmd === prev) {
+ break;
+ }
+ }
+ return normalizedCmd;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| function checkCommandForTelemetry(decodedCmd: string) { | ||||||||||||||||||||||||||||||||||||||
| if (!decodedCmd) { | ||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (decodedCmd.startsWith("ssh ")) { | ||||||||||||||||||||||||||||||||||||||
| const normalizedCmd = normalizeCmd(decodedCmd); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (normalizedCmd.startsWith("ssh ")) { | ||||||||||||||||||||||||||||||||||||||
| recordTEvent("conn:connect", { "conn:conntype": "ssh-manual" }); | ||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const editorsRegex = /^(vim|vi|nano|nvim)\b/; | ||||||||||||||||||||||||||||||||||||||
| if (editorsRegex.test(decodedCmd)) { | ||||||||||||||||||||||||||||||||||||||
| if (editorsRegex.test(normalizedCmd)) { | ||||||||||||||||||||||||||||||||||||||
| recordTEvent("action:term", { "action:type": "cli-edit" }); | ||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const tailFollowRegex = /(^|\|\s*)tail\s+-[fF]\b/; | ||||||||||||||||||||||||||||||||||||||
| if (tailFollowRegex.test(decodedCmd)) { | ||||||||||||||||||||||||||||||||||||||
| if (tailFollowRegex.test(normalizedCmd)) { | ||||||||||||||||||||||||||||||||||||||
| recordTEvent("action:term", { "action:type": "cli-tailf" }); | ||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const claudeRegex = /^claude\b/; | ||||||||||||||||||||||||||||||||||||||
| if (claudeRegex.test(decodedCmd)) { | ||||||||||||||||||||||||||||||||||||||
| if (ClaudeCodeRegex.test(normalizedCmd)) { | ||||||||||||||||||||||||||||||||||||||
| recordTEvent("action:term", { "action:type": "claude" }); | ||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const opencodeRegex = /^opencode\b/; | ||||||||||||||||||||||||||||||||||||||
| if (opencodeRegex.test(decodedCmd)) { | ||||||||||||||||||||||||||||||||||||||
| if (opencodeRegex.test(normalizedCmd)) { | ||||||||||||||||||||||||||||||||||||||
| recordTEvent("action:term", { "action:type": "opencode" }); | ||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export function isClaudeCodeCommand(decodedCmd: string): boolean { | ||||||||||||||||||||||||||||||||||||||
| if (!decodedCmd) { | ||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| return ClaudeCodeRegex.test(normalizeCmd(decodedCmd)); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| function handleShellIntegrationCommandStart( | ||||||||||||||||||||||||||||||||||||||
| termWrap: TermWrap, | ||||||||||||||||||||||||||||||||||||||
| blockId: string, | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -101,16 +118,20 @@ function handleShellIntegrationCommandStart( | |||||||||||||||||||||||||||||||||||||
| const decodedCmd = base64ToString(cmd.data.cmd64); | ||||||||||||||||||||||||||||||||||||||
| rtInfo["shell:lastcmd"] = decodedCmd; | ||||||||||||||||||||||||||||||||||||||
| globalStore.set(termWrap.lastCommandAtom, decodedCmd); | ||||||||||||||||||||||||||||||||||||||
| const isCC = isClaudeCodeCommand(decodedCmd); | ||||||||||||||||||||||||||||||||||||||
| globalStore.set(termWrap.claudeCodeActiveAtom, isCC); | ||||||||||||||||||||||||||||||||||||||
| checkCommandForTelemetry(decodedCmd); | ||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||
| console.error("Error decoding cmd64:", e); | ||||||||||||||||||||||||||||||||||||||
| rtInfo["shell:lastcmd"] = null; | ||||||||||||||||||||||||||||||||||||||
| globalStore.set(termWrap.lastCommandAtom, null); | ||||||||||||||||||||||||||||||||||||||
| globalStore.set(termWrap.claudeCodeActiveAtom, false); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| rtInfo["shell:lastcmd"] = null; | ||||||||||||||||||||||||||||||||||||||
| globalStore.set(termWrap.lastCommandAtom, null); | ||||||||||||||||||||||||||||||||||||||
| globalStore.set(termWrap.claudeCodeActiveAtom, false); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| rtInfo["shell:lastcmdexitcode"] = null; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -287,6 +308,7 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo | |||||||||||||||||||||||||||||||||||||
| case "A": { | ||||||||||||||||||||||||||||||||||||||
| rtInfo["shell:state"] = "ready"; | ||||||||||||||||||||||||||||||||||||||
| globalStore.set(termWrap.shellIntegrationStatusAtom, "ready"); | ||||||||||||||||||||||||||||||||||||||
| globalStore.set(termWrap.claudeCodeActiveAtom, false); | ||||||||||||||||||||||||||||||||||||||
| const marker = terminal.registerMarker(0); | ||||||||||||||||||||||||||||||||||||||
| if (marker) { | ||||||||||||||||||||||||||||||||||||||
| termWrap.promptMarkers.push(marker); | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -324,6 +346,7 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo | |||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||
| case "D": | ||||||||||||||||||||||||||||||||||||||
| globalStore.set(termWrap.claudeCodeActiveAtom, false); | ||||||||||||||||||||||||||||||||||||||
| if (cmd.data.exitcode != null) { | ||||||||||||||||||||||||||||||||||||||
| rtInfo["shell:lastcmdexitcode"] = cmd.data.exitcode; | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -337,6 +360,7 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo | |||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||
| case "R": | ||||||||||||||||||||||||||||||||||||||
| globalStore.set(termWrap.shellIntegrationStatusAtom, null); | ||||||||||||||||||||||||||||||||||||||
| globalStore.set(termWrap.claudeCodeActiveAtom, false); | ||||||||||||||||||||||||||||||||||||||
| if (terminal.buffer.active.type === "alternate") { | ||||||||||||||||||||||||||||||||||||||
| terminal.write("\x1b[?1049l"); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
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.
Anchor Claude detection to the command token (argv[0]) only.
Line 28 currently matches
/claudeanywhere in the command string, so inputs likeecho /usr/local/bin/claudeare false positives.🔧 Suggested fix
Also applies to: 91-96
🤖 Prompt for AI Agents