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
5 changes: 3 additions & 2 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,9 @@ opencli weread ranking --limit 10 # 排行榜
opencli jimeng generate --prompt "描述" # AI 生图
opencli jimeng history --limit 10 # 生成历史

# Grok (Desktop)
opencli grok ask "问题" # 提问 Grok (text positional)
# Grok (default + explicit web)
opencli grok ask --prompt "问题" # 提问 Grok(兼容默认路径)
opencli grok ask --prompt "问题" --web # 显式 grok.com consumer web UI 路径

# HuggingFace (public)
opencli hf top --limit 10 # 热门模型
Expand Down
34 changes: 26 additions & 8 deletions docs/adapters/browser/grok.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
# Grok

**Mode**: 🔐 Browser · **Domain**: `grok.com`
**Mode**: Default Grok adapter + optional explicit consumer web path · **Domain**: `grok.com`

## Commands

| Command | Description |
|---------|-------------|
| `opencli grok ask` | Send a message to Grok and get response |
| `opencli grok ask` | Keep the default Grok ask behavior |
| `opencli grok ask --web` | Use the explicit grok.com consumer web UI flow |

## Usage Examples

```bash
# Ask Grok a question
# Default / compatibility path
opencli grok ask --prompt "Explain quantum computing in simple terms"

# Start a new chat session
opencli grok ask --prompt "Hello" --new
# Explicit consumer web path
opencli grok ask --prompt "Explain quantum computing in simple terms" --web

# Best-effort fresh chat on the consumer web path
opencli grok ask --prompt "Hello" --web --new

# Set custom timeout (default: 120s)
opencli grok ask --prompt "Write a long essay" --timeout 180
opencli grok ask --prompt "Write a long essay" --web --timeout 180
```

### Options
Expand All @@ -27,9 +31,23 @@ opencli grok ask --prompt "Write a long essay" --timeout 180
|--------|-------------|
| `--prompt` | The message to send (required) |
| `--timeout` | Wait timeout in seconds (default: 120) |
| `--new` | Start a new chat session (default: false) |
| `--new` | Start a new chat before sending (default: false) |
| `--web` | Opt into the explicit grok.com consumer web flow (default: false) |

## Behavior

- `opencli grok ask` keeps the upstream/default behavior intact.
- `opencli grok ask --web` switches to the newer hardened consumer-web implementation.
- The `--web` path adds stricter composer detection, clearer blocked/session-gated hints, and waits for a stabilized assistant bubble before returning.

## Prerequisites

- Chrome running and **logged into** grok.com
- The Grok adapter still depends on browser-backed access to `grok.com`
- For `--web`, Chrome should already be running with an authenticated Grok consumer session
- [Browser Bridge extension](/guide/browser-bridge) installed

## Caveats

- `--web` drives the Grok consumer web UI in the browser, not an API.
- It depends on an already-authenticated session and can fail if Grok shows login, challenge, rate-limit, or other session-gating UI.
- It may break when the Grok composer DOM, submit button behavior, or message bubble structure changes.
53 changes: 53 additions & 0 deletions src/clis/grok/ask.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { describe, expect, it } from 'vitest';
import { __test__ } from './ask.js';

describe('grok ask helpers', () => {
it('normalizes boolean flags for explicit web routing', () => {
expect(__test__.normalizeBooleanFlag(true)).toBe(true);
expect(__test__.normalizeBooleanFlag('true')).toBe(true);
expect(__test__.normalizeBooleanFlag('1')).toBe(true);
expect(__test__.normalizeBooleanFlag('yes')).toBe(true);
expect(__test__.normalizeBooleanFlag('on')).toBe(true);

expect(__test__.normalizeBooleanFlag(false)).toBe(false);
expect(__test__.normalizeBooleanFlag('false')).toBe(false);
expect(__test__.normalizeBooleanFlag(undefined)).toBe(false);
});

it('ignores baseline bubbles and the echoed prompt when choosing the latest assistant candidate', () => {
const candidate = __test__.pickLatestAssistantCandidate(
['older assistant answer', 'Prompt text', 'Assistant draft', 'Assistant final'],
1,
'Prompt text',
);

expect(candidate).toBe('Assistant final');
});

it('returns empty when only the echoed prompt appeared after send', () => {
const candidate = __test__.pickLatestAssistantCandidate(
['older assistant answer', 'Prompt text'],
1,
'Prompt text',
);

expect(candidate).toBe('');
});

it('tracks stabilization by incrementing repeats and resetting on changes', () => {
expect(__test__.updateStableState('', 0, 'First chunk')).toEqual({
previousText: 'First chunk',
stableCount: 0,
});

expect(__test__.updateStableState('First chunk', 0, 'First chunk')).toEqual({
previousText: 'First chunk',
stableCount: 1,
});

expect(__test__.updateStableState('First chunk', 1, 'Second chunk')).toEqual({
previousText: 'Second chunk',
stableCount: 0,
});
});
});
Loading
Loading