@ahkohd/pi-yagami-search 0.1.2

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 ADDED
@@ -0,0 +1,39 @@
1
+ # pi-yagami-search
2
+
3
+ Pi package that adds Yagami-backed web search tools.
4
+
5
+ ## Install
6
+
7
+ From npm:
8
+
9
+ ```bash
10
+ pi install npm:@ahkohd/pi-yagami-search
11
+ ```
12
+
13
+ From a local checkout:
14
+
15
+ ```bash
16
+ pi install ./packages/pi-yagami-search
17
+ ```
18
+
19
+ ## Runtime requirements
20
+
21
+ - A running Yagami daemon (default: `http://127.0.0.1:43111`)
22
+ - Optionally set `YAGAMI_URL` to override the daemon URL
23
+
24
+ ```bash
25
+ export YAGAMI_URL=http://127.0.0.1:43111
26
+ ```
27
+
28
+ ## Tools provided
29
+
30
+ - `web_search`
31
+ - `get_code_context`
32
+ - `fetch_content`
33
+ - `company_research`
34
+ - `web_search_advanced`
35
+ - `find_similar`
36
+ - `deep_research_start`
37
+ - `deep_research_check`
38
+
39
+ These tool names match Pi's common search tool surface.
@@ -0,0 +1,273 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import { StringEnum } from "@mariozechner/pi-ai";
3
+ import { Text } from "@mariozechner/pi-tui";
4
+ import { Type } from "@sinclair/typebox";
5
+
6
+ const YAGAMI_BASE = process.env.YAGAMI_URL || "http://127.0.0.1:43111";
7
+
8
+ type YagamiResponse = {
9
+ ok?: boolean;
10
+ error?: string;
11
+ result?: unknown;
12
+ };
13
+
14
+ type RenderTheme = {
15
+ bold: (text: string) => string;
16
+ fg: (token: string, text: string) => string;
17
+ };
18
+
19
+ type RenderArgs = Record<string, unknown>;
20
+
21
+ function asString(value: unknown): string {
22
+ return typeof value === "string" ? value : "";
23
+ }
24
+
25
+ function errorMessage(error: unknown): string {
26
+ if (error instanceof Error) return error.message;
27
+ return String(error);
28
+ }
29
+
30
+ function isRecord(value: unknown): value is Record<string, unknown> {
31
+ return typeof value === "object" && value !== null;
32
+ }
33
+
34
+ function renderCallLine(toolName: string, value: unknown, theme: RenderTheme): Text {
35
+ return new Text(`${theme.bold(toolName)} ${theme.fg("muted", asString(value))}`, 0, 0);
36
+ }
37
+
38
+ async function yagamiPost(endpoint: string, body: Record<string, unknown>): Promise<unknown> {
39
+ const res = await fetch(`${YAGAMI_BASE}${endpoint}`, {
40
+ method: "POST",
41
+ headers: { "Content-Type": "application/json" },
42
+ body: JSON.stringify(body),
43
+ });
44
+
45
+ if (!res.ok) {
46
+ const text = await res.text();
47
+ throw new Error(`Yagami error ${res.status}: ${text}`);
48
+ }
49
+
50
+ const data = (await res.json()) as YagamiResponse;
51
+ if (!data.ok) throw new Error(data.error || "Unknown yagami error");
52
+ return data.result;
53
+ }
54
+
55
+ function errResult(message: string) {
56
+ return { content: [{ type: "text" as const, text: message }], details: {}, isError: true };
57
+ }
58
+
59
+ function okResult(text: string) {
60
+ return { content: [{ type: "text" as const, text }], details: {} };
61
+ }
62
+
63
+ function formatResult(result: unknown): string {
64
+ if (typeof result === "string") return result;
65
+ if (isRecord(result) && typeof result.answer === "string") return result.answer;
66
+ if (isRecord(result) && typeof result.text === "string") return result.text;
67
+ return JSON.stringify(result, null, 2);
68
+ }
69
+
70
+ export default function (pi: ExtensionAPI) {
71
+ pi.registerTool({
72
+ name: "web_search",
73
+ label: "Yagami Search",
74
+ description:
75
+ "Search the web for any topic and get clean, ready-to-use content.\n\n" +
76
+ "Best for: Finding current information, news, facts, or answering questions about any topic.\n" +
77
+ "Returns: Clean text content from top search results, ready for LLM use.",
78
+ renderCall(args: RenderArgs, theme: RenderTheme) {
79
+ return renderCallLine("web_search", args.query, theme);
80
+ },
81
+ parameters: Type.Object({
82
+ query: Type.String({ description: "Web search query" }),
83
+ }),
84
+ async execute(_id, params) {
85
+ try {
86
+ const result = await yagamiPost("/search", { query: params.query });
87
+ return okResult(formatResult(result));
88
+ } catch (error: unknown) {
89
+ return errResult(`Search error: ${errorMessage(error)}`);
90
+ }
91
+ },
92
+ });
93
+
94
+ pi.registerTool({
95
+ name: "get_code_context",
96
+ label: "Yagami Code Search",
97
+ description:
98
+ "Find code examples, documentation, and programming solutions. Searches GitHub, Stack Overflow, and official docs.\n\n" +
99
+ "Best for: Any programming question - API usage, library examples, code snippets, debugging help.\n" +
100
+ "Returns: Relevant code and documentation, formatted for easy reading.",
101
+ renderCall(args: RenderArgs, theme: RenderTheme) {
102
+ return renderCallLine("get_code_context", args.query, theme);
103
+ },
104
+ parameters: Type.Object({
105
+ query: Type.String({ description: "Search query for code context." }),
106
+ }),
107
+ async execute(_id, params) {
108
+ try {
109
+ const result = await yagamiPost("/code-context", { query: params.query });
110
+ return okResult(formatResult(result));
111
+ } catch (error: unknown) {
112
+ return errResult(`Code search error: ${errorMessage(error)}`);
113
+ }
114
+ },
115
+ });
116
+
117
+ pi.registerTool({
118
+ name: "fetch_content",
119
+ label: "Yagami Fetch",
120
+ description:
121
+ "Get the full content of a specific webpage. Use when you have an exact URL.\n\n" +
122
+ "Best for: Extracting content from a known URL.\n" +
123
+ "Returns: Full text content and metadata from the page.",
124
+ renderCall(args: RenderArgs, theme: RenderTheme) {
125
+ return renderCallLine("fetch_content", args.url, theme);
126
+ },
127
+ parameters: Type.Object({
128
+ url: Type.String({ description: "URL to crawl and extract content from" }),
129
+ maxCharacters: Type.Optional(Type.Number({ description: "Maximum characters to extract (default: 3000)" })),
130
+ }),
131
+ async execute(_id, params) {
132
+ try {
133
+ const result = await yagamiPost("/fetch", {
134
+ url: params.url,
135
+ maxCharacters: params.maxCharacters || 3000,
136
+ });
137
+ return okResult(formatResult(result));
138
+ } catch (error: unknown) {
139
+ return errResult(`Fetch error: ${errorMessage(error)}`);
140
+ }
141
+ },
142
+ });
143
+
144
+ pi.registerTool({
145
+ name: "company_research",
146
+ label: "Yagami Company Research",
147
+ description:
148
+ "Research any company to get business information, news, and insights.\n\n" +
149
+ "Best for: Learning about a company's products, services, recent news, or industry position.\n" +
150
+ "Returns: Company information from trusted business sources.",
151
+ renderCall(args: RenderArgs, theme: RenderTheme) {
152
+ return renderCallLine("company_research", args.companyName, theme);
153
+ },
154
+ parameters: Type.Object({
155
+ companyName: Type.String({ description: "Name of the company to research" }),
156
+ }),
157
+ async execute(_id, params) {
158
+ try {
159
+ const result = await yagamiPost("/company-research", { companyName: params.companyName });
160
+ return okResult(formatResult(result));
161
+ } catch (error: unknown) {
162
+ return errResult(`Company research error: ${errorMessage(error)}`);
163
+ }
164
+ },
165
+ });
166
+
167
+ pi.registerTool({
168
+ name: "web_search_advanced",
169
+ label: "Yagami Advanced Search",
170
+ description:
171
+ "Advanced web search with full control over filters, domains, dates, and content options.\n\n" +
172
+ "Best for: When you need specific filters like date ranges, domain restrictions, or category filters.\n" +
173
+ "Not recommended for: Simple searches - use web_search instead.\n" +
174
+ "Returns: Search results with optional highlights, summaries, and subpage content.",
175
+ renderCall(args: RenderArgs, theme: RenderTheme) {
176
+ return renderCallLine("web_search_advanced", args.query, theme);
177
+ },
178
+ parameters: Type.Object({
179
+ query: Type.String({ description: "Search query" }),
180
+ includeDomains: Type.Optional(
181
+ Type.Array(Type.String(), { description: "Only include results from these domains" }),
182
+ ),
183
+ excludeDomains: Type.Optional(Type.Array(Type.String(), { description: "Exclude results from these domains" })),
184
+ category: Type.Optional(Type.String({ description: "Filter results to a specific category" })),
185
+ }),
186
+ async execute(_id, params) {
187
+ try {
188
+ const result = await yagamiPost("/search/advanced", params);
189
+ return okResult(formatResult(result));
190
+ } catch (error: unknown) {
191
+ return errResult(`Advanced search error: ${errorMessage(error)}`);
192
+ }
193
+ },
194
+ });
195
+
196
+ pi.registerTool({
197
+ name: "find_similar",
198
+ label: "Yagami Similar",
199
+ description:
200
+ "Find web pages similar to a given URL. Useful for finding alternatives, related resources, or similar documentation.",
201
+ renderCall(args: RenderArgs, theme: RenderTheme) {
202
+ return renderCallLine("find_similar", args.url, theme);
203
+ },
204
+ parameters: Type.Object({
205
+ url: Type.String({ description: "URL to find similar pages for" }),
206
+ }),
207
+ async execute(_id, params) {
208
+ try {
209
+ const result = await yagamiPost("/find-similar", { url: params.url });
210
+ return okResult(formatResult(result));
211
+ } catch (error: unknown) {
212
+ return errResult(`Find similar error: ${errorMessage(error)}`);
213
+ }
214
+ },
215
+ });
216
+
217
+ pi.registerTool({
218
+ name: "deep_research_start",
219
+ label: "Yagami Deep Research",
220
+ description:
221
+ "Start an AI research agent that searches, reads, and writes a detailed report.\n\n" +
222
+ "Best for: Complex research questions needing deep analysis and synthesis.\n" +
223
+ "Returns: Research ID - use deep_research_check to get results.\n" +
224
+ "Important: Call deep_research_check with the returned research ID to get the report.",
225
+ renderCall(args: RenderArgs, theme: RenderTheme) {
226
+ const preview = asString(args.instructions).slice(0, 80);
227
+ return renderCallLine("deep_research_start", preview, theme);
228
+ },
229
+ parameters: Type.Object({
230
+ instructions: Type.String({ description: "Complex research question or detailed instructions." }),
231
+ effort: Type.Optional(
232
+ StringEnum(["fast", "balanced", "thorough"] as const, {
233
+ description: "'fast', 'balanced', or 'thorough'. Default: fast",
234
+ }),
235
+ ),
236
+ }),
237
+ async execute(_id, params) {
238
+ try {
239
+ const result = await yagamiPost("/deep-research/start", {
240
+ instructions: params.instructions,
241
+ effort: params.effort || "fast",
242
+ });
243
+ return okResult(JSON.stringify(result, null, 2));
244
+ } catch (error: unknown) {
245
+ return errResult(`Research start error: ${errorMessage(error)}`);
246
+ }
247
+ },
248
+ });
249
+
250
+ pi.registerTool({
251
+ name: "deep_research_check",
252
+ label: "Yagami Research Check",
253
+ description:
254
+ "Check status and get results from a deep research task.\n\n" +
255
+ "Best for: Getting the research report after calling deep_research_start.\n" +
256
+ "Returns: Research report when complete, or status update if still running.\n" +
257
+ "Important: Keep calling with the same research ID until status is 'completed'.",
258
+ renderCall(args: RenderArgs, theme: RenderTheme) {
259
+ return renderCallLine("deep_research_check", args.researchId, theme);
260
+ },
261
+ parameters: Type.Object({
262
+ researchId: Type.String({ description: "The research ID returned from deep_research_start" }),
263
+ }),
264
+ async execute(_id, params) {
265
+ try {
266
+ const result = await yagamiPost("/deep-research/check", { researchId: params.researchId });
267
+ return okResult(JSON.stringify(result, null, 2));
268
+ } catch (error: unknown) {
269
+ return errResult(`Research check error: ${errorMessage(error)}`);
270
+ }
271
+ },
272
+ });
273
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@ahkohd/pi-yagami-search",
3
+ "version": "0.1.2",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "type": "module",
9
+ "description": "Pi package providing Yagami web search tools",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/ahkohd/yagami.git",
13
+ "directory": "packages/pi-yagami-search"
14
+ },
15
+ "homepage": "https://github.com/ahkohd/yagami/tree/main/packages/pi-yagami-search",
16
+ "bugs": {
17
+ "url": "https://github.com/ahkohd/yagami/issues"
18
+ },
19
+ "keywords": [
20
+ "pi-package",
21
+ "yagami",
22
+ "pi-extension",
23
+ "search"
24
+ ],
25
+ "pi": {
26
+ "extensions": [
27
+ "./extensions"
28
+ ]
29
+ },
30
+ "peerDependencies": {
31
+ "@mariozechner/pi-ai": "*",
32
+ "@mariozechner/pi-coding-agent": "*",
33
+ "@mariozechner/pi-tui": "*",
34
+ "@sinclair/typebox": "*"
35
+ },
36
+ "files": [
37
+ "extensions",
38
+ "README.md",
39
+ "package.json"
40
+ ]
41
+ }