Skip to content
Gauge

ax-check.com

Tabbycat

calicotab.com

ax-check.com

Checked 18 September 2026

F

23/100

Agents need your login

Price is on the homepage, but docs and setup guidance are missing

Homepage states a flat $40 per tournament with no add-ons, but no documentation index, quickstart, or install steps exist for agents to act on.

4 fixes to start with

Concrete changes, backed by what we found.

Onboarding

Give agents a clear login handoff and a way to resume setup.

Agents cannot progress through onboarding without human help. Provide an agent-accessible path through authentication and setup.

Docs

Return Markdown when clients request text/markdown.

Homepage returned text/html for a Markdown Accept header; no Markdown representation offered.

Docs

Add links to your product documentation and quickstart in /llms.txt.

/llms.txt returns 404, so no documentation index exists at the standard path.

Docs

Publish a text or Markdown version of the quickstart.

No compact guide representation: /llms.txt returns 404 and homepage serves only HTML.

Recorded coding sessions

Same task, 3 models, no calicotab.com account. Recorded 18 September 2026 on Pi.

3 recorded · 9m 36s

9m 36s · recorded

Session findings

Select a finding to see it in context.

piv0.84.2
modelDeepSeek V4 Procwd/sandbox/repo
Help me build a simple example using Tabbycat. Tell me how pricing works, and briefly tell me whether this product will be easy for you to manage. Let me know if you get blocked. If this product has no developer workflow you can act on, say so plainly and stop. Stay light: use the hosted product through its SDK or API. Do not start local service stacks or wait for long-running commands; if the quickstart requires either, say so plainly and stop.
Thinking…

The user wants me to build a simple example using Tabbycat. They want me to tell them how pricing works, and whether the product will be easy to manage. They want me to stay light, use hosted product through SDK or API, not start local service stacks or long-running commands.

First, I need to figure out what "Tabbycat" is. There's a well-known open-source project called Tabbycat which is a debating tournament tabulation software. But there might be another API/SDK product called Tabbycat. Let me investigate.

Let me look around the current directory and maybe search the web? I don't have web access directly, but I can use bash to explore. Actually, I don't have internet access tools except bash (curl might work). Let me check.

Let me start by exploring the repo and checking what's available.

Actually, the user's mention of "developer workflow you can act on" and "SDK or API" suggests they may be referring to a specific product. Tabbycat could be:

  1. Tabbycat (tabbycat.readthedocs.io) - open source debate tabulation software.
  2. Tabbycat - some other SaaS product.

Let me investigate. I'll check the environment, package managers, and do a quick web search via curl if available.

Let me be careful. The instructions say to be concise and follow the guidance. Let me first explore.

