Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Assets.xcassets/AgentIcons/Grok.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "Grok.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
8 changes: 8 additions & 0 deletions Assets.xcassets/AgentIcons/Grok.imageset/Grok.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 67 additions & 32 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19524,49 +19524,78 @@ struct CMUXCLI {
!cwd.isEmpty else {
return nil
}
let projectURL = grokSessionsRoot(env: env)
.appendingPathComponent(grokEncodedSessionCWD(cwd), isDirectory: true)
let sessionsRoot = grokSessionsRoot(env: env)
let fileManager = FileManager.default
var isDirectory = ObjCBool(false)
guard fileManager.fileExists(atPath: projectURL.path, isDirectory: &isDirectory),
isDirectory.boolValue else {
let projectURLs = grokEncodedSessionCWDs(cwd).compactMap { encodedCWD -> URL? in
let projectURL = sessionsRoot.appendingPathComponent(encodedCWD, isDirectory: true)
var isDirectory = ObjCBool(false)
guard fileManager.fileExists(atPath: projectURL.path, isDirectory: &isDirectory),
isDirectory.boolValue else {
return nil
}
return projectURL
}
guard !projectURLs.isEmpty else {
return nil
}

if let sessionId = sessionId?.trimmingCharacters(in: .whitespacesAndNewlines),
!sessionId.isEmpty {
let sessionURL = projectURL.appendingPathComponent(sessionId, isDirectory: true)
if fileManager.fileExists(atPath: sessionURL.path, isDirectory: &isDirectory),
isDirectory.boolValue {
return sessionURL
for projectURL in projectURLs {
let sessionURL = projectURL.appendingPathComponent(sessionId, isDirectory: true)
var isDirectory = ObjCBool(false)
if fileManager.fileExists(atPath: sessionURL.path, isDirectory: &isDirectory),
isDirectory.boolValue {
return sessionURL
}
}
return nil
}

guard let sessionURLs = try? fileManager.contentsOfDirectory(
at: projectURL,
includingPropertiesForKeys: [.contentModificationDateKey, .isDirectoryKey],
options: [.skipsHiddenFiles]
) else {
return nil
return projectURLs.compactMap { projectURL -> URL? in
guard let sessionURLs = try? fileManager.contentsOfDirectory(
at: projectURL,
includingPropertiesForKeys: [.contentModificationDateKey, .isDirectoryKey],
options: [.skipsHiddenFiles]
) else {
return nil
}
return sessionURLs
.filter { url in
((try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false)
&& fileManager.fileExists(atPath: url.appendingPathComponent("chat_history.jsonl").path)
}
.max { lhs, rhs in
grokHistoryModifiedDate(lhs) < grokHistoryModifiedDate(rhs)
}
}.max { lhs, rhs in
grokHistoryModifiedDate(lhs) < grokHistoryModifiedDate(rhs)
}
}

return sessionURLs
.filter { url in
((try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false)
&& fileManager.fileExists(atPath: url.appendingPathComponent("chat_history.jsonl").path)
}
.max { lhs, rhs in
let leftDate = (try? lhs
.appendingPathComponent("chat_history.jsonl")
.resourceValues(forKeys: [.contentModificationDateKey])
.contentModificationDate) ?? .distantPast
let rightDate = (try? rhs
.appendingPathComponent("chat_history.jsonl")
.resourceValues(forKeys: [.contentModificationDateKey])
.contentModificationDate) ?? .distantPast
return leftDate < rightDate
}
private func grokHistoryModifiedDate(_ sessionURL: URL) -> Date {
(try? sessionURL
.appendingPathComponent("chat_history.jsonl")
.resourceValues(forKeys: [.contentModificationDateKey])
.contentModificationDate) ?? .distantPast
}

private func grokEncodedSessionCWDs(_ cwd: String) -> [String] {
guard let rawCwd = grokNormalizedHookPath(cwd) else {
return []
}
var seen = Set<String>()
return [rawCwd, (rawCwd as NSString).standardizingPath]
.map(grokEncodedSessionCWD)
.filter { seen.insert($0).inserted }
}

private func grokNormalizedHookPath(_ value: String?) -> String? {
let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines)
guard let trimmed, !trimmed.isEmpty else {
return nil
}
return trimmed
}

private func grokSessionsRoot(env: [String: String]) -> URL {
Comment thread
lawrencecchen marked this conversation as resolved.
Expand Down Expand Up @@ -20134,6 +20163,8 @@ struct CMUXCLI {
}
case "pi":
return agentSurfaceResumeWithOption(kind: kind, launchCommand: launchCommand, fallbackExecutable: "pi", option: "--session", sessionId: sessionId)
case "grok":
return agentSurfaceResumeWithOption(kind: kind, launchCommand: launchCommand, fallbackExecutable: "grok", option: "-r", sessionId: sessionId)
case "amp":
let original = agentSurfaceResumeCommandParts(launchCommand: launchCommand, fallbackExecutable: "amp")
return AgentLaunchSanitizer.preservedArguments(kind: kind, args: original.tail).map {
Expand Down Expand Up @@ -25695,7 +25726,11 @@ export default function cmuxPiSessionExtension(pi: ExtensionAPI) {
for def in Self.agentDefs {
if let agentFilterDef, agentFilterDef.name != def.name { continue }
let configDir = def.resolvedConfigDir()
let canUseMissingConfigDir = def.name == "opencode" || def.name == "pi" || def.name == "amp" || (!isUninstall && def.name == "rovodev")
let canUseMissingConfigDir = def.createConfigDirIfMissing
|| def.name == "opencode"
|| def.name == "pi"
|| def.name == "amp"
|| (!isUninstall && def.name == "rovodev")
if !canUseMissingConfigDir, !fm.fileExists(atPath: configDir) {
print(" \(def.name): skipped (config dir not found)")
skipped += 1
Expand Down
169 changes: 169 additions & 0 deletions Resources/bin/grok
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
#!/usr/bin/env bash
# cmux grok wrapper - installs cmux hooks and captures launch metadata.

find_real_grok() {
local self_dir
self_dir="$(cd "$(dirname "$0")" && pwd)"
local names=(grok grok-macos-aarch64 grok-macos-aarch)
local IFS=:
for d in $PATH; do
[[ "$d" == "$self_dir" ]] && continue
local name candidate
for name in "${names[@]}"; do
candidate="$d/$name"
[[ -x "$candidate" ]] || continue
if [[ "$candidate" -ef "$0" ]]; then
continue
fi
printf '%s' "$candidate"
return 0
done
done
return 1
}

resolve_cmux_bin() {
local bundled_cli self_dir
bundled_cli="${CMUX_BUNDLED_CLI_PATH:-}"
if [[ -n "$bundled_cli" && -x "$bundled_cli" ]]; then
printf '%s' "$bundled_cli"
return 0
fi

self_dir="$(cd "$(dirname "$0")" && pwd)"
bundled_cli="$self_dir/cmux"
if [[ -x "$bundled_cli" ]]; then
printf '%s' "$bundled_cli"
return 0
fi

bundled_cli="$(command -v cmux || true)"
if [[ -n "$bundled_cli" ]]; then
printf '%s' "$bundled_cli"
return 0
fi

return 1
}

cmux_socket_available() {
local socket="${CMUX_SOCKET_PATH:-}"
[[ -n "$socket" && -S "$socket" ]] || return 1

local cmux_bin
cmux_bin="$(resolve_cmux_bin)" || return 1
CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC=0.75 \
"$cmux_bin" --socket "$socket" ping >/dev/null 2>&1
}

encode_launch_argv() {
{
printf '%s\0' "$REAL_GROK"
if (( $# > 0 )); then
printf '%s\0' "$@"
fi
} | base64 | tr -d '\n'
}

grok_option_consumes_value() {
case "$1" in
--cwd|--model|-m|--permission-mode|--prompt-file|--prompt-json|\
--sandbox|--settings|--config|--profile|--resume|-r)
return 0
;;
esac
return 1
}

grok_passthrough_option_flag() {
case "$1" in
--help|-h|--version|-v|--single|--single=*|-p|--prompt-file|\
--prompt-file=*|--prompt-json|--prompt-json=*|--output-format|\
--output-format=*|--best-of-n|--best-of-n=*)
return 0
;;
esac
return 1
}

grok_builtin_command_name() {
case "$1" in
auth|completion|completions|config|help|login|logout|models|sessions|\
setup|update|upgrade|version)
return 0
;;
esac
return 1
}

grok_command_like_invocation() {
local candidate="$1"
[[ -n "$candidate" ]] || return 1
case "$candidate" in
*[![:alnum:]_-]*)
return 1
;;
esac
grok_builtin_command_name "$candidate"
}

should_install_grok_hooks() {
(( $# == 0 )) && return 0

local arg
local skip_next=false
for arg in "$@"; do
if [[ "$skip_next" == true ]]; then
skip_next=false
continue
fi
case "$arg" in
--)
return 0
;;
--resume=*|-r=*)
return 0
;;
-*)
if grok_passthrough_option_flag "$arg"; then
return 1
fi
if [[ "$arg" != *=* ]] && grok_option_consumes_value "$arg"; then
skip_next=true
fi
continue
;;
*)
if grok_command_like_invocation "$arg"; then
return 1
fi
return 0
;;
esac
done

return 0
}

REAL_GROK="$(find_real_grok)" || { echo "Error: grok not found in PATH" >&2; exit 127; }

IN_CMUX=0
if [[ -n "${CMUX_SURFACE_ID:-}" ]]; then
Comment thread
lawrencecchen marked this conversation as resolved.
IN_CMUX=1
fi

if [[ "$IN_CMUX" == "1" && "${CMUX_GROK_HOOKS_DISABLED:-}" != "1" ]] \
&& cmux_socket_available \
&& should_install_grok_hooks "$@"; then
CMUX_BIN="$(resolve_cmux_bin || true)"
if [[ -n "$CMUX_BIN" ]]; then
CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC=2 \
"$CMUX_BIN" --socket "$CMUX_SOCKET_PATH" hooks grok install --yes >/dev/null 2>&1 || true
fi
export CMUX_AGENT_LAUNCH_KIND="grok"
export CMUX_AGENT_LAUNCH_EXECUTABLE="$REAL_GROK"
export CMUX_AGENT_LAUNCH_ARGV_B64="$(encode_launch_argv "$@")"
export CMUX_AGENT_LAUNCH_CWD="$PWD"
fi

exec "$REAL_GROK" "$@"
22 changes: 13 additions & 9 deletions Resources/shell-integration/cmux-bash-integration.bash
Original file line number Diff line number Diff line change
Expand Up @@ -145,30 +145,34 @@ _cmux_restore_scrollback_once() {
}
_cmux_restore_scrollback_once
_CMUX_CLAUDE_WRAPPER="${_CMUX_CLAUDE_WRAPPER:-}"
_cmux_install_claude_wrapper() {
_CMUX_GROK_WRAPPER="${_CMUX_GROK_WRAPPER:-}"
_cmux_install_cli_wrapper() {
local command_name="$1"
local wrapper_variable="$2"
local integration_dir="${CMUX_SHELL_INTEGRATION_DIR:-}"
local existing_type=""
[[ -n "$integration_dir" ]] || return 0

integration_dir="${integration_dir%/}"
local bundle_dir="${integration_dir%/shell-integration}"
local wrapper_path="$bundle_dir/bin/claude"
local wrapper_path="$bundle_dir/bin/$command_name"
[[ -x "$wrapper_path" ]] || return 0

existing_type="$(type -t claude 2>/dev/null || true)"
existing_type="$(type -t "$command_name" 2>/dev/null || true)"
case "$existing_type" in
alias|function)
return 0
;;
esac

# Keep the bundled claude wrapper ahead of later PATH mutations. Install it
# via eval so an existing `alias claude=...` cannot break parsing.
_CMUX_CLAUDE_WRAPPER="$wrapper_path"
unalias claude >/dev/null 2>&1 || true
eval 'claude() { "$_CMUX_CLAUDE_WRAPPER" "$@"; }'
# Keep the bundled wrapper ahead of later PATH mutations. Install it
# via eval so an existing alias cannot break parsing.
printf -v "$wrapper_variable" '%s' "$wrapper_path"
unalias "$command_name" >/dev/null 2>&1 || true
eval "$command_name() { \"\${$wrapper_variable}\" \"\$@\"; }"
Comment thread
cursor[bot] marked this conversation as resolved.
}
_cmux_install_claude_wrapper
_cmux_install_cli_wrapper claude _CMUX_CLAUDE_WRAPPER
_cmux_install_cli_wrapper grok _CMUX_GROK_WRAPPER
_cmux_now() {
printf '%s\n' "${EPOCHSECONDS:-$SECONDS}"
}
Expand Down
Loading
Loading