Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
93 changes: 62 additions & 31 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.
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
164 changes: 164 additions & 0 deletions Resources/bin/grok
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#!/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 IFS=:
for d in $PATH; do
[[ "$d" == "$self_dir" ]] && continue
[[ -x "$d/grok" ]] || continue
if [[ "$d/grok" -ef "$0" ]]; then
continue
fi
printf '%s' "$d/grok"
return 0
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.
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" "$@"
24 changes: 24 additions & 0 deletions Resources/shell-integration/cmux-bash-integration.bash
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,30 @@ _cmux_install_claude_wrapper() {
eval 'claude() { "$_CMUX_CLAUDE_WRAPPER" "$@"; }'
}
_cmux_install_claude_wrapper

_CMUX_GROK_WRAPPER="${_CMUX_GROK_WRAPPER:-}"
_cmux_install_grok_wrapper() {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
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/grok"
[[ -x "$wrapper_path" ]] || return 0

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

_CMUX_GROK_WRAPPER="$wrapper_path"
unalias grok >/dev/null 2>&1 || true
eval 'grok() { "$_CMUX_GROK_WRAPPER" "$@"; }'
}
_cmux_install_grok_wrapper
_cmux_now() {
printf '%s\n' "${EPOCHSECONDS:-$SECONDS}"
}
Expand Down
16 changes: 16 additions & 0 deletions Resources/shell-integration/cmux-zsh-integration.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,22 @@ _cmux_install_claude_wrapper() {
}
_cmux_install_claude_wrapper

typeset -g _CMUX_GROK_WRAPPER=""
_cmux_install_grok_wrapper() {
local integration_dir="${CMUX_SHELL_INTEGRATION_DIR:-}"
[[ -n "$integration_dir" ]] || return 0

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

_CMUX_GROK_WRAPPER="$wrapper_path"
builtin unalias grok >/dev/null 2>&1 || true
eval 'grok() { "$_CMUX_GROK_WRAPPER" "$@"; }'
}
_cmux_install_grok_wrapper

_cmux_normalize_claude_config_dir() {
[[ -n "${CLAUDE_CONFIG_DIR:-}" && -n "${HOME:-}" ]] || return 0

Expand Down
5 changes: 2 additions & 3 deletions Sources/RestorableAgentTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ enum RestorableAgentKind: Codable, Hashable, Sendable {
static let allCases: [RestorableAgentKind] = [
.claude,
.codex,
.grok,
// Pi is registry-owned so the built-in Vault registration can be
// overridden by project config while direct .pi values still encode.
// Pi and Grok are registry-owned so the built-in Vault registrations can be
// overridden by project config while direct native values still encode.
.amp,
.cursor,
.gemini,
Expand Down
Loading
Loading