workflowskill 0.1.0 → 0.2.1
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 +25 -42
- package/dist/cli/index.mjs +2 -2
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +14 -22
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +9 -4
- package/dist/index.mjs.map +1 -1
- package/dist/{runtime-BY1CFnew.mjs → runtime-CD81H1bx.mjs} +9 -35
- package/dist/runtime-CD81H1bx.mjs.map +1 -0
- package/dist/{html-select-BZPYAr6k.mjs → web-scrape-GeEM_JNl.mjs} +50 -12
- package/dist/web-scrape-GeEM_JNl.mjs.map +1 -0
- package/package.json +1 -4
- package/skill/SKILL.md +60 -30
- package/dist/gmail-CYBJ6Dxa.mjs +0 -209
- package/dist/gmail-CYBJ6Dxa.mjs.map +0 -1
- package/dist/html-select-BZPYAr6k.mjs.map +0 -1
- package/dist/http-request-k9bp5joL.mjs +0 -91
- package/dist/http-request-k9bp5joL.mjs.map +0 -1
- package/dist/runtime-BY1CFnew.mjs.map +0 -1
- package/dist/sheets-CGy8JvVz.mjs +0 -177
- package/dist/sheets-CGy8JvVz.mjs.map +0 -1
package/skill/SKILL.md
CHANGED
|
@@ -406,7 +406,7 @@ steps:
|
|
|
406
406
|
|
|
407
407
|
- id: fetch_details
|
|
408
408
|
type: tool
|
|
409
|
-
tool:
|
|
409
|
+
tool: api.get_item # platform-specific tool; use web.scrape for HTML pages
|
|
410
410
|
each: $steps.get_ids.output.ids # iterate over ids array
|
|
411
411
|
on_error: ignore # skip failed fetches, continue
|
|
412
412
|
inputs:
|
|
@@ -432,9 +432,9 @@ outputs:
|
|
|
432
432
|
value: $steps.fetch_details.output # the collected array of per-iteration results
|
|
433
433
|
```
|
|
434
434
|
|
|
435
|
-
**Pattern: List →
|
|
435
|
+
**Pattern: List → Slice → Fetch Details**
|
|
436
436
|
|
|
437
|
-
Full example
|
|
437
|
+
Full example using generic tool names (substitute platform-specific tools as needed):
|
|
438
438
|
|
|
439
439
|
```yaml
|
|
440
440
|
inputs:
|
|
@@ -443,19 +443,19 @@ inputs:
|
|
|
443
443
|
default: 10
|
|
444
444
|
base_url:
|
|
445
445
|
type: string
|
|
446
|
-
default: "https://
|
|
446
|
+
default: "https://api.example.com/item/"
|
|
447
447
|
|
|
448
448
|
outputs:
|
|
449
|
-
|
|
449
|
+
items:
|
|
450
450
|
type: array
|
|
451
|
-
value: $steps.
|
|
451
|
+
value: $steps.fetch_details.output
|
|
452
452
|
|
|
453
453
|
steps:
|
|
454
|
-
- id:
|
|
454
|
+
- id: get_ids
|
|
455
455
|
type: tool
|
|
456
|
-
tool:
|
|
456
|
+
tool: api.list_items
|
|
457
457
|
inputs:
|
|
458
|
-
url: { type: string, value: "https://
|
|
458
|
+
url: { type: string, value: "https://api.example.com/items" }
|
|
459
459
|
outputs:
|
|
460
460
|
ids: { type: array, value: $result.body }
|
|
461
461
|
|
|
@@ -464,14 +464,16 @@ steps:
|
|
|
464
464
|
operation: filter
|
|
465
465
|
where: $index < $inputs.count # cap iteration count to avoid rate limiting
|
|
466
466
|
inputs:
|
|
467
|
-
items: { type: array, value: $steps.
|
|
467
|
+
items: { type: array, value: $steps.get_ids.output.ids }
|
|
468
468
|
outputs:
|
|
469
469
|
ids: { type: array }
|
|
470
470
|
|
|
471
|
-
- id:
|
|
471
|
+
- id: fetch_details
|
|
472
472
|
type: tool
|
|
473
|
-
tool:
|
|
473
|
+
tool: api.get_item
|
|
474
474
|
each: $steps.slice_ids.output.ids
|
|
475
|
+
delay: "2s"
|
|
476
|
+
retry: { max: 3, delay: "2s", backoff: 1.5 }
|
|
475
477
|
on_error: ignore
|
|
476
478
|
inputs:
|
|
477
479
|
url:
|
|
@@ -500,8 +502,9 @@ Use plain text output (`value: $result`) for the LLM step, then zip the LLM resu
|
|
|
500
502
|
steps:
|
|
501
503
|
- id: fetch_items
|
|
502
504
|
type: tool
|
|
503
|
-
tool:
|
|
505
|
+
tool: api.get_item # platform-specific; use web.scrape for HTML pages
|
|
504
506
|
each: $steps.get_ids.output.ids
|
|
507
|
+
delay: "2s"
|
|
505
508
|
on_error: ignore
|
|
506
509
|
inputs:
|
|
507
510
|
url:
|
|
@@ -594,42 +597,69 @@ outputs:
|
|
|
594
597
|
|
|
595
598
|
When a workflow fetches HTML and extracts structured data, follow this recipe:
|
|
596
599
|
|
|
597
|
-
### Step pattern:
|
|
600
|
+
### Step pattern: scrape → guard
|
|
601
|
+
|
|
602
|
+
`web.scrape` fetches the URL and applies CSS selectors in one step:
|
|
598
603
|
|
|
599
604
|
```yaml
|
|
600
605
|
steps:
|
|
601
|
-
- id:
|
|
606
|
+
- id: scrape_data
|
|
602
607
|
type: tool
|
|
603
|
-
tool:
|
|
608
|
+
tool: web.scrape
|
|
604
609
|
retry: { max: 3, delay: "2s", backoff: 1.5 }
|
|
605
610
|
inputs:
|
|
606
611
|
url: { type: string, value: "https://example.com/search" }
|
|
607
|
-
method: { type: string, value: "GET" }
|
|
608
612
|
headers:
|
|
609
613
|
type: object
|
|
610
614
|
value: { "User-Agent": "Mozilla/5.0", "Accept": "text/html" }
|
|
615
|
+
selector: { type: string, value: "li.result-item" }
|
|
616
|
+
fields:
|
|
617
|
+
type: object
|
|
618
|
+
value:
|
|
619
|
+
title: "h3.title"
|
|
620
|
+
url: "a.link @href"
|
|
621
|
+
id: "@data-pid"
|
|
622
|
+
limit: { type: int, value: 50 }
|
|
611
623
|
outputs:
|
|
612
|
-
|
|
624
|
+
items: { type: array, value: $result.results }
|
|
613
625
|
|
|
614
626
|
- id: guard_empty
|
|
615
627
|
type: exit
|
|
616
|
-
condition: $steps.
|
|
628
|
+
condition: $steps.scrape_data.output.items.length == 0
|
|
617
629
|
status: success
|
|
618
630
|
output: { results: [] }
|
|
631
|
+
inputs: {}
|
|
632
|
+
outputs: {}
|
|
633
|
+
```
|
|
619
634
|
|
|
620
|
-
|
|
635
|
+
### Multi-page scraping with `each`
|
|
636
|
+
|
|
637
|
+
When scraping multiple pages, combine `web.scrape` with `each`:
|
|
638
|
+
|
|
639
|
+
```yaml
|
|
640
|
+
steps:
|
|
641
|
+
- id: get_page_list
|
|
642
|
+
type: tool
|
|
643
|
+
tool: api.get_sitemap # returns list of page URLs to scrape
|
|
644
|
+
outputs:
|
|
645
|
+
pages:
|
|
646
|
+
type: array
|
|
647
|
+
|
|
648
|
+
- id: scrape_pages
|
|
621
649
|
type: tool
|
|
622
|
-
tool:
|
|
650
|
+
tool: web.scrape
|
|
651
|
+
each: $steps.get_page_list.output.pages
|
|
652
|
+
delay: "2s"
|
|
653
|
+
retry: { max: 3, delay: "2s", backoff: 1.5 }
|
|
654
|
+
on_error: ignore
|
|
623
655
|
inputs:
|
|
624
|
-
|
|
625
|
-
selector: { type: string, value: "
|
|
656
|
+
url: { type: string, value: $item }
|
|
657
|
+
selector: { type: string, value: "article.post" }
|
|
626
658
|
fields:
|
|
627
659
|
type: object
|
|
628
660
|
value:
|
|
629
|
-
title: "
|
|
630
|
-
|
|
631
|
-
id: "@data-pid"
|
|
632
|
-
limit: { type: int, value: 50 }
|
|
661
|
+
title: "h1.title"
|
|
662
|
+
body: "div.content"
|
|
633
663
|
outputs:
|
|
634
664
|
items: { type: array, value: $result.results }
|
|
635
665
|
```
|
|
@@ -645,7 +675,7 @@ Follow the Research protocol (Authoring Process, Phase 2) before writing selecto
|
|
|
645
675
|
3. **Always declare inputs and outputs.** They enable validation and composability.
|
|
646
676
|
4. **Use `value` on workflow outputs** to explicitly map step results to workflow outputs. Use `$steps.<id>.output.<field>` expressions. This is preferred over exit steps for producing output.
|
|
647
677
|
5. **Use `value` on step outputs** to map fields from the raw executor result using `$result`. Required for LLM steps (which return raw text/JSON). Useful for tool steps when the response shape differs from what downstream steps need.
|
|
648
|
-
6. **Use `each` for per-item processing** on both tool and LLM steps. Always include `delay` on every `each` loop that calls an external service — `delay: "2s"` for
|
|
678
|
+
6. **Use `each` for per-item processing** on both tool and LLM steps. Always include `delay` on every `each` loop that calls an external service — `delay: "2s"` for tool steps (including `web.scrape`), `delay: "1s"` for LLM steps. See *Iteration Patterns*.
|
|
649
679
|
7. **Add `on_error: ignore` for non-critical steps** like notifications.
|
|
650
680
|
8. **Add `retry` for external API calls** (tool steps that might fail transiently).
|
|
651
681
|
9. **Use `condition` guards for early exits** rather than letting empty data flow through.
|
|
@@ -657,7 +687,7 @@ Follow the Research protocol (Authoring Process, Phase 2) before writing selecto
|
|
|
657
687
|
15. **Use `map` with `$index` for cross-array merging.** When multiple steps produce parallel arrays, use a `map` transform with bracket indexing (`$steps.other.output.field[$index]`) to zip them into structured objects. Never use an LLM step for pure data restructuring.
|
|
658
688
|
16. **When JSON output is used, LLM prompts must say "raw JSON only — no markdown fences, no commentary."** Models default to wrapping JSON in ``` fences. The runtime parses the raw text with `JSON.parse`, which rejects fenced output. Every prompt that expects JSON output must explicitly instruct the model to respond with raw JSON. Put this instruction **last** in the prompt, after all data and task description, immediately before the model generates. This exploits recency bias — the last instruction the model sees is the most influential, especially when data references expand to large content that can push earlier instructions out of focus. Also describe the exact expected shape (e.g., "Your entire response must be a valid JSON object starting with { and ending with }").
|
|
659
689
|
17. **Guard expensive steps behind deterministic exits.** Pattern: fetch → filter → exit guard → LLM. Use deterministic expressions (e.g., `$item.department == "Engineering"` or `$item.title contains "Product Manager"`) in `transform filter` steps before any LLM call. See *Patterns*.
|
|
660
|
-
18. **Prefer bulk endpoints over per-item iteration.** When `each` + `
|
|
690
|
+
18. **Prefer bulk endpoints over per-item iteration.** When per-item `each` + tool calls (including `web.scrape`) are unavoidable, always add `delay: "2s"` (minimum), cap iteration count, and add `retry` with `backoff`. `delay` is not optional — external sites and APIs rate-limit without warning and delays are free. Same applies to `each` + `llm` steps: always add `delay: "1s"`. See *Iteration Patterns*.
|
|
661
691
|
19. **Prefer plain text LLM output over JSON.** For single-value tasks (summarization, classification, scoring), use `value: $result` and instruct the model to return plain text. Reserve JSON (`value: $result.field`) for multi-field output where every field requires LLM reasoning. In `each` + LLM patterns, always use plain text + a `map` transform to zip LLM output with structural data from the source array. See *Iteration Patterns*.
|
|
662
692
|
|
|
663
693
|
## Output Format
|
|
@@ -720,6 +750,6 @@ After writing the file, always validate it against the runtime. The validation c
|
|
|
720
750
|
- [ ] LLM prompts expecting JSON include "raw JSON only — no markdown fences" instruction
|
|
721
751
|
- [ ] LLM steps with `each` prefer plain text output (`value: $result`) over JSON (`value: $result.field`)
|
|
722
752
|
- [ ] Every `each` loop that calls an external service has `delay` (tool steps: `"2s"` minimum; LLM steps: `"1s"` minimum)
|
|
723
|
-
- [ ] `each` + `
|
|
753
|
+
- [ ] `each` + `web.scrape` steps are bounded (preceded by a cap) and have `retry` with `backoff`
|
|
724
754
|
|
|
725
755
|
If validation fails, fix the errors and revalidate.
|
package/dist/gmail-CYBJ6Dxa.mjs
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { gmail } from "@googleapis/gmail";
|
|
2
|
-
|
|
3
|
-
//#region src/dev-tools/tools/gmail.ts
|
|
4
|
-
const searchDescriptor = {
|
|
5
|
-
name: "gmail.search",
|
|
6
|
-
description: "Search Gmail messages matching a query.",
|
|
7
|
-
inputSchema: {
|
|
8
|
-
type: "object",
|
|
9
|
-
properties: {
|
|
10
|
-
query: {
|
|
11
|
-
type: "string",
|
|
12
|
-
description: "Gmail search query (same syntax as Gmail search bar)"
|
|
13
|
-
},
|
|
14
|
-
max_results: {
|
|
15
|
-
type: "number",
|
|
16
|
-
description: "Maximum messages to return (default: 10)"
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
required: ["query"]
|
|
20
|
-
},
|
|
21
|
-
outputSchema: {
|
|
22
|
-
type: "object",
|
|
23
|
-
properties: { messages: {
|
|
24
|
-
type: "array",
|
|
25
|
-
items: {
|
|
26
|
-
type: "object",
|
|
27
|
-
properties: {
|
|
28
|
-
id: { type: "string" },
|
|
29
|
-
from: { type: "string" },
|
|
30
|
-
to: { type: "string" },
|
|
31
|
-
subject: { type: "string" },
|
|
32
|
-
snippet: { type: "string" },
|
|
33
|
-
date: { type: "string" }
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
} }
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
const readDescriptor = {
|
|
40
|
-
name: "gmail.read",
|
|
41
|
-
description: "Read a full Gmail message by ID.",
|
|
42
|
-
inputSchema: {
|
|
43
|
-
type: "object",
|
|
44
|
-
properties: { message_id: {
|
|
45
|
-
type: "string",
|
|
46
|
-
description: "Gmail message ID"
|
|
47
|
-
} },
|
|
48
|
-
required: ["message_id"]
|
|
49
|
-
},
|
|
50
|
-
outputSchema: {
|
|
51
|
-
type: "object",
|
|
52
|
-
properties: {
|
|
53
|
-
id: { type: "string" },
|
|
54
|
-
from: { type: "string" },
|
|
55
|
-
to: { type: "string" },
|
|
56
|
-
subject: { type: "string" },
|
|
57
|
-
body: { type: "string" },
|
|
58
|
-
date: { type: "string" }
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
const sendDescriptor = {
|
|
63
|
-
name: "gmail.send",
|
|
64
|
-
description: "Send an email via Gmail.",
|
|
65
|
-
inputSchema: {
|
|
66
|
-
type: "object",
|
|
67
|
-
properties: {
|
|
68
|
-
to: {
|
|
69
|
-
type: "string",
|
|
70
|
-
description: "Recipient email address"
|
|
71
|
-
},
|
|
72
|
-
subject: {
|
|
73
|
-
type: "string",
|
|
74
|
-
description: "Email subject"
|
|
75
|
-
},
|
|
76
|
-
body: {
|
|
77
|
-
type: "string",
|
|
78
|
-
description: "Email body (plain text)"
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
required: [
|
|
82
|
-
"to",
|
|
83
|
-
"subject",
|
|
84
|
-
"body"
|
|
85
|
-
]
|
|
86
|
-
},
|
|
87
|
-
outputSchema: {
|
|
88
|
-
type: "object",
|
|
89
|
-
properties: { message_id: { type: "string" } }
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
function getHeader(headers, name) {
|
|
93
|
-
return headers?.find((h) => h.name?.toLowerCase() === name.toLowerCase())?.value ?? "";
|
|
94
|
-
}
|
|
95
|
-
function decodeBody(message) {
|
|
96
|
-
const parts = message.payload?.parts;
|
|
97
|
-
if (parts) {
|
|
98
|
-
const textPart = parts.find((p) => p.mimeType === "text/plain");
|
|
99
|
-
if (textPart?.body?.data) return Buffer.from(textPart.body.data, "base64url").toString("utf-8");
|
|
100
|
-
}
|
|
101
|
-
if (message.payload?.body?.data) return Buffer.from(message.payload.body.data, "base64url").toString("utf-8");
|
|
102
|
-
return message.snippet ?? "";
|
|
103
|
-
}
|
|
104
|
-
function createHandlers(auth) {
|
|
105
|
-
const client = gmail({
|
|
106
|
-
version: "v1",
|
|
107
|
-
auth
|
|
108
|
-
});
|
|
109
|
-
async function search(args) {
|
|
110
|
-
const query = args.query;
|
|
111
|
-
const maxResults = args.max_results ?? 10;
|
|
112
|
-
if (!query || typeof query !== "string") return {
|
|
113
|
-
output: null,
|
|
114
|
-
error: "gmail.search: \"query\" is required"
|
|
115
|
-
};
|
|
116
|
-
try {
|
|
117
|
-
const messageIds = (await client.users.messages.list({
|
|
118
|
-
userId: "me",
|
|
119
|
-
q: query,
|
|
120
|
-
maxResults
|
|
121
|
-
})).data.messages ?? [];
|
|
122
|
-
return { output: { messages: await Promise.all(messageIds.map(async (m) => {
|
|
123
|
-
const msg = await client.users.messages.get({
|
|
124
|
-
userId: "me",
|
|
125
|
-
id: m.id,
|
|
126
|
-
format: "metadata",
|
|
127
|
-
metadataHeaders: [
|
|
128
|
-
"From",
|
|
129
|
-
"To",
|
|
130
|
-
"Subject",
|
|
131
|
-
"Date"
|
|
132
|
-
]
|
|
133
|
-
});
|
|
134
|
-
const headers = msg.data.payload?.headers;
|
|
135
|
-
return {
|
|
136
|
-
id: msg.data.id ?? "",
|
|
137
|
-
from: getHeader(headers, "From"),
|
|
138
|
-
to: getHeader(headers, "To"),
|
|
139
|
-
subject: getHeader(headers, "Subject"),
|
|
140
|
-
snippet: msg.data.snippet ?? "",
|
|
141
|
-
date: getHeader(headers, "Date")
|
|
142
|
-
};
|
|
143
|
-
})) } };
|
|
144
|
-
} catch (err) {
|
|
145
|
-
return {
|
|
146
|
-
output: null,
|
|
147
|
-
error: `gmail.search failed: ${err instanceof Error ? err.message : String(err)}`
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
async function read(args) {
|
|
152
|
-
const messageId = args.message_id;
|
|
153
|
-
if (!messageId || typeof messageId !== "string") return {
|
|
154
|
-
output: null,
|
|
155
|
-
error: "gmail.read: \"message_id\" is required"
|
|
156
|
-
};
|
|
157
|
-
try {
|
|
158
|
-
const msg = await client.users.messages.get({
|
|
159
|
-
userId: "me",
|
|
160
|
-
id: messageId,
|
|
161
|
-
format: "full"
|
|
162
|
-
});
|
|
163
|
-
const headers = msg.data.payload?.headers;
|
|
164
|
-
return { output: {
|
|
165
|
-
id: msg.data.id ?? "",
|
|
166
|
-
from: getHeader(headers, "From"),
|
|
167
|
-
to: getHeader(headers, "To"),
|
|
168
|
-
subject: getHeader(headers, "Subject"),
|
|
169
|
-
body: decodeBody(msg.data),
|
|
170
|
-
date: getHeader(headers, "Date")
|
|
171
|
-
} };
|
|
172
|
-
} catch (err) {
|
|
173
|
-
return {
|
|
174
|
-
output: null,
|
|
175
|
-
error: `gmail.read failed: ${err instanceof Error ? err.message : String(err)}`
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
async function send(args) {
|
|
180
|
-
const to = args.to;
|
|
181
|
-
const subject = args.subject;
|
|
182
|
-
const body = args.body;
|
|
183
|
-
if (!to || !subject || !body) return {
|
|
184
|
-
output: null,
|
|
185
|
-
error: "gmail.send: \"to\", \"subject\", and \"body\" are required"
|
|
186
|
-
};
|
|
187
|
-
try {
|
|
188
|
-
const raw = Buffer.from(`To: ${to}\r\nSubject: ${subject}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n${body}`).toString("base64url");
|
|
189
|
-
return { output: { message_id: (await client.users.messages.send({
|
|
190
|
-
userId: "me",
|
|
191
|
-
requestBody: { raw }
|
|
192
|
-
})).data.id ?? "" } };
|
|
193
|
-
} catch (err) {
|
|
194
|
-
return {
|
|
195
|
-
output: null,
|
|
196
|
-
error: `gmail.send failed: ${err instanceof Error ? err.message : String(err)}`
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return {
|
|
201
|
-
search,
|
|
202
|
-
read,
|
|
203
|
-
send
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
//#endregion
|
|
208
|
-
export { createHandlers, readDescriptor, searchDescriptor, sendDescriptor };
|
|
209
|
-
//# sourceMappingURL=gmail-CYBJ6Dxa.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gmail-CYBJ6Dxa.mjs","names":[],"sources":["../src/dev-tools/tools/gmail.ts"],"sourcesContent":["// Tools: gmail.search, gmail.read, gmail.send — Gmail integration via googleapis.\n\nimport { gmail, gmail_v1 } from '@googleapis/gmail';\nimport type { OAuth2Client } from 'google-auth-library';\nimport type { ToolDescriptor, ToolResult } from '../../types/index.js';\n\n// ─── Tool descriptors ────────────────────────────────────────────────────────\n\nexport const searchDescriptor: ToolDescriptor = {\n name: 'gmail.search',\n description: 'Search Gmail messages matching a query.',\n inputSchema: {\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Gmail search query (same syntax as Gmail search bar)' },\n max_results: { type: 'number', description: 'Maximum messages to return (default: 10)' },\n },\n required: ['query'],\n },\n outputSchema: {\n type: 'object',\n properties: {\n messages: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: { type: 'string' },\n from: { type: 'string' },\n to: { type: 'string' },\n subject: { type: 'string' },\n snippet: { type: 'string' },\n date: { type: 'string' },\n },\n },\n },\n },\n },\n};\n\nexport const readDescriptor: ToolDescriptor = {\n name: 'gmail.read',\n description: 'Read a full Gmail message by ID.',\n inputSchema: {\n type: 'object',\n properties: {\n message_id: { type: 'string', description: 'Gmail message ID' },\n },\n required: ['message_id'],\n },\n outputSchema: {\n type: 'object',\n properties: {\n id: { type: 'string' },\n from: { type: 'string' },\n to: { type: 'string' },\n subject: { type: 'string' },\n body: { type: 'string' },\n date: { type: 'string' },\n },\n },\n};\n\nexport const sendDescriptor: ToolDescriptor = {\n name: 'gmail.send',\n description: 'Send an email via Gmail.',\n inputSchema: {\n type: 'object',\n properties: {\n to: { type: 'string', description: 'Recipient email address' },\n subject: { type: 'string', description: 'Email subject' },\n body: { type: 'string', description: 'Email body (plain text)' },\n },\n required: ['to', 'subject', 'body'],\n },\n outputSchema: {\n type: 'object',\n properties: {\n message_id: { type: 'string' },\n },\n },\n};\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction getHeader(\n headers: gmail_v1.Schema$MessagePartHeader[] | undefined,\n name: string,\n): string {\n return headers?.find((h) => h.name?.toLowerCase() === name.toLowerCase())?.value ?? '';\n}\n\nfunction decodeBody(message: gmail_v1.Schema$Message): string {\n // Try to get plain text body from parts\n const parts = message.payload?.parts;\n if (parts) {\n const textPart = parts.find((p) => p.mimeType === 'text/plain');\n if (textPart?.body?.data) {\n return Buffer.from(textPart.body.data, 'base64url').toString('utf-8');\n }\n }\n // Fallback to payload body\n if (message.payload?.body?.data) {\n return Buffer.from(message.payload.body.data, 'base64url').toString('utf-8');\n }\n return message.snippet ?? '';\n}\n\n// ─── Handlers ────────────────────────────────────────────────────────────────\n\nexport function createHandlers(auth: OAuth2Client) {\n const client = gmail({ version: 'v1', auth });\n\n async function search(args: Record<string, unknown>): Promise<ToolResult> {\n const query = args.query as string | undefined;\n const maxResults = (args.max_results as number | undefined) ?? 10;\n\n if (!query || typeof query !== 'string') {\n return { output: null, error: 'gmail.search: \"query\" is required' };\n }\n\n try {\n const listRes = await client.users.messages.list({\n userId: 'me',\n q: query,\n maxResults,\n });\n\n const messageIds = listRes.data.messages ?? [];\n const messages = await Promise.all(\n messageIds.map(async (m: gmail_v1.Schema$Message) => {\n const msg = await client.users.messages.get({\n userId: 'me',\n id: m.id!,\n format: 'metadata',\n metadataHeaders: ['From', 'To', 'Subject', 'Date'],\n });\n const headers = msg.data.payload?.headers;\n return {\n id: msg.data.id ?? '',\n from: getHeader(headers, 'From'),\n to: getHeader(headers, 'To'),\n subject: getHeader(headers, 'Subject'),\n snippet: msg.data.snippet ?? '',\n date: getHeader(headers, 'Date'),\n };\n }),\n );\n\n return { output: { messages } };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { output: null, error: `gmail.search failed: ${message}` };\n }\n }\n\n async function read(args: Record<string, unknown>): Promise<ToolResult> {\n const messageId = args.message_id as string | undefined;\n\n if (!messageId || typeof messageId !== 'string') {\n return { output: null, error: 'gmail.read: \"message_id\" is required' };\n }\n\n try {\n const msg = await client.users.messages.get({\n userId: 'me',\n id: messageId,\n format: 'full',\n });\n\n const headers = msg.data.payload?.headers;\n return {\n output: {\n id: msg.data.id ?? '',\n from: getHeader(headers, 'From'),\n to: getHeader(headers, 'To'),\n subject: getHeader(headers, 'Subject'),\n body: decodeBody(msg.data),\n date: getHeader(headers, 'Date'),\n },\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { output: null, error: `gmail.read failed: ${message}` };\n }\n }\n\n async function send(args: Record<string, unknown>): Promise<ToolResult> {\n const to = args.to as string | undefined;\n const subject = args.subject as string | undefined;\n const body = args.body as string | undefined;\n\n if (!to || !subject || !body) {\n return { output: null, error: 'gmail.send: \"to\", \"subject\", and \"body\" are required' };\n }\n\n try {\n const raw = Buffer.from(\n `To: ${to}\\r\\nSubject: ${subject}\\r\\nContent-Type: text/plain; charset=utf-8\\r\\n\\r\\n${body}`,\n ).toString('base64url');\n\n const res = await client.users.messages.send({\n userId: 'me',\n requestBody: { raw },\n });\n\n return { output: { message_id: res.data.id ?? '' } };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { output: null, error: `gmail.send failed: ${message}` };\n }\n }\n\n return { search, read, send };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"html-select-BZPYAr6k.mjs","names":[],"sources":["../src/dev-tools/tools/html-select.ts"],"sourcesContent":["// Tool: html.select — extract data from HTML using CSS selectors.\n\nimport * as cheerio from 'cheerio';\nimport type { ToolDescriptor, ToolResult } from '../../types/index.js';\n\nexport interface HtmlSelectArgs {\n html: string;\n selector: string;\n attribute?: string;\n limit?: number;\n fields?: Record<string, string>;\n}\n\nexport const descriptor: ToolDescriptor = {\n name: 'html.select',\n description:\n 'Extract data from HTML using CSS selectors. Returns text content, attribute values, or structured objects.',\n inputSchema: {\n type: 'object',\n properties: {\n html: { type: 'string', description: 'HTML content to search' },\n selector: { type: 'string', description: 'CSS selector to match elements' },\n attribute: {\n type: 'string',\n description: 'Attribute to extract instead of text content (ignored when fields is present)',\n },\n limit: { type: 'number', description: 'Maximum number of results to return' },\n fields: {\n type: 'object',\n description:\n 'Map of field names to sub-selectors for structured extraction. Each value is a CSS selector scoped to the matched parent. Append \" @attr\" to extract an attribute (e.g. \"a @href\", \"img @src\"). Bare \"@attr\" extracts from the parent itself. When present, returns array of objects instead of strings.',\n },\n },\n required: ['html', 'selector'],\n },\n outputSchema: {\n type: 'object',\n properties: {\n results: {\n type: 'array',\n description: 'Extracted values: strings without fields, objects with fields',\n },\n },\n },\n};\n\n/**\n * Parse a field spec string into a sub-selector and optional attribute name.\n * Formats: \"h3.title\" → text, \"a.link @href\" → attribute, \"@data-id\" → parent attribute.\n */\nfunction parseFieldSpec(spec: string): { subSelector: string | null; attr: string | null } {\n const atIndex = spec.lastIndexOf(' @');\n if (atIndex >= 0) {\n // \"sub-selector @attr\"\n const subSelector = spec.slice(0, atIndex).trim() || null;\n const attr = spec.slice(atIndex + 2).trim();\n return { subSelector, attr: attr || null };\n }\n if (spec.startsWith('@')) {\n // Bare \"@attr\" — extract attribute from parent\n return { subSelector: null, attr: spec.slice(1) };\n }\n // Plain sub-selector — extract text\n return { subSelector: spec, attr: null };\n}\n\nexport async function handler(args: Record<string, unknown>): Promise<ToolResult> {\n const { html, selector, attribute, limit, fields } = args as unknown as HtmlSelectArgs;\n\n if (!html || typeof html !== 'string') {\n return { output: null, error: 'html.select: \"html\" is required and must be a string' };\n }\n if (!selector || typeof selector !== 'string') {\n return { output: null, error: 'html.select: \"selector\" is required and must be a string' };\n }\n\n const $ = cheerio.load(html);\n const elements = $(selector);\n\n if (fields && typeof fields === 'object') {\n // Structured extraction: return array of objects\n const parsedFields = Object.entries(fields).map(([name, spec]) => ({\n name,\n ...parseFieldSpec(spec),\n }));\n\n let results: Array<Record<string, string | null>> = [];\n elements.each((_i, el) => {\n const row: Record<string, string | null> = {};\n for (const { name, subSelector, attr } of parsedFields) {\n const target = subSelector ? $(el).find(subSelector).first() : $(el);\n if (target.length === 0) {\n row[name] = null;\n } else if (attr) {\n row[name] = target.attr(attr) ?? null;\n } else {\n row[name] = target.text().trim();\n }\n }\n results.push(row);\n });\n\n if (limit !== undefined && limit > 0) {\n results = results.slice(0, limit);\n }\n\n return { output: { results } };\n }\n\n // Simple extraction: return array of strings (original behavior)\n let results: string[] = [];\n elements.each((_i, el) => {\n if (attribute) {\n const val = $(el).attr(attribute);\n if (val !== undefined) {\n results.push(val);\n }\n } else {\n results.push($(el).text().trim());\n }\n });\n\n if (limit !== undefined && limit > 0) {\n results = results.slice(0, limit);\n }\n\n return { output: { results } };\n}\n"],"mappings":";;;AAaA,MAAa,aAA6B;CACxC,MAAM;CACN,aACE;CACF,aAAa;EACX,MAAM;EACN,YAAY;GACV,MAAM;IAAE,MAAM;IAAU,aAAa;IAA0B;GAC/D,UAAU;IAAE,MAAM;IAAU,aAAa;IAAkC;GAC3E,WAAW;IACT,MAAM;IACN,aAAa;IACd;GACD,OAAO;IAAE,MAAM;IAAU,aAAa;IAAuC;GAC7E,QAAQ;IACN,MAAM;IACN,aACE;IACH;GACF;EACD,UAAU,CAAC,QAAQ,WAAW;EAC/B;CACD,cAAc;EACZ,MAAM;EACN,YAAY,EACV,SAAS;GACP,MAAM;GACN,aAAa;GACd,EACF;EACF;CACF;;;;;AAMD,SAAS,eAAe,MAAmE;CACzF,MAAM,UAAU,KAAK,YAAY,KAAK;AACtC,KAAI,WAAW,EAIb,QAAO;EAAE,aAFW,KAAK,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI;EAE/B,MADT,KAAK,MAAM,UAAU,EAAE,CAAC,MAAM,IACP;EAAM;AAE5C,KAAI,KAAK,WAAW,IAAI,CAEtB,QAAO;EAAE,aAAa;EAAM,MAAM,KAAK,MAAM,EAAE;EAAE;AAGnD,QAAO;EAAE,aAAa;EAAM,MAAM;EAAM;;AAG1C,eAAsB,QAAQ,MAAoD;CAChF,MAAM,EAAE,MAAM,UAAU,WAAW,OAAO,WAAW;AAErD,KAAI,CAAC,QAAQ,OAAO,SAAS,SAC3B,QAAO;EAAE,QAAQ;EAAM,OAAO;EAAwD;AAExF,KAAI,CAAC,YAAY,OAAO,aAAa,SACnC,QAAO;EAAE,QAAQ;EAAM,OAAO;EAA4D;CAG5F,MAAM,IAAI,QAAQ,KAAK,KAAK;CAC5B,MAAM,WAAW,EAAE,SAAS;AAE5B,KAAI,UAAU,OAAO,WAAW,UAAU;EAExC,MAAM,eAAe,OAAO,QAAQ,OAAO,CAAC,KAAK,CAAC,MAAM,WAAW;GACjE;GACA,GAAG,eAAe,KAAK;GACxB,EAAE;EAEH,IAAI,UAAgD,EAAE;AACtD,WAAS,MAAM,IAAI,OAAO;GACxB,MAAM,MAAqC,EAAE;AAC7C,QAAK,MAAM,EAAE,MAAM,aAAa,UAAU,cAAc;IACtD,MAAM,SAAS,cAAc,EAAE,GAAG,CAAC,KAAK,YAAY,CAAC,OAAO,GAAG,EAAE,GAAG;AACpE,QAAI,OAAO,WAAW,EACpB,KAAI,QAAQ;aACH,KACT,KAAI,QAAQ,OAAO,KAAK,KAAK,IAAI;QAEjC,KAAI,QAAQ,OAAO,MAAM,CAAC,MAAM;;AAGpC,WAAQ,KAAK,IAAI;IACjB;AAEF,MAAI,UAAU,UAAa,QAAQ,EACjC,WAAU,QAAQ,MAAM,GAAG,MAAM;AAGnC,SAAO,EAAE,QAAQ,EAAE,SAAS,EAAE;;CAIhC,IAAI,UAAoB,EAAE;AAC1B,UAAS,MAAM,IAAI,OAAO;AACxB,MAAI,WAAW;GACb,MAAM,MAAM,EAAE,GAAG,CAAC,KAAK,UAAU;AACjC,OAAI,QAAQ,OACV,SAAQ,KAAK,IAAI;QAGnB,SAAQ,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;GAEnC;AAEF,KAAI,UAAU,UAAa,QAAQ,EACjC,WAAU,QAAQ,MAAM,GAAG,MAAM;AAGnC,QAAO,EAAE,QAAQ,EAAE,SAAS,EAAE"}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
//#region src/dev-tools/tools/http-request.ts
|
|
2
|
-
const descriptor = {
|
|
3
|
-
name: "http.request",
|
|
4
|
-
description: "Make an HTTP request and return the response status, headers, and body.",
|
|
5
|
-
inputSchema: {
|
|
6
|
-
type: "object",
|
|
7
|
-
properties: {
|
|
8
|
-
url: {
|
|
9
|
-
type: "string",
|
|
10
|
-
description: "The URL to request"
|
|
11
|
-
},
|
|
12
|
-
method: {
|
|
13
|
-
type: "string",
|
|
14
|
-
description: "HTTP method (default: GET)"
|
|
15
|
-
},
|
|
16
|
-
headers: {
|
|
17
|
-
type: "object",
|
|
18
|
-
description: "Request headers as key-value pairs"
|
|
19
|
-
},
|
|
20
|
-
body: {
|
|
21
|
-
type: "string",
|
|
22
|
-
description: "Request body (for POST/PUT/PATCH)"
|
|
23
|
-
},
|
|
24
|
-
timeout: {
|
|
25
|
-
type: "number",
|
|
26
|
-
description: "Request timeout in milliseconds (default: 30000)"
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
required: ["url"]
|
|
30
|
-
},
|
|
31
|
-
outputSchema: {
|
|
32
|
-
type: "object",
|
|
33
|
-
properties: {
|
|
34
|
-
status: {
|
|
35
|
-
type: "number",
|
|
36
|
-
description: "HTTP status code"
|
|
37
|
-
},
|
|
38
|
-
headers: {
|
|
39
|
-
type: "object",
|
|
40
|
-
description: "Response headers"
|
|
41
|
-
},
|
|
42
|
-
body: {
|
|
43
|
-
type: "object",
|
|
44
|
-
description: "Response body — parsed object when content-type is application/json, raw string otherwise"
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
async function handler(args) {
|
|
50
|
-
const { url, method, headers, body: requestBody, timeout } = args;
|
|
51
|
-
if (!url || typeof url !== "string") return {
|
|
52
|
-
output: null,
|
|
53
|
-
error: "http.request: \"url\" is required and must be a string"
|
|
54
|
-
};
|
|
55
|
-
try {
|
|
56
|
-
const controller = new AbortController();
|
|
57
|
-
const timeoutMs = timeout ?? 3e4;
|
|
58
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
59
|
-
const response = await fetch(url, {
|
|
60
|
-
method: method ?? "GET",
|
|
61
|
-
headers,
|
|
62
|
-
body: requestBody,
|
|
63
|
-
signal: controller.signal
|
|
64
|
-
});
|
|
65
|
-
clearTimeout(timer);
|
|
66
|
-
const responseText = await response.text();
|
|
67
|
-
const responseHeaders = {};
|
|
68
|
-
response.headers.forEach((value, key) => {
|
|
69
|
-
responseHeaders[key] = value;
|
|
70
|
-
});
|
|
71
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
72
|
-
let responseBody = responseText;
|
|
73
|
-
if (contentType.includes("application/json")) try {
|
|
74
|
-
responseBody = JSON.parse(responseText);
|
|
75
|
-
} catch {}
|
|
76
|
-
return { output: {
|
|
77
|
-
status: response.status,
|
|
78
|
-
headers: responseHeaders,
|
|
79
|
-
body: responseBody
|
|
80
|
-
} };
|
|
81
|
-
} catch (err) {
|
|
82
|
-
return {
|
|
83
|
-
output: null,
|
|
84
|
-
error: `http.request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
//#endregion
|
|
90
|
-
export { descriptor, handler };
|
|
91
|
-
//# sourceMappingURL=http-request-k9bp5joL.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"http-request-k9bp5joL.mjs","names":[],"sources":["../src/dev-tools/tools/http-request.ts"],"sourcesContent":["// Tool: http.request — make HTTP requests using Node built-in fetch.\n\nimport type { ToolDescriptor, ToolResult } from '../../types/index.js';\n\nexport interface HttpRequestArgs {\n url: string;\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n timeout?: number;\n}\n\nexport const descriptor: ToolDescriptor = {\n name: 'http.request',\n description: 'Make an HTTP request and return the response status, headers, and body.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'The URL to request' },\n method: { type: 'string', description: 'HTTP method (default: GET)' },\n headers: { type: 'object', description: 'Request headers as key-value pairs' },\n body: { type: 'string', description: 'Request body (for POST/PUT/PATCH)' },\n timeout: { type: 'number', description: 'Request timeout in milliseconds (default: 30000)' },\n },\n required: ['url'],\n },\n outputSchema: {\n type: 'object',\n properties: {\n status: { type: 'number', description: 'HTTP status code' },\n headers: { type: 'object', description: 'Response headers' },\n body: {\n type: 'object',\n description:\n 'Response body — parsed object when content-type is application/json, raw string otherwise',\n },\n },\n },\n};\n\nexport async function handler(args: Record<string, unknown>): Promise<ToolResult> {\n const { url, method, headers, body: requestBody, timeout } = args as unknown as HttpRequestArgs;\n\n if (!url || typeof url !== 'string') {\n return { output: null, error: 'http.request: \"url\" is required and must be a string' };\n }\n\n try {\n const controller = new AbortController();\n const timeoutMs = timeout ?? 30000;\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n const response = await fetch(url, {\n method: method ?? 'GET',\n headers: headers,\n body: requestBody,\n signal: controller.signal,\n });\n\n clearTimeout(timer);\n\n const responseText = await response.text();\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n // Auto-parse JSON responses so workflows can access fields directly\n const contentType = response.headers.get('content-type') ?? '';\n let responseBody: unknown = responseText;\n if (contentType.includes('application/json')) {\n try {\n responseBody = JSON.parse(responseText);\n } catch {\n // Keep raw text if JSON parsing fails\n }\n }\n\n return {\n output: {\n status: response.status,\n headers: responseHeaders,\n body: responseBody,\n },\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { output: null, error: `http.request failed: ${message}` };\n }\n}\n"],"mappings":";AAYA,MAAa,aAA6B;CACxC,MAAM;CACN,aAAa;CACb,aAAa;EACX,MAAM;EACN,YAAY;GACV,KAAK;IAAE,MAAM;IAAU,aAAa;IAAsB;GAC1D,QAAQ;IAAE,MAAM;IAAU,aAAa;IAA8B;GACrE,SAAS;IAAE,MAAM;IAAU,aAAa;IAAsC;GAC9E,MAAM;IAAE,MAAM;IAAU,aAAa;IAAqC;GAC1E,SAAS;IAAE,MAAM;IAAU,aAAa;IAAoD;GAC7F;EACD,UAAU,CAAC,MAAM;EAClB;CACD,cAAc;EACZ,MAAM;EACN,YAAY;GACV,QAAQ;IAAE,MAAM;IAAU,aAAa;IAAoB;GAC3D,SAAS;IAAE,MAAM;IAAU,aAAa;IAAoB;GAC5D,MAAM;IACJ,MAAM;IACN,aACE;IACH;GACF;EACF;CACF;AAED,eAAsB,QAAQ,MAAoD;CAChF,MAAM,EAAE,KAAK,QAAQ,SAAS,MAAM,aAAa,YAAY;AAE7D,KAAI,CAAC,OAAO,OAAO,QAAQ,SACzB,QAAO;EAAE,QAAQ;EAAM,OAAO;EAAwD;AAGxF,KAAI;EACF,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,YAAY,WAAW;EAC7B,MAAM,QAAQ,iBAAiB,WAAW,OAAO,EAAE,UAAU;EAE7D,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ,UAAU;GACT;GACT,MAAM;GACN,QAAQ,WAAW;GACpB,CAAC;AAEF,eAAa,MAAM;EAEnB,MAAM,eAAe,MAAM,SAAS,MAAM;EAC1C,MAAM,kBAA0C,EAAE;AAClD,WAAS,QAAQ,SAAS,OAAO,QAAQ;AACvC,mBAAgB,OAAO;IACvB;EAGF,MAAM,cAAc,SAAS,QAAQ,IAAI,eAAe,IAAI;EAC5D,IAAI,eAAwB;AAC5B,MAAI,YAAY,SAAS,mBAAmB,CAC1C,KAAI;AACF,kBAAe,KAAK,MAAM,aAAa;UACjC;AAKV,SAAO,EACL,QAAQ;GACN,QAAQ,SAAS;GACjB,SAAS;GACT,MAAM;GACP,EACF;UACM,KAAK;AAEZ,SAAO;GAAE,QAAQ;GAAM,OAAO,wBADd,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACC"}
|