Skip to content

Commit 94a075f

Browse files
committed
fix(auth): install official grok cli
1 parent 64ff87f commit 94a075f

22 files changed

Lines changed: 296 additions & 68 deletions

File tree

packages/api/src/services/project-auth.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const projectClaudeLabelKey = "CLAUDE_AUTH_LABEL"
2929
const projectGeminiLabelKey = "GEMINI_AUTH_LABEL"
3030
const projectGrokLabelKey = "GROK_AUTH_LABEL"
3131
const defaultGitUser = "x-access-token"
32+
const grokEnvApiKeyNames: ReadonlyArray<string> = ["GROK_DEPLOYMENT_KEY", "GROK_API_KEY", "XAI_API_KEY"]
3233

3334
const normalizeLabel = (value: string): string => {
3435
const trimmed = value.trim()
@@ -258,6 +259,12 @@ const grokUserSettingsCredentialMarkers: ReadonlyArray<RegExp> = [
258259
/"oauth"\s*:\s*\{[\s\S]*?"(?:apiKey|accessToken|access_token|authToken|refreshToken|refresh_token|token)"\s*:\s*"[^"]+"/u
259260
]
260261

262+
const grokAuthJsonCredentialMarkers: ReadonlyArray<RegExp> = [
263+
/"key"\s*:\s*"[^"]+"/u,
264+
/"token"\s*:\s*"[^"]+"/u,
265+
/"accessToken"\s*:\s*"[^"]+"/u
266+
]
267+
261268
const hasGrokUserSettingsCredentials = (
262269
fs: FileSystem.FileSystem,
263270
settingsPath: string
@@ -272,6 +279,20 @@ const hasGrokUserSettingsCredentials = (
272279
return grokUserSettingsCredentialMarkers.some((marker) => marker.test(settingsText))
273280
})
274281

282+
const hasGrokAuthJsonCredentials = (
283+
fs: FileSystem.FileSystem,
284+
authJsonPath: string
285+
): Effect.Effect<boolean, PlatformError> =>
286+
Effect.gen(function*(_) {
287+
const hasFile = yield* _(hasFileAtPath(fs, authJsonPath))
288+
if (!hasFile) {
289+
return false
290+
}
291+
292+
const authJsonText = yield* _(fs.readFileString(authJsonPath), Effect.orElseSucceed(() => ""))
293+
return grokAuthJsonCredentialMarkers.some((marker) => marker.test(authJsonText))
294+
})
295+
275296
const hasGrokAccountCredentials = (
276297
fs: FileSystem.FileSystem,
277298
accountPath: string
@@ -282,12 +303,19 @@ const hasGrokAccountCredentials = (
282303
return Effect.succeed(true)
283304
}
284305

285-
return hasApiKeyInEnvFile(fs, `${accountPath}/.env`, "GROK_API_KEY").pipe(
306+
return Effect.forEach(grokEnvApiKeyNames, (key) => hasApiKeyInEnvFile(fs, `${accountPath}/.env`, key)).pipe(
307+
Effect.map((results) => results.some((result) => result)),
286308
Effect.flatMap((hasEnvApiKey) => {
287309
if (hasEnvApiKey) {
288310
return Effect.succeed(true)
289311
}
290-
return hasGrokUserSettingsCredentials(fs, `${accountPath}/.grok/user-settings.json`)
312+
return hasGrokAuthJsonCredentials(fs, `${accountPath}/.grok/auth.json`).pipe(
313+
Effect.flatMap((hasAuthJson) =>
314+
hasAuthJson
315+
? Effect.succeed(true)
316+
: hasGrokUserSettingsCredentials(fs, `${accountPath}/.grok/user-settings.json`)
317+
)
318+
)
291319
})
292320
)
293321
})