$ ls -la && echo "---" && cat README.md 2>/dev/null | head -50 && echo "--- env ---" && env | grep -i pi_ | head -20{"content":[{"text":"total 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 18 21:59 ..\ndrwxr-xr-x 8 root root 4096 Sep 18 08:00…ctrl+o
{"content":[{"text":"total 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 18 21:59 ..\ndrwxr-xr-x 8 root root 4096 Sep 18 08:00 .git\n-rw-r--r-- 1 root root  408 Sep 18 08:00 .gitignore\n---\n--- env ---\nPI_CODING_AGENT=true\nPI_REASONING_LEVEL=medium\nPI_SESSION_FILE=/sandbox/.gauge/pi/sessions/2026-09-18T21-59-07-288Z_01a0b687-c918-7f3f-9414-878c9ce3836b.jsonl\nPI_PROVIDER=gauge-fireworks\nPI_MODEL=accounts/fireworks/models/deepseek-v4-pro-0813\nPI_SESSION_ID=01a0b687-c918-7f3f-9414-878c9ce3836b\nPI_GATEWAY_API_KEY=unused\n","type":"text"}]}
$ python3 --version; node --version; echo "--- check curl internet ---"; curl -sS --max-time 10 https://api.github.com 2>&1 | head -5{"content":[{"text":"Python 3.12.3\nv22.23.2\n--- check curl internet ---\n{\"current_user_url\":\"https://api.github.com/user\",\"current_user_authorizations_h…ctrl+o
{"content":[{"text":"Python 3.12.3\nv22.23.2\n--- check curl internet ---\n{\"current_user_url\":\"https://api.github.com/user\",\"current_user_authorizations_html_url\":\"https://github.com/settings/connections/applications{/client_id}\",\"authorizations_url\":\"https://api.github.com/authorizations\",\"code_search_url\":\"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}\",\"commit_search_url\":\"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}\",\"emails_url\":\"https://api.github.com/user/emails\",\"emojis_url\":\"https://api.github.com/emojis\",\"events_url\":\"https://api.github.com/events\",\"feeds_url\":\"https://api.github.com/feeds\",\"followers_url\":\"https://api.github.com/user/followers\",\"following_url\":\"https://api.github.com/user/following{/target}\",\"gists_url\":\"https://api.github.com/gists{/gist_id}\",\"hub_url\":\"https://api.github.com/hub\",\"issue_search_url\":\"https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}\",\"issues_url\":\"https://api.github.com/issues\",\"keys_url\":\"https://api.github.com/user/keys\",\"label_search_url\":\"https://api.github.com/search/labels?q={query}&repository_id={repository_id}{&page,per_page}\",\"notifications_url\":\"https://api.github.com/notifications\",\"organization_url\":\"https://api.github.com/orgs/{org}\",\"organization_repositories_url\":\"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}\",\"organization_teams_url\":\"https://api.github.com/orgs/{org}/teams\",\"public_gists_url\":\"https://api.github.com/gists/public\",\"rate_limit_url\":\"https://api.github.com/rate_limit\",\"repository_url\":\"https://api.github.com/repos/{owner}/{repo}\",\"repository_search_url\":\"https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}\",\"current_user_repositories_url\":\"https://api.github.com/user/repos{?type,page,per_page,sort}\",\"starred_url\":\"https://api.github.com/user/starred{/owner}{/repo}\",\"starred_gists_url\":\"https://api.github.com/gists/starred\",\"topic_search_url\":\"https://api.github.com/search/topics?q={query}{&page,per_page}\",\"user_url\":\"https://api.github.com/users/{user}\",\"user_organizations_url\":\"https://api.github.com/user/orgs\",\"user_repositories_url\":\"https://api.github.com/users/{user}/repos{?type,page,per_page,sort}\",\"user_search_url\":\"https://api.github.com/search/users?q={query}{&page,per_page,sort,order}\"}","type":"text"}]}
Thinking…

There's internet access. The repo is empty (only .git and .gitignore). Let me check what's in .gitignore and any git remote to understand context.

Now, what is Tabbycat? Let me search. There's a GitHub project. Let me search GitHub for "Tabbycat".

$ cat .gitignore; echo "--- git remote ---"; git remote -v; echo "--- git log ---"; git log --oneline -5 2>&1{"content":[{"text":"# Keep generated and vendored files out of source diffs.\n\n# Logs / OS cruft\n*.log\n.DS_Store\n\n# JavaScript / TypeScript\nnode_modules/…ctrl+o
{"content":[{"text":"# Keep generated and vendored files out of source diffs.\n\n# Logs / OS cruft\n*.log\n.DS_Store\n\n# JavaScript / TypeScript\nnode_modules/\n.next/\ndist/\nbuild/\ncoverage/\n\n# Python\n.venv/\nvenv/\n__pycache__/\n*.py[cod]\n.pytest_cache/\n*.egg-info/\n\n# Rust\ntarget/\n\n# Go / PHP / Ruby vendored deps\nvendor/\n.bundle/\n\n# Java / Kotlin / Scala\n.gradle/\n\n# .NET\nbin/\nobj/\n\n# Elixir\n_build/\ndeps/\n\n# Dart / Flutter\n.dart_tool/\n--- git remote ---\n--- git log ---\n9ebf5fb Initial workspace\n","type":"text"}]}
$ curl -sS --max-time 15 "https://api.github.com/search/repositories?q=tabbycat&per_page=10" 2>&1 | python3 -c "import sys,json; d=json.load(sys.stdin); [print(r['full_name'], '|', r.get('description'),…{"content":[{"text":"TabbycatDebate/tabbycat | Debating tournament tabulation software for British Parliamentary and a variety of two-team parliamentary formats…ctrl+o
curl -sS --max-time 15 "https://api.github.com/search/repositories?q=tabbycat&per_page=10" 2>&1 | python3 -c "import sys,json; d=json.load(sys.stdin); [print(r['full_name'], '|', r.get('description'), '|', r.get('html_url')) for r in d.get('items',[])]"

{"content":[{"text":"TabbycatDebate/tabbycat | Debating tournament tabulation software for British Parliamentary and a variety of two-team parliamentary formats | https://github.com/TabbycatDebate/tabbycat\nSchrodingerZhu/tabbycat | dotgraph generator for rust | https://github.com/SchrodingerZhu/tabbycat\nLil1th/TabbyCat | Tabby Cat hacks, because I made so many it needed a different repo. Find Chomusuke tweaks on @TheSwiftPhantom's profile | https://github.com/Lil1th/TabbyCat\nSnsaiu/TabbyCat_OpenSource | 狸花猫AI助手开源项目 | https://github.com/Snsaiu/TabbyCat_OpenSource\nolihawkins/tabbycat | An R package for tabulating and summarising categorical variables. | https://github.com/olihawkins/tabbycat\ntasdidtahsin/TabbycatImporter | None | https://github.com/tasdidtahsin/TabbycatImporter\nJLimperg/tabbycat | None | https://github.com/JLimperg/tabbycat\nTabbycatDebate/CSV-Importers | Importers for Tabbycat based on the importtournament command with CSV | https://github.com/TabbycatDebate/CSV-Importers\nTabbycatDebate/tabbycatdebate.github.io | None | https://github.com/TabbycatDebate/tabbycatdebate.github.io\ntiyd-fee-2016-02/TabbyCatOrphanage | We are a non-profit organization, dedicated to the care of orphaned tabby cats.  Every tabby cat needs a loving home.  | https://github.com/tiyd-fee-2016-02/TabbyCatOrphanage\n","type":"text"}]}
Thinking…

The main "Tabbycat" is the debating tournament tabulation software. But the user says "use the hosted product through its SDK or API." Tabbycat (the debate software) is self-hosted Django, no hosted SaaS or API/SDK. Hmm.

Wait, there's also "Tabbycat" — maybe there's a hosted/SaaS product with an API. Let me think. Actually, there might be a newer product. Let me search npm / PyPI for "tabbycat" SDK.

Actually, let me reconsider. The user might mean a different product. Tabbycat could be:

  • TabbyCat (tabbycat.dev?) - there might be a hosted AI product.

Let me search more broadly. Let me check npm and PyPI and also search the web.

$ echo "=== npm ==="; curl -sS --max-time 15 "https://registry.npmjs.org/-/v1/search?text=tabbycat&size=10" | python3 -c "import sys,json; d=json.load(sys.stdin); [print(o['package']['name'],'|',o['pack…{"content":[{"text":"=== npm ===\ntabbycat | Promise-based client for Tableau Server Rest API for the browser and node.js using axios. Tableau are moving toward…ctrl+o
echo "=== npm ==="; curl -sS --max-time 15 "https://registry.npmjs.org/-/v1/search?text=tabbycat&size=10" | python3 -c "import sys,json; d=json.load(sys.stdin); [print(o['package']['name'],'|',o['package'].get('description'),'|',o['package'].get('links',{}).get('repository')) for o in d.get('objects',[])]"
echo "=== pypi ==="; curl -sS --max-time 15 "https://pypi.org/pypi/tabbycat/json" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['info']['summary']); print(d['info'].get('home_page')); print(d['info'].get('project_urls'))" 2>&1 | head -20

{"content":[{"text":"=== npm ===\ntabbycat | Promise-based client for Tableau Server Rest API for the browser and node.js using axios. Tableau are moving towards openAPI specs which will mean clients can be generated, but that's not the case for the main API. In an attempt to be as complete as possi | git://github.com/iamdavehart/tabbycat.git\n@tabbycat-ai/sdk | SDK for building apps on the Tabbycat platform with authentication and persistent storage | git+https://github.com/launchflow/tabbycat.git\n=== pypi ===\nTraceback (most recent call last):\n  File \"<string>\", line 1, in <module>\nKeyError: 'info'\n","type":"text"}]}
$ echo "=== crates ==="; curl -sS --max-time 15 "https://crates.io/api/v1/crates?q=tabbycat&per_page=10" | python3 -c "import sys,json; d=json.load(sys.stdin); [print(c.get('name'),'|',c.get('descriptio…{"content":[{"text":"=== crates ===\nTraceback (most recent call last):\n File \"<string>\", line 1, in <module>\n File \"/usr/lib/python3.12/json/__init__.py…ctrl+o
echo "=== crates ==="; curl -sS --max-time 15 "https://crates.io/api/v1/crates?q=tabbycat&per_page=10" | python3 -c "import sys,json; d=json.load(sys.stdin); [print(c.get('name'),'|',c.get('description'),'|',c.get('homepage')) for c in d.get('crates',[])]" 2>&1 | head -20

{"content":[{"text":"=== crates ===\nTraceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n  File \"/usr/lib/python3.12/json/__init__.py\", line 293, in load\n    return loads(fp.read(),\n           ^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.12/json/__init__.py\", line 346, in loads\n    return _default_decoder.decode(s)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.12/json/decoder.py\", line 337, in decode\n    obj, end = self.raw_decode(s, idx=_w(s, 0).end())\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.12/json/decoder.py\", line 355, in raw_decode\n    raise JSONDecodeError(\"Expecting value\", s, err.value) from None\njson.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)\n","type":"text"}]}
Thinking…

There's @tabbycat-ai/sdk — "SDK for building apps on the Tabbycat platform with authentication and persistent storage" from github.com/launchflow/tabbycat. This looks like the "Tabbycat" product the user means — a hosted platform with an SDK. Let me investigate this.

Let me look at the npm package details and the GitHub repo.

$ curl -sS --max-time 15 "https://registry.npmjs.org/@tabbycat-ai/sdk" | python3 -c "{"content":[{"text":"name: @tabbycat-ai/sdk\ndescription: SDK for building apps on the Tabbycat platform with authentication and persistent storage\ndist-tags: …ctrl+o
curl -sS --max-time 15 "https://registry.npmjs.org/@tabbycat-ai/sdk" | python3 -c "
import sys,json
d=json.load(sys.stdin)
print('name:', d.get('name'))
print('description:', d.get('description'))
print('dist-tags:', d.get('dist-tags'))
latest = d.get('dist-tags',{}).get('latest')
v = d['versions'][latest]
print('version:', latest)
print('homepage:', v.get('homepage'))
print('repository:', v.get('repository'))
print('keywords:', v.get('keywords'))
print('main:', v.get('main'), 'module:', v.get('module'), 'types:', v.get('types'))
print('deps:', v.get('dependencies'))
print('peerDeps:', v.get('peerDependencies'))
print('bin:', v.get('bin'))
print('--- README ---')
print(v.get('readme','')[:4000])
"

{"content":[{"text":"name: @tabbycat-ai/sdk\ndescription: SDK for building apps on the Tabbycat platform with authentication and persistent storage\ndist-tags: {'latest': '0.1.7'}\nversion: 0.1.7\nhomepage: https://github.com/launchflow/tabbycat#readme\nrepository: {'type': 'git', 'url': 'git+https://github.com/launchflow/tabbycat.git'}\nkeywords: ['tabbycat', 'cloudflare', 'workers', 'hono', 'sdk', 'platform']\nmain: ./dist/index.js module: ./dist/index.js types: ./dist/index.d.ts\ndeps: {'hono': '^4.6.0', 'jose': '^6.1.3', 'zod': '^3.23.0'}\npeerDeps: {'@cloudflare/workers-types': '^4.0.0'}\nbin: None\n--- README ---\n\n","type":"text"}]}
$ curl -sS --max-time 15 "https://api.github.com/repos/launchflow/tabbycat" | python3 -c "import sys,json; d=json.load(sys.stdin); print('full_name:',d.get('full_name')); print('desc:',d.get('descriptio…{"content":[{"text":"full_name: None\ndesc: None\nhomepage: None\ndefault_branch: None\ntopics: None\nhtml_url: None\n","type":"text"}]}ctrl+o
curl -sS --max-time 15 "https://api.github.com/repos/launchflow/tabbycat" | python3 -c "import sys,json; d=json.load(sys.stdin); print('full_name:',d.get('full_name')); print('desc:',d.get('description')); print('homepage:',d.get('homepage')); print('default_branch:',d.get('default_branch')); print('topics:',d.get('topics')); print('html_url:',d.get('html_url'))"

Thinking…

The GitHub repo launchflow/tabbycat returned None — maybe rate limit or the repo is private/renamed. Let me check the raw response.

$ curl -sS --max-time 15 "https://api.github.com/repos/launchflow/tabbycat" | head -30{"content":[{"text":"{\n \"message\": \"Not Found\",\n \"documentation_url\": \"https://docs.github.com/rest/repos/repos#get-a-repository\",\n \"status\": \"…ctrl+o
{"content":[{"text":"{\n  \"message\": \"Not Found\",\n  \"documentation_url\": \"https://docs.github.com/rest/repos/repos#get-a-repository\",\n  \"status\": \"404\"\n}\n","type":"text"}]}
$ curl -sS --max-time 15 "https://raw.githubusercontent.com/launchflow/tabbycat/main/README.md" 2>&1 | head -100{"content":[{"text":"404: Not Found","type":"text"}]}
Thinking…

The repo launchflow/tabbycat is now 404 (private or deleted). But the npm package exists and can be installed. Let me get the full README from npm (readme was empty in that output — maybe truncated?). Actually the readme field printed empty. Let me check the npm package tarball to read the README and source.

Let me download the npm tarball and inspect.

$ cd /tmp && curl -sS --max-time 20 "https://registry.npmjs.org/@tabbycat-ai/sdk/-/sdk-0.1.7.tgz" -o tabbycat-sdk.tgz && tar tzf tabbycat-sdk.tgz | head -50{"content":[{"text":"package/dist/agent-tools.js\npackage/dist/auth.js\npackage/dist/storage/backend.js\npackage/dist/blobs.js\npackage/dist/client.js\npackage/…ctrl+o
{"content":[{"text":"package/dist/agent-tools.js\npackage/dist/auth.js\npackage/dist/storage/backend.js\npackage/dist/blobs.js\npackage/dist/client.js\npackage/dist/storage/durable-object.js\npackage/dist/index.js\npackage/dist/storage/index.js\npackage/dist/openapi.js\npackage/dist/permissions.js\npackage/dist/secrets.js\npackage/dist/types.js\npackage/package.json\npackage/dist/agent-tools.d.ts.map\npackage/dist/auth.d.ts.map\npackage/dist/storage/backend.d.ts.map\npackage/dist/blobs.d.ts.map\npackage/dist/client.d.ts.map\npackage/dist/storage/durable-object.d.ts.map\npackage/dist/index.d.ts.map\npackage/dist/storage/index.d.ts.map\npackage/dist/openapi.d.ts.map\npackage/dist/permissions.d.ts.map\npackage/dist/secrets.d.ts.map\npackage/dist/types.d.ts.map\npackage/README.md\npackage/dist/agent-tools.d.ts\npackage/dist/auth.d.ts\npackage/dist/storage/backend.d.ts\npackage/dist/blobs.d.ts\npackage/dist/client.d.ts\npackage/dist/storage/durable-object.d.ts\npackage/dist/index.d.ts\npackage/dist/storage/index.d.ts\npackage/dist/openapi.d.ts\npackage/dist/permissions.d.ts\npackage/dist/secrets.d.ts\npackage/dist/types.d.ts\n","type":"text"}]}
$ cd /tmp && tar xzf tabbycat-sdk.tgz && cat package/README.md{"content":[{"text":"# @tabbycat/sdk\n\nSDK for building apps on the Tabbycat platform with authentication and persistent storage.\n\n## Installation\n\n```bash…ctrl+o
{"content":[{"text":"# @tabbycat/sdk\n\nSDK for building apps on the Tabbycat platform with authentication and persistent storage.\n\n## Installation\n\n```bash\nnpm install @tabbycat/sdk\n```\n\n## Quick Start\n\n```typescript\nimport { createApp, PlatformStorage } from '@tabbycat/sdk'\n\n// Export the PlatformStorage Durable Object\nexport { PlatformStorage }\n\nconst app = createApp({ appId: 'my-app' })\n\n// All /api/* routes automatically have user auth and storage\napp.get('/api/me', (c) => {\n  const user = c.get('user')\n  return c.json({ user })\n})\n\n// Use persistent storage (scoped per user/org)\napp.get('/api/data', async (c) => {\n  const storage = c.get('storage')\n  const data = await storage.get('my-collection', 'my-key')\n  return c.json({ data })\n})\n\napp.post('/api/data', async (c) => {\n  const storage = c.get('storage')\n  const body = await c.req.json()\n  await storage.set('my-collection', 'my-key', body)\n  return c.json({ success: true })\n})\n\n// Serve static assets\napp.get('*', async (c) => {\n  return c.env.ASSETS.fetch(c.req.raw)\n})\n\nexport default app\n```\n\n## Features\n\n### Authentication\n\nThe SDK automatically handles authentication via:\n1. **X-User-Context header** - Injected by the Tabbycat dispatch worker (W4P mode)\n2. **PropelAuth tokens** - Direct JWT verification (standalone mode)\n\nAccess the authenticated user in any `/api/*` route:\n\n```typescript\napp.get('/api/profile', (c) => {\n  const user = c.get('user')\n  // user.id, user.email, user.orgId\n  return c.json({ user })\n})\n```\n\n### Persistent Storage\n\nStorage is automatically scoped per user (or per organization if the user belongs to one).\n\n```typescript\nconst storage = c.get('storage')\n\n// Get a value\nconst item = await storage.get<MyType>('collection', 'key')\n\n// Set a value\nawait storage.set('collection', 'key', { foo: 'bar' })\n\n// Delete a value\nawait storage.delete('collection', 'key')\n\n// List items in a collection\nconst result = await storage.list<MyType>('collection', {\n  limit: 10,\n  cursor: 'optional-cursor'\n})\n// result.items, result.cursor\n```\n\n## Wrangler Configuration\n\nAdd the PlatformStorage Durable Object to your `wrangler.toml`:\n\n```toml\nname = \"my-app\"\nmain = \"worker/index.ts\"\ncompatibility_date = \"2024-12-18\"\ncompatibility_flags = [\"nodejs_compat\"]\n\n[assets]\ndirectory = \"./dist/client\"\nbinding = \"ASSETS\"\n\n[[durable_objects.bindings]]\nname = \"PLATFORM_STORAGE\"\nclass_name = \"PlatformStorage\"\n\n[[migrations]]\ntag = \"v1\"\nnew_classes = [\"PlatformStorage\"]\n```\n\n## Environment Variables\n\n| Variable | Description |\n|----------|-------------|\n| `SIGNING_SECRET` | Secret for verifying X-User-Context signatures |\n| `PROPELAUTH_VERIFIER_KEY` | PropelAuth public key (standalone mode) |\n| `PROPELAUTH_AUTH_URL` | PropelAuth auth URL (standalone mode) |\n\n## License\n\nMIT\n","type":"text"}]}
Thinking…

Now I understand. "Tabbycat" is a platform for building Cloudflare Workers apps with authentication and persistent storage. The SDK is @tabbycat-ai/sdk (published as @tabbycat/sdk in README but actual package is @tabbycat-ai/sdk).

Wait — the README says @tabbycat/sdk but the package is @tabbycat-ai/sdk. Let me check the package.json name.

The user says: "Help me build a simple example using Tabbycat. Tell me how pricing works, and briefly tell me whether this product will be easy for you to manage. Let me know if you get blocked. If this product has no developer workflow you can act on, say so plainly and stop. Stay light: use the hosted product through its SDK or API. Do not start local service stacks or wait for long-running commands; if the quickstart requires either, say so plainly and stop."

So I need to:

  1. Find the actual product: Tabbycat (tabbycat.ai? or tabbycat.dev?). It appears to be a platform that hosts Cloudflare Workers apps with auth and storage. Related to LaunchFlow? Actually the repo was launchflow/tabbycat. Hmm, "launchflow" is another product. But the Tabbycat platform itself — I need to find its website and pricing.

Let me investigate more. The SDK requires the app to be deployed to Tabbycat's platform. Is there a hosted Tabbycat? Let me search the web for "Tabbycat platform" "tabbycat.dev" or "tabbycat.ai".

Let me check the package.json name, and search for the product website.

Actually, wait. The user's question is premised on there being a hosted product with an SDK/API. The SDK is real. But is there a hosted Tabbycat platform I can actually deploy to without a local service stack? Deploying a Cloudflare Worker typically requires wrangler CLI, which is a local tool but not a "local service stack" — it deploys to Cloudflare. However, Tabbycat's own platform might deploy via their own service.

Let me investigate the SDK source to understand how it connects to the platform, and search for the Tabbycat website/pricing.

Let me look at the source dist files.

$ cd /tmp && cat package/package.json{"content":[{"text":"{\n \"name\": \"@tabbycat-ai/sdk\",\n \"version\": \"0.1.7\",\n \"description\": \"SDK for building apps on the Tabbycat platform with a…ctrl+o
{"content":[{"text":"{\n  \"name\": \"@tabbycat-ai/sdk\",\n  \"version\": \"0.1.7\",\n  \"description\": \"SDK for building apps on the Tabbycat platform with authentication and persistent storage\",\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.js\"\n    },\n    \"./storage\": {\n      \"types\": \"./dist/storage/index.d.ts\",\n      \"import\": \"./dist/storage/index.js\"\n    },\n    \"./permissions\": {\n      \"types\": \"./dist/permissions.d.ts\",\n      \"import\": \"./dist/permissions.js\"\n    },\n    \"./agent-tools\": {\n      \"types\": \"./dist/agent-tools.d.ts\",\n      \"import\": \"./dist/agent-tools.js\"\n    },\n    \"./secrets\": {\n      \"types\": \"./dist/secrets.d.ts\",\n      \"import\": \"./dist/secrets.js\"\n    },\n    \"./openapi\": {\n      \"types\": \"./dist/openapi.d.ts\",\n      \"import\": \"./dist/openapi.js\"\n    },\n    \"./client\": {\n      \"types\": \"./dist/client.d.ts\",\n      \"import\": \"./dist/client.js\"\n    }\n  },\n  \"files\": [\n    \"dist\",\n    \"README.md\"\n  ],\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"keywords\": [\n    \"tabbycat\",\n    \"cloudflare\",\n    \"workers\",\n    \"hono\",\n    \"sdk\",\n    \"platform\"\n  ],\n  \"author\": \"Tabbycat\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/launchflow/tabbycat\"\n  },\n  \"dependencies\": {\n    \"hono\": \"^4.6.0\",\n    \"jose\": \"^6.1.3\",\n    \"zod\": \"^3.23.0\"\n  },\n  \"peerDependencies\": {\n    \"@cloudflare/workers-types\": \"^4.0.0\"\n  },\n  \"devDependencies\": {\n    \"@cloudflare/workers-types\": \"^4.20241218.0\",\n    \"typescript\": \"^5.7.2\"\n  }\n}\n","type":"text"}]}
$ cd /tmp/package/dist && head -100 index.d.ts && echo "=== client.d.ts ===" && cat client.d.ts{"content":[{"text":"import { Hono } from 'hono';\nimport type { PlatformConfig, PlatformContext } from './types';\nimport { StorageEnv } from './storage';\nexp…ctrl+o
{"content":[{"text":"import { Hono } from 'hono';\nimport type { PlatformConfig, PlatformContext } from './types';\nimport { StorageEnv } from './storage';\nexport { PlatformStorage } from './storage';\nexport { verifyPropelAuthToken, extractBearerToken } from './auth';\nexport type { User, PlatformConfig, PlatformContext, StorageModule, AuthModule, SecretsModule, BlobsModule, BlobPutOptions, BlobGetResult, BlobListOptions, BlobListResult, ListOptions, ListResult, PropelAuthConfig } from './types';\nexport type { AppPermissions, PermissionGrant, ResourceScope, ScopeCategory } from './permissions';\nexport { hasScope, getGrantedScopes, SCOPE_PRESETS, AVAILABLE_SCOPES, migratePermissions } from './permissions';\nexport type PlatformEnv = StorageEnv & {\n    ASSETS?: Fetcher;\n    BLOBS?: R2Bucket;\n    PROPELAUTH_VERIFIER_KEY?: string;\n    PROPELAUTH_AUTH_URL?: string;\n    [key: string]: unknown;\n};\nexport type PlatformApp = Hono<{\n    Bindings: PlatformEnv;\n    Variables: PlatformContext;\n}>;\nexport declare function createApp(config?: PlatformConfig): PlatformApp;\nexport declare function extendApp(baseApp: Hono<{\n    Bindings: PlatformEnv;\n}>, config?: PlatformConfig): PlatformApp;\n//# sourceMappingURL=index.d.ts.map=== client.d.ts ===\n/**\n * Client-side SDK module for user apps embedded in iframes.\n * Handles session expiry recovery via postMessage with the parent shell.\n *\n * Usage in user app frontend:\n *   import { authenticatedFetch, initSessionListener } from '@tabbycat-ai/sdk/client'\n *   initSessionListener()\n *   const response = await authenticatedFetch('/api/files')\n */\n/**\n * Initialize the session listener for bootstrap/recovery messages from the parent shell.\n * Call this once when the app starts (e.g., in main.tsx or App.tsx).\n */\nexport declare function initSessionListener(): void;\n/**\n * Wrapper around fetch that handles session expiry recovery.\n * If a request returns 401, it triggers the SESSION_EXPIRED → REBOOTSTRAP flow\n * and retries the original request once.\n */\nexport declare function authenticatedFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;\n/**\n * Check if the current session is active.\n */\nexport declare function isSessionActive(): boolean;\n/**\n * Check if session recovery is in progress.\n */\nexport declare function isRecovering(): boolean;\n//# sourceMappingURL=client.d.ts.map","type":"text"}]}
Thinking…

Now let me look at the types.d.ts to understand PlatformConfig, and check the secrets/openapi modules. Also I should figure out the Tabbycat hosted product website and whether there's an actual hosted service I can use (deploy to).

Key question: is there a hosted "Tabbycat" platform I can actually use via API without local stacks? The SDK is for building apps ON the Tabbycat platform (Cloudflare Workers based). But to actually run anything, you need to deploy to the platform. The SDK's createApp creates a Hono app to be deployed as a Cloudflare Worker. There's no visible "hosted API" I can just call from here.

But wait — there's also secrets module and openapi module and agent-tools, suggesting the platform has MCP/agent tooling and secrets. Let me check those.

Also, importantly: what is the actual Tabbycat product? Let me search the web. The GitHub repo is 404 now. Let me search Google/Bing via curl, or check if there's a docs site like tabbycat.ai, tabbycat.dev, docs.tabbycat.ai.

Let me look at the source for hints of domain names (e.g., API endpoints).

$ cd /tmp/package/dist && grep -rhoE "https?://[a-zA-Z0-9./_-]+" . | sort -u | head -50{"content":[{"text":"http://localhost\nhttps://tabbycat.ai\n","type":"text"}]}
$ cd /tmp/package/dist && echo "=== types.d.ts ===" && cat types.d.ts && echo "=== secrets.d.ts ===" && cat secrets.d.ts && echo "=== openapi.d.ts ===" && cat openapi.d.ts{"content":[{"text":"=== types.d.ts ===\nexport type User = {\n id: string;\n email: string;\n name?: string;\n firstName?: string;\n lastName?: …ctrl+o
{"content":[{"text":"=== types.d.ts ===\nexport type User = {\n    id: string;\n    email: string;\n    name?: string;\n    firstName?: string;\n    lastName?: string;\n    pictureUrl?: string;\n    orgId?: string;\n    orgName?: string;\n    roles?: string[];\n    permissions?: string[];\n};\nexport type PropelAuthConfig = {\n    verifierKey: string;\n    authUrl?: string;\n    issuer?: string;\n};\nexport type PlatformConfig = {\n    appId?: string;\n    requiredScope?: string;\n    auth?: {\n        propelAuth?: PropelAuthConfig;\n        mockUser?: User | null;\n    };\n    storage?: {\n        provider?: 'durable-object' | 'kv' | 'memory';\n    };\n};\nexport type ListOptions = {\n    limit?: number;\n    cursor?: string;\n    prefix?: string;\n};\nexport type ListResult<T = unknown> = {\n    items: T[];\n    cursor?: string;\n    hasMore: boolean;\n};\nexport interface StorageBackend {\n    get<T = unknown>(table: string, id: string): Promise<T | null>;\n    set<T = unknown>(table: string, id: string, value: T): Promise<void>;\n    delete(table: string, id: string): Promise<boolean>;\n    query<T = unknown>(table: string, filter?: Record<string, unknown>): Promise<T[]>;\n    list<T = unknown>(table: string, options?: ListOptions): Promise<ListResult<T>>;\n}\nexport type StorageModule = {\n    get: <T = unknown>(table: string, id: string) => Promise<T | null>;\n    set: <T = unknown>(table: string, id: string, value: T) => Promise<void>;\n    delete: (table: string, id: string) => Promise<boolean>;\n    query: <T = unknown>(table: string, filter?: Record<string, unknown>) => Promise<T[]>;\n    list: <T = unknown>(table: string, options?: ListOptions) => Promise<ListResult<T>>;\n};\nexport type AuthModule = {\n    getUser: () => User | null;\n    requireUser: () => User;\n    requireRole: (role: string) => User;\n    isAuthenticated: () => boolean;\n};\nexport type SecretsModule = {\n    get: (key: string) => string | null;\n    has: (key: string) => boolean;\n    require: (key: string) => string;\n};\nexport type BlobsModule = {\n    put: (key: string, value: ReadableStream | ArrayBuffer | string, options?: BlobPutOptions) => Promise<void>;\n    get: (key: string) => Promise<BlobGetResult | null>;\n    delete: (key: string) => Promise<void>;\n    list: (options?: BlobListOptions) => Promise<BlobListResult>;\n    has: (key: string) => Promise<boolean>;\n};\nexport type BlobPutOptions = {\n    contentType?: string;\n    metadata?: Record<string, string>;\n};\nexport type BlobGetResult = {\n    body: ReadableStream;\n    contentType: string;\n    size: number;\n    metadata?: Record<string, string>;\n};\nexport type BlobListOptions = {\n    prefix?: string;\n    limit?: number;\n    cursor?: string;\n};\nexport type BlobListResult = {\n    items: Array<{\n        key: string;\n        size: number;\n        uploaded: string;\n    }>;\n    cursor?: string;\n    hasMore: boolean;\n};\nexport type PlatformContext = {\n    user: User | null;\n    auth: AuthModule;\n    storage: StorageModule;\n    secrets: SecretsModule;\n    blobs: BlobsModule;\n};\n//# sourceMappingURL=types.d.ts.map=== secrets.d.ts ===\n/**\n * Secrets types for user secret management and app secret injection\n */\n/**\n * Encrypted secret stored in KV\n * Key format: secrets:{userId}:{secretName}\n */\nexport interface EncryptedSecret {\n    encryptedValue: string;\n    iv: string;\n    createdAt: number;\n}\n/**\n * User secret metadata (stored in D1)\n * Users can name their secrets anything they want\n */\nexport interface UserSecret {\n    id: string;\n    userId: string;\n    name: string;\n    description?: string;\n    createdAt: number;\n    updatedAt: number;\n    lastUsedAt?: number;\n    usageCount: number;\n}\n/**\n * Secret mapping - connects an app's requested key to a user's secret\n * e.g., app requests \"EXA_API_KEY\" -> user maps to their \"my_exa_key\" secret\n */\nexport interface SecretMapping {\n    id: string;\n    userId: string;\n    appId: string;\n    requestedKey: string;\n    secretName: string;\n    grantedAt: number;\n}\n/**\n * Session data structure for secret mappings\n * Map: appId -> { requestedKey -> secretName }\n *\n * Example:\n * {\n *   \"my-custom-app\": {\n *     \"EXA_API_KEY\": \"my_exa_key\",\n *     \"OPENAI_KEY\": \"prod_openai\"\n *   }\n * }\n */\nexport type SecretMappingsMap = Record<string, Record<string, string>>;\n/**\n * App's required secrets (from wrangler.jsonc)\n */\nexport interface AppSecretRequirements {\n    appId: string;\n    requiredSecrets: string[];\n}\n/**\n * Request body for granting secret access\n */\nexport interface GrantSecretRequest {\n    appId: string;\n    requestedKey: string;\n    secretName?: string;\n    newSecret?: {\n        name: string;\n        value: string;\n        description?: string;\n    };\n}\n/**\n * Response for listing secrets (without values)\n */\nexport interface SecretsListResponse {\n    secrets: UserSecret[];\n}\n/**\n * Response for listing secret mappings\n */\nexport interface SecretMappingsResponse {\n    mappings: SecretMapping[];\n}\n/**\n * App secret metadata (stored in D1)\n * These are secrets set by the app developer, bound to the deployed worker\n * Unlike user secrets which are mapped per-user, app secrets are tied to the deployment\n * When an app is forked, the fork gets a new deployment with NO app secrets\n *\n * App secrets are accessed via `env.SECRET_NAME` in the worker (Cloudflare native binding)\n */\nexport interface AppSecret {\n    id: string;\n    appId: string;\n    userId: string;\n    secretName: string;\n    description?: string;\n    createdAt: number;\n    updatedAt: number;\n}\n/**\n * Request body for creating/updating an app secret\n */\nexport interface CreateAppSecretRequest {\n    secretName: string;\n    secretValue: string;\n    description?: string;\n}\n/**\n * Response for listing app secrets (values are never exposed)\n */\nexport interface AppSecretsListResponse {\n    secrets: AppSecret[];\n}\n//# sourceMappingURL=secrets.d.ts.map=== openapi.d.ts ===\nimport { z } from 'zod';\nexport declare const SearchRequestSchema: z.ZodObject<{\n    query: z.ZodString;\n    limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;\n    cursor: z.ZodOptional<z.ZodString>;\n}, \"strip\", z.ZodTypeAny, {\n    query: string;\n    cursor?: string | undefined;\n    limit?: number | undefined;\n}, {\n    query: string;\n    cursor?: string | undefined;\n    limit?: number | undefined;\n}>;\nexport declare const SearchResultItemSchema: z.ZodObject<{\n    id: z.ZodString;\n    title: z.ZodString;\n    description: z.ZodOptional<z.ZodString>;\n    url: z.ZodOptional<z.ZodString>;\n    type: z.ZodOptional<z.ZodString>;\n    updatedAt: z.ZodOptional<z.ZodString>;\n    metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;\n}, \"strip\", z.ZodTypeAny, {\n    title: string;\n    id: string;\n    type?: string | undefined;\n    metadata?: Record<string, unknown> | undefined;\n    description?: string | undefined;\n    url?: string | undefined;\n    updatedAt?: string | undefined;\n}, {\n    title: string;\n    id: string;\n    type?: string | undefined;\n    metadata?: Record<string, unknown> | undefined;\n    description?: string | undefined;\n    url?: string | undefined;\n    updatedAt?: string | undefined;\n}>;\nexport declare const SearchResponseSchema: z.ZodObject<{\n    results: z.ZodArray<z.ZodObject<{\n        id: z.ZodString;\n        title: z.ZodString;\n        description: z.ZodOptional<z.ZodString>;\n        url: z.ZodOptional<z.ZodString>;\n        type: z.ZodOptional<z.ZodString>;\n        updatedAt: z.ZodOptional<z.ZodString>;\n        metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;\n    }, \"strip\", z.ZodTypeAny, {\n        title: string;\n        id: string;\n        type?: string | undefined;\n        metadata?: Record<string, unknown> | undefined;\n        description?: string | undefined;\n        url?: string | undefined;\n        updatedAt?: string | undefined;\n    }, {\n        title: string;\n        id: string;\n        type?: string | undefined;\n        metadata?: Record<string, unknown> | undefined;\n        description?: string | undefined;\n        url?: string | undefined;\n        updatedAt?: string | undefined;\n    }>, \"many\">;\n    cursor: z.ZodOptional<z.ZodString>;\n    hasMore: z.ZodDefault<z.ZodBoolean>;\n    total: z.ZodOptional<z.ZodNumber>;\n}, \"strip\", z.ZodTypeAny, {\n    hasMore: boolean;\n    results: {\n        title: string;\n        id: string;\n        type?: string | undefined;\n        metadata?: Record<string, unknown> | undefined;\n        description?: string | undefined;\n        url?: string | undefined;\n        updatedAt?: string | undefined;\n    }[];\n    cursor?: string | undefined;\n    total?: number | undefined;\n}, {\n    results: {\n        title: string;\n        id: string;\n        type?: string | undefined;\n        metadata?: Record<string, unknown> | undefined;\n        description?: string | undefined;\n        url?: string | undefined;\n        updatedAt?: string | undefined;\n    }[];\n    cursor?: string | undefined;\n    hasMore?: boolean | undefined;\n    total?: number | undefined;\n}>;\nexport type SearchRequest = z.infer<typeof SearchRequestSchema>;\nexport type SearchResultItem = z.infer<typeof SearchResultItemSchema>;\nexport type SearchResponse = z.infer<typeof SearchResponseSchema>;\nexport interface OpenAPIConfig {\n    title: string;\n    version?: string;\n    description?: string;\n    serverUrl?: string;\n}\n/**\n * Build the standard OpenAPI documentation config for use with\n * `openAPIRouteHandler(app, { documentation: buildOpenAPIDoc(config) })`\n */\nexport declare function buildOpenAPIDoc(config: OpenAPIConfig): {\n    info: {\n        description?: string | undefined;\n        title: string;\n        version: string;\n    };\n    servers: {\n        url: string;\n        description: string;\n    }[];\n};\n/**\n * Add `x-tabbycat-search: true` to a describeRoute() config\n * to mark an endpoint as the app's cross-app search endpoint.\n * The parent app uses this to discover which apps support search.\n */\nexport declare const TABBYCAT_SEARCH_EXTENSION = \"x-tabbycat-search\";\n//# sourceMappingURL=openapi.d.ts.map","type":"text"}]}
Thinking…

There's https://tabbycat.ai as a domain. Let me check the website and look for docs, pricing, and API. Also let me check agent-tools.d.ts — this is likely MCP tools for agents, which is exactly the "developer workflow" I could act on from here.

$ cd /tmp/package/dist && cat agent-tools.d.ts{"content":[{"text":"/**\n * Agent Tools SDK for iframe apps to register tools with the parent TabbyCat app.\n * This module provides helper functions for regis…ctrl+o
{"content":[{"text":"/**\n * Agent Tools SDK for iframe apps to register tools with the parent TabbyCat app.\n * This module provides helper functions for registering agent tools and handling tool execution.\n *\n * Usage in iframe apps:\n * ```typescript\n * import { setupAgentTools, registerAgentTools } from '@tabbycat-ai/sdk/agent-tools'\n *\n * // Set up message listener (call once on app init)\n * const cleanup = setupAgentTools()\n *\n * // Register your tools\n * registerAgentTools([\n *   {\n *     name: 'my_tool',\n *     description: 'Does something useful',\n *     parameters: { input: { type: 'string', description: 'The input' } }\n *   }\n * ], async (toolName, args) => {\n *   // Handle tool execution\n *   if (toolName === 'my_tool') {\n *     return { success: true, result: args.input }\n *   }\n *   return { success: false, error: 'Unknown tool' }\n * })\n *\n * // Cleanup on unmount\n * cleanup()\n * ```\n */\nexport interface ToolParameter {\n    type: 'string' | 'number' | 'boolean' | 'array' | 'object';\n    description?: string;\n    optional?: boolean;\n}\n/**\n * Display configuration for how a tool appears in the AI chat UI.\n */\nexport interface ToolDisplayConfig {\n    /** Human-readable label (e.g., \"Read file\" instead of \"read_file\") */\n    label?: string;\n    /** Icon name from lucide-react (e.g., \"FileText\", \"Search\", \"Pencil\") */\n    icon?: string;\n    /** Display style: 'minimal' shows just icon + label + status, 'detailed' is expandable */\n    style?: 'minimal' | 'detailed';\n    /**\n     * Streaming preview configuration. When set, the tool's input is rendered\n     * as a live diff-style preview during streaming instead of the default\n     * expandable tool card.\n     */\n    streaming?: ToolStreamingConfig;\n}\n/**\n * Configuration for rendering a tool's input as a streaming diff preview.\n * Specifies which input parameter contains the large content field that\n * should be streamed incrementally.\n */\nexport interface ToolStreamingConfig {\n    /** The input parameter name that holds the main streamable content (e.g., 'content', 'replaceText') */\n    contentField: string;\n    /** Optional input parameter name for content being replaced (shown as red removal lines, e.g., 'searchText') */\n    removalField?: string;\n    /** Diff mode controls the badge/icon style: 'new' | 'edit' | 'overwrite' | 'append'. Defaults to 'new'. */\n    diffMode?: 'new' | 'edit' | 'overwrite' | 'append';\n}\n/**\n * Action that can be triggered when clicking on a tool result in the chat UI.\n * Include this as `_action` in your tool result to make the tool clickable.\n *\n * @example\n * ```typescript\n * return {\n *   success: true,\n *   results: [...],\n *   _action: {\n *     type: 'open-tab',\n *     path: '/results/123',\n *     title: 'Search Results'\n *   }\n * }\n * ```\n */\nexport interface ToolResultAction {\n    /** Action type - currently only 'open-tab' is supported */\n    type: 'open-tab';\n    /** App ID to open the tab in (e.g., 'exa-search', 'file-editor') */\n    appId: string;\n    /** Path to open in the app (relative to the app root) */\n    path: string;\n    /** Title for the new tab */\n    title?: string;\n}\nexport interface SerializableToolDefinition {\n    name: string;\n    description: string;\n    parameters: Record<string, ToolParameter>;\n    /** Optional display configuration for the chat UI */\n    display?: ToolDisplayConfig;\n}\nexport type ToolExecuteHandler = (toolName: string, args: Record<string, unknown>) => Promise<unknown>;\n/**\n * Get the current tab ID.\n */\nexport declare function getTabId(): string | null;\n/**\n * Register agent tools with the parent app.\n * The parent app will call back via postMessage when the AI invokes these tools.\n *\n * @param tools - Array of tool definitions (without execute functions)\n * @param executeHandler - Function to handle tool execution requests\n */\nexport declare function registerAgentTools(tools: SerializableToolDefinition[], executeHandler: ToolExecuteHandler): void;\n/**\n * Unregister all agent tools for this tab.\n */\nexport declare function unregisterAgentTools(): void;\n/**\n * Capture a screenshot of the current app's viewport.\n * Uses html2canvas (dynamically imported to avoid bundling in apps that don't need it).\n * Returns a base64 JPEG data URL, resized to fit within MAX_DIM to keep payloads small.\n */\nexport declare function captureScreenshot(): Promise<string>;\n/**\n * Set up the message listener for agent tools.\n * Call this once when the iframe app initializes.\n *\n * @returns Cleanup function to remove the listener\n */\nexport declare function setupAgentTools(): () => void;\n/**\n * Open a new tab in the parent app.\n */\nexport declare function openNewTab(path: string, title?: string): void;\n/**\n * Update the title of the current tab.\n */\nexport declare function updateTabTitle(title: string): void;\n/**\n * Notify the parent app that a file/data has changed.\n * This triggers a broadcast to all iframes of the same app type to refetch data.\n */\nexport declare function notifyDataChanged(dataType: string, id?: string): void;\n/**\n * Register a callback to be called when data changes in other iframes.\n * Use this to trigger refetches when files are modified by AI tools.\n */\nexport declare function onDataChanged(callback: (dataType: string, id?: string) => void): void;\nexport interface ChatReferenceOptions {\n    type: string;\n    displayId: string;\n    content: string;\n}\n/**\n * Add a reference to the chat input.\n * The reference will appear as an @mention chip that the user can include in their message.\n */\nexport declare function addChatReference(options: ChatReferenceOptions): void;\nexport interface OpenChatWithReferenceOptions extends ChatReferenceOptions {\n    message?: string;\n}\n/** Prompt suggestion for quick actions in the chat sidebar */\nexport interface PromptSuggestion {\n    /** Icon name from lucide-react (e.g., \"FileText\", \"Search\", \"Pencil\") */\n    icon: string;\n    /** Display label for the suggestion */\n    label: string;\n    /** The prompt text to fill or submit */\n    prompt: string;\n    /** Optional tag badge (e.g., \"New\", \"Beta\") */\n    tag?: string;\n    /** Color for the tag badge (default: 'blue') */\n    tagColor?: 'blue' | 'green' | 'yellow' | 'red' | 'purple' | 'orange';\n    /** If true, immediately submit the prompt. If false, fill the input for user to customize */\n    autoSubmit?: boolean;\n}\n/** Configuration for prompt suggestions shown in the chat sidebar */\nexport interface PromptSuggestionsConfig {\n    /** Heading text shown above suggestions (e.g., \"How can I help?\") */\n    heading: string;\n    /** Array of prompt suggestions */\n    suggestions: PromptSuggestion[];\n}\n/**\n * Open the chat sidebar and add a reference to the input.\n * Optionally pre-fill a message.\n */\nexport declare function openChatWithReference(options: OpenChatWithReferenceOptions): void;\n/**\n * Register prompt suggestions for this app.\n * These will be shown in the chat sidebar when this app's tab is active.\n *\n * @param config - The prompt suggestions configuration\n */\nexport declare function registerPromptSuggestions(config: PromptSuggestionsConfig): void;\n/**\n * Unregister prompt suggestions for this app.\n */\nexport declare function unregisterPromptSuggestions(): void;\n/** Configuration for app context sent to the AI agent */\nexport interface AppContextConfig {\n    /** Short label for the context (e.g., file name, search query) */\n    title: string;\n    /** The actual context content (e.g., first N lines of a file, search results summary) */\n    summary: string;\n}\nexport declare function setAppContext(context: AppContextConfig): void;\n/**\n * Clear the app context for the current tab.\n */\nexport declare function clearAppContext(): void;\n//# sourceMappingURL=agent-tools.d.ts.map","type":"text"}]}
$ echo "=== tabbycat.ai homepage ==="; curl -sSL --max-time 20 "https://tabbycat.ai" 2>&1 | head -100{"content":[{"text":"=== tabbycat.ai homepage ===\n<!DOCTYPE html>\n<html>\n <head>\n <title>Sign in ・ Cloudflare Access</title>\n \n <meta charset=\"…ctrl+o
{"content":[{"text":"=== tabbycat.ai homepage ===\n<!DOCTYPE html>\n<html>\n  <head>\n    <title>Sign in ・ Cloudflare Access</title>\n    \n    <meta charset=\"utf-8\" />\n    <meta name=\"robots\" content=\"noindex\" />\n    <meta name=\"viewport\" content=\"initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 62 50'%3E %3Cstyle%3E path %7B fill: url(%23gradient-light); %7D @media (prefers-color-scheme: dark) %7B path %7B fill: url(%23gradient-dark); %7D %7D %3C/style%3E %3Cdefs%3E %3ClinearGradient id='gradient-light' x1='30.7' y1='0' x2='30.7' y2='49.8' gradientUnits='userSpaceOnUse'%3E %3Cstop stop-color='%23332CB3'/%3E %3Cstop offset='1' stop-color='%23456FDD'/%3E %3C/linearGradient%3E %3ClinearGradient id='gradient-dark' x1='30.7' y1='0' x2='30.7' y2='49.8' gradientUnits='userSpaceOnUse'%3E %3Cstop stop-color='%23FFFFFF'/%3E %3Cstop offset='1' stop-color='%23E0E0E0'/%3E %3C/linearGradient%3E %3C/defs%3E %3Cpath d='M21.3 15.6h-7.8C17.2 6.4 26.1 0 36.6 0c13.7 0 24.9 11.1 24.9 24.9 0 13.7-11.1 24.9-24.9 24.9-10.4 0-19.4-6.4-23.1-15.6h7.8c3.1 5.1 8.8 8.6 15.3 8.6 9.9 0 17.9-8 17.9-17.9 0-9.9-8-17.9-17.9-17.9-6.5 0-12.1 3.4-15.3 8.6zm-12 14l-3.1-3.1h36.7c1.4 0 2.1 1.7 1.1 2.7l-6.2 6.2-2.2-2.2 3.6-3.6H9.3zm23-12.9l2.2-2.2 6.2 6.2c1 1 .3 2.7-1.1 2.7H3.1L0 20.2h35.9l-3.6-3.5z'/%3E %3C/svg%3E \" />\n    <article id=\"data\"\n      data-auto-redirect-to-identity=\"false\"\n      data-auto-redirect-url=\"\"\n      data-message=\"\">\n    </article>\n    <style>@charset \"UTF-8\";:root{color-scheme:light;--color-kumo-canvas:oklch(98.75% 0 0);--color-kumo-elevated:oklch(98% 0 0);--color-kumo-recessed:oklch(96% 0 0);--color-kumo-base:#fff;--color-kumo-tint:oklch(97% 0 0);--color-kumo-control:#fff;--color-kumo-line:oklch(14.5% 0 0 / 0.1);--color-kumo-hairline:oklch(93.5% 0 0);--color-kumo-fill:oklch(92.2% 0 0);--color-kumo-brand:oklch(57.72% 0.2324 260);--color-kumo-danger:oklch(63.7% 0.237 25.331);--color-kumo-success:oklch(59.6% 0.145 163.225);--color-kumo-focus:oklch(15% 0 0);--color-interstitial-deny:oklch(62% 0.19 25.331);--color-interstitial-approve:oklch(62.7% 0.19 149.214);--text-color-kumo-default:oklch(21% 0.006 285.885);--text-color-kumo-subtle:oklch(55.6% 0 0);--text-color-kumo-placeholder:oklch(70.8% 0 0);--text-color-kumo-link:oklch(42.4% 0.199 265.638);--text-color-kumo-info:oklch(42.4% 0.199 265.638);--text-color-kumo-warning:oklch(47.6% 0.114 61.907);--text-color-kumo-danger:oklch(50.5% 0.213 27.518);--text-color-kumo-success:oklch(43.2% 0.095 166.913);--radius-sm:4px;--radius-md:6px;--radius-lg:8px;--spacing:4px;--content-width:460px;--content-width-wide:540px;--content-width-xl:680px;--page-bg-override:initial}*,::after,::before{-webkit-box-sizing:border-box;box-sizing:border-box}body,html{margin:0;padding:0;min-height:100vh;min-height:100dvh}html{text-rendering:optimizeLegibility;word-wrap:break-word}.Content,body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}body,html{background:var(--page-bg-override, #fbfbfb)}body{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333333;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:calc(var(--spacing)*8) calc(var(--spacing)*2);text-align:center}button{font-family:inherit}.Content{width:100%;max-width:var(--content-width);margin:0 auto;gap:calc(var(--spacing)*5)}.Content-is-wide{max-width:var(--content-width-wide)}.Content-is-xl{max-width:var(--content-width-xl)}.base_AccessLogo{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.Content::after{content:\"\";display:block;height:41px;-ms-flex-negative:0;flex-shrink:0}.base_AccessLogo svg{width:280px;max-width:100%;height:auto;display:block}.AuthBox{width:100%;background:var(--color-kumo-base);border:1px solid var(--color-kumo-hairline);border-radius:var(--radius-lg);overflow:hidden;text-align:left}.AuthBox-body{padding:calc(var(--spacing)*8);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:calc(var(--spacing)*4)}.AuthBox-body-is-centered,.OrgAvatarLink{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.AuthBox-body-is-centered{text-align:center}.OrgAvatarLink{display:-webkit-box;display:-ms-flexbox;display:flex;gap:calc(var(--spacing)*3);text-decoration:none;color:inherit}.OrgAvatarLink-logo{-ms-flex-negative:0;flex-shrink:0;height:36px}.OrgAvatarLink-logo:has(img[src=\"\"]){display:none}.OrgAvatarLink-logo img{display:block;height:36px;width:auto;border-radius:var(--radius-sm)}.OrgAvatarLink-title{font-size:14px;line-height:20px;font-weight:500;color:var(--text-color-kumo-subtle)}.AuthBox-App{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:var(--spacing)}.Card-title{margin:0;font-size:24px;line-height:32px;font-weight:600;color:var(--text-color-kumo-default)}.AuthBox-body p,.Card-subtitle,.Result p{margin:0;font-size:14px;line-height:20px;color:var(--text-color-kumo-default)}.Header-text{background:var(--color-kumo-tint);border:1px solid var(--color-kumo-hairline);border-radius:var(--radius-md);padding:calc(var(--spacing)*3) calc(var(--spacing)*4);font-size:13px;line-height:18px;color:var(--text-color-kumo-default)}.AuthBox-App>.Header-text{margin-top:calc(var(--spacing)*3)}.AuthBox-text,.InfoRow h3{font-size:14px;line-height:20px;font-weight:500;color:var(--text-color-kumo-default);margin:0 0 2px}.Result{gap:var(--spacing)}.Result p{color:var(--text-color-kumo-subtle)}.InfoList,.InfoRow,.Result{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.InfoList{gap:calc(var(--spacing)*5)}.InfoRow{gap:var(--spacing)}.InfoRow h3{margin:0;font-size:13px;line-height:18px;font-weight:600}.InfoRow p,.StandardInput{font-size:14px;color:var(--text-color-kumo-default)}.InfoRow p{margin:0;line-height:20px}.StandardInput{-moz-appearance:none;appearance:none;-webkit-appearance:none;display:block;width:100%;height:44px;padding:0 calc(var(--spacing)*3);font:inherit;background:var(--color-kumo-control);border:1px solid var(--color-kumo-line);border-radius:var(--radius-lg);-webkit-transition:background-color 120ms ease;transition:background-color 120ms ease}.StandardInput::-webkit-input-placeholder{color:var(--text-color-kumo-placeholder)}.StandardInput::-moz-placeholder{color:var(--text-color-kumo-placeholder)}.StandardInput:-ms-input-placeholder{color:var(--text-color-kumo-placeholder)}.StandardInput::-ms-input-placeholder{color:var(--text-color-kumo-placeholder)}.StandardInput::placeholder{color:var(--text-color-kumo-placeholder)}.AuthFormLogin,.AuthFormLogin-row{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.AuthFormLogin{gap:calc(var(--spacing)*3);margin:0}.AuthFormLogin-row{gap:var(--spacing)}.AuthFormLogin-row label,.Button{font-size:14px;line-height:20px;font-weight:500}.AuthFormLogin-row label{color:var(--text-color-kumo-default);margin-bottom:2px}.AuthFormLogin input[hidden]{display:none}.Button{-moz-appearance:none;appearance:none;-webkit-appearance:none;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:calc(var(--spacing)*2);height:44px;padding:0 calc(var(--spacing)*4);font-family:inherit;border-radius:var(--radius-lg);border:1px solid transparent;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,.05);box-shadow:0 1px 2px 0 rgba(0,0,0,.05);cursor:pointer;text-decoration:none;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:background-color 120ms ease,border-color 120ms ease,color 120ms ease,-webkit-box-shadow 120ms ease,-webkit-transform 60ms ease;transition:background-color 120ms ease,border-color 120ms ease,color 120ms ease,box-shadow 120ms ease,transform 60ms ease;transition:background-color 120ms ease,border-color 120ms ease,color 120ms ease,box-shadow 120ms ease,transform 60ms ease,-webkit-box-shadow 120ms ease,-webkit-transform 60ms ease}.Button:focus-visible{outline-offset:2px}.Button:active{-webkit-transform:translateY(.5px);-ms-transform:translateY(.5px);transform:translateY(.5px)}.Button:disabled{opacity:.5;cursor:not-allowed;-webkit-transform:none;-ms-transform:none;transform:none}.Button-is-block{width:100%}.Button-is-destructive,.Button-is-interstitial-approve,.Button-is-interstitial-deny,.Button-is-primary{background:-webkit-gradient(linear,left top,left bottom,from(color-mix(in oklch,var(--button-emphasis-color),white 15%)),to(var(--button-emphasis-color)));background:linear-gradient(to bottom,color-mix(in oklch,var(--button-emphasis-color),white 15%),var(--button-emphasis-color));color:#fff;border-color:color-mix(in oklch,var(--button-emphasis-color),black 10%)}.Button-is-destructive:hover:not(:disabled),.Button-is-interstitial-approve:hover:not(:disabled),.Button-is-interstitial-deny:hover:not(:disabled),.Button-is-primary:hover:not(:disabled){background:-webkit-gradient(linear,left top,left bottom,from(color-mix(in oklch,var(--button-emphasis-color),white 30%)),to(var(--button-emphasis-color)));background:linear-gradient(to bottom,color-mix(in oklch,var(--button-emphasis-color),white 30%),var(--button-emphasis-color))}.Button-is-primary{--button-emphasis-color:var(--color-kumo-brand)}.Button-is-destructive{--button-emphasis-color:var(--color-kumo-danger)}.Button-is-interstitial-deny{--button-emphasis-color:var(--color-interstitial-deny)}.Button-is-interstitial-approve{--button-emphasis-color:var(--color-interstitial-approve)}.Button-is-secondary{background:var(--color-kumo-base);color:var(--text-color-kumo-default);border-color:var(--color-kumo-hairline)}.Button-is-secondary:hover:not(:disabled){background:var(--color-kumo-tint)}.Button-is-secondary:active:not(:disabled){background:var(--color-kumo-fill)}.Button-is-auth{background:var(--color-kumo-base);color:var(--text-color-kumo-default);border-color:var(--color-kumo-hairline);-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;padding:0 calc(var(--spacing)*4);gap:calc(var(--spacing)*3)}.Button-is-auth:hover:not(:disabled){background:var(--color-kumo-tint)}.Button-is-auth:active:not(:disabled){background:var(--color-kumo-fill)}.Button-is-small{height:26px;padding:0 calc(var(--spacing)*2);gap:calc(var(--spacing)*1);font-size:12px;line-height:16px;border-radius:var(--radius-md)}.Button-auth-service-icon{-ms-flex-negative:0;flex-shrink:0;width:20px;height:20px;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.Button-auth-service-icon img,.Button-auth-service-icon svg{width:20px;height:20px;display:block}.AuthBoxRow--name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500}.CardActions{display:-webkit-box;display:-ms-flexbox;display:flex;gap:calc(var(--spacing)*3);margin:0}.CardActions .Button{-webkit-box-flex:1;-ms-flex:1;flex:1}.Footer-text{margin:calc(var(--spacing)*4) calc(var(--spacing)*-8) calc(var(--spacing)*-8);padding:calc(var(--spacing)*4) calc(var(--spacing)*8);background:var(--color-kumo-elevated);border-top:1px solid var(--color-kumo-hairline);font-size:12px;line-height:18px;color:var(--text-color-kumo-subtle);text-align:left}.Footer-text span{white-space:pre-line}.Message,.resend-confirmation{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;gap:calc(var(--spacing)*3);padding:calc(var(--spacing)*3) calc(var(--spacing)*4);border:1px solid;border-radius:var(--radius-lg);font-size:14px;line-height:20px;text-align:left}.Message{display:-webkit-box;display:-ms-flexbox;display:flex}.Message svg{-ms-flex-negative:0;flex-shrink:0;width:16px;height:16px;margin-top:2px}.Message svg path{fill:currentColor}.Message-is-info{background:#f4f9ff;border-color:#5b9bd5;color:var(--text-color-kumo-info)}.Message-is-warning{background:#fffef6;border-color:#e6c84a;color:var(--text-color-kumo-warning)}.Message-is-error{background:#fffbfb;border-color:#fd959b;color:var(--text-color-kumo-danger)}.Message-is-success,.resend-confirmation{background:#f8fefb;border-color:#5cb87a;color:var(--text-color-kumo-success)}@media (max-width:480px){body{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;padding:calc(var(--spacing)*8) calc(var(--spacing)*4) calc(var(--spacing)*4)}.base_AccessLogo svg{width:220px}.Content::after{display:none}.AuthBox-body{padding:calc(var(--spacing)*5)}.Card-title{font-size:20px}.Footer-text{margin:calc(var(--spacing)*4) calc(var(--spacing)*-5) calc(var(--spacing)*-5);padding:calc(var(--spacing)*3) calc(var(--spacing)*5)}}@media (prefers-reduced-motion:reduce){*,::after,::before{-webkit-transition-duration:.01ms!important;transition-duration:.01ms!important;-webkit-animation-duration:.01ms!important;animation-duration:.01ms!important}}.AuthBox-AuthGroup,.AuthBox-Normal{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.AuthBox-Normal{display:-webkit-box;display:-ms-flexbox;display:flex;gap:calc(var(--spacing)*4)}.AuthBox-AuthGroup{gap:var(--spacing)}.AuthBox-AuthGroup,.AuthServiceLogin,.kumo-divider{display:-webkit-box;display:-ms-flexbox;display:flex}.AuthServiceLogin{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:calc(var(--spacing)*2)}.kumo-divider{-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:calc(var(--spacing)*3);color:var(--text-color-kumo-subtle)}.kumo-divider::after,.kumo-divider::before{content:\"\";-webkit-box-flex:1;-ms-flex:1;flex:1;height:1px;background:var(--color-kumo-hairline)}.kumo-divider__label{font-size:13px;line-height:18px;font-weight:500}.AuthBox-RequestCode{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:calc(var(--spacing)*2)}</style>\n\n    <!-- URL polyfill for IE11 -->\n    <script>!function(e){\"use strict\";function t(t){return!!t&&(\"Symbol\"in e&&\"iterator\"in e.Symbol&&\"function\"==typeof t[Symbol.iterator]||!!Array.isArray(t))}function n(e){return\"from\"in Array?Array.from(e):Array.prototype.slice.call(e)}!function(){var r,a=e.URL;try{if(a){if(\"searchParams\"in(r=new e.URL(\"http://example.com\")))return;\"href\"in r||(r=void 0)}}catch(e){}function i(e){var t=\"\",n=!0;return e.forEach(function(e){var r=encodeURIComponent(e.name),a=encodeURIComponent(e.value);n||(t+=\"&\"),t+=r+\"=\"+a,n=!1}),t.replace(/%20/g,\"+\")}function o(e,t){var n=e.split(\"&\");t&&-1===n[0].indexOf(\"=\")&&(n[0]=\"=\"+n[0]);var r=[];n.forEach(function(e){if(0!==e.length){var t=e.indexOf(\"=\");if(-1!==t)var n=e.substring(0,t),a=e.substring(t+1);else n=e,a=\"\";n=n.replace(/\\+/g,\" \"),a=a.replace(/\\+/g,\" \"),r.push({name:n,value:a})}});var a=[];return r.forEach(function(e){a.push({name:decodeURIComponent(e.name),value:decodeURIComponent(e.value)})}),a}function u(e){var r=this;this._list=[],null==e||(e instanceof u?this._list=o(String(e)):\"object\"==typeof e&&t(e)?n(e).forEach(function(e){if(!t(e))throw TypeError();var a=n(e);if(2!==a.length)throw TypeError();r._list.push({name:String(a[0]),value:String(a[1])})}):\"object\"==typeof e&&e?Object.keys(e).forEach(function(t){r._list.push({name:String(t),value:String(e[t])})}):(\"?\"===(e=String(e)).substring(0,1)&&(e=e.substring(1)),this._list=o(e))),this._url_object=null,this._setList=function(e){a||(r._list=e)};var a=!1;this._update_steps=function(){a||(a=!0,r._url_object&&(\"about:\"===r._url_object.protocol&&-1!==r._url_object.pathname.indexOf(\"?\")&&(r._url_object.pathname=r._url_object.pathname.split(\"?\")[0]),r._url_object.search=i(r._list),a=!1))}}function l(e,t){var n=0;this.next=function(){if(n>=e.length)return{done:!0,value:void 0};var r=e[n++];return{done:!1,value:\"key\"===t?r.name:\"value\"===t?r.value:[r.name,r.value]}}}function c(t,n){if(!(this instanceof e.URL))throw new TypeError(\"Failed to construct 'URL': Please use the 'new' operator.\");n&&(t=function(){if(r)return new a(t,n).href;var e;try{var i;if(\"[object OperaMini]\"===Object.prototype.toString.call(window.operamini)?((e=document.createElement(\"iframe\")).style.display=\"none\",document.documentElement.appendChild(e),i=e.contentWindow.document):document.implementation&&document.implementation.createHTMLDocument?i=document.implementation.createHTMLDocument(\"\"):document.implementation&&document.implementation.createDocument?((i=document.implementation.createDocument(\"http://www.w3.org/1999/xhtml\",\"html\",null)).documentElement.appendChild(i.createElement(\"head\")),i.documentElement.appendChild(i.createElement(\"body\"))):window.ActiveXObject&&((i=new window.ActiveXObject(\"htmlfile\")).write(\"<head></head><body></body>\"),i.close()),!i)throw Error(\"base not supported\");var o=i.createElement(\"base\");o.href=n,i.getElementsByTagName(\"head\")[0].appendChild(o);var u=i.createElement(\"a\");return u.href=t,u.href}finally{e&&e.parentNode.removeChild(e)}}());var i=function(e){if(r)return new a(e);var t=document.createElement(\"a\");return t.href=e,t}(t||\"\"),l=function(){if(!(\"defineProperties\"in Object))return!1;try{var e={};return Object.defineProperties(e,{prop:{get:function(){return!0}}}),e.prop}catch(e){return!1}}()?this:document.createElement(\"a\"),c=new u(i.search?i.search.substring(1):null);function s(){var e=i.href.replace(/#$|\\?$|\\?(?=#)/g,\"\");i.href!==e&&(i.href=e)}function f(){c._setList(i.search?o(i.search.substring(1)):[]),c._update_steps()}return c._url_object=l,Object.defineProperties(l,{href:{get:function(){return i.href},set:function(e){i.href=e,s(),f()},enumerable:!0,configurable:!0},origin:{get:function(){return\"origin\"in i?i.origin:this.protocol+\"//\"+this.host},enumerable:!0,configurable:!0},protocol:{get:function(){return i.protocol},set:function(e){i.protocol=e},enumerable:!0,configurable:!0},username:{get:function(){return i.username},set:function(e){i.username=e},enumerable:!0,configurable:!0},password:{get:function(){return i.password},set:function(e){i.password=e},enumerable:!0,configurable:!0},host:{get:function(){var e={\"http:\":/:80$/,\"https:\":/:443$/,\"ftp:\":/:21$/}[i.protocol];return e?i.host.replace(e,\"\"):i.host},set:function(e){i.host=e},enumerable:!0,configurable:!0},hostname:{get:function(){return i.hostname},set:function(e){i.hostname=e},enumerable:!0,configurable:!0},port:{get:function(){return i.port},set:function(e){i.port=e},enumerable:!0,configurable:!0},pathname:{get:function(){return\"/\"!==i.pathname.charAt(0)?\"/\"+i.pathname:i.pathname},set:function(e){i.pathname=e},enumerable:!0,configurable:!0},search:{get:function(){return i.search},set:function(e){i.search!==e&&(i.search=e,s(),f())},enumerable:!0,configurable:!0},searchParams:{get:function(){return c},enumerable:!0,configurable:!0},hash:{get:function(){return i.hash},set:function(e){i.hash=e,s()},enumerable:!0,configurable:!0},toString:{value:function(){return i.toString()},enumerable:!1,configurable:!0},valueOf:{value:function(){return i.valueOf…
Thinking…

Now I understand the whole picture:

  • Tabbycat (tabbycat.ai) is a hosted product by LaunchFlow. It's gated behind Cloudflare Access with Google Workspace sign-in restricted to launchflow.com domain (hd=launchflow.com). So it's an internal product (LaunchFlow's own tool), not publicly accessible. There is no public sign-up, no public API docs, no public pricing.

  • The SDK (@tabbycat-ai/sdk) is a framework for building iframe-embedded apps inside the Tabbycat shell (a Cloudflare Workers + Hono framework with auth via PropelAuth, storage via Durable Objects/KV, secrets, blobs via R2). But to actually "use" Tabbycat, you'd need to:

    1. Be a member of launchflow.com Google Workspace to even log in to tabbycat.ai.
    2. Deploy a Cloudflare Worker (with wrangler CLI) as an app, and have the Tabbycat platform host/dispatch it.

So the developer workflow that's actionable from here: The only publicly available artifact is the npm SDK. I could write a sample Worker using the SDK (the "simple example" code). But I can't actually deploy it or run it against the hosted product because:

  • The hosted product requires launchflow.com Google Workspace auth (I can't sign in).
  • Even the SDK itself: I can install it from npm and write example code. But running it requires Cloudflare Workers runtime (wrangler dev / deploy) — a local service stack / long-running deploy process, which the user told me to avoid.

Wait, let me re-read the user's instructions: "Stay light: use the hosted product through its SDK or API. Do not start local service stacks or wait for long-running commands; if the quickstart requires either, say so plainly and stop."

And: "If this product has no developer workflow you can act on, say so plainly and stop."

So the key question: Does Tabbycat have a developer workflow I can act on (via SDK/API) from here, without local service stacks or long-running commands?

Analysis:

  • Tabbycat.ai is the hosted product. It's behind Cloudflare Access restricted to launchflow.com Google Workspace. So I cannot create an account, cannot get credentials, cannot authenticate. No public API keys.
  • The SDK is installable from npm and I can write a sample app with it — that's a "simple example." But running it would require wrangler dev (local service stack, long-running) or wrangler deploy (deploy to Cloudflare, needs an account).
  • There's no public hosted API endpoint I can call from here.
  • Pricing: not published. The product isn't publicly sold; there's no pricing page. Sign-in is gated to LaunchFlow's own org.

Additional check: maybe there's a docs site like docs.tabbycat.ai or tabbycat.dev. But given sign-in is gated to launchflow.com, it's clearly a private/internal tool (LaunchFlow is a company; Tabbycat appears to be their internal AI "cat" app platform). Let me quickly verify there's no public docs/pricing page before concluding.

Let me check:

Let me also check the LaunchFlow connection — launchflow.com is a company (Cloud infra for AI apps). The npm SDK repo was launchflow/tabbycat (now private). So Tabbycat is LaunchFlow's internal product, exposed via SDK on npm but gated.

Let me do a couple quick checks (launchflow.com, docs subdomains) to be thorough, but not long-running. Then I'll write up the findings.

Actually, the user asked me to "build a simple example using Tabbycat." I can do the light thing: install the SDK and write the canonical "Hello Tabbycat" worker example from the README. That's actionable and lightweight (npm install is fast, no service stack). But I should be honest that I can't actually deploy/run it against the hosted product because auth is gated and no public account/API.

Hmm, but wait — "If this product has no developer workflow you can act on, say so plainly and stop." There IS a developer workflow in principle (write a Worker app using the SDK), but the hosted product is not accessible to me. The question is whether to write example code anyway.

Let me think about what's most useful and honest:

  1. The "simple example" — I can write it (the SDK usage). This is light: install the npm package, write a small Hono worker. That doesn't require a local service stack or long-running command. Installing npm packages is quick.

  2. But actually running/deploying requires wrangler + Cloudflare account + Tabbycat access, which I can't do. I should say so plainly.

  3. Pricing: no public pricing (product is gated/internal). I should say that plainly.

I think the right move:

  • Quickly verify no public docs/pricing (a few curl HEADs with short timeouts).
  • Install SDK + write a minimal, correct example app (the "simple example") that compiles/type-checks. This is a real developer artifact I can produce without local stacks.
  • Report: what Tabbycat is, what I built, pricing (not published / gated), manageability assessment, and the blocker (can't authenticate to hosted product, so I can't run/deploy it against Tabbycat itself; doing so needs wrangler deploy and a launchflow.com account).

Actually, let me reconsider whether to even install and write the example. The user said "help me build a simple example using Tabbycat" and "use the hosted product through its SDK or API." The SDK is the way. Writing the example is legitimate. But I must not pretend I can deploy it. And I should be clear about the blocker: hosted product is behind a private Cloudflare Access login (launchflow.com only), so there's no way for me to actually exercise it from here.

Let me think about "easy for you to manage" — the user wants my brief assessment: will Tabbycat be easy for me (the agent) to manage? I should assess based on what I found: the SDK is a thin Hono wrapper with clean type definitions, storage/auth/secrets/blobs modules, an agent-tools module. From a code standpoint, it's manageable (small, typed, MIT). But operationally, it's not manageable from here because I can't authenticate/deploy. Also it's actually designed to host other agents' tools (MCP-style iframe apps), which is ironic but relevant: Tabbycat is a platform for building AI-agent tools.

Let me check the LaunchFlow website and a few subdomains quickly, then check npm install feasibility. Actually the npm package install is fast; let me just do it and write the example.

Let me also double check: is there a public pypi or other SDK? The pypi "tabbycat" returned an error (no 'info'), meaning 404 for that name. There's also the R package "tabbycat" by olihawkins, and the debate software. But given the npm @tabbycat-ai/sdk points to tabbycat.ai and is literally "SDK for building apps on the Tabbycat platform", and the user is speaking in a developer-workflow/SDK frame (this is a coding agent harness, so the user is evaluating products for the agent to use), the relevant product is clearly tabbycat.ai.

Let me verify a few endpoints quickly (launchflow.com, docs/pricing subdomains) and then install and write.

Let me be efficient with parallel calls.

$ for u in "https://tabbycat.ai/" "https://tabbycat.ai/pricing" "https://docs.tabbycat.ai/" "https://www.tabbycat.ai/" "https://launchflow.com/" "https://launchflow.com/pricing"; do code=$(curl -sS -o /…{"content":[{"text":"200 https://launchflow.cloudflareaccess.com/cdn-cgi/access/login/tabbycat.ai?kid=fe65cd7270506db6c8c42f56c4d934b37073d268c05c659d52843fc8bb…ctrl+o
for u in "https://tabbycat.ai/" "https://tabbycat.ai/pricing" "https://docs.tabbycat.ai/" "https://www.tabbycat.ai/" "https://launchflow.com/" "https://launchflow.com/pricing"; do code=$(curl -sS -o /dev/null -w "%{http_code} %{url_effective}" --max-time 10 -L "$u" 2>&1); echo "$code"; done

{"content":[{"text":"200 https://launchflow.cloudflareaccess.com/cdn-cgi/access/login/tabbycat.ai?kid=fe65cd7270506db6c8c42f56c4d934b37073d268c05c659d52843fc8bbf6705c&meta=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImZhYzVkYTZjOTQ5Y2E0ZGRiZTg2NzYyMjdhZDYyYTBhNmZjODQ3OTBhYTEzOTZlMzcxMmRlNGYyMmU1NWE1YjcifQ.eyJ0eXBlIjoibWV0YSIsImF1ZCI6ImZlNjVjZDcyNzA1MDZkYjZjOGM0MmY1NmM0ZDkzNGIzNzA3M2QyNjhjMDVjNjU5ZDUyODQzZmM4YmJmNjcwNWMiLCJob3N0bmFtZSI6InRhYmJ5Y2F0LmFpIiwicmVkaXJlY3RfdXJsIjoiLyIsInNlcnZpY2VfdG9rZW5fc3RhdHVzIjpmYWxzZSwiaXNfd2FycCI6ZmFsc2UsImlzX2dhdGV3YXkiOmZhbHNlLCJleHAiOjE3ODk3NjkxMjksIm5iZiI6MTc4OTc2ODgyOSwiaWF0IjoxNzg5NzY4ODI5LCJhdXRoX3N0YXR1cyI6Ik5PTkUiLCJtdGxzX2F1dGgiOnsiY2VydF9pc3N1ZXJfZG4iOiIiLCJjZXJ0X3NlcmlhbCI6IiIsImNlcnRfaXNzdWVyX3NraSI6IiIsImNlcnRfcHJlc2VudGVkIjpmYWxzZSwiY29tbW9uX25hbWUiOiIiLCJhdXRoX3N0YXR1cyI6Ik5PTkUifSwicmVhbF9jb3VudHJ5IjoiVVMiLCJhcHBfc2Vzc2lvbl9oYXNoIjoiZjM2ZGZlOTFkYTVjOTZmOWU1NTU5MzRmM2E2OTk1NWE0MTQ4NmJhZDViM2Q0NzI1NjA3NDU5ZTVhYTk3NmM2NSJ9.mbzYK-to7UwJ-jjRY4n31fIqSijPcCgRcUWVNbgn0mtFJBrzMjJ543yXWIOC9LDrzrsHrPEm_Bwi7SAKbl8EAuiQXuTCmKShNPjfrvHJe27D-Rg_tbYkQ6vZ9HTD16LYOkcBfcgyr54CDKkSo_xfKG952Y_ufyBJUejHvAHmLNbl9ZaS-jtX36qN4naWGviEsxOpu7nfdlfTv0XlSa1zGEWTpH0FNg-fkohieLogfz-PzO_smfRywy1ddNTteUOYzL4BIJH5ox_IT5Ejbu737vxbj0VtDR4eIB18Y32JnZZKMij6B3_XqHwFooZjkho7GsqlLYMJo3cSeOup8B-zoA&redirect_url=%2F\n200 https://launchflow.cloudflareaccess.com/cdn-cgi/access/login/tabbycat.ai?kid=fe65cd7270506db6c8c42f56c4d934b37073d268c05c659d52843fc8bbf6705c&meta=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImZhYzVkYTZjOTQ5Y2E0ZGRiZTg2NzYyMjdhZDYyYTBhNmZjODQ3OTBhYTEzOTZlMzcxMmRlNGYyMmU1NWE1YjcifQ.eyJ0eXBlIjoibWV0YSIsImF1ZCI6ImZlNjVjZDcyNzA1MDZkYjZjOGM0MmY1NmM0ZDkzNGIzNzA3M2QyNjhjMDVjNjU5ZDUyODQzZmM4YmJmNjcwNWMiLCJob3N0bmFtZSI6InRhYmJ5Y2F0LmFpIiwicmVkaXJlY3RfdXJsIjoiL3ByaWNpbmciLCJzZXJ2aWNlX3Rva2VuX3N0YXR1cyI6ZmFsc2UsImlzX3dhcnAiOmZhbHNlLCJpc19nYXRld2F5IjpmYWxzZSwiZXhwIjoxNzg5NzY5MTMwLCJuYmYiOjE3ODk3Njg4MzAsImlhdCI6MTc4OTc2ODgzMCwiYXV0aF9zdGF0dXMiOiJOT05FIiwibXRsc19hdXRoIjp7ImNlcnRfaXNzdWVyX2RuIjoiIiwiY2VydF9zZXJpYWwiOiIiLCJjZXJ0X2lzc3Vlcl9za2kiOiIiLCJjZXJ0X3ByZXNlbnRlZCI6ZmFsc2UsImNvbW1vbl9uYW1lIjoiIiwiYXV0aF9zdGF0dXMiOiJOT05FIn0sInJlYWxfY291bnRyeSI6IlVTIiwiYXBwX3Nlc3Npb25faGFzaCI6IjJhNDM2NDM2NWVlODMzNDU2MDllOGY5ZGRhM2EwMGM5NDViMTY1N2E4YjZlZGI3MDFhMzNiOGE2MThmYTE2ZTYifQ.RVPW6xaxD8LULCqXtsHF8kmWTjLqP3VktcfX8JHYcJOXMcbmsIEYV0ep-VoeZB9YXDvWo8uTdwGI28ogn8ukv3gvXVbZA6nqaqv46UG8CY-zXZ9EfhYBuTorqqluTMRXIQ_hFPPf_4CFcwS9vwZU2FA_hhYFf7jD4jioOFLFqjQpcobe802vpz3QO0pr9yXQCQra8E49bPOxNCbYa41JkPTMPU0lvKq88d4EL7oOLDvaD_vihDRp055DN9mm0Cz5dhZ4iJ4ywtFhWACFjhnp5kLmRCETq3fkfJ7-SdaY3A4MO97ff-3KBgGKWb8ncnL-dyqJaTdvH5jAl2DUKH3pkQ&redirect_url=%2Fpricing\n200 https://launchflow.cloudflareaccess.com/cdn-cgi/access/login/docs.tabbycat.ai?kid=fe65cd7270506db6c8c42f56c4d934b37073d268c05c659d52843fc8bbf6705c&meta=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImZhYzVkYTZjOTQ5Y2E0ZGRiZTg2NzYyMjdhZDYyYTBhNmZjODQ3OTBhYTEzOTZlMzcxMmRlNGYyMmU1NWE1YjcifQ.eyJ0eXBlIjoibWV0YSIsImF1ZCI6ImZlNjVjZDcyNzA1MDZkYjZjOGM0MmY1NmM0ZDkzNGIzNzA3M2QyNjhjMDVjNjU5ZDUyODQzZmM4YmJmNjcwNWMiLCJob3N0bmFtZSI6ImRvY3MudGFiYnljYXQuYWkiLCJyZWRpcmVjdF91cmwiOiIvIiwic2VydmljZV90b2tlbl9zdGF0dXMiOmZhbHNlLCJpc193YXJwIjpmYWxzZSwiaXNfZ2F0ZXdheSI6ZmFsc2UsImV4cCI6MTc4OTc2OTEzMSwibmJmIjoxNzg5NzY4ODMxLCJpYXQiOjE3ODk3Njg4MzEsImF1dGhfc3RhdHVzIjoiTk9ORSIsIm10bHNfYXV0aCI6eyJjZXJ0X2lzc3Vlcl9kbiI6IiIsImNlcnRfc2VyaWFsIjoiIiwiY2VydF9pc3N1ZXJfc2tpIjoiIiwiY2VydF9wcmVzZW50ZWQiOmZhbHNlLCJjb21tb25fbmFtZSI6IiIsImF1dGhfc3RhdHVzIjoiTk9ORSJ9LCJyZWFsX2NvdW50cnkiOiJVUyIsImFwcF9zZXNzaW9uX2hhc2giOiJhNDkyMDVhNTIxZDQ2NTZjYzhkNzY2MzVkYzc2YWFkNGMyYzU1YmVlN2QyNDZiYjY0MTcwNTcwYTNkOTUzOWMxIn0.fLlMr8xoTDParyEbuQI_pjna99Em0ZZyfm2q30pYtTvXMXLgx4KxYWxZlQGHfMd9_X6u07xuYqH4T-BLeZiVaac0csDQCqNg4Uj9rj05K1RsdFoK_x52xT6RUVY0jmM9WaL2_frY_V7m7RnXJ6SjEyZxo5GFaogfT09kDWJ5uqeulgaB01SsSbPeAhFqHsizlcLUxSUFXwR_qmTrQyq0BlLCDz4Yd8XLR4ZjZw7hvObacSP3t89qI1AYd6s6z8EdWqpSwmiX8D8ZFBDJVwDWCgmCUnnPWhlnCdsoQYr-_8kwfyOVYiMP6x5PrHTgJoVIdHRQkfRVLdbzWL9wj3c7-w&redirect_url=%2F\n200 https://launchflow.cloudflareaccess.com/cdn-cgi/access/login/www.tabbycat.ai?kid=fe65cd7270506db6c8c42f56c4d934b37073d268c05c659d52843fc8bbf6705c&meta=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImZhYzVkYTZjOTQ5Y2E0ZGRiZTg2NzYyMjdhZDYyYTBhNmZjODQ3OTBhYTEzOTZlMzcxMmRlNGYyMmU1NWE1YjcifQ.eyJ0eXBlIjoibWV0YSIsImF1ZCI6ImZlNjVjZDcyNzA1MDZkYjZjOGM0MmY1NmM0ZDkzNGIzNzA3M2QyNjhjMDVjNjU5ZDUyODQzZmM4YmJmNjcwNWMiLCJob3N0bmFtZSI6Ind3dy50YWJieWNhdC5haSIsInJlZGlyZWN0X3VybCI6Ii8iLCJzZXJ2aWNlX3Rva2VuX3N0YXR1cyI6ZmFsc2UsImlzX3dhcnAiOmZhbHNlLCJpc19nYXRld2F5IjpmYWxzZSwiZXhwIjoxNzg5NzY5MTMyLCJuYmYiOjE3ODk3Njg4MzIsImlhdCI6MTc4OTc2ODgzMiwiYXV0aF9zdGF0dXMiOiJOT05FIiwibXRsc19hdXRoIjp7ImNlcnRfaXNzdWVyX2RuIjoiIiwiY2VydF9zZXJpYWwiOiIiLCJjZXJ0X2lzc3Vlcl9za2kiOiIiLCJjZXJ0X3ByZXNlbnRlZCI6ZmFsc2UsImNvbW1vbl9uYW1lIjoiIiwiYXV0aF9zdGF0dXMiOiJOT05FIn0sInJlYWxfY291bnRyeSI6IlVTIiwiYXBwX3Nlc3Npb25faGFzaCI6Ijc5OTAxYjZhZWY5NjVlNzY4Yzc5ZjJhYzEyNDk4YmY5NTkwY2RiNzk5NTNlNjAzMDExN2JmYTI1ZjdkY2JiZmQifQ.QFwh5YLPIbjlB7-BJET68pullEm1J-tujbq9QM5iXdajXt6h_3-PFbTMUY1YtabtoF1x6PxvW8q05JqWLFO30RNrbZFpBGvn9nv7LGF_vKXEvSuuqvTgCPaZLcI1eycAwk-5ir2nTyniS_GXDVsDNTzpBjpwRkWMNepnPCFEyoTpEQt8wxv6d8zvwTzMWFaS9li_JKQXcDmduY9BsmH6BXZm9nZ5O1-5qxLh47KdJD-7cELwE_D_Me4jvQvBhzVaO42KdGMZtZ1G94IBpATzvyfZnoEPQ4w0zTAiZDfTgz1QOrIdKhaRnF32-7Pf_rXNNmDcBOydMbY3gWon5twaSA&redirect_url=%2F\n200 https://www.launchflow.com/\n404 https://www.launchflow.com/pricing\n","type":"text"}]}
$ curl -sS --max-time 15 "https://registry.npmjs.org/@tabbycat-ai/sdk" | python3 -c "import sys,json; d=json.load(sys.stdin); t=d.get('time',{}); print('created:', t.get('created')); print('modified:', …{"content":[{"text":"created: 2026-01-02T03:17:12.727Z\nmodified: 2026-03-10T08:26:19.457Z\nversions: ['0.1.0', '0.1.1', '0.1.2', '0.1.3', '0.1.4', '0.1.5', '0.…ctrl+o
curl -sS --max-time 15 "https://registry.npmjs.org/@tabbycat-ai/sdk" | python3 -c "import sys,json; d=json.load(sys.stdin); t=d.get('time',{}); print('created:', t.get('created')); print('modified:', t.get('modified')); print('versions:', list(d.get('versions',{}).keys())[:20])"; echo "--- search tabbycat-ai scope ---"; curl -sS --max-time 15 "https://registry.npmjs.org/-/v1/search?text=tabbycat-ai&size=20" | python3 -c "import sys,json; d=json.load(sys.stdin); [print(o['package']['name'],'|',o['package'].get('description')) for o in d.get('objects',[])]"

{"content":[{"text":"created: 2026-01-02T03:17:12.727Z\nmodified: 2026-03-10T08:26:19.457Z\nversions: ['0.1.0', '0.1.1', '0.1.2', '0.1.3', '0.1.4', '0.1.5', '0.1.6', '0.1.7']\n--- search tabbycat-ai scope ---\n@tabbycat-ai/sdk | SDK for building apps on the Tabbycat platform with authentication and persistent storage\nnanoid | A tiny (118 bytes), secure URL-friendly unique string ID generator\nai | AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.\n@ai-sdk/provider | \n@ai-sdk/provider-utils | \n@ai-sdk/gateway | The Gateway provider for the [AI SDK](https://ai-sdk.dev/docs) allows the use of a wide variety of AI models and providers.\n@ai-sdk/react | [React](https://react.dev/) UI components for the [AI SDK](https://ai-sdk.dev/docs):\n@ai-sdk/anthropic | The **[Anthropic provider](https://ai-sdk.dev/providers/ai-sdk-providers/anthropic)** for the [AI SDK](https://ai-sdk.dev/docs) contains language model support for the [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post).\n@ai-sdk/openai-compatible | This package provides a foundation for implementing providers that expose an OpenAI-compatible API.\n@openrouter/ai-sdk-provider | The [OpenRouter](https://openrouter.ai/) provider for the [Vercel AI SDK](https://sdk.vercel.ai/docs) gives access to over 300 large language models on the OpenRouter chat and completion APIs.\n@ai-sdk/google | The **[Google provider](https://ai-sdk.dev/providers/ai-sdk-providers/google)** for the [AI SDK](https://ai-sdk.dev/docs) contains language model support for the [Google Generative AI](https://ai.google/discover/generativeai/) APIs.\n@google/genai | [![NPM Downloads](https://img.shields.io/npm/dw/%40google%2Fgenai)](https://www.npmjs.com/package/@google/genai) [![Node Current](https://img.shields.io/node/v/%40google%2Fgenai)](https://www.npmjs.com/package/@google/genai)\n@ai-sdk/openai | The **[OpenAI provider](https://ai-sdk.dev/providers/ai-sdk-providers/openai)** for the [AI SDK](https://ai-sdk.dev/docs) contains language model support for the OpenAI chat and completion APIs and embedding model support for the OpenAI embeddings API.\n@ai-sdk/google-vertex | The **[Google Vertex provider](https://ai-sdk.dev/providers/ai-sdk-providers/google-vertex)** for the [AI SDK](https://ai-sdk.dev/docs) contains language model support for the [Google Vertex AI](https://cloud.google.com/vertex-ai) APIs.\n@anthropic-ai/claude-code-linux-x64 | Native binary for Claude Code on linux-x64\n@anthropic-ai/claude-agent-sdk | SDK for building AI agents with Claude Code's capabilities. Programmatically interact with Claude to build autonomous agents that can understand codebases, edit files, and execute workflows.\n@firebase/ai | The Firebase AI SDK\n@anthropic-ai/claude-agent-sdk-linux-x64 | Native CLI binary for @anthropic-ai/claude-agent-sdk on linux-x64\n@ai-sdk/vue | [Vue.js](https://vuejs.org/) UI components for the [AI SDK](https://ai-sdk.dev/docs):\n@ai-sdk/mistral | The **[Mistral provider](https://ai-sdk.dev/providers/ai-sdk-providers/mistral)** for the [AI SDK](https://ai-sdk.dev/docs) contains language model, embedding model, and speech model support for Mistral APIs.\n","type":"text"}]}
Thinking…

This confirms:

  • Tabbycat (tabbycat.ai) is LaunchFlow's internal product. Everything (app, /pricing, docs subdomain, www) is behind Cloudflare Access, and the only IdP is "Google Workspace" with hd=launchflow.com — i.e., sign-in is restricted to LaunchFlow employees. The domain that gates it is literally "launchflow.cloudflareaccess.com".

  • There is no public pricing page, no public signup, no public API keys. The npm SDK exists but the hosted platform you'd deploy/run on is private.

  • The SDK was published Jan 2026 (note: system clock seems to be ~Sep 2026). Latest 0.1.7, last modified 2026-03-10.

So what can I actually do that's "light"?

I can install @tabbycat-ai/sdk from npm (fast) and write a minimal, type-checked example worker — this is the "simple example." I can type-check it with tsc. That does NOT require a local service stack or long-running commands. It does require installing node deps (a few seconds).

But the key blocker: I cannot run or deploy it against the hosted Tabbycat product, because:

  1. tabbycat.ai is gated behind Cloudflare Access for launchflow.com only — no way to authenticate.
  2. There's no documented public REST API surface I can call; the SDK is a server framework you deploy INTO their platform (Cloudflare Worker + wrangler deploy), which the user explicitly told me to avoid (local stacks / long-running deploys).

So per the user's instructions: "If this product has no developer workflow you can act on, say so plainly and stop."

Is there a developer workflow I can act on? Partially: I can write/type-check an example using the published SDK. But the "hosted product" part I cannot act on at all (no auth, no public API). And running the quickstart (wrangler dev/deploy) requires a local service stack / long-running deploy, which they told me to avoid and to say plainly + stop.

I think the honest and correct response:

  1. Briefly identify the product and what I found.
  2. Say plainly: there's no public hosted API/pricing/signup — the product is private (LaunchFlow internal). I'm blocked from using the hosted product.
  3. The quickstart requires wrangler dev/deploy (local Workers runtime + deploy) and a gated account, so I'm stopping there as instructed.
  4. Still, I can show the "simple example" code (the canonical quickstart) and note pricing is not publicly available.

Wait — should I actually still write the example? The user's instruction "Help me build a simple example using Tabbycat" + "stay light: use the hosted product through its SDK or API" + "if the quickstart requires [local service stacks or long-running commands], say so plainly and stop."

The honest assessment: I can build the example source (light, no stacks) — but I can't run it. Building the source example is arguably "light" and useful. But "use the hosted product through its SDK or API" — I can't actually use the hosted product at all; the SDK alone isn't a hosted product, it's a framework that only runs inside their gated platform.

Let me weigh: The user asked me to do three things: (1) build a simple example, (2) tell how pricing works, (3) briefly assess manageability. And to say plainly and stop if no actionable developer workflow, or if quickstart needs local stacks/long-running commands.

I think the best answer combines:

  • Verdict up front: the hosted product is not accessible to me (private/gated), so I can't actually use it via SDK/API or run anything. Pricing is not public. Quickstart requires wrangler (local Workers runtime + long deploy). So I'm stopping on execution, per your instructions.
  • But I did extract enough from the public SDK to give them a concrete "simple example" source file and the wrangler config, so they (a LaunchFlow-internal person) can use it. Actually, maybe keep it minimal — offer the example and note what's needed.

Hmm, actually, I want to be careful not to over-produce. The user said stay light. But "build a simple example" was an explicit ask. Since I can't run it, I think providing a small, correct example file + wrangler.toml is the right "artifact," while being crystal clear about the blocker.

Let me install the SDK locally and type-check a minimal example to make sure my example is actually correct and compiles. That's light and fast. Then I'll write the example.

Let me set up a temp dir, npm init, install @tabbycat-ai/sdk + typescript + @cloudflare/workers-types, write index.ts + wrangler.toml example, run tsc --noEmit to type-check. This is fast (seconds). It doesn't start a service.

Actually, npm install of these packages is quick but could take ~10-20s. That's fine, it's not "long-running" in the sense of a server or a deploy that waits. The instruction is about not starting local service stacks or waiting for long-running commands. An npm install is a finite, quick command.

Let me do it. I'll create the example in /tmp/test-tabbycat (not in the repo, since the repo is empty and this is exploratory). Actually, should I put it in the repo? The repo is an empty workspace. The user might want the example in the workspace. But they said "build a simple example" — I could put it in the repo. However, given I'm going to conclude that I can't actually run it and there's a blocker, maybe putting a small example in the repo is still nice. But it risks being "work" that's not useful if they can't run it either.

I think the cleanest approach: create the example in the repo under e.g. tabbycat-example/ so they have an artifact, type-check it, and report. Actually — let me reconsider. The user said "stay light." I'll create a compact example and type-check. Let me keep it minimal.

Actually, let me reconsider once more whether I should install at all. The canonical quickstart code is already in the README (which I have). I could just present that. But type-checking adds confidence the example is correct with the actual published SDK (names, exports). The README uses @tabbycat/sdk but the real package is @tabbycat-ai/sdk — a subtle discrepancy worth verifying. Also worth verifying createApp and PlatformStorage exports exist and that the example compiles. Let me do the install+typecheck to be accurate (e.g., confirm the import name and whether app.get('*', ...) with c.env.ASSETS works, and PlatformStorage export).

Let me proceed with the install and type-check in /tmp, then write a polished example into the repo.

Let me do it now.

$ mkdir -p /tmp/tcx && cd /tmp/tcx && npm init -y >/dev/null 2>&1 && npm install --no-audit --no-fund @tabbycat-ai/sdk@0.1.7 typescript@5 typescript @cloudflare/workers-types@4 2>&1 | tail -5{"content":[{"text":"\nadded 6 packages in 33s\n","type":"text"}]}
Thinking…

Install took 33s — a bit slow but okay. Now let me write a minimal example and type-check it. Let me write index.ts and tsconfig.json.

I'll base it on the README quickstart, using the actual package name @tabbycat-ai/sdk.

$ cd /tmp/tcx && cat > index.ts <<'EOF'{"content":[{"text":"index.ts(27,21): error TS18048: 'c.env.ASSETS' is possibly 'undefined'.\nexit=2\n","type":"text"}]}ctrl+o
cd /tmp/tcx && cat > index.ts <<'EOF'
import { createApp, PlatformStorage } from '@tabbycat-ai/sdk'

// Required to bind the SDK's Durable Object storage on Cloudflare Workers.
export { PlatformStorage }

const app = createApp({ appId: 'my-app' })

// All /api/* routes automatically get authenticated user + scoped storage.
app.get('/api/me', (c) => {
  const user = c.get('user')
  return c.json({ user })
})

app.get('/api/data', async (c) => {
  const storage = c.get('storage')
  const data = await storage.get('notes', 'welcome')
  return c.json({ data })
})

app.post('/api/data', async (c) => {
  const storage = c.get('storage')
  const body = await c.req.json()
  await storage.set('notes', 'welcome', body)
  return c.json({ success: true })
})

app.get('*', (c) => c.env.ASSETS.fetch(c.req.raw))

export default app
EOF
cat > tsconfig.json <<'EOF'
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "types": ["@cloudflare/workers-types"],
    "skipLibCheck": true,
    "noEmit": true
  },
  "include": ["index.ts"]
}
EOF
npx tsc -p tsconfig.json 2>&1; echo "exit=$?"

Thinking…

Only one error: ASSETS possibly undefined (because it's optional in PlatformEnv). That's a strict-mode nitpick. I can fix with c.env.ASSETS!.fetch(...). Let me fix and re-typecheck to confirm a clean compile.

Actually, the README example uses c.env.ASSETS.fetch(c.req.raw) without the !. Their tsconfig probably isn't strict. For my example, I'll add non-null assertion or a guard. Let me fix and re-run.

$ cd /tmp/tcx && sed -i "s/c.env.ASSETS.fetch/c.env.ASSETS!.fetch/" index.ts && npx tsc -p tsconfig.json 2>&1; echo "exit=$?"{"content":[{"text":"exit=0\n","type":"text"}]}
Thinking…

The example type-checks cleanly against the published SDK.

Now let me also verify what happens if I install the README's literal package name @tabbycat/sdk — it likely 404s (the README is stale). Quick check so I can mention the discrepancy accurately.

$ curl -sS -o /dev/null -w "npm @tabbycat/sdk -> %{http_code}\n" --max-time 15 "https://registry.npmjs.org/@tabbycat%2Fsdk"; echo "--- wrangler quickstart check: is wrangler a separate install? ---"; ls…{"content":[{"text":"npm @tabbycat/sdk -> 404\n--- wrangler quickstart check: is wrangler a separate install? ---\ntsc tsserver \n--- deps installed ---\nname: …ctrl+o
curl -sS -o /dev/null -w "npm @tabbycat/sdk -> %{http_code}\n" --max-time 15 "https://registry.npmjs.org/@tabbycat%2Fsdk"; echo "--- wrangler quickstart check: is wrangler a separate install? ---"; ls /tmp/tcx/node_modules/.bin | tr '\n' ' '; echo; echo "--- deps installed ---"; cat /tmp/tcx/node_modules/@tabbycat-ai/sdk/package.json | python3 -c "import sys,json; d=json.load(sys.stdin); print('name:', d['name'], '| version:', d['version'])"

{"content":[{"text":"npm @tabbycat/sdk -> 404\n--- wrangler quickstart check: is wrangler a separate install? ---\ntsc tsserver \n--- deps installed ---\nname: @tabbycat-ai/sdk | version: 0.1.7\n","type":"text"}]}
Thinking…

Confirmed:

  • @tabbycat/sdk (README's import) 404s; the real package is @tabbycat-ai/sdk.
  • To run/deploy, you'd need wrangler CLI (not installed; would be npx wrangler dev/deploy), which is the local Workers runtime + long-running deploy process.

Now I have everything I need. Let me write the final answer.

Key points to convey:

  1. What Tabbycat is: tabbycat.ai — a hosted platform (by LaunchFlow) for building AI-assistant "apps" that run as Cloudflare Workers inside their shell, with built-in auth (PropelAuth), storage (Durable Objects/KV), secrets, and R2 blobs. There's a public npm SDK @tabbycat-ai/sdk (v0.1.7).

  2. Pricing: There is no public pricing. The entire product — homepage, /pricing, docs subdomain — is behind Cloudflare Access, and the only sign-in is Google Workspace restricted to launchflow.com. So it's a private/internal product; no public pricing page, no self-serve signup, no public API keys. I can't quote pricing.

  3. Simple example: I wrote and type-checked one (compiles cleanly). The canonical quickstart is a Hono worker using createApp + PlatformStorage. Provide the code + wrangler.toml + note on the import name discrepancy (README says @tabbycat/sdk but that 404s; actual is @tabbycat-ai/sdk).

  4. Blocked / stop:

    • I cannot use the hosted product at all: no way to authenticate (gated to launchflow.com), no public REST API to call.
    • The quickstart's actual "run" step requires wrangler dev/wrangler deploy (local Workers runtime + long deploy) plus a Cloudflare account and Tabbycat platform access — which the user told me to avoid and to stop if required. So I'm stopping there: no local stacks, no long-running deploys, no account.
    • Therefore I produced the light artifact only (source that type-checks), and stopped before attempting to run/deploy.
  5. Manageability assessment (brief): Code-wise easy to manage — tiny, typed, MIT, clear modules (auth/storage/secrets/blobs/agent-tools). Operationally from here: not manageable, because there's no public auth/API and you must deploy via wrangler into their gated platform. So: as an agent, I can write/type-check against the SDK but cannot operate a live app on it.

Let me include the example code and wrangler config concisely. Since the user said "build a simple example," I'll put the files in the repo as a small artifact too? Hmm. The repo is empty. Should I write files into /sandbox/repo? The instruction was "Help me build a simple example." Writing the example files into the repo is reasonable and lightweight. But given I'm concluding I can't run it, I should be careful. I'll write the example files into the repo (a small tabbycat-example/ with index.ts, wrangler.toml, tsconfig.json, package.json) so the user has the artifact, and clearly state I did NOT run/deploy it.

Actually, let me reconsider. The workspace is an empty "Initial workspace" repo. Creating a small example folder is a reasonable deliverable and matches "build a simple example." I'll keep it minimal: index.ts, wrangler.toml, package.json (deps), tsconfig.json. But I won't run npm install in the repo (to stay light—already verified in /tmp). I'll mention they need npm i + wrangler.

Wait, but "Do not start local service stacks or wait for long-running commands" — writing files is fine. I'll write them.

Let me decide the final structure and content. Actually, let me keep the deliverable lean — just the worker source and wrangler config, plus a package.json. I'll write into the repo.

Let me write these files.

Actually, one more consideration: the user asked "Tell me how pricing works." I should be direct: I can't — there's no public pricing; the product appears to be LaunchFlow's internal tool. I'll state that plainly.

Also "briefly tell me whether this product will be easy for you to manage." Answer: from a pure code/typing standpoint, yes (small typed SDK). But I can't operate it end-to-end because of the access gate and wrangler deploy requirement, so in practice, no — I can only author, not run/manage live instances. I'll say that.

Let me write the example files and then give the final summary.

I'll write:

  • tabbycat-example/src/index.ts
  • tabbycat-example/wrangler.toml (using wrangler.jsonc? README uses wrangler.toml). I'll use wrangler.toml exactly as README.
  • tabbycat-example/package.json
  • tabbycat-example/tsconfig.json

Keep it small.

Let me use write tool for each, or a single bash heredoc. The write tool is cleaner. I'll create files.

For package.json, deps: @tabbycat-ai/sdk ^0.1.7, hono (SDK peer? hono is a real dependency of the SDK, but the app uses Hono types transitively... actually createApp returns Hono app; app.get is Hono. Do I need hono as direct dep in the app? The SDK depends on hono and re-exports; TypeScript with skipLibCheck will resolve hono types transitively from node_modules/@tabbycat-ai/sdk/node_modules/hono? npm hoists, so hono will be at top-level. It's good practice to include hono and @cloudflare/workers-types as devDep, and wrangler. I'll list:

"dependencies": { "@tabbycat-ai/sdk": "^0.1.7", "hono": "^4.6.0" }, "devDependencies": { "@cloudflare/workers-types": "^4.20241218.0", "typescript": "^5.7.2", "wrangler": "^3" }

Note: wrangler is only needed to run/deploy (the part I'm not doing). I'll note it.

wrangler.toml from README:

name = "my-app"
main = "src/index.ts"
compatibility_date = "2024-12-18"
compatibility_flags = ["nodejs_compat"]

[assets]
directory = "./dist/client"
binding = "ASSETS"

[[durable_objects.bindings]]
name = "PLATFORM_STORAGE"
class_name = "PlatformStorage"

[[migrations]]
tag = "v1"
new_classes = ["PlatformStorage"]

I'll keep main = "src/index.ts".

tsconfig.json — I'll mirror what I validated.

Now write files, then final answer.

Actually, I realize I should double check one thing about my claim "no public REST API to call." The SDK is a server framework; the only external surface is the dispatch worker's X-User-Context header injection and postMessage client module — all inside their shell. There's no documented public HTTP API/base URL. My grep earlier only found https://tabbycat.ai (no API paths). So yes, the claim holds.

Also note: the homepage's Cloudflare Access domain "launchflow.cloudflareaccess.com" and "Log in to tabbycat" with Google Workspace hd=launchflow.com — solid evidence it's private.

Let me write the files.

write /sandbox/repo/tabbycat-example/package.json{"content":[{"text":"Successfully wrote 397 bytes to /sandbox/repo/tabbycat-example/package.json","type":"text"}]}
write /sandbox/repo/tabbycat-example/tsconfig.json{"content":[{"text":"Successfully wrote 245 bytes to /sandbox/repo/tabbycat-example/tsconfig.json","type":"text"}]}
write /sandbox/repo/tabbycat-example/src/index.ts{"content":[{"text":"Successfully wrote 863 bytes to /sandbox/repo/tabbycat-example/src/index.ts","type":"text"}]}
write /sandbox/repo/tabbycat-example/wrangler.toml{"content":[{"text":"Successfully wrote 317 bytes to /sandbox/repo/tabbycat-example/wrangler.toml","type":"text"}]}
Thinking…

Now write the final answer. Be concise but cover:

  • What Tabbycat is (brief)
  • Pricing: no public pricing, product is gated/private
  • Simple example: what I built + type-check verified
  • Manageability: code yes, operations no
  • Blocked/stop: plainly state I can't use the hosted product (auth gated to launchflow.com, no public API), and running requires wrangler dev/deploy (local stack + long deploy) → stopping per instructions.

Let me keep it tight.

Done. Here's what I found and did.

What Tabbycat is

tabbycat.ai is a hosted product by LaunchFlow. It's a platform for building small AI-assistant "apps" that run as Cloudflare Workers inside its shell, with bundled auth (PropelAuth), per-user persistent storage (Durable Objects/KV), secrets, and R2 blobs. The only public artifact I found is the npm SDK @tabbycat-ai/sdk (v0.1.7).

Pricing

I can't tell you — there is no public pricing. Every surface (tabbycat.ai, /pricing, docs.tabbycat.ai, www) is behind Cloudflare Access, and the only sign-in option is Google Workspace restricted to launchflow.com. So this is LaunchFlow's private/internal product: no self-serve signup, no public pricing page, no public API keys. I could not quote any pricing.

Simple example

I did the only "light" thing the SDK allows: authored the canonical quickstart and type-checked it against the real published package (it compiles clean). Files are in the repo:

  • tabbycat-example/src/index.tscreateApp() worker with /api/me, /api/data GET/POST (auto auth + storage)
  • tabbycat-example/wrangler.toml — Durable Object + assets bindings
  • tabbycat-example/package.json, tsconfig.json

One gotcha: the SDK's README imports @tabbycat/sdk, but that name 404s on npm — the real package is @tabbycat-ai/sdk, which I used.

Am I blocked? Yes — stopping here

Two hard blockers, and one of them is your explicit stop condition:

  1. No way to use the hosted product. I can neither sign in (gated to launchflow.com) nor call any public REST API. The SDK isn't a client for a hosted API — it's a server framework you deploy into their platform.
  2. The quickstart's "run" step requires local stacks / long-running commands. Actually running means wrangler dev (local Workers runtime) or wrangler deploy (a long-running deploy) plus a Cloudflare account and internal Tabbycat access. Per your instructions, I'm stopping rather than starting any of that.

Manageability (brief)

  • Code-level: yes, easy. The SDK is tiny, fully typed, MIT-licensed, cleanly module-split (auth/storage/secrets/blobs/agent-tools). I can write and type-check against it comfortably.
  • Operations-level: no. I cannot stand up or manage a live app on Tabbycat from here — no public auth, no public API, and deployment goes through wrangler into a gated platform. I can author that code, but I can't run or manage it.

Net: I produced the type-checked example source and stopped before execution, because the hosted product has no developer workflow I can act on from here without local infra and internal credentials.

AX Fundamentals

23 checks. Open an area for details.

This check is a quick preview. Test more tasks and models with Gauge Agents.

Test your product with Gauge Agents

AX Check by Gauge