zot/examples/extensions/scratchpad
patriceckhart 5dbbcb9040 feat(extensions): typescript example + path-aware exec resolution
examples/extensions/scratchpad: real .ts (not .js) extension, no
build step, no SDK. Runs via `npx --yes tsx index.ts` so authors
can use TypeScript without forcing a global install. Demonstrates:

  /note <text>   slash command (typed CommandResponse)
  /notes         slash command (display action)
  /clear-notes   slash command
  read_notes     LLM-callable tool (typed ToolResult)

Plus a typed wire-format subset inline so the file shows what the
protocol actually looks like from the consumer side. Pure node +
tsx, zero npm deps beyond tsx itself (~5 MB cached on first call).

Manager fix: extension exec paths are now resolved by shape:

  absolute               used as-is
  starts with ./ or ../  joined to ext.Dir
  contains a separator   joined to ext.Dir (other relative form)
  bare name (no sep)     left as-is so $PATH lookup works

Before this, "exec": "npx" was being looked up at
extensions/scratchpad/npx and failing with a "no such file or
directory" error. With the fix, "node", "npx", "python3", "tsx",
etc. resolve via $PATH like users intuitively expect.

Bumped WaitForReady grace from 500ms to 3s so slow runtimes
(npx tsx cold-start ≈ 1.4s) get their register_tool frames
in before the agent's tool registry is built. Extensions that
send ready quickly still release the wait immediately; the
extra grace only applies to laggards.

Verified end-to-end live against anthropic:
  prompt: "Use the read_notes tool now and tell me what's in the
           scratchpad"
  -> [tool_call] read_notes({})
  -> [tool_result] (scratchpad is empty)
  -> "The scratchpad is empty."
2026-04-19 15:06:00 +02:00
..
extension.json feat(extensions): typescript example + path-aware exec resolution 2026-04-19 15:06:00 +02:00
index.ts feat(extensions): typescript example + path-aware exec resolution 2026-04-19 15:06:00 +02:00
README.md feat(extensions): typescript example + path-aware exec resolution 2026-04-19 15:06:00 +02:00

scratchpad — TypeScript extension example

Real .ts (not .js), no build step, no SDK. Runs via npx tsx, which downloads itself into the npm cache on first invocation and runs from cache on every subsequent call.

Demonstrates:

  • registering slash commands (/note, /notes, /clear-notes)
  • registering an LLM-callable tool (read_notes)
  • the wire protocol from a typed TypeScript perspective
  • running TypeScript via npx tsx from extension.json

Requirements

Node 18+. The first time you use it, npx will quietly fetch tsx into the npm cache (~5 MB, one-shot). If you'd rather pre-install globally:

npm install -g tsx

…and change extension.json's exec to "tsx" and args to ["index.ts"] if you want.

Install

From this directory:

zot ext install .

Use

In zot:

  • /note remind me to update the changelog — appends to the scratchpad
  • /notes — shows everything stored
  • /clear-notes — wipes the scratchpad

The model also has a read_notes tool. Ask it:

"What did I tell you to remember?"

…and it will call the tool and tell you. The scratchpad is process-local: it lives only as long as the extension subprocess (i.e. the duration of one zot session).

Why TypeScript here

The extension protocol is small enough that you can hand-write it in any language. JS works fine; TS adds type safety on the frame shapes without any infrastructure beyond tsx. If you want richer ergonomics (decorators, schema-from-types), publish your own SDK on top.

See also

  • examples/extensions/clock — JS sibling (no tsx required)
  • examples/extensions/hello — Go SDK
  • examples/extensions/weather — Go SDK, exposes one tool
  • examples/extensions/guard — Go SDK, demonstrates intercepts
  • docs/extensions.md — full protocol reference