packages/api/tests/project-auth.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,41 @@ describe("project auth service", () => {
130130

131131
expect(snapshot.activeGrokLabel).toBe("default")
132132
expect(yield* _(fs.readFileString(envProjectPath))).toContain("GROK_AUTH_LABEL=default")
133+
134+
yield* _(fs.writeFileString(envProjectPath, "# project env\n"))
135+
yield* _(fs.remove(path.join(grokDefaultAuth, ".api-key")))
136+
yield* _(fs.makeDirectory(path.join(grokDefaultAuth, ".grok"), { recursive: true }))
137+
yield* _(fs.writeFileString(path.join(grokDefaultAuth, ".grok", "auth.json"), "{\"scope\":{\"key\":\"xai-oauth\"}}\n"))
138+
139+
const oauthSnapshot = yield* _(
140+
withProjectsRoot(
141+
projectsRoot,
142+
service.runProjectAuthFlow(project, {
143+
flow: "ProjectGrokConnect",
144+
label: "default"
145+
})
146+
)
147+
)
148+
149+
expect(oauthSnapshot.activeGrokLabel).toBe("default")
150+
expect(yield* _(fs.readFileString(envProjectPath))).toContain("GROK_AUTH_LABEL=default")
151+
152+
yield* _(fs.writeFileString(envProjectPath, "# project env\n"))
153+
yield* _(fs.remove(path.join(grokDefaultAuth, ".grok"), { recursive: true }))
154+
yield* _(fs.writeFileString(path.join(grokDefaultAuth, ".env"), "GROK_DEPLOYMENT_KEY='xai-deploy'\n"))
155+
156+
const envSnapshot = yield* _(
157+
withProjectsRoot(
158+
projectsRoot,
159+
service.runProjectAuthFlow(project, {
160+
flow: "ProjectGrokConnect",
161+
label: "default"
162+
})
163+
)
164+
)
165+
166+
expect(envSnapshot.activeGrokLabel).toBe("default")
167+
expect(yield* _(fs.readFileString(envProjectPath))).toContain("GROK_AUTH_LABEL=default")
133168
})
134169
).pipe(Effect.provide(NodeContext.layer)))
135170
})

packages/app/src/docker-git/cli/parser-auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ const buildGeminiCommand = (action: string, options: AuthOptions): Either.Either
198198
// WHY: issue #304 requires docker-git auth grok login/status/logout support
199199
// QUOTE(ТЗ): "Реализовать поддержку авторизации grok"
200200
// REF: issue-304
201-
// SOURCE: https://www.npmjs.com/package/grok-dev
201+
// SOURCE: https://x.ai/news/grok-build-cli
202202
// FORMAT THEOREM: forall action: buildGrokCommand(action, opts) = AuthCommand | ParseError
203203
// PURITY: CORE
204204
// EFFECT: n/a

