Local-first Pocket-like CLI built with TypeScript and SQLite.
- CLI reference:
docs/CLI_REFERENCE.md
Node 22.x is required for local development (see .nvmrc).
nvm use
pnpm install
cp .env.example .env
pnpm run setupOptional (recommended once per machine) to honor the pinned pnpm version from packageManager:
corepack enablepnpm run setup now also installs Playwright Chromium for X/Twitter status/<id> extraction support.
If you need to re-download the browser later (for example after Playwright updates or cache cleanup), run:
pnpm exec playwright install chromiumRun the local PWA frontend + API with a single command:
pnpm run webThis script builds the TypeScript backend and React frontend, then starts:
- API:
http://127.0.0.1:4173 - PWA:
http://127.0.0.1:5173
You can configure these via .env:
STASH_WEB_HOST=127.0.0.1
STASH_API_PORT=4173
STASH_PWA_PORT=5173Or override at runtime:
stash web --host 127.0.0.1 --api-port 4173 --pwa-port 5173If you see a better-sqlite3 binding error on first run, or after switching Node versions:
nvm use
pnpm approve-builds
pnpm rebuild better-sqlite3
pnpm run setupIf pnpm rebuild better-sqlite3 does not fix it, reinstall dependencies under the active Node 22 runtime:
nvm use
pnpm install
pnpm run setuppnpm run lint
pnpm run format:check
pnpm run checkTo apply formatting and safe lint fixes:
pnpm run format
pnpm run lint:fix- Install the
Biomeextension (biomejs.biome). - This workspace is configured for auto-fix on save (format + fix-all + organize imports).
- After switching Node versions, run
nvm usein the integrated terminal before using debug/dev commands so native modules (for examplebetter-sqlite3) match the active runtime. - Use CLI checks to verify parity:
pnpm run lintpnpm run format:check
For local development (before publishing):
pnpm install
pnpm run setup
pnpm link --global
stash --versionRemove global dev link:
pnpm unlink --global stashAfter publishing to npm:
npm install -g stash
stash --versionDefault database path is ~/.stash/stash.db. Override with --db-path.
For local development scripts (pnpm run dev, pnpm run setup, pnpm run db:migrate, pnpm run db:doctor, pnpm run start), .env is auto-loaded. The template sets:
STASH_DB_PATH=.db/stash.dbPath precedence is unchanged:
--db-path <path>STASH_DB_PATH~/.stash/stash.dbfallback
Most CLI commands auto-run pending migrations, so manual migration is usually not needed.
pnpm run db:migrate
pnpm run db:doctorstash db migrate --json
stash db doctor --json- Save URLs with automatic content + thumbnail extraction
- Public X/Twitter
status/<id>extraction via Playwright Chromium (headless, public-only) - Extract or re-extract content for existing items
- Organize with tags
- Mark items as read/unread
- Generate TTS audio from extracted article content
- Play previously generated TTS audio in the web UI
- Machine-friendly JSON output
- Local SQLite storage
stash save https://example.com --tag typescript --tag cli
stash save https://example.com --no-extract # skip content extraction
stash list --status unread --tag typescript --tag-mode all
stash list --json
stash tags list --json
stash tag add 1 productivity
stash tag rm 1 productivity
stash mark read 1
stash mark unread 1
stash extract 1 # extract content + thumbnail metadata for an existing item
stash extract 1 --force # re-extract even if content exists
stash tts 1 --json
stash tts 1 --wait --json
stash tts status 12 --json
stash tts doctor --json
stash jobs worker --once --jsonstashautomatically uses a Playwright Chromium-based extractor for public X/Twitter status URLs (x.com/.../status/<id>andtwitter.com/.../status/<id>).- The extractor renders the page in a headless browser and parses the rendered DOM for post/article text and thumbnail metadata.
- Scope is public content only and single bookmarked status only (no thread/conversation expansion in phase 1).
- X URLs do not fall back to the generic non-JS extractor to avoid storing partial/truncated content.
- If X extraction fails (missing Playwright/Chromium, timeout, layout changes, blocked page),
stash savestill succeeds and skips extraction. stash extract <id>returnsEXTRACTION_FAILEDand surfaces an actionable setup/rendering error message.
Setup:
pnpm exec playwright install chromiumLinux note (if system deps are missing):
pnpm exec playwright install-deps chromium- Command:
stash tts <id> [--voice <name>] [--format mp3|wav] [--wait] [--json] - Status command:
stash tts status <jobId> [--json] - Health check command:
stash tts doctor [--json] - Worker command:
stash jobs worker [--poll-ms <n>] [--once] [--json] - Provider: Coqui TTS (local, high quality)
- Default voice:
tts_models/en/vctk/vits|p241 - Setup: Python 3.11 env +
pip install TTS+brew install espeak-ng - CLI discovery: auto-detects binaries from
PATHwith optional env overrides (STASH_COQUI_TTS_CLI,STASH_ESPEAK_CLI,STASH_FFMPEG_CLI, etc.) - See
docs/COQUI_SETUP.mdfor full setup instructions - See
docs/TTS_MACOS.mdfor all TTS options - Default queue polling interval:
1500ms - Async queue model:
stash tts <id>enqueues job and returnsjob_idimmediately.stash tts <id> --waitwaits for terminal status and prints generated output metadata.stash tts doctorchecks required local binaries and Coqui CLI flag compatibility (--text_file,--progress_bar).stash jobs workerprocesses queued jobs (--onceis test/dev friendly).
- Worker audio output directory precedence:
STASH_AUDIO_DIR~/.stash/audio
- Web/API persistence model for playback is latest-only per item (
item_audiotable, no backfill for old files). - Web/API
POST /api/items/:id/ttsresponse includes:jobpayload (queued|running|succeeded|failed)poll_url: /api/tts-jobs/<job_id>poll_interval_ms
- Web/API job endpoints:
GET /api/tts-jobs/:idGET /api/items/:id/tts-jobs?limit=<n>&offset=<n>
GET /api/audio/:fileNameis inline-playable by default and attachment when?download=1is set.
- JSON mode via
--json - Deterministic list order:
created_at DESC, id DESC - Pagination via
--limitand--offset - Tag filtering via repeated
--tagand--tag-mode any|all - Web/API item payloads include:
thumbnail_url(string | null)has_extracted_content(boolean)tts_audio(null | { file_name, format, provider, voice, bytes, generated_at })
- TypeScript
- Commander (CLI)
- SQLite (
better-sqlite3) - dotenv (local dev script env loading)
- Drizzle ORM + Drizzle Kit
- Mozilla Readability + linkedom (content extraction)
- React + Vite (web frontend)
- Material UI (web UI component system)
- ✅ Save URLs with titles and tags
- ✅ List items with filters (status, tags)
- ✅ Tag management (add, remove, list)
- ✅ Mark items as read/unread
- ✅ JSON output mode for automation
- ✅ Basic content extraction
- ✅ Async TTS job queue (Coqui-first) with worker command
- ✅ Web playback for latest generated item audio
- 🔍 Full-text search - Search across article content
- 📄 PDF export - Save articles for offline reading
- 🗄️ Archive & delete commands
- 📂 Import/export functionality
- 🔗 Open command for quick access
- 📊 Enhanced metadata extraction
- Migrations are SQL files in
drizzle/. - Schema source is
packages/core/src/db/schema.ts. - Current schema includes
items,tags,item_tags,notes,item_audio, andtts_jobs.