Everything needed to develop, test and release the plugin.
src/main.ts plugin: 3 tools + env config + RPC client
src/verify.ts truth engine (raw-text verification, proximity)
src/env.ts .env parsing + cache (mtime+size+TTL)
src/errors.ts AnyTxtError codes
src/types.ts RPC envelopes + tool args
.opencode/plugins/anytxt.ts OpenCode shim — one-line re-export (no logic)
bin/install.mjs installer (bunx [email protected] install/remove/update)
skills/anytxt/SKILL.md agent skill
command/anytxt-param.md /anytxt-param command
tests/verify.test.ts unit: verify engine
tests/errors.test.ts unit: error codes
tests/env.test.ts unit: env cache
tests/probe.test.ts e2e: live ATGUI (skipped when down) + error path
docs/user.md end-user guide
opencode.json project permission rules (asks)
Shim pattern: .opencode/plugins/anytxt.ts only re-exports
../../src/main.ts — OpenCode loads the shim, all logic lives in src/.
The installer GENERATES the shim with ../src/main.ts because the global
config dir has one less nesting level than the repo.
bun install
bun test # unit always; live e2e when ATGUI port 9920/9921 is up
bun run typecheck # tsc --noEmitAdd dev deps with bun add -d <pkg> (typescript, @types/bun). tsconfig.json
is strict (noUncheckedIndexedAccess, verbatimModuleSyntax, noEmit).
- TDD: write the failing test first (
bun test tests/x.test.ts), watch it fail for the right reason, implement the minimum, watch it pass. tests/probe.test.tspings 9920/9921 at import time; every live test is skipped when ATGUI is down. Fixtures live under$HOMEbecause ATGUI ignores/tmpeven afterSyncIndexreturns OK.- The error-path test forces
ANYTXT_PORT=9911(closed port) — deterministic regardless of ATGUI state. - e2e asserts: sync feedback,
1.0.0≠1.0.1exclusion,st=1punctuation, fragment lie warning, permission ask raised.
HTTP JSON-RPC 2.0 hosted inside the ATGUI process. Base:
POST http://127.0.0.1:9920 (plugin falls back to 9921). Envelope:
{ "id": 123, "jsonrpc": "2.0", "method": "ATRpcServer.Searcher.V1.<Method>", "params": { "input": { } } }Response: result.data.output. Non-commercial use only (AnyTXT license).
| Method | Input | Output |
|---|---|---|
Search |
pattern, filterDir, filterExt, lastModifyBegin, lastModifyEnd | { count } — matching file count (no pagination) |
GetResult |
Search inputs + limit, offset, order | { count, field, files: [fid, lastModify, size, file][] } |
GetFragment |
fid, pattern | { text } — one fragment, highlights *<<*…*>>* |
GetFragmentAll |
fid, pattern | { count, text[] } — all fragments, highlights *<<*…*>>* |
SyncIndex |
folder | {} — no feedback |
GetRawTextByFID |
fid | { text } — raw text (verification source of truth) |
OCR |
file | OCR text — OCR version only |
pattern: advanced syntax (below); quoted phrases = exact, not tokenized.filterDir: folder restriction; server defaults to"C:"when omitted.filterExt:"*"or"doc;pdf;ppt"— multiple via;.lastModifyBegin/lastModifyEnd: Unix timestamps,0..2147483647.limit: page size;offset: page start.order: 0 default, 1 lastModify ASC, 2 lastModify DESC, 3 filterDir ASC, 4 filterDir DESC.
| Operator | Meaning |
|---|---|
& |
AND — terms on both sides included |
| |
OR — at least one side matches |
! |
NOT — exclude results containing the term after |
( ) |
grouping — combinable with other operators |
" " |
exact match — phrase not tokenized/split |
test | hello
test | "hello word"
test | "hello word" !this
test & (hello | "this is") !that
curl --location '127.0.0.1:9920' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"id": 123, "jsonrpc": "2.0",
"method": "ATRpcServer.Searcher.V1.GetResult",
"params": { "input": {
"pattern": "Hello", "filterDir": "C:\\", "filterExt": "*",
"lastModifyBegin": 0, "lastModifyEnd": 2147483647,
"limit": "300", "offset": 0, "order": 0 } } }'filterDiromitted → server defaults to"C:"(Windows heritage) → 0 results on Linux. The plugin always sends it.- Version tokenization is broken:
1.0.0≡1.0.1(search AND fragments). Raw-text verification is the only cure. stparam (plugin extension):1exact (unquoted phrase with punctuation works),2advanced,4regexp → always 0 over RPC (unsupported).- Orders: 0 default, 1 modtime ASC, 2 DESC, 3 filterDir ASC, 4 DESC.
- No filename filter over RPC;
Search ""/"*"→ 0 (no "list all"). - Indexing is async (~45 s);
/tmpnever indexed; folders outside indexed roots are silently ignored even after a successfulSyncIndex. - False positives confirmed on disk: matched files with 0 real occurrences.
- Official API lists
OCR(inputfile);
- Verification pipeline (
anytxt_search): GetResult → per-fileGetRawTextByFID→verifyRaw(literal substring match,&/|/!parsed,&pairs withinANYTXT_NEARwindow, markdown chars stripped) → false positives excluded and reported → disk cross-check (mtime vs indexedlastModify, content) → per-file occurrence counts. - Env cache (
src/env.ts): stat mtimeNs + size + TTL (1 s default) —/anytxt-paramedits land immediately, no readFileSync per call. Same-tick rewrites keep mtime (fs granularity) — size guards that. - Errors (
src/errors.ts):unreachable(network) /http_error/rpc_error(logic) — distinguishable, prefixedanytxt[code]:. - Permission ask: the opencode engine does not prompt for plugin tools
unless the tool itself calls
ToolContext.ask(). Both halves required: plugin raises ask (ANYTXT_ASK) ANDopencode.jsoncarries matching rules — otherwise the agent's allow-all default wins silently. - Plugin exports: only
AnyTxtPlugin— every function export of a plugin module is treated as a plugin factory by OpenCode; never export helpers frommain.ts(they live inverify.ts/env.ts/…).
- Bump
versioninpackage.json(semver, matches the changelog phase). - Update
CHANGELOG.md(Keep a Changelog, current order: newest released at top,[Unreleased]with planned phases, diff links at the bottom). npm publish --dry-run— verify the tarball (bin, src, .opencode, skills, command, .env.example).- Tag
vX.Y.Z, push,npm publish. - Test the installer end-to-end:
bun bin/install.mjs /tmp/anytxt-testthen import the generated shim. - Post release:
[Unreleased]diff link moves to the new tag. - Bump the pinned bunx version (
@x.y.z) in README, docs/user.md, docs/dev.md, bin/install.mjs comment — never leave barebunx anytxt-opencode(stale cache).