packages/app/src/docker-git/frontend-lib/core/auth-domain.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export interface AuthGeminiLogoutCommand {
111111
// WHY: issue #304 requires Grok login/status/logout profiles with isolated auth storage
112112
// QUOTE(ТЗ): "Реализовать поддержку авторизации grok"
113113
// REF: issue-304
114-
// SOURCE: https://www.npmjs.com/package/grok-dev
114+
// SOURCE: https://x.ai/news/grok-build-cli
115115
// FORMAT THEOREM: forall cmd ∈ AuthGrokCommand: cmd.grokAuthPath is valid path
116116
// PURITY: CORE
117117
// EFFECT: n/a

packages/app/src/lib/core/auth-domain.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export interface AuthGeminiLogoutCommand {
111111
// WHY: issue #304 requires Grok login/status/logout profiles with isolated auth storage
112112
// QUOTE(ТЗ): "Реализовать поддержку авторизации grok"
113113
// REF: issue-304
114-
// SOURCE: https://www.npmjs.com/package/grok-dev
114+
// SOURCE: https://x.ai/news/grok-build-cli
115115
// FORMAT THEOREM: forall cmd ∈ AuthGrokCommand: cmd.grokAuthPath is valid path
116116
// PURITY: CORE
117117
// EFFECT: n/a

packages/app/src/lib/core/templates-entrypoint/grok.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { TemplateConfig } from "../domain.js"
55
// WHY: issue #304 requires Grok auth, Playwright MCP and unrestricted agent permissions
66
// QUOTE(ТЗ): "Реализовать поддержку авторизации grok"
77
// REF: issue-304
8-
// SOURCE: https://www.npmjs.com/package/grok-dev
8+
// SOURCE: https://x.ai/news/grok-build-cli
99
// FORMAT THEOREM: renderEntrypointGrokConfig(config) -> valid_bash_script
1010
// PURITY: CORE
1111
// INVARIANT: Grok credentials are isolated by GROK_AUTH_LABEL
@@ -18,10 +18,11 @@ const grokAuthRootContainerPath = (sshUser: string): string => `/home/${sshUser}
1818
// QUOTE(ТЗ): "GitHub Actions ... all CI checks are passing"
1919
// REF: issue-304-ci
2020
// SOURCE: n/a
21-
// FORMAT THEOREM: unset(GROK_API_KEY) -> safe_empty_value(GROK_API_KEY)
21+
// FORMAT THEOREM: unset(GROK_DEPLOYMENT_KEY) -> safe_empty_value(GROK_DEPLOYMENT_KEY)
2222
// PURITY: CORE
2323
// INVARIANT: Optional Grok API credentials never require a bound environment variable
2424
// COMPLEXITY: O(1)
25+
const grokDeploymentKeyDefaultExpansion = "${GROK_DEPLOYMENT_KEY:-}"
2526
const grokApiKeyDefaultExpansion = "${GROK_API_KEY:-}"
2627
const xaiApiKeyDefaultExpansion = "${XAI_API_KEY:-}"
2728

@@ -82,6 +83,10 @@ docker_git_prepare_grok_home_dir
8283
8384
docker_git_link_grok_file "$GROK_CONFIG_DIR/.api-key" "$GROK_HOME_DIR/.api-key"
8485
docker_git_link_grok_file "$GROK_CONFIG_DIR/.env" "$GROK_HOME_DIR/.env"
86+
docker_git_link_grok_file "$GROK_SHARED_HOME_DIR/auth.json" "$GROK_HOME_DIR/auth.json"
87+
docker_git_link_grok_file "$GROK_SHARED_HOME_DIR/config.toml" "$GROK_HOME_DIR/config.toml"
88+
docker_git_link_grok_file "$GROK_SHARED_HOME_DIR/managed_config.toml" "$GROK_HOME_DIR/managed_config.toml"
89+
docker_git_link_grok_file "$GROK_SHARED_HOME_DIR/requirements.toml" "$GROK_HOME_DIR/requirements.toml"
8590
docker_git_link_grok_file "$GROK_SHARED_HOME_DIR/user-settings.json" "$GROK_HOME_DIR/user-settings.json"
8691
docker_git_link_grok_file "$GROK_SHARED_HOME_DIR/settings.json" "$GROK_HOME_DIR/settings.json"
8792
@@ -106,16 +111,29 @@ fi
106111
107112
docker_git_refresh_grok_env() {
108113
if [[ -f "$GROK_HOME_DIR/.api-key" ]]; then
109-
export GROK_API_KEY="$(cat "$GROK_HOME_DIR/.api-key" | tr -d '\r\n')"
114+
API_KEY="$(cat "$GROK_HOME_DIR/.api-key" | tr -d '\r\n')"
110115
elif [[ -f "$GROK_HOME_DIR/.env" ]]; then
111-
API_KEY="$(grep "^GROK_API_KEY=" "$GROK_HOME_DIR/.env" | cut -d'=' -f2- | sed "s/^['\"]//;s/['\"]$//")"
112-
if [[ -n "$API_KEY" ]]; then
113-
export GROK_API_KEY="$API_KEY"
114-
fi
116+
API_KEY="$(grep -E "^(GROK_DEPLOYMENT_KEY|GROK_API_KEY|XAI_API_KEY)=" "$GROK_HOME_DIR/.env" 2>/dev/null | head -n 1 | cut -d'=' -f2- | sed "s/^['\"]//;s/['\"]$//" || true)"
117+
else
118+
API_KEY=""
119+
fi
120+
if [[ -n "$API_KEY" ]]; then
121+
export GROK_DEPLOYMENT_KEY="$API_KEY"
122+
export GROK_API_KEY="$API_KEY"
123+
export XAI_API_KEY="$API_KEY"
124+
fi
125+
if [[ -n "${grokDeploymentKeyDefaultExpansion}" ]]; then
126+
export GROK_API_KEY="${grokDeploymentKeyDefaultExpansion}"
127+
export XAI_API_KEY="${grokDeploymentKeyDefaultExpansion}"
115128
fi
116129
if [[ -n "${grokApiKeyDefaultExpansion}" ]]; then
130+
export GROK_DEPLOYMENT_KEY="${grokApiKeyDefaultExpansion}"
117131
export XAI_API_KEY="${grokApiKeyDefaultExpansion}"
118132
fi
133+
if [[ -n "${xaiApiKeyDefaultExpansion}" ]]; then
134+
export GROK_DEPLOYMENT_KEY="${xaiApiKeyDefaultExpansion}"
135+
export GROK_API_KEY="${xaiApiKeyDefaultExpansion}"
136+
fi
119137
}
120138
121139
docker_git_refresh_grok_env`
@@ -177,13 +195,16 @@ printf "export GROK_HOME=%q\n" "${config.grokHome}" >> "$GROK_PROFILE"
177195
printf "alias grok='/usr/local/bin/grok-wrapper'\n" >> "$GROK_PROFILE"
178196
cat <<'EOF' >> "$GROK_PROFILE"
179197
if [[ -f "$GROK_HOME/.api-key" ]]; then
180-
export GROK_API_KEY="$(cat "$GROK_HOME/.api-key" | tr -d '\r\n')"
181-
export XAI_API_KEY="${grokApiKeyDefaultExpansion}"
198+
API_KEY="$(cat "$GROK_HOME/.api-key" | tr -d '\r\n')"
199+
export GROK_DEPLOYMENT_KEY="$API_KEY"
200+
export GROK_API_KEY="$API_KEY"
201+
export XAI_API_KEY="$API_KEY"
182202
fi
183203
EOF
184204
chmod 0644 "$GROK_PROFILE" || true
185205
186206
docker_git_upsert_ssh_env "GROK_AUTH_LABEL" "$GROK_AUTH_LABEL"
207+
docker_git_upsert_ssh_env "GROK_DEPLOYMENT_KEY" "${grokDeploymentKeyDefaultExpansion}"
187208
docker_git_upsert_ssh_env "GROK_API_KEY" "${grokApiKeyDefaultExpansion}"
188209
docker_git_upsert_ssh_env "XAI_API_KEY" "${xaiApiKeyDefaultExpansion}"`
189210

packages/app/src/lib/core/templates/dockerfile.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,11 @@ RUN mkdir -p /usr/local/nvm \
8787
RUN printf "export NVM_DIR=/usr/local/nvm\\n[ -s /usr/local/nvm/nvm.sh ] && . /usr/local/nvm/nvm.sh\\n" \
8888
> /etc/profile.d/nvm.sh && chmod 0644 /etc/profile.d/nvm.sh`
8989

90+
const grokCliInstallScriptUrl = "https://x.ai/cli/install.sh"
91+
const grokCliVersion = "0.1.211"
92+
9093
const renderDockerfileBunPrelude = (config: TemplateConfig): string =>
91-
`# Tooling: Bun + Codex CLI (bun) + oh-my-opencode (npm + platform binary) + Claude Code CLI (npm) + Grok CLI (npm)
94+
`# Tooling: Bun + Codex CLI (bun) + oh-my-opencode (npm + platform binary) + Claude Code CLI (npm) + Grok CLI (xAI installer)
9295
ENV TERM=xterm-256color
9396
RUN set -eu; \
9497
for attempt in 1 2 3 4 5; do \
@@ -119,7 +122,14 @@ RUN npm install -g @anthropic-ai/claude-code@latest
119122
RUN claude --version
120123
RUN npm install -g @google/gemini-cli@latest --force
121124
RUN gemini --version
122-
RUN npm install -g grok-dev@1.1.7
125+
RUN set -eu; \
126+
curl -fsSL --retry 5 --retry-all-errors --retry-delay 2 ${grokCliInstallScriptUrl} -o /tmp/grok-install.sh; \
127+
HOME=/tmp/grok-install-home GROK_BIN_DIR=/usr/local/bin bash /tmp/grok-install.sh ${grokCliVersion}; \
128+
install -m 0755 "$(readlink -f /usr/local/bin/grok)" /usr/local/bin/grok.real; \
129+
install -m 0755 "$(readlink -f /usr/local/bin/agent)" /usr/local/bin/agent.real; \
130+
mv -f /usr/local/bin/grok.real /usr/local/bin/grok; \
131+
mv -f /usr/local/bin/agent.real /usr/local/bin/agent; \
132+
rm -rf /tmp/grok-install.sh /tmp/grok-install-home
123133
RUN grok --version`
124134

125135
// CHANGE: install RTK as a real command-output optimizer in generated containers.

packages/app/src/lib/usecases/auth-grok-helpers.ts

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ export const grokImageName = "docker-git-auth-grok:latest"
2222
export const grokImageDir = ".docker-git/.orch/auth/grok/.image"
2323
export const grokContainerHomeDir = "/grok-home"
2424
export const grokCredentialsDir = ".grok"
25-
export const grokDevPackageSpec = "grok-dev@1.1.7"
25+
export const grokCliInstallScriptUrl = "https://x.ai/cli/install.sh"
26+
export const grokCliVersion = "0.1.211"
2627

2728
export type GrokAccountContext = {
2829
readonly accountLabel: string
@@ -41,27 +42,34 @@ export const grokEnvFilePath = (accountPath: string): string => `${accountPath}/
4142
export const grokCredentialsPath = (accountPath: string): string => `${accountPath}/${grokCredentialsDir}`
4243
export const grokUserSettingsPath = (accountPath: string): string =>
4344
`${grokCredentialsPath(accountPath)}/user-settings.json`
45+
export const grokAuthJsonPath = (accountPath: string): string => `${grokCredentialsPath(accountPath)}/auth.json`
46+
47+
const grokEnvApiKeyNames: ReadonlyArray<string> = ["GROK_DEPLOYMENT_KEY", "GROK_API_KEY", "XAI_API_KEY"]
4448

4549
// CHANGE: render Dockerfile for Grok CLI authentication image
4650
// WHY: Grok browser/OAuth auth must run in an isolated shell that persists ~/.grok
4751
// QUOTE(ТЗ): "Signing in with Grok..."
4852
// REF: issue-304
49-
// SOURCE: https://www.npmjs.com/package/grok-dev
53+
// SOURCE: https://x.ai/news/grok-build-cli
5054
// FORMAT THEOREM: renderGrokDockerfile() -> valid_dockerfile
5155
// PURITY: CORE
5256
// EFFECT: n/a
53-
// INVARIANT: image includes Node.js and a grok executable
57+
// INVARIANT: image includes the official xAI grok executable
5458
// COMPLEXITY: O(1)
5559
export const renderGrokDockerfile = (): string =>
5660
String.raw`FROM ubuntu:24.04
5761
ENV DEBIAN_FRONTEND=noninteractive
5862
RUN apt-get update \
5963
&& apt-get install -y --no-install-recommends ca-certificates curl bsdutils \
6064
&& rm -rf /var/lib/apt/lists/*
61-
RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
62-
&& apt-get install -y --no-install-recommends nodejs \
63-
&& rm -rf /var/lib/apt/lists/*
64-
RUN npm install -g ${grokDevPackageSpec}
65+
RUN set -eu; \
66+
curl -fsSL --retry 5 --retry-all-errors --retry-delay 2 ${grokCliInstallScriptUrl} -o /tmp/grok-install.sh; \
67+
HOME=/tmp/grok-install-home GROK_BIN_DIR=/usr/local/bin bash /tmp/grok-install.sh ${grokCliVersion}; \
68+
install -m 0755 "$(readlink -f /usr/local/bin/grok)" /usr/local/bin/grok.real; \
69+
install -m 0755 "$(readlink -f /usr/local/bin/agent)" /usr/local/bin/agent.real; \
70+
mv -f /usr/local/bin/grok.real /usr/local/bin/grok; \
71+
mv -f /usr/local/bin/agent.real /usr/local/bin/agent; \
72+
rm -rf /tmp/grok-install.sh /tmp/grok-install-home
6573
RUN grok --version
6674
`
6775

@@ -127,12 +135,15 @@ const readApiKeyFromEnvFile = (
127135
const envContent = yield* _(fs.readFileString(envFilePath), Effect.orElseSucceed(() => ""))
128136
for (const line of envContent.split("\n")) {
129137
const trimmed = line.trim()
130-
if (!trimmed.startsWith("GROK_API_KEY=")) {
131-
continue
132-
}
133-
const value = trimmed.slice("GROK_API_KEY=".length).replaceAll(/^['"]|['"]$/g, "").trim()
134-
if (value.length > 0) {
135-
return value
138+
for (const key of grokEnvApiKeyNames) {
139+
const prefix = `${key}=`
140+
if (!trimmed.startsWith(prefix)) {
141+
continue
142+
}
143+
const value = trimmed.slice(prefix.length).replaceAll(/^['"]|['"]$/g, "").trim()
144+
if (value.length > 0) {
145+
return value
146+
}
136147
}
137148
}
138149
return null
@@ -165,6 +176,13 @@ export const hasGrokCredentials = (
165176
if (apiKey !== null) {
166177
return true
167178
}
179+
const hasAuthJson = yield* _(isRegularFile(fs, grokAuthJsonPath(accountPath)))
180+
if (hasAuthJson) {
181+
const authJson = yield* _(fs.readFileString(grokAuthJsonPath(accountPath)), Effect.orElseSucceed(() => ""))
182+
if (hasGrokAuthJsonCredentials(authJson)) {
183+
return true
184+
}
185+
}
168186
const hasUserSettings = yield* _(isRegularFile(fs, grokUserSettingsPath(accountPath)))
169187
if (!hasUserSettings) {
170188
return false
@@ -184,6 +202,15 @@ const grokUserSettingsCredentialMarkers: ReadonlyArray<RegExp> = [
184202
const hasGrokUserSettingsCredentials = (content: string): boolean =>
185203
grokUserSettingsCredentialMarkers.some((marker) => marker.test(content))
186204

205+
const grokAuthJsonCredentialMarkers: ReadonlyArray<RegExp> = [
206+
/"key"\s*:\s*"[^"]+"/u,
207+
/"token"\s*:\s*"[^"]+"/u,
208+
/"accessToken"\s*:\s*"[^"]+"/u
209+
]
210+
211+
const hasGrokAuthJsonCredentials = (content: string): boolean =>
212+
grokAuthJsonCredentialMarkers.some((marker) => marker.test(content))
213+
187214
export const resolveGrokAuthMethod = (
188215
fs: FileSystem.FileSystem,
189216
accountPath: string

packages/app/src/lib/usecases/auth-grok-logout.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { autoSyncState } from "./state-repo.js"
1212
// WHY: allow revoking Grok CLI access deterministically
1313
// QUOTE(ТЗ): "Реализовать поддержку авторизации grok"
1414
// REF: issue-304
15-
// SOURCE: https://www.npmjs.com/package/grok-dev
15+
// SOURCE: https://x.ai/news/grok-build-cli
1616
// FORMAT THEOREM: forall cmd: authGrokLogout(cmd) -> credentials_cleared(cmd)
1717
// PURITY: SHELL
1818
// EFFECT: Effect<void, PlatformError | CommandFailedError, GrokRuntime>

packages/app/src/lib/usecases/auth-grok-oauth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { AuthError, CommandFailedError } from "../shell/errors.js"
1010
// WHY: issue #304 expects `grok login` style URL handoff and callback paste support
1111
// QUOTE(ТЗ): "Paste the URL here if it doesn't connect"
1212
// REF: issue-304
13-
// SOURCE: https://www.npmjs.com/package/grok-dev
13+
// SOURCE: https://x.ai/news/grok-build-cli
1414
// FORMAT THEOREM: forall cmd: runGrokOauthLogin(cmd) -> grok_credentials_stored | error
1515
// PURITY: SHELL
1616
// EFFECT: Effect<void, AuthError | CommandFailedError | PlatformError, CommandExecutor>

0 commit comments

Comments
 (0)