portable-agent-layer 0.18.0 → 0.19.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
+ ```
@@ -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 | 2 parallel agents |
13
- | "extensive research" / "deep research" | Extensive | 6 parallel agents |
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 **2 different queries** optimized for each researcher's strengths, then spawn both **in parallel (in a single message)**:
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 **6 queries** (2 per researcher type, each from a different angle), then spawn all **in parallel (in a single message)**:
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();
@@ -20,7 +20,7 @@
20
20
  "Bash(stat //*)",
21
21
  "Bash(readlink //*)",
22
22
  "Bash(bun ~/.agents/skills/*/tools/*.ts *)",
23
- "Bash(bun run tool:wisdom-frame *)"
23
+ "Bash(bun ~/.agents/PAL/tools/*.ts *)"
24
24
  ]
25
25
  },
26
26
  "hooks": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {