portable-agent-layer 0.18.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -118,6 +118,7 @@ pal cli install # all available (default)
|
|
|
118
118
|
| Variable | Description |
|
|
119
119
|
|----------|-------------|
|
|
120
120
|
| `GEMINI_API_KEY` | For YouTube video analysis skill |
|
|
121
|
+
| `XAI_API_KEY` | For Grok real-time research skill (X/web search) |
|
|
121
122
|
| `PAL_HOME` | Override user state directory (default: `~/.pal` or repo root) |
|
|
122
123
|
| `PAL_PKG` | Override package root |
|
|
123
124
|
| `PAL_CLAUDE_DIR` | Override Claude config dir (default: `~/.claude`) |
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grok-researcher
|
|
3
|
+
description: Real-time research via Grok/X API — fetches live data from X (Twitter), trending topics, and breaking news. Use for research requiring up-to-the-minute information about current events, public sentiment, or rapidly evolving situations.
|
|
4
|
+
tools: WebSearch, WebFetch, Bash, Read, Grep, Glob
|
|
5
|
+
model: sonnet
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are a research specialist focused on **real-time information and current events** using the Grok API and X (Twitter) data.
|
|
9
|
+
|
|
10
|
+
## Primary Path — grok-search tool
|
|
11
|
+
|
|
12
|
+
Use the `grok-search` tool to query the Grok API with real-time search grounding. The tool handles authentication, API formatting, and source extraction.
|
|
13
|
+
|
|
14
|
+
### Current events / breaking news (web + X sources)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun ~/.agents/skills/research/tools/grok-search.ts -- "<your research query>" --sources web,x
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Social sentiment / trending topics (X only)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bun ~/.agents/skills/research/tools/grok-search.ts -- "Search X for recent posts about: <topic>. Summarize key themes, notable accounts, and overall sentiment." --sources x
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Web-only search
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
bun ~/.agents/skills/research/tools/grok-search.ts -- "<query>" --sources web
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The tool outputs findings as markdown with a `## Sources` section listing URLs and X posts.
|
|
33
|
+
|
|
34
|
+
## Fallback Path — WebSearch
|
|
35
|
+
|
|
36
|
+
If the grok-search tool fails (missing `XAI_API_KEY` or API error), fall back to WebSearch and WebFetch with a **recency focus**:
|
|
37
|
+
|
|
38
|
+
1. **Search** using WebSearch with time-sensitive queries — prepend "2026" or "latest" or "today" to queries
|
|
39
|
+
2. **Prioritize** news sources, social media aggregators, and live blogs
|
|
40
|
+
3. **Fetch** the most recent results with WebFetch to extract detail
|
|
41
|
+
4. **Note** in your output that you used the fallback path (no Grok API access)
|
|
42
|
+
|
|
43
|
+
## Methodology
|
|
44
|
+
|
|
45
|
+
1. **Assess** whether the query needs real-time data (breaking news, current events) vs X/social data (sentiment, trends, reactions)
|
|
46
|
+
2. **Query** via grok-search — use `--sources web,x` for current events, `--sources x` for sentiment
|
|
47
|
+
3. **Extract** key facts, dates, and source references from the output
|
|
48
|
+
4. **Cross-reference** with a WebSearch if the grok-search output lacks detail or sources
|
|
49
|
+
5. **Synthesize** findings with emphasis on timeliness and recency
|
|
50
|
+
|
|
51
|
+
## Guidelines
|
|
52
|
+
|
|
53
|
+
- Always note the recency of information — include dates and "as of" timestamps
|
|
54
|
+
- Distinguish between confirmed reports and unverified social media claims
|
|
55
|
+
- For trending topics, note scale (approximate engagement/post volume if available)
|
|
56
|
+
- Flag rapidly evolving situations where facts may change
|
|
57
|
+
- If a claim has no strong source, say so — do not fabricate citations
|
|
58
|
+
- If using the fallback path, be transparent about reduced real-time capability
|
|
59
|
+
|
|
60
|
+
## Output Format
|
|
61
|
+
|
|
62
|
+
```markdown
|
|
63
|
+
## Findings
|
|
64
|
+
|
|
65
|
+
[Numbered list of discoveries, each with timestamp/recency indicator]
|
|
66
|
+
- 🔴 Breaking (< 1 hour)
|
|
67
|
+
- 🟠 Recent (< 24 hours)
|
|
68
|
+
- 🟡 This week
|
|
69
|
+
- ⚪ Older context
|
|
70
|
+
|
|
71
|
+
## Sources
|
|
72
|
+
|
|
73
|
+
[Verified URLs with one-line descriptions — only include URLs you actually visited or received from Grok]
|
|
74
|
+
|
|
75
|
+
## Sentiment (if applicable)
|
|
76
|
+
|
|
77
|
+
[Summary of public reaction/sentiment from X data, with notable voices]
|
|
78
|
+
|
|
79
|
+
## Confidence
|
|
80
|
+
|
|
81
|
+
[High/Medium/Low rating per finding — note if single-sourced or unverified social media]
|
|
82
|
+
|
|
83
|
+
## Gaps
|
|
84
|
+
|
|
85
|
+
[What couldn't be confirmed or needs monitoring as the situation develops]
|
|
86
|
+
```
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-pdf
|
|
3
|
+
description: Generate a structured PDF report from a topic or content using parallel research/writing agents and md-to-pdf. Use when creating a report, generating a PDF, writing a document, or producing a structured multi-section PDF.
|
|
4
|
+
argument-hint: <topic, outline, or content description>
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Create professional PDF reports by orchestrating parallel subagents for content generation, writing results as structured markdown files, then converting to PDF via `bunx md-to-pdf`.
|
|
10
|
+
|
|
11
|
+
## Workflow
|
|
12
|
+
|
|
13
|
+
### Phase 1: Plan the Report Structure
|
|
14
|
+
|
|
15
|
+
Based on the user's request, determine:
|
|
16
|
+
|
|
17
|
+
1. **Report topic and scope**
|
|
18
|
+
2. **Section breakdown** — identify 5-15 discrete sections/chapters
|
|
19
|
+
3. **Output directory** — ask the user or default to `~/Documents/<kebab-case-topic>/`
|
|
20
|
+
4. **Language** — detect from the user's request or ask
|
|
21
|
+
5. **File naming** — each section: `YYYYMMDD_snake_case_title.md` (use today's date if no specific date applies)
|
|
22
|
+
|
|
23
|
+
Present the outline to the user for approval before proceeding.
|
|
24
|
+
|
|
25
|
+
### Phase 2: Parallel Content Generation
|
|
26
|
+
|
|
27
|
+
Spawn **parallel subagents** to write sections simultaneously. Batch sections across agents — each agent handles 2-4 sections depending on total count.
|
|
28
|
+
|
|
29
|
+
**Agent spawning rules:**
|
|
30
|
+
- All agent spawns for a batch MUST be in a **single message** for true parallel execution
|
|
31
|
+
- Each agent gets a clear, self-contained prompt with: section title, scope, key points to cover, tone/style, and the output file path
|
|
32
|
+
- Agents write their sections directly as markdown files
|
|
33
|
+
- For research-heavy reports: use `investigative-researcher`, `multi-perspective-researcher`, and `claude-researcher` agent types to get different perspectives
|
|
34
|
+
- For content-heavy reports: use `general-purpose` agents with detailed writing instructions
|
|
35
|
+
|
|
36
|
+
**Prompt template for each agent:**
|
|
37
|
+
```
|
|
38
|
+
Read [any reference files if applicable].
|
|
39
|
+
Write a detailed markdown file at [output path] covering:
|
|
40
|
+
- Section title: [title]
|
|
41
|
+
- Scope: [what to cover]
|
|
42
|
+
- Key points: [specific items to include]
|
|
43
|
+
- Tone: [objective/persuasive/technical/casual]
|
|
44
|
+
- Structure: Use ## for main headings, ### for sub-sections
|
|
45
|
+
- Include sources with full URLs where applicable
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Batch sizing guide:**
|
|
49
|
+
|
|
50
|
+
| Total sections | Agents | Sections per agent |
|
|
51
|
+
|----------------|--------|--------------------|
|
|
52
|
+
| 3-6 | 2-3 | 2 each |
|
|
53
|
+
| 7-12 | 3-4 | 2-3 each |
|
|
54
|
+
| 13+ | 5 | 3-4 each |
|
|
55
|
+
|
|
56
|
+
### Phase 3: Overview File
|
|
57
|
+
|
|
58
|
+
After all agents complete, write a `00_overview.md` file containing:
|
|
59
|
+
- Report title and date
|
|
60
|
+
- Methodology description
|
|
61
|
+
- Table of contents linking to each section
|
|
62
|
+
- Executive summary synthesizing key findings from all sections
|
|
63
|
+
|
|
64
|
+
### Phase 4: Combine and Convert to PDF
|
|
65
|
+
|
|
66
|
+
**Step 1: Combine all markdown files into one.**
|
|
67
|
+
|
|
68
|
+
Concatenate files in order with page break dividers between sections:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
cat \
|
|
72
|
+
00_overview.md \
|
|
73
|
+
<(echo -e '\n\n<div style="page-break-before: always"></div>\n\n---\n\n') \
|
|
74
|
+
YYYYMMDD_section_one.md \
|
|
75
|
+
<(echo -e '\n\n<div style="page-break-before: always"></div>\n\n---\n\n') \
|
|
76
|
+
YYYYMMDD_section_two.md \
|
|
77
|
+
... \
|
|
78
|
+
> /tmp/combined_raw.md
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Step 2: Add PDF frontmatter.**
|
|
82
|
+
|
|
83
|
+
Prepend YAML frontmatter with styling, then append the combined content:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cat > <report_name>.md << 'FRONTMATTER'
|
|
87
|
+
---
|
|
88
|
+
pdf_options:
|
|
89
|
+
format: A4
|
|
90
|
+
margin: 25mm
|
|
91
|
+
printBackground: true
|
|
92
|
+
stylesheet: https://cdn.jsdelivr.net/npm/github-markdown-css/github-markdown.css
|
|
93
|
+
body_class: markdown-body
|
|
94
|
+
css: |-
|
|
95
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 11px; line-height: 1.6; color: #1a1a1a; }
|
|
96
|
+
h1 { font-size: 22px; border-bottom: 2px solid #333; padding-bottom: 8px; }
|
|
97
|
+
h2 { font-size: 17px; }
|
|
98
|
+
h3 { font-size: 14px; }
|
|
99
|
+
table { border-collapse: collapse; width: 100%; margin: 12px 0; }
|
|
100
|
+
th, td { border: 1px solid #ccc; padding: 6px 10px; text-align: left; font-size: 10px; }
|
|
101
|
+
th { background: #f0f0f0; }
|
|
102
|
+
blockquote { border-left: 3px solid #666; padding-left: 12px; color: #444; }
|
|
103
|
+
hr { border: none; border-top: 1px solid #ccc; margin: 20px 0; }
|
|
104
|
+
a { color: #0366d6; text-decoration: none; }
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
FRONTMATTER
|
|
108
|
+
cat /tmp/combined_raw.md >> <report_name>.md
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Step 3: Generate the PDF.**
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
bunx --bun md-to-pdf <report_name>.md
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Step 4: Verify.**
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
ls -lh <report_name>.pdf
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Report the file path and size to the user.
|
|
124
|
+
|
|
125
|
+
### Phase 5: Translation (Optional)
|
|
126
|
+
|
|
127
|
+
If the user requests translation to another language:
|
|
128
|
+
|
|
129
|
+
1. Spawn parallel agents (same batching as Phase 2) to translate each markdown file
|
|
130
|
+
2. Translated files get the same name with a `_<lang>` suffix (e.g., `_hu`, `_de`, `_es`)
|
|
131
|
+
3. Keep all markdown formatting, links, and structure identical — only translate text content
|
|
132
|
+
4. Keep source link titles in their original language; translate surrounding descriptive text
|
|
133
|
+
5. Combine and convert the translated files to PDF using the same Phase 4 process with a `_<lang>` suffix on the final filename
|
|
134
|
+
|
|
135
|
+
## Important
|
|
136
|
+
|
|
137
|
+
- All subagent spawns for a batch MUST be in a **single message** for true parallel execution
|
|
138
|
+
- Do NOT run agents sequentially — that defeats the purpose of parallel generation
|
|
139
|
+
- Each agent writes its own files directly — the orchestrating agent only combines and converts
|
|
140
|
+
- Always verify the final PDF exists and report its size
|
|
141
|
+
- Use `bunx --bun md-to-pdf` (NOT npx) for PDF conversion
|
|
142
|
+
- Individual markdown files are kept alongside the PDF so the user can edit and regenerate
|
|
@@ -9,14 +9,15 @@ argument-hint: <topic or question>
|
|
|
9
9
|
| User says | Mode | Agents |
|
|
10
10
|
|-----------|------|--------|
|
|
11
11
|
| "quick research" / "minor research" | Quick | 1 agent |
|
|
12
|
-
| "research" / "do research" (default) | Standard |
|
|
13
|
-
| "extensive research" / "deep research" | Extensive |
|
|
12
|
+
| "research" / "do research" (default) | Standard | 3 parallel agents |
|
|
13
|
+
| "extensive research" / "deep research" | Extensive | 8 parallel agents |
|
|
14
14
|
|
|
15
15
|
## Available Researcher Agents
|
|
16
16
|
|
|
17
17
|
- **claude-researcher** — academic depth, query decomposition, scholarly synthesis
|
|
18
18
|
- **multi-perspective-researcher** — breadth, multiple angles, diverse viewpoints
|
|
19
19
|
- **investigative-researcher** — verification rigor, triple-checks, source credibility
|
|
20
|
+
- **grok-researcher** — real-time data via Grok/X API, breaking news, social sentiment (falls back to WebSearch with recency focus if no API key)
|
|
20
21
|
|
|
21
22
|
## Quick Mode
|
|
22
23
|
|
|
@@ -28,18 +29,20 @@ Wait for the result, then deliver it directly with light formatting.
|
|
|
28
29
|
|
|
29
30
|
## Standard Mode (Default)
|
|
30
31
|
|
|
31
|
-
Craft **
|
|
32
|
+
Craft **3 different queries** optimized for each researcher's strengths, then spawn all **in parallel (in a single message)**:
|
|
32
33
|
|
|
33
34
|
- Spawn `claude-researcher` with a query optimized for depth/analysis
|
|
34
35
|
- Spawn `multi-perspective-researcher` with a query optimized for breadth/perspectives
|
|
36
|
+
- Spawn `grok-researcher` with a query optimized for real-time data, recent developments, current state
|
|
35
37
|
|
|
36
38
|
**Query design:**
|
|
37
39
|
- claude-researcher: focus on authoritative sources, technical depth, how/why
|
|
38
40
|
- multi-perspective-researcher: focus on different stakeholder views, trade-offs, alternatives
|
|
41
|
+
- grok-researcher: focus on latest news, breaking developments, social sentiment, what's happening right now
|
|
39
42
|
|
|
40
43
|
## Extensive Mode
|
|
41
44
|
|
|
42
|
-
Craft **
|
|
45
|
+
Craft **8 queries** (2 per researcher type, each from a different angle), then spawn all **in parallel (in a single message)**:
|
|
43
46
|
|
|
44
47
|
- Spawn `claude-researcher` — angle 1: core technical depth
|
|
45
48
|
- Spawn `claude-researcher` — angle 2: historical context / evolution
|
|
@@ -47,6 +50,8 @@ Craft **6 queries** (2 per researcher type, each from a different angle), then s
|
|
|
47
50
|
- Spawn `multi-perspective-researcher` — angle 4: cross-domain connections
|
|
48
51
|
- Spawn `investigative-researcher` — angle 5: verify key claims
|
|
49
52
|
- Spawn `investigative-researcher` — angle 6: find contradictions / counter-evidence
|
|
53
|
+
- Spawn `grok-researcher` — angle 7: real-time developments and breaking news
|
|
54
|
+
- Spawn `grok-researcher` — angle 8: social sentiment, public reaction, trending discourse
|
|
50
55
|
|
|
51
56
|
## Synthesis (All Modes)
|
|
52
57
|
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Grok Search — CLI tool for real-time search via the Grok/X API.
|
|
4
|
+
*
|
|
5
|
+
* Uses the Grok Responses API with web_search and x_search tools
|
|
6
|
+
* to fetch real-time information from the web and X (Twitter).
|
|
7
|
+
*
|
|
8
|
+
* Requires XAI_API_KEY environment variable.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* bun grok-search.ts -- <query> [--sources web,x] [--max-tokens 2048]
|
|
12
|
+
* bun grok-search.ts -- "latest AI news" --sources x
|
|
13
|
+
* bun grok-search.ts -- "bitcoin price today" --sources web
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { parseArgs } from "node:util";
|
|
17
|
+
|
|
18
|
+
const API_BASE = "https://api.x.ai/v1";
|
|
19
|
+
const MODEL = "grok-4-1-fast-non-reasoning";
|
|
20
|
+
|
|
21
|
+
type SourceType = "web" | "x";
|
|
22
|
+
type ToolType = "web_search" | "x_search";
|
|
23
|
+
|
|
24
|
+
interface UrlCitation {
|
|
25
|
+
type: "url_citation";
|
|
26
|
+
url: string;
|
|
27
|
+
title?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ContentPart {
|
|
31
|
+
type: string;
|
|
32
|
+
text?: string;
|
|
33
|
+
annotations?: UrlCitation[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface OutputItem {
|
|
37
|
+
type: string;
|
|
38
|
+
content?: ContentPart[];
|
|
39
|
+
status?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface GrokResponse {
|
|
43
|
+
output?: OutputItem[];
|
|
44
|
+
error?: { message: string };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function loadApiKey(): string {
|
|
48
|
+
const key = process.env.XAI_API_KEY;
|
|
49
|
+
if (!key) {
|
|
50
|
+
console.error("Error: XAI_API_KEY environment variable is not set.");
|
|
51
|
+
console.error("Get an API key at https://console.x.ai/");
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
return key;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseSources(raw: string): SourceType[] {
|
|
58
|
+
const valid: SourceType[] = ["web", "x"];
|
|
59
|
+
const parts = raw.split(",").map((s) => s.trim().toLowerCase());
|
|
60
|
+
const sources = parts.filter((s): s is SourceType => valid.includes(s as SourceType));
|
|
61
|
+
if (sources.length === 0) {
|
|
62
|
+
console.error(`Error: Invalid sources "${raw}". Valid: web, x`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
return sources;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function sourcesToTools(sources: SourceType[]): ToolType[] {
|
|
69
|
+
const map: Record<SourceType, ToolType> = { web: "web_search", x: "x_search" };
|
|
70
|
+
return sources.map((s) => map[s]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function grokSearch(
|
|
74
|
+
query: string,
|
|
75
|
+
sources: SourceType[],
|
|
76
|
+
maxTokens: number
|
|
77
|
+
): Promise<void> {
|
|
78
|
+
const apiKey = loadApiKey();
|
|
79
|
+
const tools = sourcesToTools(sources);
|
|
80
|
+
|
|
81
|
+
const body = {
|
|
82
|
+
model: MODEL,
|
|
83
|
+
input: [
|
|
84
|
+
{
|
|
85
|
+
role: "system" as const,
|
|
86
|
+
content:
|
|
87
|
+
"You are a research assistant. Provide factual, sourced answers about current events and real-time information. Always include dates and source context. Be thorough but concise.",
|
|
88
|
+
},
|
|
89
|
+
{ role: "user" as const, content: query },
|
|
90
|
+
],
|
|
91
|
+
tools: tools.map((type) => ({ type })),
|
|
92
|
+
max_output_tokens: maxTokens,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const response = await fetch(`${API_BASE}/responses`, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: {
|
|
98
|
+
Authorization: `Bearer ${apiKey}`,
|
|
99
|
+
"Content-Type": "application/json",
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify(body),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
const err = await response.text().catch(() => "");
|
|
106
|
+
console.error(`Error: HTTP ${response.status} — ${err.slice(0, 500)}`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const data = (await response.json()) as GrokResponse;
|
|
111
|
+
|
|
112
|
+
if (data.error) {
|
|
113
|
+
console.error(`Error: ${data.error.message}`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!data.output || data.output.length === 0) {
|
|
118
|
+
console.error("Error: No response output from Grok.");
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Extract text and citations from message output items
|
|
123
|
+
const textParts: string[] = [];
|
|
124
|
+
const citations = new Map<string, string>(); // url → title
|
|
125
|
+
|
|
126
|
+
for (const item of data.output) {
|
|
127
|
+
if (item.type !== "message" || !item.content) continue;
|
|
128
|
+
for (const part of item.content) {
|
|
129
|
+
if (part.type === "output_text" && part.text) {
|
|
130
|
+
textParts.push(part.text);
|
|
131
|
+
}
|
|
132
|
+
if (part.annotations) {
|
|
133
|
+
for (const ann of part.annotations) {
|
|
134
|
+
if (ann.type === "url_citation" && ann.url) {
|
|
135
|
+
citations.set(ann.url, ann.title ?? ann.url);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (textParts.length === 0) {
|
|
143
|
+
console.error("Error: No text content in Grok response.");
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(textParts.join("\n\n"));
|
|
148
|
+
|
|
149
|
+
if (citations.size > 0) {
|
|
150
|
+
console.log("\n---\n## Sources\n");
|
|
151
|
+
for (const [url, title] of citations) {
|
|
152
|
+
console.log(`- [${title}](${url})`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function run() {
|
|
158
|
+
const { positionals, values } = parseArgs({
|
|
159
|
+
allowPositionals: true,
|
|
160
|
+
options: {
|
|
161
|
+
sources: { type: "string", short: "s", default: "web,x" },
|
|
162
|
+
"max-tokens": { type: "string", short: "m", default: "2048" },
|
|
163
|
+
help: { type: "boolean", short: "h" },
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (values.help || positionals.length === 0) {
|
|
168
|
+
console.log(`Grok Search — real-time search via Grok/X API
|
|
169
|
+
|
|
170
|
+
Usage:
|
|
171
|
+
bun grok-search.ts -- <query> [options]
|
|
172
|
+
|
|
173
|
+
Options:
|
|
174
|
+
--sources, -s <web,x> Comma-separated source types (default: web,x)
|
|
175
|
+
--max-tokens, -m <n> Max response tokens (default: 2048)
|
|
176
|
+
--help, -h Show this help
|
|
177
|
+
|
|
178
|
+
Examples:
|
|
179
|
+
bun grok-search.ts -- "latest AI news"
|
|
180
|
+
bun grok-search.ts -- "bitcoin price" --sources web
|
|
181
|
+
bun grok-search.ts -- "reactions to new iPhone" --sources x`);
|
|
182
|
+
process.exit(0);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const query = positionals.join(" ");
|
|
186
|
+
const sources = parseSources(values.sources ?? "web,x");
|
|
187
|
+
const maxTokens = Number.parseInt(values["max-tokens"] ?? "2048", 10);
|
|
188
|
+
|
|
189
|
+
await grokSearch(query, sources, maxTokens);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (import.meta.main) run();
|