gemcatch 0.4.0 → 0.6.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/CHANGELOG.md +130 -1
- package/README.md +179 -9
- package/db.js +71 -4
- package/gemini.js +179 -48
- package/index.js +706 -114
- package/package.json +3 -2
- package/sources.js +580 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,133 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Research over your own data.** A Deep Research agent could only read the
|
|
15
|
+
public web through gemcatch, while the API has always let it read much more.
|
|
16
|
+
Five new flags on `research` and `batch` open that up, listed under their own
|
|
17
|
+
heading in `--help`. All of them need `--agent`; without it gemcatch exits
|
|
18
|
+
with one line naming the flag and writes nothing.
|
|
19
|
+
|
|
20
|
+
- `--attach <path|url>` (repeatable) gives the agent a PDF, CSV or image.
|
|
21
|
+
Local files go inline, in order, while the request stays under the API's
|
|
22
|
+
inline limit (100 MB, or 50 MB once a PDF is in it). The file that would
|
|
23
|
+
cross it, and every one after, is uploaded through the Files API and sent by
|
|
24
|
+
URI. Uploads expire after 48 hours, and a `--plan` run prints when. An https
|
|
25
|
+
URL is sent by reference, its query string masked everywhere but the wire,
|
|
26
|
+
and one without a supported extension is typed from a HEAD request (a
|
|
27
|
+
one-byte GET if HEAD is refused). Unknown types, missing and empty files are refused in one
|
|
28
|
+
line before the spend confirmation, with a hint to export Word files as PDF
|
|
29
|
+
and spreadsheets as CSV. A file that changes size between that check and
|
|
30
|
+
the send is refused too. A `batch` of more than one prompt uploads every
|
|
31
|
+
local file once instead of sending it inline with each prompt.
|
|
32
|
+
- `--mcp <url>` (repeatable) adds a remote MCP server, named after its host
|
|
33
|
+
unless `--mcp-name` says otherwise. `--mcp-header 'Name: value'` and
|
|
34
|
+
`--mcp-allow tool,tool` apply to the `--mcp` before them. Header values
|
|
35
|
+
and URL credentials are masked in the confirmation, `--dry-run`,
|
|
36
|
+
`list --json`, `get --raw` and API errors. A header value can read
|
|
37
|
+
`${VAR}` from the environment at send time, so the token never reaches
|
|
38
|
+
`tasks.db`; a value written out in full is kept there, since a later turn
|
|
39
|
+
has to resend it. On POSIX systems the data directory and the images
|
|
40
|
+
folder are now 0700, and `tasks.db` and each image 0600. A local or
|
|
41
|
+
private address, or credentials over plain http, get a warning.
|
|
42
|
+
- `--file-search <store>` (repeatable) lets the agent search File Search
|
|
43
|
+
stores, all in one `file_search` tool.
|
|
44
|
+
- `--no-web` drops Google Search and URL Context so the agent reads only what
|
|
45
|
+
you gave it. Code Execution stays, because it's how the agent works through a
|
|
46
|
+
CSV and draws a chart. It's refused when nothing else was given to read.
|
|
47
|
+
- `--visualize` sets `agent_config.visualization: "auto"`, alongside
|
|
48
|
+
`collaborative_planning` when `--plan` is also given. Charts in the final
|
|
49
|
+
report are written to the data directory's `images/` folder as
|
|
50
|
+
`<task-id>-<n>.<ext>`, recorded in the store, and listed under the report by
|
|
51
|
+
`get`, `watch` and `digest` and as `images` in `--json`. `export -o` copies
|
|
52
|
+
them next to the export and links them; `rm` and `prune` delete them.
|
|
53
|
+
|
|
54
|
+
Once a tool flag is given, the request lists Google Search, URL Context and
|
|
55
|
+
Code Execution alongside the new sources, because an explicit `tools` list
|
|
56
|
+
replaces the agent's defaults. `--attach` on its own sends no `tools` field.
|
|
57
|
+
`refine` and `approve` resend the plan's tools and visualization setting;
|
|
58
|
+
attachments go with the first turn only. `--dry-run` and the spend
|
|
59
|
+
confirmation list the sources above the cost, and `list` gains a SOURCES
|
|
60
|
+
column when a listed task used any of this.
|
|
61
|
+
- An unknown `--flag=value` option is reported without its value, which could
|
|
62
|
+
be a token.
|
|
63
|
+
- Additive schema migration: `tools_json`, `attachments_json`, `visualization`
|
|
64
|
+
and `images_json`, all nullable. A 0.5.0 `tasks.db` upgrades in place and
|
|
65
|
+
behaves exactly as it did.
|
|
66
|
+
|
|
67
|
+
### Notes
|
|
68
|
+
|
|
69
|
+
- A run with none of the new flags sends the same request body, byte for byte,
|
|
70
|
+
as 0.5.0 did, and a result without charts prints the same text.
|
|
71
|
+
|
|
72
|
+
## [0.5.0] - 2026-09-04
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- **Collaborative planning.** Until now the only agent flow was a blind one-shot
|
|
77
|
+
bet: gemcatch showed a cost band, asked y/N, and fired. The cost band was
|
|
78
|
+
everything you knew before spending, and there was no way to check the agent
|
|
79
|
+
had read the prompt the way you meant it. A 20-line `batch` against
|
|
80
|
+
`deep-research-max` is a $60–$140 command placed sight unseen. The Deep
|
|
81
|
+
Research docs have always documented a second mode for exactly this, and
|
|
82
|
+
gemcatch could not reach it. Now it can:
|
|
83
|
+
|
|
84
|
+
- `--plan` on `research` and `batch` submits with
|
|
85
|
+
`agent_config.collaborative_planning: true`, so **the agent returns a
|
|
86
|
+
research plan instead of a report**. Still `background: true`, still stored,
|
|
87
|
+
still collected by the daemon. The row is stored with `kind='plan'`.
|
|
88
|
+
- `gemcatch get <id>` on a plan prints the plan and then the literal next
|
|
89
|
+
command, `Approve with: gemcatch approve 8f3a1c04 · Refine with: gemcatch
|
|
90
|
+
refine 8f3a1c04 "..."`, on stderr, so `get <plan> > plan.md` still captures
|
|
91
|
+
just the plan.
|
|
92
|
+
- `gemcatch refine <id> "<instruction>"` sends the instruction back with
|
|
93
|
+
`previous_interaction_id` and planning still on, and stores the revised plan
|
|
94
|
+
linked to the one it came from, inheriting its agent and tag.
|
|
95
|
+
- `gemcatch approve <id>` is the turn that commits: `previous_interaction_id`
|
|
96
|
+
with `collaborative_planning: false`, stored as `kind='report'` linked to the
|
|
97
|
+
plan. It shows the cost band and asks, honours `--yes` when stdin is not a
|
|
98
|
+
TTY, and honours `--dry-run`.
|
|
99
|
+
|
|
100
|
+
**Planning is not cheaper.** The docs give one band per task and do not price a
|
|
101
|
+
planning turn separately, so a planning turn is quoted at the same band and the
|
|
102
|
+
line says so: *"the docs price per task and do not price a planning turn
|
|
103
|
+
separately"*. What you get for it is a look at the plan before you commit to
|
|
104
|
+
the research run, not a discount.
|
|
105
|
+
- `gemcatch list` renders a chain indented under its root, in submission order,
|
|
106
|
+
and grows a `KIND` column when a listing contains plans or reports (a store
|
|
107
|
+
with neither keeps the layout it had). `gemcatch export` follows a chain to its
|
|
108
|
+
report and leaves the intermediate plans out unless `--include-plans` is
|
|
109
|
+
passed; the Markdown sections and the JSON rows now name the turn.
|
|
110
|
+
- `gemcatch stats` now tallies plan and report turns, and totals the estimated
|
|
111
|
+
spend across the agent turns that were actually billed, from the same
|
|
112
|
+
published bands the guard quotes before each one. A chain bills per turn, so a
|
|
113
|
+
release that turns one submission into three owes the user a running total. It
|
|
114
|
+
is the documented bands applied to what was submitted, not a reading of your
|
|
115
|
+
bill, and it errs toward telling you rather than flattering you: only runs
|
|
116
|
+
that reached the server are priced (a submit that failed before it left the
|
|
117
|
+
machine is counted as an attempt but costs nothing), and an agent with no
|
|
118
|
+
published band totals to `unknown`, never to `$0.00`. A store with no agent
|
|
119
|
+
runs prints neither line.
|
|
120
|
+
- Additive schema migration: `collaborative_planning`, `previous_interaction_id`,
|
|
121
|
+
`kind` (defaulting to `'task'`) and `parent_id`. A 0.4.0 `tasks.db` upgrades in
|
|
122
|
+
place, keeps every row, and behaves exactly as it did.
|
|
123
|
+
|
|
124
|
+
### Fixed
|
|
125
|
+
|
|
126
|
+
- `approve` and `refine` fail before sending anything when the target is not a
|
|
127
|
+
plan, or is a plan that has not completed. A plan whose interaction the free
|
|
128
|
+
tier has already dropped names the retention window (1 day free, 55 days paid)
|
|
129
|
+
instead of sending a `previous_interaction_id` the server would reject. A plan
|
|
130
|
+
that expires *after* it completed locally gets the same explanation rather
|
|
131
|
+
than a bare 404 for an id the user never typed.
|
|
132
|
+
- `--plan` without `--agent` (or with `--model`) is a clean error rather than a
|
|
133
|
+
flag that quietly does nothing: collaborative planning is an agent feature.
|
|
134
|
+
- Approving the same plan twice submits a second run rather than silently
|
|
135
|
+
reusing the first, and `list` shows both under their plan.
|
|
136
|
+
|
|
10
137
|
## [0.4.0] - 2026-08-08
|
|
11
138
|
|
|
12
139
|
### Added
|
|
@@ -204,7 +331,9 @@ seen a task complete, the text is cached locally and survives that expiry — bu
|
|
|
204
331
|
something has to poll inside that window for it to be seen at all, which is what
|
|
205
332
|
`gemcatch daemon` exists to do.
|
|
206
333
|
|
|
207
|
-
[Unreleased]: https://github.com/Booyaka101/gemcatch/compare/v0.
|
|
334
|
+
[Unreleased]: https://github.com/Booyaka101/gemcatch/compare/v0.6.0...HEAD
|
|
335
|
+
[0.6.0]: https://github.com/Booyaka101/gemcatch/compare/v0.5.0...v0.6.0
|
|
336
|
+
[0.5.0]: https://github.com/Booyaka101/gemcatch/compare/v0.4.0...v0.5.0
|
|
208
337
|
[0.4.0]: https://github.com/Booyaka101/gemcatch/compare/v0.3.0...v0.4.0
|
|
209
338
|
[0.3.0]: https://github.com/Booyaka101/gemcatch/compare/v0.2.0...v0.3.0
|
|
210
339
|
[0.2.0]: https://github.com/Booyaka101/gemcatch/compare/v0.1.1...v0.2.0
|
package/README.md
CHANGED
|
@@ -81,8 +81,10 @@ This week in AI: ...
|
|
|
81
81
|
| `gemcatch research "<prompt>"` | Submits with `background: true`, stores the interaction ID, exits immediately. |
|
|
82
82
|
| `gemcatch batch <file>` | Submits many prompts from a file at once, tagged as one collectable batch. |
|
|
83
83
|
| `gemcatch status <id>` | Polls the API and prints the current state. |
|
|
84
|
-
| `gemcatch get <id>` | Prints the full response if complete, otherwise the current status. |
|
|
85
|
-
| `gemcatch
|
|
84
|
+
| `gemcatch get <id>` | Prints the full response if complete, otherwise the current status. On a [plan](#see-the-plan-before-you-pay-for-the-run), the plan plus the approve/refine commands. |
|
|
85
|
+
| `gemcatch refine <id> "<instruction>"` | Sends an instruction back to a plan and returns a revised plan. |
|
|
86
|
+
| `gemcatch approve <id>` | Approves a plan and submits the research run it describes. |
|
|
87
|
+
| `gemcatch list` | All tasks, newest first: id, age, status, prompt. Plan chains are indented under their root. |
|
|
86
88
|
| `gemcatch export` | Concatenates finished results, each under its prompt, to stdout or a file (Markdown or JSON). |
|
|
87
89
|
| `gemcatch digest` | Feeds a tag's completed results through one Gemini call into a single summary. |
|
|
88
90
|
| `gemcatch watch <id>` | Polls until the task finishes, then prints the result. |
|
|
@@ -91,7 +93,7 @@ This week in AI: ...
|
|
|
91
93
|
| `gemcatch cancel <id>` | Asks the API to stop an in-flight task. |
|
|
92
94
|
| `gemcatch rm <ids...>` | Forgets tasks locally. `--remote` deletes them server-side too. |
|
|
93
95
|
| `gemcatch prune` | Drops finished tasks older than `--days` (default 30). |
|
|
94
|
-
| `gemcatch stats` | Where the store lives
|
|
96
|
+
| `gemcatch stats` | Where the store lives, what's in it, and what the agent runs have plausibly cost. |
|
|
95
97
|
|
|
96
98
|
Useful flags:
|
|
97
99
|
|
|
@@ -100,7 +102,13 @@ Useful flags:
|
|
|
100
102
|
| `--json` | most commands | Machine-readable output. |
|
|
101
103
|
| `-m, --model <id>` | `research`, `batch` | Override the model. |
|
|
102
104
|
| `-a, --agent <id>` | `research`, `batch` | Submit to a [research agent](#research-agents) instead of a model. Mutually exclusive with `--model`. |
|
|
103
|
-
| `--
|
|
105
|
+
| `--plan` | `research`, `batch` | Ask the agent for a [research plan](#see-the-plan-before-you-pay-for-the-run) first, to refine and approve. Needs `--agent`. |
|
|
106
|
+
| `--attach <path\|url>` | `research`, `batch` | Give the agent a PDF, CSV or image ([your own data](#research-over-your-own-data)). Repeatable. Needs `--agent`. |
|
|
107
|
+
| `--mcp <url>` | `research`, `batch` | Let the agent call a remote MCP server. Repeatable; `--mcp-header`, `--mcp-allow` and `--mcp-name` apply to the one before them. Needs `--agent`. |
|
|
108
|
+
| `--file-search <store>` | `research`, `batch` | Let the agent search a File Search store. Repeatable. Needs `--agent`. |
|
|
109
|
+
| `--no-web` | `research`, `batch` | Drop Google Search and URL Context, so the agent reads only what you gave it. Needs `--agent`. |
|
|
110
|
+
| `--visualize` | `research`, `batch` | Let the agent draw charts; they're saved as image files. Needs `--agent`. |
|
|
111
|
+
| `--yes` | `research`, `batch`, `refine`, `approve` | Confirm the agent cost without asking. Required for `--agent` when stdin is not a TTY. |
|
|
104
112
|
| `-s, --system <text>` | `research`, `batch` | Set a system instruction. |
|
|
105
113
|
| `-f, --file <path>` | `research` | Read the prompt from a file. |
|
|
106
114
|
| `-t, --tag <tag>` | `research`, `batch`, `list` | Label tasks and filter them. |
|
|
@@ -112,7 +120,8 @@ Useful flags:
|
|
|
112
120
|
| `-n, --limit <n>` | `list` | Cap the rows (non-negative; `0` shows none). |
|
|
113
121
|
| `--format <md\|json>` | `export` | Output format. Default `md`. |
|
|
114
122
|
| `-o, --out <file>` | `export` | Write to a file instead of stdout. |
|
|
115
|
-
| `--
|
|
123
|
+
| `--include-plans` | `export` | Also emit a chain's plan turns, not just its report. |
|
|
124
|
+
| `--dry-run` | `research`, `batch`, `refine`, `approve`, `prune` | Show what would go — including the projected agent spend; submit/delete nothing. |
|
|
116
125
|
| `--raw` | `get` | Dump the raw interaction JSON. |
|
|
117
126
|
|
|
118
127
|
IDs are the first 8 characters of a UUID. Any unique prefix works, so `gemcatch get 8f3a` is fine.
|
|
@@ -187,14 +196,172 @@ The report lands like any other result — final answer only, none of the agent'
|
|
|
187
196
|
|
|
188
197
|
An agent run can also come back `incomplete` — that is what a `max_total_tokens` budget cap produces when the run "safely pauses" — which `gemcatch` treats as terminal, exactly like the API does: the daemon retires it and moves on.
|
|
189
198
|
|
|
199
|
+
### See the plan before you pay for the run
|
|
200
|
+
|
|
201
|
+
A cost band tells you what a run will cost. It tells you nothing about whether the agent understood the question. Add `--plan` and it doesn't start researching: with `agent_config.collaborative_planning: true`, *"the agent returns a research plan instead of a full report"*. You read it, push back on it, and approve it when it's right.
|
|
202
|
+
|
|
203
|
+
```console
|
|
204
|
+
$ gemcatch research "map the EU AI Act high-risk obligations against the UK approach" --agent deep-research --plan -t euuk
|
|
205
|
+
Agent deep-research-preview-04-2026 (planning turn) — estimated $1.00–$3.00 for this task (preview rates, subject to change; the docs price per task and do not price a planning turn separately).
|
|
206
|
+
Submit? [y/N] y
|
|
207
|
+
Plan task d014e21b submitted. Run: gemcatch get d014e21b when ready.
|
|
208
|
+
|
|
209
|
+
$ gemcatch get d014e21b
|
|
210
|
+
Research plan
|
|
211
|
+
|
|
212
|
+
1. Scope the EU AI Act high-risk regime: Annex III use cases, Article 6 classification,
|
|
213
|
+
and the Chapter III obligations (risk management, data governance, logging, human
|
|
214
|
+
oversight, conformity assessment) with their August 2026 / August 2027 dates.
|
|
215
|
+
2. Scope the UK approach: the five cross-sector principles, the regulator-led model
|
|
216
|
+
(ICO, FCA, MHRA, Ofcom), and what is guidance rather than statute.
|
|
217
|
+
3. Build an obligation-by-obligation comparison table: EU requirement, nearest UK
|
|
218
|
+
equivalent, whether it is binding, and who enforces it.
|
|
219
|
+
4. Flag the gaps in both directions and the compliance implications for a firm
|
|
220
|
+
operating in both.
|
|
221
|
+
Approve with: gemcatch approve d014e21b · Refine with: gemcatch refine d014e21b "..."
|
|
222
|
+
|
|
223
|
+
$ gemcatch refine d014e21b "cut the history, and add enforcement penalties on both sides"
|
|
224
|
+
Plan task 2140e699 submitted (refines d014e21b). Run: gemcatch get 2140e699 when ready.
|
|
225
|
+
|
|
226
|
+
$ gemcatch approve 2140e699
|
|
227
|
+
Agent deep-research-preview-04-2026 — estimated $1.00–$3.00 for this task (preview rates, subject to change).
|
|
228
|
+
Submit? [y/N] y
|
|
229
|
+
Task 96e8209b submitted (approves plan 2140e699).
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Every turn is an ordinary background task: stored, polled, and collected by the daemon before the 1-day expiry, same as everything else. `refine` chains as many times as you like; each revision inherits the agent and tag of the plan it came from.
|
|
233
|
+
|
|
234
|
+
**A planning turn is a task, and it is billed as one.** The docs publish one band per task and price no planning turn separately, so `gemcatch` quotes the same band for it and says exactly that on the line. What `--plan` buys you is a look at the plan before you commit to the research run, not a discount. Budget for the plan, each refinement, and the run.
|
|
235
|
+
|
|
236
|
+
Since a chain bills per turn, `gemcatch stats` keeps a running total across all of them:
|
|
237
|
+
|
|
238
|
+
```console
|
|
239
|
+
$ gemcatch stats
|
|
240
|
+
Store: ~/.gemcatch/tasks.db
|
|
241
|
+
Tasks: 3
|
|
242
|
+
completed 3
|
|
243
|
+
Agent runs:
|
|
244
|
+
deep-research-preview-04-2026 3
|
|
245
|
+
Plan chains: 2 plan, 1 report
|
|
246
|
+
Estimated spend: $3.00–$9.00 across 3 billed task(s) (preview rates, subject to change).
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
That's the chain above: one plan, one refinement, one run, three tasks at the same band. It's the published bands applied to what you actually submitted, not a reading of your bill, and it errs toward telling you rather than flattering you:
|
|
250
|
+
|
|
251
|
+
- Only runs that **reached the server** are priced. A submit that failed before it left the machine (bad key, rejected agent id) is still counted under "Agent runs" as an attempt, but it costs nothing and isn't billed.
|
|
252
|
+
- An agent with **no published band** totals to `unknown`, never to `$0.00`. Quoting zero for a run that costs real money is the one thing a spend guard must not do.
|
|
253
|
+
|
|
254
|
+
`list` shows the chain as one thing, and `export` follows it to the report:
|
|
255
|
+
|
|
256
|
+
```console
|
|
257
|
+
$ gemcatch list
|
|
258
|
+
ID AGE STATUS KIND AGENT PROMPT
|
|
259
|
+
d014e21b 22s completed plan deep-research map the EU AI Act high-risk obligations against the UK ap...
|
|
260
|
+
2140e699 12s completed plan deep-research └─ cut the history, and add enforcement penalties on both sides
|
|
261
|
+
96e8209b 7s completed report deep-research └─ map the EU AI Act high-risk obligations against the UK ap...
|
|
262
|
+
|
|
263
|
+
$ gemcatch export --tag euuk -o report.md
|
|
264
|
+
Wrote 1 result(s) to report.md.
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The plans are working notes on the way to the report, so `export` leaves them out; pass `--include-plans` if you want the whole chain in the document. The report is filed under the question that started the chain rather than the one-line approval actually sent to the API, so an exported document reads as research.
|
|
268
|
+
|
|
269
|
+
A few things that will bite otherwise:
|
|
270
|
+
|
|
271
|
+
- `--plan` needs `--agent`. Collaborative planning is an agent feature, so `--plan --model ...` (or `--plan` on its own) is an error, not a no-op.
|
|
272
|
+
- `approve` only works on a plan that has completed. Anything else fails before a request goes out.
|
|
273
|
+
- On the free tier the plan's interaction is dropped after a day. Once that happens the chain cannot be continued. `approve` says so and names the window instead of sending a `previous_interaction_id` the server will reject. Run the daemon, or approve the same day.
|
|
274
|
+
- Approving twice submits twice. There is no dedupe, and both runs show up under the plan in `list`.
|
|
275
|
+
- `--plan` and `--watch` work together: `gemcatch research "..." --agent deep-research --plan -w` waits for the plan and then prints it.
|
|
276
|
+
|
|
190
277
|
The agent recipe, end to end:
|
|
191
278
|
|
|
192
279
|
```bash
|
|
193
|
-
$ gemcatch batch questions.txt --agent deep-research --yes #
|
|
194
|
-
$ gemcatch daemon --exit-when-idle
|
|
195
|
-
$ gemcatch
|
|
280
|
+
$ gemcatch batch questions.txt --agent deep-research --plan --yes # a plan per prompt, N × band quoted
|
|
281
|
+
$ gemcatch daemon --exit-when-idle # catch the plans
|
|
282
|
+
$ gemcatch list --tag batch-1a2b3c # read them, approve the good ones
|
|
283
|
+
$ gemcatch approve 8f3a1c04 --yes
|
|
284
|
+
$ gemcatch daemon --exit-when-idle # catch the reports before the 1-day expiry
|
|
285
|
+
$ gemcatch export --tag batch-1a2b3c -o reports.md # every report, with its sources
|
|
196
286
|
```
|
|
197
287
|
|
|
288
|
+
Drop `--plan` and the first two lines become the 0.4.0 one-shot flow, which still works exactly as it did.
|
|
289
|
+
|
|
290
|
+
## Research over your own data
|
|
291
|
+
|
|
292
|
+
Out of the box a Deep Research agent reads the public web. These flags hand it your files, your MCP servers and your File Search stores as well, and `--visualize` lets it draw charts. They are agent features, so every one of them needs `--agent`. Without it gemcatch stops with one line naming the flag and sends nothing.
|
|
293
|
+
|
|
294
|
+
### Files: `--attach`
|
|
295
|
+
|
|
296
|
+
```console
|
|
297
|
+
$ gemcatch research "Which region is growing fastest, and does the board pack agree?" --agent deep-research \
|
|
298
|
+
--attach sales-2026.csv --attach q3-board-pack.pdf --attach whiteboard.png \
|
|
299
|
+
--attach customer-contracts-2026.pdf --yes
|
|
300
|
+
Attachments: sales-2026.csv (inline, 1 KB); q3-board-pack.pdf (inline, 1 KB); whiteboard.png (inline, 1 KB); customer-contracts-2026.pdf (upload, 40.0 MB)
|
|
301
|
+
Agent deep-research-preview-04-2026 — estimated $1.00–$3.00 for this task (preview rates, subject to change).
|
|
302
|
+
Uploading customer-contracts-2026.pdf (40.0 MB) to the Files API...
|
|
303
|
+
Task 7cf00c5b submitted. Run: gemcatch get 7cf00c5b when ready.
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
PDF and CSV go in as documents; PNG, JPEG, WebP, HEIC, HEIF, GIF and BMP as images. The type comes from the extension, and anything else is refused before you're asked to pay, with a nudge to export a Word file as PDF or a spreadsheet as CSV. `--attach` also takes an https URL, which is passed to the agent by reference. A URL with no supported extension (arxiv's `https://arxiv.org/pdf/1706.03762`, say) gets one HEAD request to read its Content-Type, or a one-byte GET if the server refuses HEAD. The query string of a signed URL is sent as given and shown as `***` everywhere else, the store included.
|
|
307
|
+
|
|
308
|
+
Local files are sent inline, in the order given, as long as the request stays under the API's inline limit: 100 MB of base64, or 50 MB once a PDF is in it. The first file that doesn't fit, and every file after it, goes up through the Files API instead and is referenced by URI. Google keeps uploads for 48 hours. On a `--plan` run gemcatch prints when they expire, and a `refine` or `approve` after that gets a warning. A file given twice is sent once.
|
|
309
|
+
|
|
310
|
+
In a `batch` with more than one prompt, every local file is uploaded, once, and each prompt points at the upload. Inline bytes would otherwise go over the wire again with every prompt.
|
|
311
|
+
|
|
312
|
+
Files go with the first turn only. A `refine` or `approve` continues the same conversation, and the files are already in it.
|
|
313
|
+
|
|
314
|
+
### MCP servers: `--mcp`
|
|
315
|
+
|
|
316
|
+
```console
|
|
317
|
+
$ gemcatch research "Which accounts are at risk of churn this quarter?" --agent deep-research --plan --yes \
|
|
318
|
+
--mcp https://mcp.example-crm.com/mcp \
|
|
319
|
+
--mcp-header 'Authorization: Bearer ${CRM_TOKEN}' \
|
|
320
|
+
--mcp-allow search_accounts,get_account
|
|
321
|
+
Tools: google_search; url_context; code_execution; MCP mcp.example-crm.com https://mcp.example-crm.com/mcp (Authorization: ***) [search_accounts, get_account]
|
|
322
|
+
Agent deep-research-preview-04-2026 (planning turn) — estimated $1.00–$3.00 for this task (preview rates, subject to change; the docs price per task and do not price a planning turn separately).
|
|
323
|
+
Plan task 5451f8b9 submitted. Run: gemcatch get 5451f8b9 when ready.
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`--mcp` is repeatable, and `--mcp-header`, `--mcp-allow` and `--mcp-name` apply to the `--mcp` just before them. The server is named after its host unless you pass `--mcp-name`, and two servers on one host become `host` and `host-2`. Giving two servers the same `--mcp-name`, or one server the same header twice, is refused. Google's servers make the call, not your machine, so the URL has to be reachable from the internet; a localhost or private address gets a warning, and so does a plain `http://` URL that carries credentials.
|
|
327
|
+
|
|
328
|
+
Header values, and any user, password, query string or fragment in an MCP URL, never appear in anything gemcatch prints: the confirmation, `--dry-run`, `list --json`, `get --raw` and API errors all show `***`.
|
|
329
|
+
|
|
330
|
+
Note the single quotes in the example. gemcatch reads `${CRM_TOKEN}` from the environment itself, each time a turn is sent, so `tasks.db` only ever holds the reference. A `refine` or `approve` needs the variable set too, and stops in one line naming it if it isn't. A value written out in full, or expanded by your shell, is stored as given, because a later turn has to send it again. On Linux and macOS gemcatch keeps `~/.gemcatch` readable by you alone.
|
|
331
|
+
|
|
332
|
+
### File Search stores: `--file-search`
|
|
333
|
+
|
|
334
|
+
```console
|
|
335
|
+
$ gemcatch research "What does our playbook say about discounting?" --agent deep-research \
|
|
336
|
+
--file-search sales-playbooks --no-web --dry-run
|
|
337
|
+
Tools: code_execution; File Search fileSearchStores/sales-playbooks
|
|
338
|
+
Agent deep-research-preview-04-2026 — estimated $1.00–$3.00 for this task. Nothing submitted (--dry-run).
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Repeat it for more stores; they all go in one `file_search` tool. A bare name gets `fileSearchStores/` put in front of it. gemcatch doesn't create or fill stores, [the File Search docs](https://ai.google.dev/gemini-api/docs/file-search) cover that.
|
|
342
|
+
|
|
343
|
+
### Charts: `--visualize`
|
|
344
|
+
|
|
345
|
+
```console
|
|
346
|
+
$ gemcatch research "Which region is growing fastest, and does the board pack agree?" --agent deep-research \
|
|
347
|
+
--attach sales-2026.csv --visualize --yes -w
|
|
348
|
+
...
|
|
349
|
+
(report text)
|
|
350
|
+
|
|
351
|
+
Images:
|
|
352
|
+
/home/you/.gemcatch/images/7cf00c5b-1.png
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
This sets `agent_config.visualization` to `auto`, next to `collaborative_planning` when you also pass `--plan`. The charts in the final report are written to the data directory's `images/` folder as `<task-id>-<n>.<ext>`, and listed under the report by `get`, `watch` and `digest`, and as `images` in `--json`. `export -o report.md` copies them next to the export and links them in the Markdown. `rm` and `prune` delete them with the task.
|
|
356
|
+
|
|
357
|
+
### What the agent can reach: `--no-web`
|
|
358
|
+
|
|
359
|
+
With no source flags gemcatch sends no `tools` field and the agent gets its defaults: Google Search, URL Context and Code Execution. The API reads an explicit list as the whole set, so once you add `--mcp` or `--file-search`, gemcatch lists those three back in alongside your sources. `--attach` alone sends no list at all, since a file isn't a tool.
|
|
360
|
+
|
|
361
|
+
`--no-web` drops Google Search and URL Context, so the agent works only from what you gave it. Code Execution stays: it can't reach the web, and it's what the agent uses to crunch a CSV and draw a chart. On its own `--no-web` is refused, because it would leave the agent nothing to read.
|
|
362
|
+
|
|
363
|
+
`refine` and `approve` send the plan's tools and visualization setting again, so every turn of a chain reaches the same sources. Once a task in the listing used any of this, `list` grows a SOURCES column (`mcp`, `store`, `no-web`, `2 files`, `charts`).
|
|
364
|
+
|
|
198
365
|
## How it works
|
|
199
366
|
|
|
200
367
|
Tasks live in SQLite at `~/.gemcatch/tasks.db` (override with `GEMCATCH_HOME`):
|
|
@@ -202,7 +369,9 @@ Tasks live in SQLite at `~/.gemcatch/tasks.db` (override with `GEMCATCH_HOME`):
|
|
|
202
369
|
```sql
|
|
203
370
|
CREATE TABLE tasks (id TEXT PRIMARY KEY, prompt TEXT, interaction_id TEXT,
|
|
204
371
|
status TEXT DEFAULT 'pending', result TEXT, created_at INTEGER);
|
|
205
|
-
-- plus model, system_instruction, tag, error, usage, updated_at, agent, citations
|
|
372
|
+
-- plus model, system_instruction, tag, error, usage, updated_at, agent, citations,
|
|
373
|
+
-- collaborative_planning, previous_interaction_id, kind, parent_id,
|
|
374
|
+
-- tools_json, attachments_json, visualization, images_json
|
|
206
375
|
```
|
|
207
376
|
|
|
208
377
|
`research` calls `interactions.create({model, input, background: true})` via [`@google/genai`](https://www.npmjs.com/package/@google/genai) and keeps the returned `id`. The polling commands call `interactions.get(id)` and write the status back. Once a task completes, the text is cached in the `result` column — `gemcatch get` then answers from disk without touching the network.
|
|
@@ -252,6 +421,7 @@ Transient failures are retried with exponential backoff and full jitter, honouri
|
|
|
252
421
|
| `GEMCATCH_HOME` | Where `tasks.db` lives. Default `~/.gemcatch`. |
|
|
253
422
|
| `GEMCATCH_MODEL` | Default model. Default `gemini-3.5-flash-lite`. |
|
|
254
423
|
| `GEMCATCH_POLL_MS` | `watch` poll interval in ms. Default `10000`. |
|
|
424
|
+
| `GEMCATCH_UPLOAD_POLL_MS` | How often an upload still being processed by the Files API is checked, in ms. Default `2000`. |
|
|
255
425
|
| `GEMCATCH_DAEMON_S` | `daemon` interval in seconds. Default `300`. |
|
|
256
426
|
| `GEMCATCH_RPM` | Requests/minute ceiling. Default `15` (the free tier). `0` disables pacing. |
|
|
257
427
|
| `GEMCATCH_MAX_RETRIES` | Extra attempts on a transient failure. Default `4`. `0` disables retries. |
|
package/db.js
CHANGED
|
@@ -31,6 +31,26 @@ const MIGRATIONS = [
|
|
|
31
31
|
// the JSON array of sources an agent run returned alongside its report.
|
|
32
32
|
['agent', 'TEXT'],
|
|
33
33
|
['citations', 'TEXT'],
|
|
34
|
+
// 0.5.0: collaborative planning. `collaborative_planning` is the agent_config
|
|
35
|
+
// flag the row was submitted with (1 plan turn, 0 report turn, NULL for every
|
|
36
|
+
// run that sent no agent_config at all, including every pre-0.5.0 row);
|
|
37
|
+
// `previous_interaction_id` is the interaction this turn continues;
|
|
38
|
+
// `kind` is 'task' | 'plan' | 'report'; `parent_id` is the local task this
|
|
39
|
+
// turn continues. The DEFAULT backfills 'task' for older rows, so a 0.4.0
|
|
40
|
+
// store keeps behaving exactly as it did.
|
|
41
|
+
['collaborative_planning', 'INTEGER'],
|
|
42
|
+
['previous_interaction_id', 'TEXT'],
|
|
43
|
+
['kind', "TEXT DEFAULT 'task'"],
|
|
44
|
+
['parent_id', 'TEXT'],
|
|
45
|
+
// 0.6.0: sources. `tools_json` is the tools array the row was submitted with
|
|
46
|
+
// (header values included, so it is masked wherever it is printed);
|
|
47
|
+
// `attachments_json` records what was attached on this turn, never the bytes;
|
|
48
|
+
// `visualization` is the agent_config value; `images_json` lists the image
|
|
49
|
+
// files saved from the result. All NULL for a run that used none of them.
|
|
50
|
+
['tools_json', 'TEXT'],
|
|
51
|
+
['attachments_json', 'TEXT'],
|
|
52
|
+
['visualization', 'TEXT'],
|
|
53
|
+
['images_json', 'TEXT'],
|
|
34
54
|
];
|
|
35
55
|
|
|
36
56
|
let _db = null;
|
|
@@ -46,8 +66,18 @@ function migrate(d) {
|
|
|
46
66
|
|
|
47
67
|
function db() {
|
|
48
68
|
if (_db) return _db;
|
|
49
|
-
fs.mkdirSync(HOME, { recursive: true });
|
|
69
|
+
const created = fs.mkdirSync(HOME, { recursive: true, mode: 0o700 });
|
|
50
70
|
_db = new Database(DB_PATH);
|
|
71
|
+
// Rows can hold MCP header values. mkdir's mode only applies to a new
|
|
72
|
+
// directory, so an existing ~/.gemcatch is tightened too; a GEMCATCH_HOME the
|
|
73
|
+
// user already had is left alone. SQLite gives the -wal and -shm files the
|
|
74
|
+
// database file's mode. Best effort: some mounts refuse chmod.
|
|
75
|
+
if (process.platform !== 'win32') {
|
|
76
|
+
try {
|
|
77
|
+
if (!created && !process.env.GEMCATCH_HOME) fs.chmodSync(HOME, 0o700);
|
|
78
|
+
fs.chmodSync(DB_PATH, 0o600);
|
|
79
|
+
} catch (_) {}
|
|
80
|
+
}
|
|
51
81
|
_db.pragma('journal_mode = WAL');
|
|
52
82
|
_db.exec(BASE_SCHEMA);
|
|
53
83
|
migrate(_db);
|
|
@@ -64,8 +94,12 @@ function createTask(fields) {
|
|
|
64
94
|
const now = Date.now();
|
|
65
95
|
db()
|
|
66
96
|
.prepare(
|
|
67
|
-
'INSERT INTO tasks (id, prompt, status, created_at, updated_at, model, system_instruction, tag, agent
|
|
68
|
-
'
|
|
97
|
+
'INSERT INTO tasks (id, prompt, status, created_at, updated_at, model, system_instruction, tag, agent, ' +
|
|
98
|
+
'kind, parent_id, collaborative_planning, previous_interaction_id, ' +
|
|
99
|
+
'tools_json, attachments_json, visualization, images_json) ' +
|
|
100
|
+
'VALUES (@id, @prompt, @status, @now, @now, @model, @system_instruction, @tag, @agent, ' +
|
|
101
|
+
'@kind, @parent_id, @collaborative_planning, @previous_interaction_id, ' +
|
|
102
|
+
'@tools_json, @attachments_json, @visualization, @images_json)'
|
|
69
103
|
)
|
|
70
104
|
.run({
|
|
71
105
|
id,
|
|
@@ -76,6 +110,16 @@ function createTask(fields) {
|
|
|
76
110
|
system_instruction: t.systemInstruction || null,
|
|
77
111
|
tag: t.tag || null,
|
|
78
112
|
agent: t.agent || null,
|
|
113
|
+
kind: t.kind || 'task',
|
|
114
|
+
parent_id: t.parentId || null,
|
|
115
|
+
// Presence, not truthiness: `false` is the report turn's real flag and
|
|
116
|
+
// must be stored as 0, while a run that sends no agent_config stores NULL.
|
|
117
|
+
collaborative_planning: t.collaborativePlanning === undefined ? null : Number(!!t.collaborativePlanning),
|
|
118
|
+
previous_interaction_id: t.previousInteractionId || null,
|
|
119
|
+
tools_json: t.toolsJson || null,
|
|
120
|
+
attachments_json: t.attachmentsJson || null,
|
|
121
|
+
visualization: t.visualization || null,
|
|
122
|
+
images_json: t.imagesJson || null,
|
|
79
123
|
});
|
|
80
124
|
return id;
|
|
81
125
|
}
|
|
@@ -107,7 +151,7 @@ function setStatus(id, status, extra) {
|
|
|
107
151
|
const e = extra || {};
|
|
108
152
|
const sets = ['status = @status', 'updated_at = @now'];
|
|
109
153
|
const params = { id, status, now: Date.now() };
|
|
110
|
-
for (const key of ['result', 'error', 'usage', 'citations']) {
|
|
154
|
+
for (const key of ['result', 'error', 'usage', 'citations', 'images_json']) {
|
|
111
155
|
if (e[key] !== undefined) {
|
|
112
156
|
sets.push(`${key} = @${key}`);
|
|
113
157
|
params[key] = e[key];
|
|
@@ -181,6 +225,27 @@ function agentCounts() {
|
|
|
181
225
|
.all();
|
|
182
226
|
}
|
|
183
227
|
|
|
228
|
+
// Agent runs that actually reached the server, keyed by agent. A submit that
|
|
229
|
+
// never got an interaction_id (a bad key, a rejected agent id, a network
|
|
230
|
+
// failure) was never billed, so it must not appear in a spend total -- unlike
|
|
231
|
+
// agentCounts(), which tallies every attempt.
|
|
232
|
+
function billedAgentCounts() {
|
|
233
|
+
return db()
|
|
234
|
+
.prepare(
|
|
235
|
+
'SELECT agent, COUNT(*) AS n FROM tasks WHERE agent IS NOT NULL AND interaction_id IS NOT NULL GROUP BY agent'
|
|
236
|
+
)
|
|
237
|
+
.all();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Plan/report totals for `stats`. Ordinary tasks are not a row here; they are
|
|
241
|
+
// already accounted for in counts(), and a store that has never planned reports
|
|
242
|
+
// nothing at all.
|
|
243
|
+
function kindCounts() {
|
|
244
|
+
return db()
|
|
245
|
+
.prepare("SELECT kind, COUNT(*) AS n FROM tasks WHERE kind IS NOT NULL AND kind != 'task' GROUP BY kind")
|
|
246
|
+
.all();
|
|
247
|
+
}
|
|
248
|
+
|
|
184
249
|
function close() {
|
|
185
250
|
if (_db) _db.close();
|
|
186
251
|
_db = null;
|
|
@@ -200,5 +265,7 @@ module.exports = {
|
|
|
200
265
|
prunableTasks,
|
|
201
266
|
counts,
|
|
202
267
|
agentCounts,
|
|
268
|
+
billedAgentCounts,
|
|
269
|
+
kindCounts,
|
|
203
270
|
close,
|
|
204
271
|
};
|