← WebMCP Directory

Tool tree

gentropic.org

Media & PersonalTools verified
Website
@gcu/weir
Explore capabilities
    weir_queryItemsAnswer

    Search/list weir feed items, newest first. Filters: q (substring over title/excerpt/text), feed (a source by id OR name, e.g. "Saved Links"), category (folder name; "" = ungrouped), type (article|video|release|paper|status|track|podcast|commit|issue|note|doc), view (inbox|saved|archived), unread (bool), saved (bool), limit (default 30, max 100). PROVENANCE filters (the agent footprint): addedBy = items the agent ADDED (true = any agent, or an identity string like "claude:librarian"); taggedBy = items the agent TAGGED. Filters combine. Paginated: returns { count, total, hasMore, items, nextCursor }; page by passing nextCursor back with the SAME filters. Items are compact (id, title, url, feed, published, tags, added_by, excerpt). Use weir_listSources first to see feed/folder names.

    View tool JSON
    {
      "name": "weir_queryItems",
      "kind": "answer",
      "impl": "imperative",
      "description": "Search/list weir feed items, newest first. Filters: q (substring over title/excerpt/text), feed (a source by id OR name, e.g. \"Saved Links\"), category (folder name; \"\" = ungrouped), type (article|video|release|paper|status|track|podcast|commit|issue|note|doc), view (inbox|saved|archived), unread (bool), saved (bool), limit (default 30, max 100). PROVENANCE filters (the agent footprint): addedBy = items the agent ADDED (true = any agent, or an identity string like \"claude:librarian\"); taggedBy = items the agent TAGGED. Filters combine. Paginated: returns { count, total, hasMore, items, nextCursor }; page by passing nextCursor back with the SAME filters. Items are compact (id, title, url, feed, published, tags, added_by, excerpt). Use weir_listSources first to see feed/folder names.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "q": {
            "type": "string",
            "description": "Substring search over title/excerpt/text"
          },
          "feed": {
            "type": "string",
            "description": "Scope to one source — its feed id or display name (e.g. \"Saved Links\")"
          },
          "category": {
            "type": "string",
            "description": "Scope to one folder by name (\"\" = ungrouped)"
          },
          "type": {
            "type": "string",
            "description": "Item type filter"
          },
          "view": {
            "type": "string",
            "enum": [
              "inbox",
              "saved",
              "archived"
            ],
            "description": "Which view to scope to"
          },
          "unread": {
            "type": "boolean",
            "description": "Only unread items"
          },
          "saved": {
            "type": "boolean",
            "description": "Only saved (true) / only unsaved (false)"
          },
          "addedBy": {
            "description": "Items the agent ADDED — true (any agent) or an identity string (e.g. \"claude:librarian\")"
          },
          "taggedBy": {
            "description": "Items the agent TAGGED — true (any agent) or an identity string"
          },
          "limit": {
            "type": "integer",
            "description": "Max items per page (default 30, cap 100)"
          },
          "cursor": {
            "type": "string",
            "description": "Opaque pagination cursor from a previous call’s nextCursor — reuse the same filters"
          }
        }
      },
      "page": "/weir/"
    }
    weir_listSourcesAnswer

    Source overview (compact — safe on a 400+ feed corpus). DEFAULT returns { feedCount, folders:[{category,feeds,inbox}], health:{failing,stale,…}, troubled:[…] } — folder summaries + a health tally + ONLY the non-healthy feeds (state/lastPolled/fails/lastError), so you can spot prune candidates without dumping everything. Pass `q` to FIND a feed by name/URL/id across ALL folders (the way to locate a specific feed) → { query, count, feeds:[…incl url] }. Pass `category` for the FULL per-feed list of one folder. Pair with weir_updateFeed / weir_removeFeed to curate, weir_queryItems({feed}) to drill in.

    View tool JSON
    {
      "name": "weir_listSources",
      "kind": "answer",
      "impl": "imperative",
      "description": "Source overview (compact — safe on a 400+ feed corpus). DEFAULT returns { feedCount, folders:[{category,feeds,inbox}], health:{failing,stale,…}, troubled:[…] } — folder summaries + a health tally + ONLY the non-healthy feeds (state/lastPolled/fails/lastError), so you can spot prune candidates without dumping everything. Pass `q` to FIND a feed by name/URL/id across ALL folders (the way to locate a specific feed) → { query, count, feeds:[…incl url] }. Pass `category` for the FULL per-feed list of one folder. Pair with weir_updateFeed / weir_removeFeed to curate, weir_queryItems({feed}) to drill in.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "q": {
            "type": "string",
            "description": "Find feeds whose name/URL/id contains this (across all folders)"
          },
          "category": {
            "type": "string",
            "description": "Full per-feed detail for one folder (\"\" = ungrouped)"
          }
        }
      },
      "page": "/weir/"
    }
    weir_addFeedAction

    Subscribe to a feed by URL — adapter auto-detected (RSS/Atom/JSON Feed, YouTube channel, GitHub repo); an initial poll fires in the app. Optional `name` + `category` (folder). When YOU (Claude) add it, it is stamped source:agent + your identity and lands in weir_reviewQueue as a `feed` proposal for the user to ratify — pass `rationale` so they see WHY. Returns the created feed.

    View tool JSON
    {
      "name": "weir_addFeed",
      "kind": "act",
      "impl": "imperative",
      "description": "Subscribe to a feed by URL — adapter auto-detected (RSS/Atom/JSON Feed, YouTube channel, GitHub repo); an initial poll fires in the app. Optional `name` + `category` (folder). When YOU (Claude) add it, it is stamped source:agent + your identity and lands in weir_reviewQueue as a `feed` proposal for the user to ratify — pass `rationale` so they see WHY. Returns the created feed.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "Feed or page URL to subscribe to"
          },
          "name": {
            "type": "string",
            "description": "Display name (default: derived from the URL/adapter)"
          },
          "category": {
            "type": "string",
            "description": "Folder to file it under"
          },
          "rationale": {
            "type": "string",
            "description": "Why you are proposing this feed — shown in the review queue at ratify time"
          }
        },
        "required": [
          "url"
        ]
      },
      "page": "/weir/"
    }
    weir_updateFeedAction

    Curate a feed: ARCHIVE/unarchive it (retire a dead/dormant feed off the health warning — items kept, poller skips it, fully reversible — the never-delete alternative to removing), change its URL (e.g. fix a moved/404 feed — items stay, validators reset, it re-polls now), rename, recategorize (folder), set retention ("forever" or a day count), poll interval (minutes), image policy, or full-text auto-fetch. Identify it by `id` (from weir_listSources). Not destructive. Returns the applied patch (+ `repoll` result when the URL changed).

    View tool JSON
    {
      "name": "weir_updateFeed",
      "kind": "act",
      "impl": "imperative",
      "description": "Curate a feed: ARCHIVE/unarchive it (retire a dead/dormant feed off the health warning — items kept, poller skips it, fully reversible — the never-delete alternative to removing), change its URL (e.g. fix a moved/404 feed — items stay, validators reset, it re-polls now), rename, recategorize (folder), set retention (\"forever\" or a day count), poll interval (minutes), image policy, or full-text auto-fetch. Identify it by `id` (from weir_listSources). Not destructive. Returns the applied patch (+ `repoll` result when the URL changed).",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Feed id (from weir_listSources)"
          },
          "archived": {
            "type": "boolean",
            "description": "true = retire the feed (off the failing/stale warning, poller skips it, items kept); false = restore + re-poll. Reversible, never deletes."
          },
          "url": {
            "type": "string",
            "description": "New feed URL (fixes a moved/dead feed; re-polls immediately)"
          },
          "name": {
            "type": "string",
            "description": "Rename the feed"
          },
          "category": {
            "type": "string",
            "description": "Move to a folder (\"\" = ungrouped)"
          },
          "retention": {
            "description": "\"forever\", or a number of days to keep before archiving"
          },
          "poll_interval_minutes": {
            "type": "integer",
            "description": "How often to poll (min 5)"
          },
          "images_allowed": {
            "type": "boolean",
            "description": "Always load images for this feed"
          },
          "fetch_full_content": {
            "type": "boolean",
            "description": "Auto-fetch full article text"
          }
        },
        "required": [
          "id"
        ]
      },
      "page": "/weir/"
    }
    weir_removeFeedSensitive Action

    Unsubscribe and DELETE a feed and its items — for pruning dead/moved sources (DNS-gone, TLS-dead, 404) during a curation sweep. Destructive + NOT reversible (no trash, unlike the stacks). Gated behind a user setting; if disabled, errors and points to the UI. Confirm with the user before calling. Returns { removed, name, items }.

    View tool JSON
    {
      "name": "weir_removeFeed",
      "kind": "transact",
      "impl": "imperative",
      "description": "Unsubscribe and DELETE a feed and its items — for pruning dead/moved sources (DNS-gone, TLS-dead, 404) during a curation sweep. Destructive + NOT reversible (no trash, unlike the stacks). Gated behind a user setting; if disabled, errors and points to the UI. Confirm with the user before calling. Returns { removed, name, items }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Feed id (from weir_listSources)"
          }
        },
        "required": [
          "id"
        ]
      },
      "page": "/weir/"
    }
    weir_renameFeedAction

    Re-key a feed's ID (not its display name — use weir_updateFeed for the name). A feed id is load-bearing: the adapter mints item ids as `<feedid>:<guid>` every poll, and content files, tombstones, and catalog cards are addressed by it. This migrates ALL of that in lockstep (relocating content; nothing is data-deleted) and preserves read/saved/tags. Use it to clean up an id that was auto-derived from a bad name — e.g. a feed that landed on the generic host slug `bsky-app`. The newId is slugified; a collision with an existing id is rejected. Returns { renamed, from, items, tombstones }.

    View tool JSON
    {
      "name": "weir_renameFeed",
      "kind": "act",
      "impl": "imperative",
      "description": "Re-key a feed's ID (not its display name — use weir_updateFeed for the name). A feed id is load-bearing: the adapter mints item ids as `<feedid>:<guid>` every poll, and content files, tombstones, and catalog cards are addressed by it. This migrates ALL of that in lockstep (relocating content; nothing is data-deleted) and preserves read/saved/tags. Use it to clean up an id that was auto-derived from a bad name — e.g. a feed that landed on the generic host slug `bsky-app`. The newId is slugified; a collision with an existing id is rejected. Returns { renamed, from, items, tombstones }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Current feed id (from weir_listSources)"
          },
          "newId": {
            "type": "string",
            "description": "Desired feed id (will be slugified, e.g. \"arne-androidarts\")"
          }
        },
        "required": [
          "id",
          "newId"
        ]
      },
      "page": "/weir/"
    }
    weir_repollAction

    Force a fresh poll of one feed right now, bypassing conditional-GET so the full body re-parses even if nothing changed — re-derives titles, picks up edits. The clean way to refresh a feed (does NOT reset validators or schedule, unlike changing the URL). Use it to heal a microblog feed reading as "(untitled)", or to pull a feed immediately. Returns { id, inserted, updated, skipped } or { error }.

    View tool JSON
    {
      "name": "weir_repoll",
      "kind": "act",
      "impl": "imperative",
      "description": "Force a fresh poll of one feed right now, bypassing conditional-GET so the full body re-parses even if nothing changed — re-derives titles, picks up edits. The clean way to refresh a feed (does NOT reset validators or schedule, unlike changing the URL). Use it to heal a microblog feed reading as \"(untitled)\", or to pull a feed immediately. Returns { id, inserted, updated, skipped } or { error }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Feed id (from weir_listSources)"
          }
        },
        "required": [
          "id"
        ]
      },
      "page": "/weir/"
    }
    weir_recoverAction

    Recover a dead/truncated feed's lost history from the Internet Archive (Wayback Machine): finds old snapshots of the feed URL, re-parses their items, and stores them — archived history preserved, nothing deleted. Default QUEUES feed(s) into a gentle background drip (one IA request every few minutes, resumes across restarts) — the right tool for a batch of dead feeds; `now:true` recovers a single feed immediately as a throttled burst and returns counts (good for proving one feed before queuing many). Scope by id, ids[], or category. Returns recovery counts (now) or drip status (queued). Pairs with never-delete: recover the data, keep the feed, don't prune.

    View tool JSON
    {
      "name": "weir_recover",
      "kind": "act",
      "impl": "imperative",
      "description": "Recover a dead/truncated feed's lost history from the Internet Archive (Wayback Machine): finds old snapshots of the feed URL, re-parses their items, and stores them — archived history preserved, nothing deleted. Default QUEUES feed(s) into a gentle background drip (one IA request every few minutes, resumes across restarts) — the right tool for a batch of dead feeds; `now:true` recovers a single feed immediately as a throttled burst and returns counts (good for proving one feed before queuing many). Scope by id, ids[], or category. Returns recovery counts (now) or drip status (queued). Pairs with never-delete: recover the data, keep the feed, don't prune.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "A single feed id (from weir_listSources)"
          },
          "ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Multiple feed ids to queue into the drip"
          },
          "category": {
            "type": "string",
            "description": "Queue every feed in this folder"
          },
          "now": {
            "type": "boolean",
            "description": "Recover the single `id` immediately (foreground burst) instead of queuing the background drip"
          }
        }
      },
      "page": "/weir/"
    }
    weir_resolveLinksAction

    Kick the background resolver to process pending saved links now — resolve share.google/shortener URLs to their real destination and fetch thumbnail/title/excerpt metadata, gently over time (a couple every ~15s, so it never burst-hits the shortener). Returns { kicked, pending, running }. Imported links resolve on their own; use this to nudge it.

    View tool JSON
    {
      "name": "weir_resolveLinks",
      "kind": "act",
      "impl": "imperative",
      "description": "Kick the background resolver to process pending saved links now — resolve share.google/shortener URLs to their real destination and fetch thumbnail/title/excerpt metadata, gently over time (a couple every ~15s, so it never burst-hits the shortener). Returns { kicked, pending, running }. Imported links resolve on their own; use this to nudge it.",
      "inputSchema": {
        "type": "object",
        "properties": {}
      },
      "page": "/weir/"
    }
    weir_resolverLogAnswer

    Read the background link-resolver run log — { pending, running, resolved, parked, reasons, recent, startedAt, updatedAt }. `resolved` = links fully resolved+enriched; `parked` = gave up after retries; `reasons` tallies every failed try (http-429 = share.google throttling, no-redirect, network); `recent` = the last few parked links (host + reason). Use it to review an overnight run.

    View tool JSON
    {
      "name": "weir_resolverLog",
      "kind": "answer",
      "impl": "imperative",
      "description": "Read the background link-resolver run log — { pending, running, resolved, parked, reasons, recent, startedAt, updatedAt }. `resolved` = links fully resolved+enriched; `parked` = gave up after retries; `reasons` tallies every failed try (http-429 = share.google throttling, no-redirect, network); `recent` = the last few parked links (host + reason). Use it to review an overnight run.",
      "inputSchema": {
        "type": "object",
        "properties": {}
      },
      "page": "/weir/"
    }
    weir_reEnrichAction

    Rework saved links: clear their enriched flag so the background resolver re-fetches + re-applies metadata (title/thumbnail/excerpt). `weakTitles:true` re-does only links with a weak title (e.g. "Source: Hackaday" → real og:title); `all:true` re-does every saved link. Already-resolved urls are re-fetched directly (fast, no share.google throttle). Returns { queued, pending }.

    View tool JSON
    {
      "name": "weir_reEnrich",
      "kind": "act",
      "impl": "imperative",
      "description": "Rework saved links: clear their enriched flag so the background resolver re-fetches + re-applies metadata (title/thumbnail/excerpt). `weakTitles:true` re-does only links with a weak title (e.g. \"Source: Hackaday\" → real og:title); `all:true` re-does every saved link. Already-resolved urls are re-fetched directly (fast, no share.google throttle). Returns { queued, pending }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "weakTitles": {
            "type": "boolean",
            "description": "Only links whose title is weak"
          },
          "all": {
            "type": "boolean",
            "description": "Every saved link"
          }
        }
      },
      "page": "/weir/"
    }
    weir_getItemAnswer

    Get one weir item by id: glass facets + description (if cataloged), the card's `card` authorship block (cataloger=provider:model, reviewer human|agent, by=identity, needs_review), item provenance (`added_by`, plus `tag_src`/`tag_by` = who applied each tag), and its knowledge-graph edges — `links` (what its body links to via [[ref]], resolved to {ref,id,title}) and `backlinks` (items whose body links to it). Pass content:true to include the extracted article/note text (capped 8k).

    View tool JSON
    {
      "name": "weir_getItem",
      "kind": "answer",
      "impl": "imperative",
      "description": "Get one weir item by id: glass facets + description (if cataloged), the card's `card` authorship block (cataloger=provider:model, reviewer human|agent, by=identity, needs_review), item provenance (`added_by`, plus `tag_src`/`tag_by` = who applied each tag), and its knowledge-graph edges — `links` (what its body links to via [[ref]], resolved to {ref,id,title}) and `backlinks` (items whose body links to it). Pass content:true to include the extracted article/note text (capped 8k).",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Item id (from weir_queryItems)"
          },
          "content": {
            "type": "boolean",
            "description": "Include the extracted body text"
          }
        },
        "required": [
          "id"
        ]
      },
      "page": "/weir/"
    }
    weir_getItemsAnswer

    BATCH weir_getItem — fetch many items at once (reference work fans out: one search → pull a dozen cards). Pass `ids` (from weir_search / weir_queryItems / weir_queryCatalog). Returns { count, items:[…same shape as weir_getItem], missing?:[ids not found], omitted? }. With content:true each body is capped at 8k so the batch is capped tighter (~25 vs ~60). Saves a round-trip storm vs. calling weir_getItem N times.

    View tool JSON
    {
      "name": "weir_getItems",
      "kind": "answer",
      "impl": "imperative",
      "description": "BATCH weir_getItem — fetch many items at once (reference work fans out: one search → pull a dozen cards). Pass `ids` (from weir_search / weir_queryItems / weir_queryCatalog). Returns { count, items:[…same shape as weir_getItem], missing?:[ids not found], omitted? }. With content:true each body is capped at 8k so the batch is capped tighter (~25 vs ~60). Saves a round-trip storm vs. calling weir_getItem N times.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Item ids to fetch"
          },
          "content": {
            "type": "boolean",
            "description": "Include each item's extracted body text (capped 8k each)"
          }
        },
        "required": [
          "ids"
        ]
      },
      "page": "/weir/"
    }
    weir_searchAction

    RANKED full-text search (the librarian BM25 index) — relevance-ordered, better than weir_queryItems's substring `q` for "most relevant about X" on a large corpus. CURATION-AWARE by default: results are reranked so the curated minority (books, notes, repo `doc` items, saved links) outranks the auto-ingested feed/video firehose, with a bonus when the query matches an item's facet terms — for a reference query the curated layer is the signal (pass rerank:false for raw BM25, or curated:true to hard-scope to the curated tier only). Each hit carries its `tier` (curated|neutral|firehose) when reranked. VOCAB-SYNONYM EXPANSION is also on by default: a query term that is a controlled-vocabulary prefLabel/altLabel also matches its synonyms (the seeded cross-lingual pairs like kriging↔krigagem bridge lexically, no embeddings) — the response carries `expanded:{term→[synonyms]}` when it fires; pass expand:false for the literal query. SEES THE ARCHIVE by default (the standing corpus, never-delete) — hits carry `archived:true` when archived; pass includeArchived:false to limit to active. Optional scope filters (feed/type/category/view/unread/saved) narrow it like queryItems. Returns { ranked, reranked?, count, items:[…,score,tier?,archived?] } (ranked:false = index not ready, fell back to substring). Use queryItems to LIST a whole feed/folder; use search to FIND by relevance; use weir_queryCatalog to search WITHIN a facet set.

    View tool JSON
    {
      "name": "weir_search",
      "kind": "act",
      "impl": "imperative",
      "description": "RANKED full-text search (the librarian BM25 index) — relevance-ordered, better than weir_queryItems's substring `q` for \"most relevant about X\" on a large corpus. CURATION-AWARE by default: results are reranked so the curated minority (books, notes, repo `doc` items, saved links) outranks the auto-ingested feed/video firehose, with a bonus when the query matches an item's facet terms — for a reference query the curated layer is the signal (pass rerank:false for raw BM25, or curated:true to hard-scope to the curated tier only). Each hit carries its `tier` (curated|neutral|firehose) when reranked. VOCAB-SYNONYM EXPANSION is also on by default: a query term that is a controlled-vocabulary prefLabel/altLabel also matches its synonyms (the seeded cross-lingual pairs like kriging↔krigagem bridge lexically, no embeddings) — the response carries `expanded:{term→[synonyms]}` when it fires; pass expand:false for the literal query. SEES THE ARCHIVE by default (the standing corpus, never-delete) — hits carry `archived:true` when archived; pass includeArchived:false to limit to active. Optional scope filters (feed/type/category/view/unread/saved) narrow it like queryItems. Returns { ranked, reranked?, count, items:[…,score,tier?,archived?] } (ranked:false = index not ready, fell back to substring). Use queryItems to LIST a whole feed/folder; use search to FIND by relevance; use weir_queryCatalog to search WITHIN a facet set.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "q": {
            "type": "string",
            "description": "The search query"
          },
          "feed": {
            "type": "string",
            "description": "Scope to a source (id or display name)"
          },
          "category": {
            "type": "string",
            "description": "Scope to a folder (\"\" = ungrouped)"
          },
          "type": {
            "type": "string",
            "description": "Scope to an item type (e.g. \"doc\" for repo/project docs)"
          },
          "view": {
            "type": "string",
            "enum": [
              "inbox",
              "saved",
              "archived"
            ],
            "description": "Scope to a view"
          },
          "unread": {
            "type": "boolean",
            "description": "Only unread"
          },
          "saved": {
            "type": "boolean",
            "description": "Only saved"
          },
          "curated": {
            "type": "boolean",
            "description": "Hard-scope to the curated tier only (books/notes/repo docs/saved links) — excludes the feed firehose"
          },
          "rerank": {
            "type": "boolean",
            "description": "Curation-aware reranking (default true); pass false for raw BM25 relevance"
          },
          "expand": {
            "type": "boolean",
            "description": "Vocab-synonym query expansion (default true): a query term that is a controlled-vocabulary prefLabel/altLabel also matches its synonyms — bridges curated + cross-lingual pairs (kriging↔krigagem) lexically. Pass false for the literal query. When it fires, the response carries `expanded:{term→[synonyms]}`."
          },
          "weights": {
            "type": "object",
            "description": "PER-CALL rerank weight override (ephemeral — for tuning; defaults are curated 2.5 / neutral 1.0 / firehose 0.6 / facet 0.2). Clamped 0–10. Sweep these against the eval queries, then report the winning set to bake as the default.",
            "properties": {
              "curated": {
                "type": "number"
              },
              "neutral": {
                "type": "number"
              },
              "firehose": {
                "type": "number"
              },
              "facet": {
                "type": "number",
                "description": "per-matched-facet-term bonus increment"
              }
            }
          },
          "explain": {
            "type": "boolean",
            "description": "Surface the raw lexical `lex` score per hit + the `weights` used, so you can SEE why each item ranked where it did while tuning"
          },
          "includeArchived": {
            "type": "boolean",
            "description": "Include archived items (default TRUE — the reference desk sees the whole archive; pass false to limit to active)"
          },
          "limit": {
            "type": "integer",
            "description": "Max hits (default 20, cap 100)"
          }
        },
        "required": [
          "q"
        ]
      },
      "page": "/weir/"
    }
    weir_setStateAction

    Set read / saved / archived (each a boolean; pass only the ones to change) — on ONE item (`id`), a LIST of specific items (`ids`: an array — applied in one atomic pass, the right way to flip many known items at once), or every item matching a query (pass q/feed/category/type/view/unread/saved — e.g. archive a dead feed, mark a folder read). A query bulk call REQUIRES a scoping filter (no accidental whole-corpus change). All reversible — archive never deletes (weir_unarchiveAll reverses a sweep). Returns the item (id mode) or { matched, patch, missing? } (ids/query mode).

    View tool JSON
    {
      "name": "weir_setState",
      "kind": "act",
      "impl": "imperative",
      "description": "Set read / saved / archived (each a boolean; pass only the ones to change) — on ONE item (`id`), a LIST of specific items (`ids`: an array — applied in one atomic pass, the right way to flip many known items at once), or every item matching a query (pass q/feed/category/type/view/unread/saved — e.g. archive a dead feed, mark a folder read). A query bulk call REQUIRES a scoping filter (no accidental whole-corpus change). All reversible — archive never deletes (weir_unarchiveAll reverses a sweep). Returns the item (id mode) or { matched, patch, missing? } (ids/query mode).",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "A single item id; omit to bulk over `ids` or a query"
          },
          "ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "An explicit list of item ids — flip them all in one atomic write (preferred over many single-id calls). Unknown ids come back in `missing`."
          },
          "read": {
            "type": "boolean",
            "description": "Mark read/unread"
          },
          "saved": {
            "type": "boolean",
            "description": "Save/unsave (star)"
          },
          "archived": {
            "type": "boolean",
            "description": "Archive/unarchive (non-destructive)"
          },
          "q": {
            "type": "string",
            "description": "Bulk scope: substring over title/excerpt/text"
          },
          "feed": {
            "type": "string",
            "description": "Bulk scope: a source by id or name"
          },
          "category": {
            "type": "string",
            "description": "Bulk scope: a folder (\"\" = ungrouped)"
          },
          "type": {
            "type": "string",
            "description": "Bulk scope: item type"
          },
          "view": {
            "type": "string",
            "enum": [
              "inbox",
              "saved",
              "archived"
            ],
            "description": "Bulk scope: a view (use view:\"saved\" to scope to saved items)"
          },
          "unread": {
            "type": "boolean",
            "description": "Bulk scope: only unread"
          }
        }
      },
      "page": "/weir/"
    }
    weir_tagAction

    Add and/or remove tags — on ONE item (pass `id`) or on EVERY item matching a query (pass any of q/feed/category/type/view/unread/saved — the "tag all these results" verb). Tags are stamped source:'llm' (the UI shows them apart from your 'human' tags), are immediately searchable/queryable, and feed the glass `entity` facet on the next catalog. Returns the updated item (id mode) or { matched, changed } (query mode). Use weir_queryItems first to preview a bulk scope.

    View tool JSON
    {
      "name": "weir_tag",
      "kind": "act",
      "impl": "imperative",
      "description": "Add and/or remove tags — on ONE item (pass `id`) or on EVERY item matching a query (pass any of q/feed/category/type/view/unread/saved — the \"tag all these results\" verb). Tags are stamped source:'llm' (the UI shows them apart from your 'human' tags), are immediately searchable/queryable, and feed the glass `entity` facet on the next catalog. Returns the updated item (id mode) or { matched, changed } (query mode). Use weir_queryItems first to preview a bulk scope.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Tag a single item by id (from weir_queryItems); omit to bulk-tag a query"
          },
          "q": {
            "type": "string",
            "description": "Bulk scope: substring over title/excerpt/text"
          },
          "feed": {
            "type": "string",
            "description": "Bulk scope: a source by id or display name"
          },
          "category": {
            "type": "string",
            "description": "Bulk scope: a folder name (\"\" = ungrouped)"
          },
          "type": {
            "type": "string",
            "description": "Bulk scope: item type (article|video|paper|note|…)"
          },
          "view": {
            "type": "string",
            "enum": [
              "inbox",
              "saved",
              "archived"
            ],
            "description": "Bulk scope: a view"
          },
          "unread": {
            "type": "boolean",
            "description": "Bulk scope: only unread"
          },
          "saved": {
            "type": "boolean",
            "description": "Bulk scope: only saved"
          },
          "add": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags to add"
          },
          "remove": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags to remove"
          }
        }
      },
      "page": "/weir/"
    }
    weir_unarchiveAllAction

    Bring EVERY archived item back to active and clear its expiry (so retention won't re-shelve it) — the one-shot "I keep everything" restore that reverses an over-eager auto-archive sweep. Reversible; nothing is deleted. Returns { unarchived }.

    View tool JSON
    {
      "name": "weir_unarchiveAll",
      "kind": "act",
      "impl": "imperative",
      "description": "Bring EVERY archived item back to active and clear its expiry (so retention won't re-shelve it) — the one-shot \"I keep everything\" restore that reverses an over-eager auto-archive sweep. Reversible; nothing is deleted. Returns { unarchived }.",
      "inputSchema": {
        "type": "object",
        "properties": {}
      },
      "page": "/weir/"
    }
    weir_provenanceMigrateAction

    One-shot cleanup: normalize the agent's historical authorship stamps to the unified `source:agent` tier — tags previously marked 'llm' and relation edges marked 'claude' (both meant Claude) become 'agent'. Pass `backfillBooks` (an identity string, e.g. "claude:librarian") to ALSO repair agent-proposed book holdings whose `added_by` was stripped by the pre-fix makeItem bug — identified by their `web-proposed` tag — so they re-enter the review queue. Idempotent and safe to re-run; nothing is deleted. Returns { migrated: { tags, edges, books } }.

    View tool JSON
    {
      "name": "weir_provenanceMigrate",
      "kind": "act",
      "impl": "imperative",
      "description": "One-shot cleanup: normalize the agent's historical authorship stamps to the unified `source:agent` tier — tags previously marked 'llm' and relation edges marked 'claude' (both meant Claude) become 'agent'. Pass `backfillBooks` (an identity string, e.g. \"claude:librarian\") to ALSO repair agent-proposed book holdings whose `added_by` was stripped by the pre-fix makeItem bug — identified by their `web-proposed` tag — so they re-enter the review queue. Idempotent and safe to re-run; nothing is deleted. Returns { migrated: { tags, edges, books } }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "backfillBooks": {
            "type": "string",
            "description": "Identity to stamp on web-proposed books missing added_by (e.g. \"claude:librarian\"); omit to skip the book backfill"
          }
        }
      },
      "page": "/weir/"
    }
    weir_addBookSensitive Action

    Add OR update owned/physical books in the holdings (the "Books" library) — e.g. cataloging a real shelf, or stamping series/seq onto books already there. Each book: title (required), author, isbn (→ Open Library fills cover/date/publisher), series + seq (the volume number — series+seq keep a numbered set TOGETHER and in volume order on the shelf; without seq a series scatters by year), date, tags (yours), ddc/lcc (display codes). To UPDATE an existing holding in place, pass its `id` (e.g. "book:269049145", from the shelf list / weir_queryItems) — read/saved/tags and the catalog card are preserved, and `structured` is MERGED, so you only pass the fields you are changing (existing isbn/ddc/lcc/series/seq are kept; a plain LibraryThing re-import will not wipe a stamped series). Without an id a new book is created (keyed by isbn||title). Batch with `books:[…]` (preferred) or pass ONE book inline. A NEW book YOU add is stamped source:agent and surfaces in weir_reviewQueue as a `book` proposal (tag it `to-buy` for a wishlist item you don't own yet); pass `rationale` so the user sees why. Returns { inserted, updated, books }.

    View tool JSON
    {
      "name": "weir_addBook",
      "kind": "transact",
      "impl": "imperative",
      "description": "Add OR update owned/physical books in the holdings (the \"Books\" library) — e.g. cataloging a real shelf, or stamping series/seq onto books already there. Each book: title (required), author, isbn (→ Open Library fills cover/date/publisher), series + seq (the volume number — series+seq keep a numbered set TOGETHER and in volume order on the shelf; without seq a series scatters by year), date, tags (yours), ddc/lcc (display codes). To UPDATE an existing holding in place, pass its `id` (e.g. \"book:269049145\", from the shelf list / weir_queryItems) — read/saved/tags and the catalog card are preserved, and `structured` is MERGED, so you only pass the fields you are changing (existing isbn/ddc/lcc/series/seq are kept; a plain LibraryThing re-import will not wipe a stamped series). Without an id a new book is created (keyed by isbn||title). Batch with `books:[…]` (preferred) or pass ONE book inline. A NEW book YOU add is stamped source:agent and surfaces in weir_reviewQueue as a `book` proposal (tag it `to-buy` for a wishlist item you don't own yet); pass `rationale` so the user sees why. Returns { inserted, updated, books }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "books": {
            "type": "array",
            "description": "The books to add/update (batch).",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Existing holding id to UPDATE in place (e.g. \"book:269049145\"); omit to create a new book"
                },
                "title": {
                  "type": "string",
                  "description": "Book title (required for a NEW book; omit when updating an existing holding by id, e.g. a shelved-status write)"
                },
                "author": {
                  "type": "string",
                  "description": "Author (\"Surname, Given\" or \"Given Surname\")"
                },
                "isbn": {
                  "type": "string",
                  "description": "ISBN-10/13 (enables Open Library enrichment)"
                },
                "series": {
                  "type": "string",
                  "description": "Series title for a numbered set (e.g. \"YKK\")"
                },
                "seq": {
                  "type": "number",
                  "description": "Volume number within the series"
                },
                "date": {
                  "type": "string",
                  "description": "Publication date (year or ISO date)"
                },
                "tags": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Your tags (stamped as human; only applied when creating)"
                },
                "ddc": {
                  "type": "string",
                  "description": "Dewey number (display metadata)"
                },
                "lcc": {
                  "type": "string",
                  "description": "Library of Congress class (display metadata)"
                },
                "shelved": {
                  "type": "boolean",
                  "description": "Physical-shelf status — round-trips with the shelf-list export’s \"shelved\" JSON; preloads the sheet checkboxes"
                },
                "rationale": {
                  "type": "string",
                  "description": "Why you are proposing this book (e.g. a to-buy suggestion) — shown in the review queue; tag it \"to-buy\" for a wishlist item you don't own yet"
                }
              }
            }
          },
          "id": {
            "type": "string",
            "description": "Shortcut: update a single existing holding by id"
          },
          "title": {
            "type": "string",
            "description": "Shortcut: add/update a single book by its title (with the same sibling fields)"
          },
          "author": {
            "type": "string"
          },
          "isbn": {
            "type": "string"
          },
          "series": {
            "type": "string"
          },
          "seq": {
            "type": "number"
          },
          "date": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "ddc": {
            "type": "string"
          },
          "lcc": {
            "type": "string"
          }
        }
      },
      "page": "/weir/"
    }
    weir_addLinkAction

    Save a link into weir's "Saved Links" — the same source the Telegram bot feeds, so you can save a URL directly (not only via Telegram). Pass `url` (one link) or `links:[{ url, title?, tags?, date? }]`. The background resolver follows wrapped/shortened URLs and fetches title/excerpt/thumbnail over time; it then catalogs like any item. Idempotent (keyed by URL hash) — re-saving updates in place and never resets read/saved/tags. A NEW save is stamped source:agent + your identity (its tags too); a re-import of an existing link is not relabeled. Returns { inserted, updated, links }.

    View tool JSON
    {
      "name": "weir_addLink",
      "kind": "act",
      "impl": "imperative",
      "description": "Save a link into weir's \"Saved Links\" — the same source the Telegram bot feeds, so you can save a URL directly (not only via Telegram). Pass `url` (one link) or `links:[{ url, title?, tags?, date? }]`. The background resolver follows wrapped/shortened URLs and fetches title/excerpt/thumbnail over time; it then catalogs like any item. Idempotent (keyed by URL hash) — re-saving updates in place and never resets read/saved/tags. A NEW save is stamped source:agent + your identity (its tags too); a re-import of an existing link is not relabeled. Returns { inserted, updated, links }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "A single link URL to save"
          },
          "title": {
            "type": "string",
            "description": "Optional title (else derived from the host; the resolver may improve it)"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Optional tags to apply (stamped as yours-via-agent)"
          },
          "date": {
            "type": "string",
            "description": "Optional publish date (ISO or year)"
          },
          "links": {
            "type": "array",
            "description": "Batch: [{ url, title?, tags?, date? }] (preferred over a single url for many)",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "tags": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "date": {
                  "type": "string"
                }
              },
              "required": [
                "url"
              ]
            }
          }
        }
      },
      "page": "/weir/"
    }
    weir_ingestRepoAction

    Ingest a code repo’s own DOCS as a first-class source (SPEC-repos-as-source) — so the GCU constellation becomes a queryable subgraph in weir. weir never runs git; YOU (with the files + git) decide WHAT to ingest. `repo` = the repo name/path (e.g. "auditable"). `anchor` = the commit SHA these docs are from. TWO ways to supply content: (a) **`paths`** = [path strings, or { path, title?, url? }] — weir reads each NAMED file from the read-only repos folder you mounted in Settings (no full text through this call; the way to ingest at fidelity without a verbatim conduit); (b) **`docs`** = [{ path, title?, markdown, url?, date? }] — you pass the body inline (use for a gitignored/untracked file like CLAUDE.md, or when no folder is mounted). Mix both. README/SPEC/docs/CLAUDE.md — docs, NOT code. First call CREATES the source as a proposal (→ weir_reviewQueue, ratify like a feed); later calls REFRESH it (and can fix the name/category/rationale in place). REFRESH recipe: read the stored anchor from weir_listSources, run `git diff --name-only <anchor> HEAD -- <doc globs>` (+ `--diff-filter=D` for deletions) locally, then call this with only the changed `paths`/`docs` + the new HEAD as `anchor` + deleted paths in `removed`. Idempotent (stable ids; never resets read/saved/tags); `removed` archives (never deletes). Docs become `doc` items — searchable, quotable, relatable; relate your dive-map (a stacks note) to them with weir_relate. Returns { ok, source, inserted, updated, removed, anchor, bodyless?, bodylessReason? } — `bodyless` lists paths that landed with NO body (they can't be cataloged — no text to read), and `bodylessReason` maps each to WHY (not-found | read-error:<msg> | empty | no-path) so you can self-diagnose. Paths are repo-relative (weir prepends the repo slug → `<mount>/<repo>/<path>`); keep them repo-relative so re-ingest updates the same items in place.

    View tool JSON
    {
      "name": "weir_ingestRepo",
      "kind": "act",
      "impl": "imperative",
      "description": "Ingest a code repo’s own DOCS as a first-class source (SPEC-repos-as-source) — so the GCU constellation becomes a queryable subgraph in weir. weir never runs git; YOU (with the files + git) decide WHAT to ingest. `repo` = the repo name/path (e.g. \"auditable\"). `anchor` = the commit SHA these docs are from. TWO ways to supply content: (a) **`paths`** = [path strings, or { path, title?, url? }] — weir reads each NAMED file from the read-only repos folder you mounted in Settings (no full text through this call; the way to ingest at fidelity without a verbatim conduit); (b) **`docs`** = [{ path, title?, markdown, url?, date? }] — you pass the body inline (use for a gitignored/untracked file like CLAUDE.md, or when no folder is mounted). Mix both. README/SPEC/docs/CLAUDE.md — docs, NOT code. First call CREATES the source as a proposal (→ weir_reviewQueue, ratify like a feed); later calls REFRESH it (and can fix the name/category/rationale in place). REFRESH recipe: read the stored anchor from weir_listSources, run `git diff --name-only <anchor> HEAD -- <doc globs>` (+ `--diff-filter=D` for deletions) locally, then call this with only the changed `paths`/`docs` + the new HEAD as `anchor` + deleted paths in `removed`. Idempotent (stable ids; never resets read/saved/tags); `removed` archives (never deletes). Docs become `doc` items — searchable, quotable, relatable; relate your dive-map (a stacks note) to them with weir_relate. Returns { ok, source, inserted, updated, removed, anchor, bodyless?, bodylessReason? } — `bodyless` lists paths that landed with NO body (they can't be cataloged — no text to read), and `bodylessReason` maps each to WHY (not-found | read-error:<msg> | empty | no-path) so you can self-diagnose. Paths are repo-relative (weir prepends the repo slug → `<mount>/<repo>/<path>`); keep them repo-relative so re-ingest updates the same items in place.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "repo": {
            "type": "string",
            "description": "The repo name or path (e.g. \"auditable\" or \"../auditable\") — the source key + mounted-folder dir is its basename"
          },
          "name": {
            "type": "string",
            "description": "Display name for the source (default: \"<repo> (repo)\"); updatable on refresh"
          },
          "anchor": {
            "type": "string",
            "description": "The commit SHA these docs are from — the dive-ledger anchor; advanced on each refresh"
          },
          "paths": {
            "type": "array",
            "description": "Doc paths to READ from the mounted repos folder: [\"README.md\", …] or [{ path, title?, url? }]. No verbatim conduit — weir reads the named files itself.",
            "items": {}
          },
          "docs": {
            "type": "array",
            "description": "Docs with inline bodies: [{ path, title?, markdown, url?, date? }] — for gitignored/untracked files or when no folder is mounted",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "markdown": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "date": {
                  "type": "string"
                }
              },
              "required": [
                "path"
              ]
            }
          },
          "removed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Doc paths deleted since the anchor — archived (never deleted)"
          },
          "category": {
            "type": "string",
            "description": "Folder to group the source under (default \"repos\"); updatable on refresh"
          },
          "rationale": {
            "type": "string",
            "description": "Why you are adding this source — shown in the review queue; updatable on refresh (capped to a short blurb)"
          }
        },
        "required": [
          "repo"
        ]
      },
      "page": "/weir/"
    }
    weir_ingestDocumentAction

    Ingest PDF document(s) you have DROPPED under the read-only repos folder Arthur mounted in weir (Settings → Courier) — the way to add binary documents at fidelity without a verbatim conduit or a bridge fetch. YOU (free to download from anywhere) save the PDFs under the mounted GitHub folder, then pass their `paths` (relative to the mount root, e.g. ["weir-desk/documents/ccg/declustering.pdf"], optionally [{ path, title?, author?, url? }]). weir reads each file, extracts its text (+ a per-page offset map for page-anchored weir_quote), and stores it as a first-class, content-addressed `document` item: full-text searchable, quotable, catalogable, relatable. Idempotent (same bytes → same id, updates in place). Ingested LIVE + attributed (source:agent + your identity) — NOT gated in the review queue (a corpus the user asked for shouldn't flood it; see your footprint via weir_listMine). NOTE: extraction is currently naive reading order (v0) — fine for single-column; two-column docs (CCG guidebooks) shuffle until the reconstruction pass ships, after which the corpus is re-extracted in place (the binary is the source of truth). Returns { added, items:[{id,path,title,pages,chars}], failed?:{path:reason} }.

    View tool JSON
    {
      "name": "weir_ingestDocument",
      "kind": "act",
      "impl": "imperative",
      "description": "Ingest PDF document(s) you have DROPPED under the read-only repos folder Arthur mounted in weir (Settings → Courier) — the way to add binary documents at fidelity without a verbatim conduit or a bridge fetch. YOU (free to download from anywhere) save the PDFs under the mounted GitHub folder, then pass their `paths` (relative to the mount root, e.g. [\"weir-desk/documents/ccg/declustering.pdf\"], optionally [{ path, title?, author?, url? }]). weir reads each file, extracts its text (+ a per-page offset map for page-anchored weir_quote), and stores it as a first-class, content-addressed `document` item: full-text searchable, quotable, catalogable, relatable. Idempotent (same bytes → same id, updates in place). Ingested LIVE + attributed (source:agent + your identity) — NOT gated in the review queue (a corpus the user asked for shouldn't flood it; see your footprint via weir_listMine). NOTE: extraction is currently naive reading order (v0) — fine for single-column; two-column docs (CCG guidebooks) shuffle until the reconstruction pass ships, after which the corpus is re-extracted in place (the binary is the source of truth). Returns { added, items:[{id,path,title,pages,chars}], failed?:{path:reason} }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "paths": {
            "type": "array",
            "description": "PDF paths relative to the mounted folder root: [\"weir-desk/documents/x.pdf\", …] or [{ path, title?, author?, url? }]. weir reads the named files itself (no bytes through this call).",
            "items": {}
          },
          "path": {
            "type": "string",
            "description": "Convenience: a single path (use `paths` for batches)"
          }
        }
      },
      "page": "/weir/"
    }
    weir_catalogItemAction

    Catalog one item with the configured LLM right now (fills its glass facets + description) — best for items with real body text. Returns glass_id, facets, description. On a metadata-only item (too little text to read, e.g. a book with no body/abstract) it ABSTAINS rather than fabricate: returns ok:false + skipped + a note, leaving it needs_review — author it yourself via weir_reviewItem({ id, description, facets }). Needs the cataloger configured and reachable (Lemonade via the bridge).

    View tool JSON
    {
      "name": "weir_catalogItem",
      "kind": "act",
      "impl": "imperative",
      "description": "Catalog one item with the configured LLM right now (fills its glass facets + description) — best for items with real body text. Returns glass_id, facets, description. On a metadata-only item (too little text to read, e.g. a book with no body/abstract) it ABSTAINS rather than fabricate: returns ok:false + skipped + a note, leaving it needs_review — author it yourself via weir_reviewItem({ id, description, facets }). Needs the cataloger configured and reachable (Lemonade via the bridge).",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Item id (from weir_queryItems)"
          }
        },
        "required": [
          "id"
        ]
      },
      "page": "/weir/"
    }
    weir_listModelsAnswer

    List the catalog provider's available models (so you can pick one). Optional `provider` overrides the configured one (lemonade|ollama|nanogpt|groq|custom). Returns { provider, count, models }.

    View tool JSON
    {
      "name": "weir_listModels",
      "kind": "answer",
      "impl": "imperative",
      "description": "List the catalog provider's available models (so you can pick one). Optional `provider` overrides the configured one (lemonade|ollama|nanogpt|groq|custom). Returns { provider, count, models }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "provider": {
            "type": "string",
            "description": "Override the configured provider"
          }
        }
      },
      "page": "/weir/"
    }
    weir_setCatalogAction

    Set cataloger config — provider, model, baseUrl, paceMs (delay between calls; 0 = fastest, good for cloud), maxBodyChars (doc text sent; cost/context). NOT the API key (set that in the UI). Takes effect on the next cataloged item; a running batch picks it up. Returns the new config.

    View tool JSON
    {
      "name": "weir_setCatalog",
      "kind": "act",
      "impl": "imperative",
      "description": "Set cataloger config — provider, model, baseUrl, paceMs (delay between calls; 0 = fastest, good for cloud), maxBodyChars (doc text sent; cost/context). NOT the API key (set that in the UI). Takes effect on the next cataloged item; a running batch picks it up. Returns the new config.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "provider": {
            "type": "string",
            "description": "lemonade | ollama | nanogpt | groq | custom"
          },
          "model": {
            "type": "string",
            "description": "Model id (see weir_listModels)"
          },
          "baseUrl": {
            "type": "string",
            "description": "Override base URL (local/custom providers)"
          },
          "paceMs": {
            "type": "integer",
            "description": "Delay between catalog calls in ms (0 = no pause)"
          },
          "maxBodyChars": {
            "type": "integer",
            "description": "Max doc chars sent to the LLM (500–20000)"
          },
          "mailto": {
            "type": "string",
            "description": "Contact email for the Crossref/OpenAlex polite pool (biblio enrich); sent only to those scholarly APIs"
          }
        }
      },
      "page": "/weir/"
    }
    weir_catalogControlAction

    Start / stop / clear / inspect the catalog batch. action:"start" catalogs un-cataloged non-archived items (paced, runs in the page) — optionally SCOPED to one feed/folder/type via feed|category|type (omit all → whole corpus); "stop" cancels; "clear" discards ALL cards + un-files every item (items/content/reading state untouched; reversible by re-cataloging) for a clean restart; "status" (default) reports running state, progress {total,done,failed}, and cataloged/total counts. start returns {running, todo, deferred}.

    View tool JSON
    {
      "name": "weir_catalogControl",
      "kind": "act",
      "impl": "imperative",
      "description": "Start / stop / clear / inspect the catalog batch. action:\"start\" catalogs un-cataloged non-archived items (paced, runs in the page) — optionally SCOPED to one feed/folder/type via feed|category|type (omit all → whole corpus); \"stop\" cancels; \"clear\" discards ALL cards + un-files every item (items/content/reading state untouched; reversible by re-cataloging) for a clean restart; \"status\" (default) reports running state, progress {total,done,failed}, and cataloged/total counts. start returns {running, todo, deferred}.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "start",
              "stop",
              "clear",
              "status"
            ],
            "description": "Default: status"
          },
          "feed": {
            "type": "string",
            "description": "start scope: a source by id OR display name (e.g. \"Saved Links\")"
          },
          "category": {
            "type": "string",
            "description": "start scope: a folder name (\"\" = ungrouped)"
          },
          "type": {
            "type": "string",
            "description": "start scope: item type (article|video|paper|…)"
          },
          "recatalog": {
            "type": "boolean",
            "description": "with action:start + a scope: discard that scope's cataloger-generated cards and re-catalog from scratch (re-do a batch cataloged under an old rule). SAFE-BY-DEFAULT: hand-authored / human-reviewed cards (a `reviewer` stamp) are PRESERVED — only regenerable cataloger cards are redone — so it can't silently destroy authored curation (the cataloger abstains on thin/metadata-only items like book holdings, which would have nothing to regenerate them). Returns `preserved` (count kept)."
          },
          "includeAuthored": {
            "type": "boolean",
            "description": "with recatalog: ALSO redo hand-authored / human-reviewed cards in scope (default false → they're preserved). Use only when you want authored cards re-derived — e.g. body text has since arrived and the body-fed cards beat the hand facets."
          }
        }
      },
      "page": "/weir/"
    }
    weir_reviewQueueAction

    The UNIFIED review queue — everything awaiting human attention, tagged by `kind`: "catalog" = cataloger cards flagged low-confidence/unparseable (carry facets + confidence); "feed"/"relation"/"book" = things the agent ADDED (source:agent) not yet ratified — a feed, a relation edge, or a book holding (e.g. a to-buy suggestion). Each item carries the proposer `by` identity, a `rationale` (why), and `ratifyWith` — the tool to act on it: catalog → weir_reviewItem (confirm/correct), feed/relation/book → weir_ratify (bless or dismiss). Returns { counts:{catalog,feed,relation,book,total}, count, items }. Optional `kind` filters to one. This is the decides-vs-proposes gate: the agent proposes, you ratify here.

    View tool JSON
    {
      "name": "weir_reviewQueue",
      "kind": "act",
      "impl": "imperative",
      "description": "The UNIFIED review queue — everything awaiting human attention, tagged by `kind`: \"catalog\" = cataloger cards flagged low-confidence/unparseable (carry facets + confidence); \"feed\"/\"relation\"/\"book\" = things the agent ADDED (source:agent) not yet ratified — a feed, a relation edge, or a book holding (e.g. a to-buy suggestion). Each item carries the proposer `by` identity, a `rationale` (why), and `ratifyWith` — the tool to act on it: catalog → weir_reviewItem (confirm/correct), feed/relation/book → weir_ratify (bless or dismiss). Returns { counts:{catalog,feed,relation,book,total}, count, items }. Optional `kind` filters to one. This is the decides-vs-proposes gate: the agent proposes, you ratify here.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "catalog",
              "feed",
              "relation",
              "book"
            ],
            "description": "Filter to one kind (default: all)"
          },
          "limit": {
            "type": "integer",
            "description": "Max items (default 30, cap 100)"
          }
        }
      },
      "page": "/weir/"
    }
    weir_listMineAnswer

    Your footprint on the corpus — every contribution stamped by an agent identity, across kinds, with its ratification status. The provenance LENS for self-audit, the propose-vs-ratify ledger, and cross-channel coordination; pairs with weir_reviewQueue (which shows only the pending tray). Defaults to the CALLING channel's identity; pass identity:"*" for any agent (the cross-channel view), or a specific identity string. `kinds` filters to a subset of tag|note|edge|feed|book|catalog; `status` filters to one (pending|ratified|applied|authored|dismissed). Returns { identity, counts:{total, byKind, byStatus}, contributions:[{ kind, id, label, identity, status, … }] }. IMPORTANT: this is a CURRENT-STATE lens, not a history — contributions later undone or corrected by a human are NOT shown (weir's stamps are attribution; the undo paths erase them). It is the ground truth to reconcile your MEMORY against (memory says you added X but it is not here → your memory is stale). Read-only.

    View tool JSON
    {
      "name": "weir_listMine",
      "kind": "answer",
      "impl": "imperative",
      "description": "Your footprint on the corpus — every contribution stamped by an agent identity, across kinds, with its ratification status. The provenance LENS for self-audit, the propose-vs-ratify ledger, and cross-channel coordination; pairs with weir_reviewQueue (which shows only the pending tray). Defaults to the CALLING channel's identity; pass identity:\"*\" for any agent (the cross-channel view), or a specific identity string. `kinds` filters to a subset of tag|note|edge|feed|book|catalog; `status` filters to one (pending|ratified|applied|authored|dismissed). Returns { identity, counts:{total, byKind, byStatus}, contributions:[{ kind, id, label, identity, status, … }] }. IMPORTANT: this is a CURRENT-STATE lens, not a history — contributions later undone or corrected by a human are NOT shown (weir's stamps are attribution; the undo paths erase them). It is the ground truth to reconcile your MEMORY against (memory says you added X but it is not here → your memory is stale). Read-only.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "identity": {
            "type": "string",
            "description": "Scope to an identity (default: the calling channel); \"*\" = any agent"
          },
          "kinds": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "tag",
                "note",
                "edge",
                "feed",
                "book",
                "catalog"
              ]
            },
            "description": "Limit to these contribution kinds"
          },
          "status": {
            "type": "string",
            "description": "Filter to one status: pending | ratified | applied | authored | dismissed"
          },
          "limit": {
            "type": "integer",
            "description": "Max contributions (default 100, cap 500)"
          }
        }
      },
      "page": "/weir/"
    }
    weir_ratifyAction

    Ratify or dismiss an agent STRUCTURAL proposal from the review queue (decides-vs-proposes §2.1) — a feed the agent added, a relation edge it proposed, or a book holding it suggested. `action:"ratify"` blesses it (it stays, marked ratified, and leaves the queue); `action:"dismiss"` undoes it (feed → remove it + its just-polled items; relation → unrelate; book → archive it, non-destructive). For kind "feed" pass `feedId`; "book" pass `bookId`; "relation" pass `from` + `to` (+ optional `type`). Catalog cards are confirmed via weir_reviewItem instead, not here. Returns the action taken.

    View tool JSON
    {
      "name": "weir_ratify",
      "kind": "act",
      "impl": "imperative",
      "description": "Ratify or dismiss an agent STRUCTURAL proposal from the review queue (decides-vs-proposes §2.1) — a feed the agent added, a relation edge it proposed, or a book holding it suggested. `action:\"ratify\"` blesses it (it stays, marked ratified, and leaves the queue); `action:\"dismiss\"` undoes it (feed → remove it + its just-polled items; relation → unrelate; book → archive it, non-destructive). For kind \"feed\" pass `feedId`; \"book\" pass `bookId`; \"relation\" pass `from` + `to` (+ optional `type`). Catalog cards are confirmed via weir_reviewItem instead, not here. Returns the action taken.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "feed",
              "relation",
              "book"
            ],
            "description": "What kind of proposal"
          },
          "action": {
            "type": "string",
            "enum": [
              "ratify",
              "dismiss"
            ],
            "description": "ratify = keep + bless; dismiss = undo (default ratify)"
          },
          "feedId": {
            "type": "string",
            "description": "kind \"feed\": the proposed feed id (from weir_reviewQueue)"
          },
          "bookId": {
            "type": "string",
            "description": "kind \"book\": the proposed book holding id"
          },
          "from": {
            "type": "string",
            "description": "kind \"relation\": the source item id or glass_id"
          },
          "to": {
            "type": "string",
            "description": "kind \"relation\": the target item id or glass_id"
          },
          "type": {
            "type": "string",
            "description": "kind \"relation\": optionally scope to one edge type"
          }
        },
        "required": [
          "kind"
        ]
      },
      "page": "/weir/"
    }
    weir_reviewItemAction

    Confirm, correct, or AUTHOR a catalog card. Facets-only on an existing card = confirm (clears needs_review). Pass a `description` (a precise one-sentence summary) to set the card's blurb by hand — and if the item has no card yet, this CREATES one (a deliberate "I know this item" add, e.g. a metadata-only book the cataloger can only hallucinate). Authored over MCP, the card is stamped source:agent + your identity. `facets` is an object of facet→string[] overwriting only the given facets (e.g. {"entity":["minecraft"],"scale":[]}). Pairs with weir_addBook → weir_reviewItem to land a pointed add fully formed.

    View tool JSON
    {
      "name": "weir_reviewItem",
      "kind": "act",
      "impl": "imperative",
      "description": "Confirm, correct, or AUTHOR a catalog card. Facets-only on an existing card = confirm (clears needs_review). Pass a `description` (a precise one-sentence summary) to set the card's blurb by hand — and if the item has no card yet, this CREATES one (a deliberate \"I know this item\" add, e.g. a metadata-only book the cataloger can only hallucinate). Authored over MCP, the card is stamped source:agent + your identity. `facets` is an object of facet→string[] overwriting only the given facets (e.g. {\"entity\":[\"minecraft\"],\"scale\":[]}). Pairs with weir_addBook → weir_reviewItem to land a pointed add fully formed.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Item id (from weir_reviewQueue / weir_queryItems)"
          },
          "description": {
            "type": "string",
            "description": "A precise one-sentence card summary to set by hand (creates the card if none exists)"
          },
          "facets": {
            "type": "object",
            "description": "Optional facet corrections, e.g. {\"scale\":[],\"entity\":[\"minecraft\"]} — overwrites only the given facets"
          }
        },
        "required": [
          "id"
        ]
      },
      "page": "/weir/"
    }
    weir_mergeFacetTermAction

    Thesaurus normalization: rewrite a facet term across the WHOLE catalog (from → to within one facet, de-duped) so facet-browsing stops splitting one concept across spelling/synonym variants — e.g. spatial usa→united states, entity ai→artificial intelligence. Also RECORDS the merge in the controlled vocabulary: the from-term becomes a skos:altLabel (synonym) of the target, so the decision is remembered (inspect via weir_vocab), not just applied. An empty/omitted `to` DROPS the term (records nothing). Use weir_listFacets to spot variants first. Pass one {facet, from, to} or a {merges:[…]} batch (one atomic flush). Pure card edit — items/reading state untouched, reversible. Returns each merge's card-change count.

    View tool JSON
    {
      "name": "weir_mergeFacetTerm",
      "kind": "act",
      "impl": "imperative",
      "description": "Thesaurus normalization: rewrite a facet term across the WHOLE catalog (from → to within one facet, de-duped) so facet-browsing stops splitting one concept across spelling/synonym variants — e.g. spatial usa→united states, entity ai→artificial intelligence. Also RECORDS the merge in the controlled vocabulary: the from-term becomes a skos:altLabel (synonym) of the target, so the decision is remembered (inspect via weir_vocab), not just applied. An empty/omitted `to` DROPS the term (records nothing). Use weir_listFacets to spot variants first. Pass one {facet, from, to} or a {merges:[…]} batch (one atomic flush). Pure card edit — items/reading state untouched, reversible. Returns each merge's card-change count.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "facet": {
            "type": "string",
            "description": "Facet to edit (domain|entity|process|method|scale|spatial|stance|temporal|form|provenance)"
          },
          "from": {
            "type": "string",
            "description": "Existing term to rewrite"
          },
          "to": {
            "type": "string",
            "description": "Replacement term (empty/omitted = drop the from-term)"
          },
          "merges": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "facet": {
                  "type": "string"
                },
                "from": {
                  "type": "string"
                },
                "to": {
                  "type": "string"
                }
              }
            },
            "description": "Batch of {facet, from, to} merges applied atomically"
          }
        }
      },
      "page": "/weir/"
    }
    weir_vocabAction

    Inspect the controlled vocabulary / thesaurus (SKOS-shaped, GLASS §7). No args → per-facet concept-count overview. `facet` → that facet's concepts. `facet`+`term` → one concept (prefLabel + altLabels/synonyms + broader/narrower/related). `export:true` → SKOS JSON-LD (optionally for one `facet`). The vocabulary GROWS from curation: weir_mergeFacetTerm records the merged term as a synonym (altLabel); weir_relateTerm declares BT/NT/RT.

    View tool JSON
    {
      "name": "weir_vocab",
      "kind": "act",
      "impl": "imperative",
      "description": "Inspect the controlled vocabulary / thesaurus (SKOS-shaped, GLASS §7). No args → per-facet concept-count overview. `facet` → that facet's concepts. `facet`+`term` → one concept (prefLabel + altLabels/synonyms + broader/narrower/related). `export:true` → SKOS JSON-LD (optionally for one `facet`). The vocabulary GROWS from curation: weir_mergeFacetTerm records the merged term as a synonym (altLabel); weir_relateTerm declares BT/NT/RT.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "facet": {
            "type": "string",
            "description": "Limit to one facet"
          },
          "term": {
            "type": "string",
            "description": "With facet: return just this term's concept"
          },
          "export": {
            "type": "boolean",
            "description": "Return SKOS JSON-LD instead"
          }
        }
      },
      "page": "/weir/"
    }
    weir_relateTermAction

    Declare typed thesaurus relations (SKOS, GLASS §7) on a term: broader (BT), narrower (NT), related (RT), or alt (a synonym/altLabel that redirects to this term). Inverses are maintained automatically (declare broader → the target gains narrower). Each value is a string or list of terms. This is the ratified way to grow hierarchy — a similarity signal may propose, but a relation exists only once you declare it here (decides-vs-proposes). Use weir_vocab to inspect.

    View tool JSON
    {
      "name": "weir_relateTerm",
      "kind": "act",
      "impl": "imperative",
      "description": "Declare typed thesaurus relations (SKOS, GLASS §7) on a term: broader (BT), narrower (NT), related (RT), or alt (a synonym/altLabel that redirects to this term). Inverses are maintained automatically (declare broader → the target gains narrower). Each value is a string or list of terms. This is the ratified way to grow hierarchy — a similarity signal may propose, but a relation exists only once you declare it here (decides-vs-proposes). Use weir_vocab to inspect.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "facet": {
            "type": "string",
            "description": "Facet (e.g. spatial, domain, entity)"
          },
          "term": {
            "type": "string",
            "description": "The preferred term to relate"
          },
          "broader": {
            "description": "Broader term(s) — string or array (BT)"
          },
          "narrower": {
            "description": "Narrower term(s) — string or array (NT)"
          },
          "related": {
            "description": "Related term(s) — string or array (RT)"
          },
          "alt": {
            "description": "Synonym(s) that redirect to this term — string or array (altLabel/UF)"
          }
        },
        "required": [
          "facet",
          "term"
        ]
      },
      "page": "/weir/"
    }
    weir_buildGazetteerAction

    Bulk-populate SPATIAL containment from the vendored gazetteer (GLASS §7 thesaurus): the CC0 CIA World Factbook backbone (country→continent, capital→country) + a curated supplement (US states, major cities). Declares the broader/narrower edges so the faceted browser + weir_queryCatalog roll up — selecting a parent (e.g. japan) catches its subtree (tokyo, osaka). Deterministic + idempotent (re-running links only what is new). Inspect with weir_vocab({facet:"spatial"}). Returns { linked, total }.

    View tool JSON
    {
      "name": "weir_buildGazetteer",
      "kind": "act",
      "impl": "imperative",
      "description": "Bulk-populate SPATIAL containment from the vendored gazetteer (GLASS §7 thesaurus): the CC0 CIA World Factbook backbone (country→continent, capital→country) + a curated supplement (US states, major cities). Declares the broader/narrower edges so the faceted browser + weir_queryCatalog roll up — selecting a parent (e.g. japan) catches its subtree (tokyo, osaka). Deterministic + idempotent (re-running links only what is new). Inspect with weir_vocab({facet:\"spatial\"}). Returns { linked, total }.",
      "inputSchema": {
        "type": "object",
        "properties": {}
      },
      "page": "/weir/"
    }
    weir_worksAction

    Inspect (and optionally rebuild) FRBR work-grouping (GLASS §4.1): items that are the same Work across manifestations — wire-syndication, re-uploads, cross-posts. `regroup:true` runs the deterministic grouping pass (identical canonical URL + SimHash near-duplicate; NOT an LLM call) then reports; default reports current grouping. Returns { stats:{items,works,manifestations,biggest}, works:[{work_id,size,members:[{title,feed}]}] } biggest-first — to eyeball precision. De-dup as GROUPING not discarding: nothing is deleted, work_id is a reversible overlay.

    View tool JSON
    {
      "name": "weir_works",
      "kind": "act",
      "impl": "imperative",
      "description": "Inspect (and optionally rebuild) FRBR work-grouping (GLASS §4.1): items that are the same Work across manifestations — wire-syndication, re-uploads, cross-posts. `regroup:true` runs the deterministic grouping pass (identical canonical URL + SimHash near-duplicate; NOT an LLM call) then reports; default reports current grouping. Returns { stats:{items,works,manifestations,biggest}, works:[{work_id,size,members:[{title,feed}]}] } biggest-first — to eyeball precision. De-dup as GROUPING not discarding: nothing is deleted, work_id is a reversible overlay.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "regroup": {
            "type": "boolean",
            "description": "Recompute the grouping before reporting (run this first — the corpus has no work_ids until you do)"
          },
          "maxHamming": {
            "type": "integer",
            "description": "SimHash near-dup threshold in bits (default 3; higher = looser grouping)"
          },
          "limit": {
            "type": "integer",
            "description": "Max example clusters to return (default 20, cap 100)"
          }
        }
      },
      "page": "/weir/"
    }
    weir_relatedToAnswer

    The knowledge graph around an item OR stacks note (GLASS §10): its ratified `related` edges — outgoing + backlinks — PLUS on-demand SUGGESTIONS (set suggest:false to skip) from facet co-occurrence, each carrying the shared facet terms (the "why") and a score, PLUS the soft `wikilinks` layer (resolved [[name]] cross-references + who annotates this, navigable without ratification). Pass an item id (from weir_queryItems), a glass_id, or a stacks PATH (e.g. "gcu/README.md"). An uncataloged note has no ratified edges yet but still shows its wikilinks. Suggestions/wikilinks are NOT edges until ratified via weir_relate.

    View tool JSON
    {
      "name": "weir_relatedTo",
      "kind": "answer",
      "impl": "imperative",
      "description": "The knowledge graph around an item OR stacks note (GLASS §10): its ratified `related` edges — outgoing + backlinks — PLUS on-demand SUGGESTIONS (set suggest:false to skip) from facet co-occurrence, each carrying the shared facet terms (the \"why\") and a score, PLUS the soft `wikilinks` layer (resolved [[name]] cross-references + who annotates this, navigable without ratification). Pass an item id (from weir_queryItems), a glass_id, or a stacks PATH (e.g. \"gcu/README.md\"). An uncataloged note has no ratified edges yet but still shows its wikilinks. Suggestions/wikilinks are NOT edges until ratified via weir_relate.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Item id, glass_id, or stacks path"
          },
          "suggest": {
            "type": "boolean",
            "description": "Include facet-overlap suggestions (default true)"
          },
          "limit": {
            "type": "integer",
            "description": "Max suggestions (default 8, cap 25)"
          }
        },
        "required": [
          "id"
        ]
      },
      "page": "/weir/"
    }
    weir_relateAction

    Propose a typed `related` edge between two items or stacks notes — decides-vs-proposes (GLASS §2.1): a facet-overlap suggestion becomes a real edge ONLY when declared here. `from`/`to` are item ids, glass_ids, OR stacks paths (e.g. "gcu/README.md"); an uncataloged endpoint (e.g. a fresh note) is auto-carded at Stage-0 so it can join the graph — no manual catalog step needed. `type` ∈ related | same-topic | extends | contradicts | responds-to | same-work (default "related"). Pass `rationale` (why these relate) — it shows in weir_reviewQueue as a `relation` proposal until the user ratifies. Pass remove:true to delete the edge (optionally just one type). Stored on the from-item, stamped source:agent + your identity; reversible (weir never deletes the items).

    View tool JSON
    {
      "name": "weir_relate",
      "kind": "act",
      "impl": "imperative",
      "description": "Propose a typed `related` edge between two items or stacks notes — decides-vs-proposes (GLASS §2.1): a facet-overlap suggestion becomes a real edge ONLY when declared here. `from`/`to` are item ids, glass_ids, OR stacks paths (e.g. \"gcu/README.md\"); an uncataloged endpoint (e.g. a fresh note) is auto-carded at Stage-0 so it can join the graph — no manual catalog step needed. `type` ∈ related | same-topic | extends | contradicts | responds-to | same-work (default \"related\"). Pass `rationale` (why these relate) — it shows in weir_reviewQueue as a `relation` proposal until the user ratifies. Pass remove:true to delete the edge (optionally just one type). Stored on the from-item, stamped source:agent + your identity; reversible (weir never deletes the items).",
      "inputSchema": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "description": "Source item id / glass_id / stacks path"
          },
          "to": {
            "type": "string",
            "description": "Target item id / glass_id / stacks path"
          },
          "type": {
            "type": "string",
            "description": "related | same-topic | extends | contradicts | responds-to | same-work"
          },
          "rationale": {
            "type": "string",
            "description": "Why these relate — shown in the review queue at ratify time"
          },
          "remove": {
            "type": "boolean",
            "description": "Remove the edge instead of creating it"
          }
        },
        "required": [
          "from",
          "to"
        ]
      },
      "page": "/weir/"
    }
    weir_listFacetsAnswer

    Glass catalog facets across the non-archived corpus: each facet → { total, terms:[{term,count}], omitted }, top terms by count (entity alone can be thousands). Facets: domain, entity, process, method, scale, spatial, temporal, form, provenance. Cataloged items contribute LLM facets; the rest contribute deterministic Stage-0 facets. Use facet+limit to drill into one.

    View tool JSON
    {
      "name": "weir_listFacets",
      "kind": "answer",
      "impl": "imperative",
      "description": "Glass catalog facets across the non-archived corpus: each facet → { total, terms:[{term,count}], omitted }, top terms by count (entity alone can be thousands). Facets: domain, entity, process, method, scale, spatial, temporal, form, provenance. Cataloged items contribute LLM facets; the rest contribute deterministic Stage-0 facets. Use facet+limit to drill into one.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "facet": {
            "type": "string",
            "description": "Limit to one facet (domain|entity|process|method|scale|spatial|temporal|form|provenance)"
          },
          "limit": {
            "type": "integer",
            "description": "Top terms per facet by count (default 25, max 200)"
          },
          "minCount": {
            "type": "integer",
            "description": "Only terms appearing at least this many times"
          }
        }
      },
      "page": "/weir/"
    }
    weir_queryCatalogAnswer

    Faceted INTERSECTION query over the catalog — the Ranganathan move: find interdisciplinary material by intersecting facets instead of one shelf or keyword. Pass `facets`, a map of facet → term(s): within a facet the terms UNION, across facets they INTERSECT — e.g. { domain:["geostatistics"], entity:["kriging","itabirite"], process:["estimation"] } = (geostatistics) AND (kriging OR itabirite) AND (estimation). Facets: domain, entity, process, method, scale, spatial, stance, form, provenance, temporal (weir_listFacets shows live terms). Terms resolve against the controlled vocabulary (a synonym → its preferred term); unknown or zero-hit terms come back in `vocabularyNotes` instead of silently missing. Optional `q` ANDs a ranked full-text constraint (composes with weir_search). Finds what excerpt/substring search cannot — e.g. entity:"itabirite" hits a cataloged item even when the word is not in its title. Read-only. Returns { count, total, items:[{ …, glass_id?, facets, matchedTerms }], vocabularyNotes? }.

    View tool JSON
    {
      "name": "weir_queryCatalog",
      "kind": "answer",
      "impl": "imperative",
      "description": "Faceted INTERSECTION query over the catalog — the Ranganathan move: find interdisciplinary material by intersecting facets instead of one shelf or keyword. Pass `facets`, a map of facet → term(s): within a facet the terms UNION, across facets they INTERSECT — e.g. { domain:[\"geostatistics\"], entity:[\"kriging\",\"itabirite\"], process:[\"estimation\"] } = (geostatistics) AND (kriging OR itabirite) AND (estimation). Facets: domain, entity, process, method, scale, spatial, stance, form, provenance, temporal (weir_listFacets shows live terms). Terms resolve against the controlled vocabulary (a synonym → its preferred term); unknown or zero-hit terms come back in `vocabularyNotes` instead of silently missing. Optional `q` ANDs a ranked full-text constraint (composes with weir_search). Finds what excerpt/substring search cannot — e.g. entity:\"itabirite\" hits a cataloged item even when the word is not in its title. Read-only. Returns { count, total, items:[{ …, glass_id?, facets, matchedTerms }], vocabularyNotes? }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "facets": {
            "type": "object",
            "description": "Map of facet name → term or [terms]. e.g. { domain: [\"geostatistics\"], entity: [\"kriging\",\"itabirite\"] }. Union within a facet, intersect across facets."
          },
          "q": {
            "type": "string",
            "description": "Optional free-text to AND a ranked-search constraint (weir_search) onto the intersection"
          },
          "includeArchived": {
            "type": "boolean",
            "description": "Include archived items (default TRUE — sees the whole archive; pass false to limit to active)"
          },
          "limit": {
            "type": "integer",
            "description": "Max items (default 20, cap 100)"
          }
        },
        "required": [
          "facets"
        ]
      },
      "page": "/weir/"
    }
    weir_quoteAnswer

    Verify a citation against a source — strict-grounding's self-check. Given an item `id` and a candidate `quote`, confirm the text actually appears in that source and get a stable locator + surrounding context (so you can cite it precisely, or REFUSE if it is not there). Whitespace is normalized before matching (HTML/wrapping ignored), with a case-insensitive fallback. Returns { found:true, locator:"<glass_id|id>#start-end", quote (the source's verbatim span), before, after, match } when present, or { found:false, note } when the quote is NOT in the source — in which case do not assert it as grounded. Read-only. Pair with weir_getItem (the body + citation chain) and weir_search/weir_queryCatalog (to find the id).

    View tool JSON
    {
      "name": "weir_quote",
      "kind": "answer",
      "impl": "imperative",
      "description": "Verify a citation against a source — strict-grounding's self-check. Given an item `id` and a candidate `quote`, confirm the text actually appears in that source and get a stable locator + surrounding context (so you can cite it precisely, or REFUSE if it is not there). Whitespace is normalized before matching (HTML/wrapping ignored), with a case-insensitive fallback. Returns { found:true, locator:\"<glass_id|id>#start-end\", quote (the source's verbatim span), before, after, match } when present, or { found:false, note } when the quote is NOT in the source — in which case do not assert it as grounded. Read-only. Pair with weir_getItem (the body + citation chain) and weir_search/weir_queryCatalog (to find the id).",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The item id to verify against (from weir_search / weir_queryItems / weir_queryCatalog)"
          },
          "quote": {
            "type": "string",
            "description": "The candidate quote/claim text to locate in the source"
          },
          "context": {
            "type": "integer",
            "description": "Chars of surrounding context to return on each side (default 240, max 1000)"
          }
        },
        "required": [
          "id",
          "quote"
        ]
      },
      "page": "/weir/"
    }
    weir_citeAnswer

    Render a canonical, stable citation for an item — the companion to weir_quote (quote VERIFIES a span; cite RENDERS the reference). Pass `id` for one item, or `ids` for a batch. SINGLE mode: pass `quote` to fold verification in — you get `cited:true` + the verified verbatim span + a `glass_id#start-end` locator embedded in the reference, or `cited:false` (and NO reference) if the quote is not in the source, so a fabricated claim cannot get a citation. (Or pass a `locator` you already got from weir_quote to splice it without re-verifying.) Each citation comes in every form: `inline` "(Author year)", `reference` (a full reference-list line with a durable weir handle that survives a dead URL), `footnote` (markdown [^key]: …), `wikilink` ([[handle]] — a LIVE graph backlink once written into a stacks note), and `csl` (CSL-JSON for machine reuse), plus `missing` (essentials absent — cite what is known, never fabricate). BATCH mode (`ids`): returns per-item `entries`, a `keys` map (stable BibTeX-style cite-keys), and an assembled `bibliography` (markdown footnotes by default; `style:"numbered"|"plain"`). Read-only; pure render over metadata weir already holds.

    View tool JSON
    {
      "name": "weir_cite",
      "kind": "answer",
      "impl": "imperative",
      "description": "Render a canonical, stable citation for an item — the companion to weir_quote (quote VERIFIES a span; cite RENDERS the reference). Pass `id` for one item, or `ids` for a batch. SINGLE mode: pass `quote` to fold verification in — you get `cited:true` + the verified verbatim span + a `glass_id#start-end` locator embedded in the reference, or `cited:false` (and NO reference) if the quote is not in the source, so a fabricated claim cannot get a citation. (Or pass a `locator` you already got from weir_quote to splice it without re-verifying.) Each citation comes in every form: `inline` \"(Author year)\", `reference` (a full reference-list line with a durable weir handle that survives a dead URL), `footnote` (markdown [^key]: …), `wikilink` ([[handle]] — a LIVE graph backlink once written into a stacks note), and `csl` (CSL-JSON for machine reuse), plus `missing` (essentials absent — cite what is known, never fabricate). BATCH mode (`ids`): returns per-item `entries`, a `keys` map (stable BibTeX-style cite-keys), and an assembled `bibliography` (markdown footnotes by default; `style:\"numbered\"|\"plain\"`). Read-only; pure render over metadata weir already holds.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "One item id / glass_id to cite"
          },
          "ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Batch: item ids → entries + a deduped bibliography with cite-keys"
          },
          "quote": {
            "type": "string",
            "description": "Single mode: a candidate quote to VERIFY + embed (refuses with cited:false if not in the source)"
          },
          "locator": {
            "type": "string",
            "description": "Single mode: a weir_quote locator (glass_id#start-end) to splice in without re-verifying"
          },
          "style": {
            "type": "string",
            "description": "Batch bibliography format: footnotes (default) | numbered | plain"
          }
        }
      },
      "page": "/weir/"
    }
    weir_stacksListAnswer

    List the STACKS — weir’s notes/files vault (authored markdown notes + dropped files, living as real files under /stacks/). Optional `path` scopes to a folder (recursively). Returns { count, folders:[…], entries:[{ id, path, uid, type:note|file, title, tags, missing, glass_id }] }. The `id` is a normal item id (stacks:<uid>) — pass it to weir_getItem/weir_catalogItem/weir_setState. Stacks entries are also queryable via weir_queryItems({ feed: "Stacks" }).

    View tool JSON
    {
      "name": "weir_stacksList",
      "kind": "answer",
      "impl": "imperative",
      "description": "List the STACKS — weir’s notes/files vault (authored markdown notes + dropped files, living as real files under /stacks/). Optional `path` scopes to a folder (recursively). Returns { count, folders:[…], entries:[{ id, path, uid, type:note|file, title, tags, missing, glass_id }] }. The `id` is a normal item id (stacks:<uid>) — pass it to weir_getItem/weir_catalogItem/weir_setState. Stacks entries are also queryable via weir_queryItems({ feed: \"Stacks\" }).",
      "inputSchema": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Scope to a folder (recursive), e.g. \"specs/weir\""
          },
          "limit": {
            "type": "integer",
            "description": "Max entries (default 200, cap 500)"
          }
        }
      },
      "page": "/weir/"
    }
    weir_stacksReadAnswer

    Read one stacks entry by `path` (from weir_stacksList). For a note: returns its markdown body (capped 16k) + tags + metadata. For a file: metadata only (mime, no bytes). Returns { id, path, uid, type, title, tags, glass_id, markdown? , mime? }.

    View tool JSON
    {
      "name": "weir_stacksRead",
      "kind": "answer",
      "impl": "imperative",
      "description": "Read one stacks entry by `path` (from weir_stacksList). For a note: returns its markdown body (capped 16k) + tags + metadata. For a file: metadata only (mime, no bytes). Returns { id, path, uid, type, title, tags, glass_id, markdown? , mime? }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Entry path, e.g. \"inbox/huffman.md\""
          }
        },
        "required": [
          "path"
        ]
      },
      "page": "/weir/"
    }
    weir_stacksWriteAction

    Create OR update a stacks NOTE — "draft a note straight into the stacks". If `path` names an existing note, its body is updated (uid/created preserved); otherwise a new note is created. Address it with `path` (e.g. "specs/weir/idea.md") OR `folder`+`name`. If you give NO folder (bare name / bare path), the note is filed to inbox/ as triage and the result says so (routedToInbox:true) — pass `folder` (or a path that includes a folder) to file it directly. `markdown` is the body (required); `title` and `tags` optional (tags stamped as yours-via-Claude). Link other notes/holdings with [[uid]] or [[name]] (resolved by weir_relatedTo). For a one-line change, prefer weir_stacksEdit (find/replace/append) over rewriting the whole note here. Returns { ok, id, path, uid, title, tags, folder, routedToInbox? }. Files are dropped via Telegram or the app, not here.

    View tool JSON
    {
      "name": "weir_stacksWrite",
      "kind": "act",
      "impl": "imperative",
      "description": "Create OR update a stacks NOTE — \"draft a note straight into the stacks\". If `path` names an existing note, its body is updated (uid/created preserved); otherwise a new note is created. Address it with `path` (e.g. \"specs/weir/idea.md\") OR `folder`+`name`. If you give NO folder (bare name / bare path), the note is filed to inbox/ as triage and the result says so (routedToInbox:true) — pass `folder` (or a path that includes a folder) to file it directly. `markdown` is the body (required); `title` and `tags` optional (tags stamped as yours-via-Claude). Link other notes/holdings with [[uid]] or [[name]] (resolved by weir_relatedTo). For a one-line change, prefer weir_stacksEdit (find/replace/append) over rewriting the whole note here. Returns { ok, id, path, uid, title, tags, folder, routedToInbox? }. Files are dropped via Telegram or the app, not here.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Target path, e.g. \"specs/weir/idea.md\" (folder + filename)"
          },
          "folder": {
            "type": "string",
            "description": "Folder (alternative to a full path); defaults to \"inbox\""
          },
          "name": {
            "type": "string",
            "description": "Filename (alternative to a full path)"
          },
          "title": {
            "type": "string",
            "description": "Note title (defaults to the first heading / filename)"
          },
          "markdown": {
            "type": "string",
            "description": "The note body (markdown)"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags to set"
          }
        },
        "required": [
          "markdown"
        ]
      },
      "page": "/weir/"
    }
    weir_stacksEditAction

    Partial edit of a stacks NOTE — change one part without rewriting the whole note (the way the agent Edit tool works). Address it by `path`. Either replace text: `find` (the exact string to locate) + `replace` (its replacement; omit/empty to delete the text) — `find` must be UNIQUE unless you pass replaceAll:true. OR append: `append` (a block added to the end of the note). Exact-string match; errors are explicit (find-not-found / find-not-unique), never silent. Identity (uid/created) and source-stamp are preserved. Notes only (not files). Returns { ok, id, path, uid, title, tags, markdown }.

    View tool JSON
    {
      "name": "weir_stacksEdit",
      "kind": "act",
      "impl": "imperative",
      "description": "Partial edit of a stacks NOTE — change one part without rewriting the whole note (the way the agent Edit tool works). Address it by `path`. Either replace text: `find` (the exact string to locate) + `replace` (its replacement; omit/empty to delete the text) — `find` must be UNIQUE unless you pass replaceAll:true. OR append: `append` (a block added to the end of the note). Exact-string match; errors are explicit (find-not-found / find-not-unique), never silent. Identity (uid/created) and source-stamp are preserved. Notes only (not files). Returns { ok, id, path, uid, title, tags, markdown }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Entry path, e.g. \"gcu/README.md\""
          },
          "find": {
            "type": "string",
            "description": "Exact text to locate (must be unique unless replaceAll)"
          },
          "replace": {
            "type": "string",
            "description": "Replacement for `find` (omit or empty string to delete it)"
          },
          "replaceAll": {
            "type": "boolean",
            "description": "Replace every occurrence of `find` (default false → must be unique)"
          },
          "append": {
            "type": "string",
            "description": "A block to append to the end of the note (alternative to find/replace)"
          }
        },
        "required": [
          "path"
        ]
      },
      "page": "/weir/"
    }
    weir_stacksMoveAction

    Move/refile a stacks entry to another folder. Identity (uid) is preserved, so its tags, read-state, catalog card and inbound [[uid]] links ride along. Returns { ok, movedFrom, id, path, uid, title }.

    View tool JSON
    {
      "name": "weir_stacksMove",
      "kind": "act",
      "impl": "imperative",
      "description": "Move/refile a stacks entry to another folder. Identity (uid) is preserved, so its tags, read-state, catalog card and inbound [[uid]] links ride along. Returns { ok, movedFrom, id, path, uid, title }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Current entry path"
          },
          "toFolder": {
            "type": "string",
            "description": "Destination folder, e.g. \"papers/kriging\""
          }
        },
        "required": [
          "path",
          "toFolder"
        ]
      },
      "page": "/weir/"
    }
    weir_stacksTagAction

    Add/remove tags on a stacks entry by `path`. Tags are stamped source:'llm' and mirrored into the note’s frontmatter / the file’s .meta.json sidecar (portable, Obsidian-readable). Returns the updated entry.

    View tool JSON
    {
      "name": "weir_stacksTag",
      "kind": "act",
      "impl": "imperative",
      "description": "Add/remove tags on a stacks entry by `path`. Tags are stamped source:'llm' and mirrored into the note’s frontmatter / the file’s .meta.json sidecar (portable, Obsidian-readable). Returns the updated entry.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Entry path"
          },
          "add": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags to add"
          },
          "remove": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags to remove"
          }
        },
        "required": [
          "path"
        ]
      },
      "page": "/weir/"
    }
    weir_stacksTrashAction

    Delete a stacks entry by `path` — but weir never really deletes: the file (+ sidecar) is moved into /stacks/.trash/ (a hidden folder the scanner ignores), so it disappears from weir while the bytes survive on disk, recoverable. Drops the index entry. Returns { ok, trashed, dest }.

    View tool JSON
    {
      "name": "weir_stacksTrash",
      "kind": "act",
      "impl": "imperative",
      "description": "Delete a stacks entry by `path` — but weir never really deletes: the file (+ sidecar) is moved into /stacks/.trash/ (a hidden folder the scanner ignores), so it disappears from weir while the bytes survive on disk, recoverable. Drops the index entry. Returns { ok, trashed, dest }.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Entry path to delete"
          }
        },
        "required": [
          "path"
        ]
      },
      "page": "/weir/"
    }
    weir_mcpDiagAnswer

    Diagnostics for weir's numen fs channels (transport debugging — for tracking down why a channel won't connect). No input. Per known channel returns: connection state + clientId, whether a token is set, the folder-handle NAME it points at, what weir reads from that folder's bridge.live (present? session, ageMs, fresh under 90s?), and the folder's top-level entries. Reading: bridgeLive absent or stale while the bridge insists it is announcing ⇒ FOLDER MISMATCH (weir and the bridge are on different directories); bridgeLive fresh but clientId null ⇒ STALE HANDSHAKE (re-dial / token). If a channel shows hasHandle:false, open weir Settings → Connections once to cache the handle, then re-run.

    View tool JSON
    {
      "name": "weir_mcpDiag",
      "kind": "answer",
      "impl": "imperative",
      "description": "Diagnostics for weir's numen fs channels (transport debugging — for tracking down why a channel won't connect). No input. Per known channel returns: connection state + clientId, whether a token is set, the folder-handle NAME it points at, what weir reads from that folder's bridge.live (present? session, ageMs, fresh under 90s?), and the folder's top-level entries. Reading: bridgeLive absent or stale while the bridge insists it is announcing ⇒ FOLDER MISMATCH (weir and the bridge are on different directories); bridgeLive fresh but clientId null ⇒ STALE HANDSHAKE (re-dial / token). If a channel shows hasHandle:false, open weir Settings → Connections once to cache the handle, then re-run.",
      "inputSchema": {
        "type": "object",
        "properties": {}
      },
      "page": "/weir/"
    }