From 45745e851d908410c92a90275014d49f27c6dbb8 Mon Sep 17 00:00:00 2001 From: Eduardo Borjas Date: Tue, 2 Jun 2026 12:16:14 -0600 Subject: [PATCH 1/3] docs: apply GitHub repo growth standard polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix CI badge URL: eduardoborjas → aedneth (was pointing at wrong username) - Add npm version badge to README header - Add "Why magnus?" section: positions vs raw yt-dlp, browser extensions, and manual Apify runs with outcome-first reasoning - Add Roadmap section (v0.1.0 checked, v0.2.0–v1.0.0 milestones) - Promote CHANGELOG [Unreleased] → [0.1.0] 2026-06-02 with Fixed entries for the two CI bugs resolved on the branch - Fix wrong GitHub username in CONTRIBUTING.md clone URL and .github/ISSUE_TEMPLATE/config.yml security policy link Co-Authored-By: Claude Sonnet 4.6 --- .github/ISSUE_TEMPLATE/config.yml | 2 +- CHANGELOG.md | 12 ++++++++++-- CONTRIBUTING.md | 2 +- README.md | 20 +++++++++++++++++++- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 2c9cd68..e3707d2 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: false contact_links: - name: Security vulnerability - url: https://github.com/eduardoborjas/magnus-cli/security/policy + url: https://github.com/aedneth/magnus-cli/security/policy about: Please report security issues privately per SECURITY.md, not as public issues. - name: Commercial license inquiry url: mailto:eduardoa.borjas@gmail.com diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e3dcd2..9fb0bd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,11 @@ All notable changes to this project are documented here. The format is based on ## [Unreleased] +## [0.1.0] — 2026-06-02 + ### Added -- Initial project scaffold: TypeScript + ESM, `tsup` bundler, Vitest, ESLint/Prettier, CI. +- Initial project scaffold: TypeScript + ESM, `tsup` bundler, Vitest, ESLint/Prettier, CI matrix (ubuntu/macos/windows × node 20/22). - Download core: ordered platform detection (`music.youtube.com` before `youtube.com`), audio/video flag builders, configurable output-folder map, `yt-dlp` subprocess wrapper. - Commands: `download` (default), `audio`, `batch`, `capture`, `stats`, `config`, @@ -20,4 +22,10 @@ All notable changes to this project are documented here. The format is based on never-store-expiring-MP4 rule. - Dual licensing: AGPL-3.0 + commercial. -[Unreleased]: https://github.com/eduardoborjas/magnus-cli/commits/main +### Fixed + +- CI workflow: moved `npm run build` before `npm test` so `dist/cli.js` exists when agent-mode integration tests run. +- CLI stdout flushing: replaced `process.exit()` with `process.exitCode` assignment to prevent pipe-buffer truncation on macOS + Node 20 for large JSON outputs (e.g. `magnus manifest --json`). + +[Unreleased]: https://github.com/aedneth/magnus-cli/compare/v0.1.0...HEAD +[0.1.0]: https://github.com/aedneth/magnus-cli/releases/tag/v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39a4776..164872b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ Thanks for your interest in improving Magnus! This guide covers the basics. ## Development setup ```bash -git clone https://github.com/eduardoborjas/magnus-cli.git +git clone https://github.com/aedneth/magnus-cli.git cd magnus-cli npm install npm run build diff --git a/README.md b/README.md index de63d12..7cc85d4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ **Universal `yt-dlp` media downloader + Apify-driven social-capture pipeline — built for humans *and* terminal AI agents.** -[![CI](https://github.com/eduardoborjas/magnus-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/eduardoborjas/magnus-cli/actions/workflows/ci.yml) +[![CI](https://github.com/aedneth/magnus-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/aedneth/magnus-cli/actions/workflows/ci.yml) +[![npm](https://img.shields.io/npm/v/magnus.svg)](https://www.npmjs.com/package/magnus) [![License: AGPL-3.0](https://img.shields.io/badge/license-AGPL--3.0%20%2B%20Commercial-blue.svg)](#license) [![Node](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org) @@ -67,6 +68,14 @@ magnus config list # show resolved config > **Tip:** always quote URLs containing `&`, or your shell will background the command. +## Why magnus? + +**vs raw `yt-dlp`:** Every platform needs different flags, different output paths, and different post-processing. Magnus encodes those rules once — `music.youtube.com` always goes to `~/Music/YT Music/`, YouTube video always goes to `~/Videos/Social Media/YouTube/`, TikTok always strips the watermark — so you type one command and get the right file, in the right place, every time. It also wraps every operation in a clean JSON envelope, making `yt-dlp`-backed workflows scriptable and CI-safe. + +**vs browser extensions:** Browser extensions are one-at-a-time, TTY-dependent, and invisible to scripts and agents. Magnus runs headless, accepts URLs from stdin, and pipelines into `jq`, shell scripts, Claude Code, and CI jobs. `cat urls.txt | magnus batch - --json` is a valid production command. + +**vs manual Apify runs:** Apify's web UI has no spend controls, no resume capability, and returns raw JSON with no rendering. Magnus adds a `$40-abort / $50-hard-cap` canary gate, per-platform SQLite resume queues (restart any run from where it stopped), and renders results directly into CKIS/Obsidian Markdown — one command from saved posts to vault notes. + ## Configuration Config lives under XDG paths (`~/.config/magnus/` on Linux): @@ -80,6 +89,15 @@ magnus config path # print the resolved config directory magnus config set capture.budgetHardCapUsd 50 ``` +## Roadmap + +- [x] **v0.1.0** — download core (ordered platform detection, audio/video flag sets, output-folder map), Apify capture pipeline (SQLite queues, canary, spend caps, CKIS Markdown), full agent-native layer (`--json`, `--yes`, manifest, MCP server), dual AGPL-3.0 + commercial license +- [ ] **v0.2.0** — interactive Ink v5 TUI: live progress bars, download spinners, capture cost dashboard +- [ ] **v0.3.0** — `magnus doctor` full auto-fix mode, `npm publish` pipeline, first public npm release +- [ ] **v0.4.0** — playlist support, concurrent download pool, per-item retry with back-off +- [ ] **v0.5.0** — capture queue management TUI, per-actor Apify cost breakdown, queue drain/pause commands +- [ ] **v1.0.0** — stable public API, full docs site, public launch + ## License Magnus is **dual-licensed**: From db3f65a96b7fd1f023b91afba69ef5af53228415 Mon Sep 17 00:00:00 2001 From: Eduardo Borjas Date: Tue, 2 Jun 2026 12:50:35 -0600 Subject: [PATCH 2/3] fix: apply Opus architecture audit findings (security, observability, cost) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C1 — queue.ts: narrow table params to QueueTable union type; use bound LIMIT parameter instead of string-interpolated Number(limit) to close the SQL concatenation pattern in an agent-callable tool. M1 — capture runners: coerce --budget-cap/--limit from string (Commander returns option values as strings; runners were casting string as number, making assertCanaryAcceptable compare number > string via JS coercion). M2 — capture runners: wire --limit across all three runners (instagram, tiktok, youtube); the flag existed in the manifest but was never read. M3 — capture runners: pass projected next-batch cost to assertWithinBudget before each runActor call so the hard cap fires pre-charge, not post- charge. Track per-URL canary rate and multiply by batch size. M4 — youtube.ts: rethrow non-ENOENT yt-dlp startup errors as MagnusError(Network) instead of silently continuing into a render loop that finds no info.json and exits 0 with 0 notes processed. M5 — db.ts: key openDb cache by resolved file path (was module-global singleton, so --config dir isolation did not affect the DB handle). m1 — redact.ts: add Bearer token and generic key=VALUE patterns alongside the existing Apify token regex. m2 — apify.ts: warn to stderr when Apify cost fields are absent (schema drift) instead of silently recording $0; budget guard would not fire. m3 — instagram.ts: add shortCode-based fallback lookup for dataset items (URL normalization mismatch on query-string variants like ?igshid=). m4 — config.ts: remove duplicate ctx.log.info in config list that wrote the config JSON to both stderr and stdout. m5 — cli.ts: guard injectDefaultCommand against argv.length < 2; replace non-null assertions with typed casts. s1 — remove dead ink, react, @types/react dependencies (zero imports). s5 — cli.ts: use process.exitCode in main().catch for consistency with the execute() drain pattern; avoids premature stream teardown. Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 645 ----------------------------------- package.json | 5 +- src/capture/apify.ts | 11 +- src/capture/db.ts | 9 +- src/capture/queue.ts | 22 +- src/capture/run/instagram.ts | 31 +- src/capture/run/tiktok.ts | 19 +- src/capture/run/youtube.ts | 8 +- src/cli.ts | 5 +- src/commands/config.ts | 1 - src/util/redact.ts | 7 +- 11 files changed, 86 insertions(+), 677 deletions(-) diff --git a/package-lock.json b/package-lock.json index adff97a..220a7b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,7 @@ "commander": "^13.0.0", "env-paths": "^3.0.0", "execa": "^9.4.0", - "ink": "^5.0.1", "picocolors": "^1.1.0", - "react": "^18.3.1", "yaml": "^2.5.1", "zod": "^3.23.8" }, @@ -29,7 +27,6 @@ "@eslint/js": "^9.10.0", "@types/better-sqlite3": "^7.6.11", "@types/node": "^20.16.0", - "@types/react": "^18.3.5", "eslint": "^9.10.0", "prettier": "^3.3.3", "tsup": "^8.2.4", @@ -41,31 +38,6 @@ "node": ">=20" } }, - "node_modules/@alcalzone/ansi-tokenize": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.3.tgz", - "integrity": "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=14.13.1" - } - }, - "node_modules/@alcalzone/ansi-tokenize/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@apify/consts": { "version": "2.53.2", "resolved": "https://registry.npmjs.org/@apify/consts/-/consts-2.53.2.tgz", @@ -1707,24 +1679,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.29", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz", - "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.60.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", @@ -2224,21 +2178,6 @@ "node": ">=6" } }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2346,18 +2285,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/auto-bind": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", - "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/axios": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", @@ -2668,89 +2595,6 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "license": "ISC" }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", - "license": "MIT", - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cli-truncate/node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/code-excerpt": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", - "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", - "license": "MIT", - "dependencies": { - "convert-to-spaces": "^2.0.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2838,15 +2682,6 @@ "node": ">= 0.6" } }, - "node_modules/convert-to-spaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", - "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -2896,13 +2731,6 @@ "node": ">= 8" } }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, - "license": "MIT" - }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", @@ -3069,12 +2897,6 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -3119,18 +2941,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -3183,16 +2993,6 @@ "node": ">= 0.4" } }, - "node_modules/es-toolkit": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.0.tgz", - "integrity": "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==", - "license": "MIT", - "workspaces": [ - "docs", - "benchmarks" - ] - }, "node_modules/esbuild": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", @@ -3993,18 +3793,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -4359,18 +4147,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -4383,84 +4159,6 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, - "node_modules/ink": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz", - "integrity": "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg==", - "license": "MIT", - "dependencies": { - "@alcalzone/ansi-tokenize": "^0.1.3", - "ansi-escapes": "^7.0.0", - "ansi-styles": "^6.2.1", - "auto-bind": "^5.0.1", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "cli-cursor": "^4.0.0", - "cli-truncate": "^4.0.0", - "code-excerpt": "^4.0.0", - "es-toolkit": "^1.22.0", - "indent-string": "^5.0.0", - "is-in-ci": "^1.0.0", - "patch-console": "^2.0.0", - "react-reconciler": "^0.29.0", - "scheduler": "^0.23.0", - "signal-exit": "^3.0.7", - "slice-ansi": "^7.1.0", - "stack-utils": "^2.0.6", - "string-width": "^7.2.0", - "type-fest": "^4.27.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0", - "ws": "^8.18.0", - "yoga-layout": "~3.2.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "react": ">=18.0.0", - "react-devtools-core": "^4.19.1" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react-devtools-core": { - "optional": true - } - } - }, - "node_modules/ink/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ink/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ink/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, "node_modules/ip-address": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", @@ -4489,18 +4187,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -4514,21 +4200,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-in-ci": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", - "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", - "license": "MIT", - "bin": { - "is-in-ci": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -4638,12 +4309,6 @@ "node": ">=10" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -4781,18 +4446,6 @@ "dev": true, "license": "MIT" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/loupe": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", @@ -4898,15 +4551,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -5120,21 +4764,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5339,15 +4968,6 @@ "node": ">= 0.8" } }, - "node_modules/patch-console": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", - "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5794,34 +5414,6 @@ "node": ">=0.10.0" } }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-reconciler": { - "version": "0.29.2", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", - "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, "node_modules/read-yaml-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", @@ -5909,28 +5501,6 @@ "node": ">=8" } }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -6069,15 +5639,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, "node_modules/semver": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", @@ -6308,49 +5869,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -6436,27 +5954,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -6489,50 +5986,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -7714,21 +7167,6 @@ "node": ">=8" } }, - "node_modules/widest-line": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -7739,89 +7177,12 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/yaml": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", @@ -7862,12 +7223,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yoga-layout": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", - "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", - "license": "MIT" - }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", diff --git a/package.json b/package.json index 37e0496..39e51f8 100644 --- a/package.json +++ b/package.json @@ -63,9 +63,7 @@ "commander": "^13.0.0", "env-paths": "^3.0.0", "execa": "^9.4.0", - "ink": "^5.0.1", "picocolors": "^1.1.0", - "react": "^18.3.1", "yaml": "^2.5.1", "zod": "^3.23.8" }, @@ -74,12 +72,11 @@ "@eslint/js": "^9.10.0", "@types/better-sqlite3": "^7.6.11", "@types/node": "^20.16.0", - "@types/react": "^18.3.5", "eslint": "^9.10.0", - "typescript-eslint": "^8.5.0", "prettier": "^3.3.3", "tsup": "^8.2.4", "typescript": "^5.5.4", + "typescript-eslint": "^8.5.0", "vitest": "^2.1.1" } } diff --git a/src/capture/apify.ts b/src/capture/apify.ts index 2c3f7f3..0ab9400 100644 --- a/src/capture/apify.ts +++ b/src/capture/apify.ts @@ -54,12 +54,19 @@ export async function runActor( } const { items } = await client.dataset(run.defaultDatasetId).listItems(); const stats = (run as unknown as { usageTotalUsd?: number; stats?: { costInUsd?: number } }); - const costUsd = stats.usageTotalUsd ?? stats.stats?.costInUsd ?? 0; + const costUsd = stats.usageTotalUsd ?? stats.stats?.costInUsd; + if (costUsd === undefined) { + // Apify renamed or removed the cost fields. Recording $0 means the budget + // guard will not fire — investigate and update the field names above. + process.stderr.write( + `[magnus] warn: Apify cost fields absent on run ${run.id}; recording $0. Budget guard may not trip.\n`, + ); + } return { status: run.status, datasetId: run.defaultDatasetId, runId: run.id, - costUsd, + costUsd: costUsd ?? 0, items: items as Array>, }; } diff --git a/src/capture/db.ts b/src/capture/db.ts index 96d6102..9f52070 100644 --- a/src/capture/db.ts +++ b/src/capture/db.ts @@ -5,16 +5,17 @@ import { dbFile } from '../config/paths.js'; export type DB = Database.Database; -let cached: DB | null = null; +const dbCache = new Map(); -/** Open (and memoize) the magnus SQLite database, creating all tables. */ +/** Open (and memoize by path) the magnus SQLite database, creating all tables. */ export function openDb(file = dbFile()): DB { - if (cached) return cached; + const existing = dbCache.get(file); + if (existing) return existing; if (file !== ':memory:') mkdirSync(dirname(file), { recursive: true }); const db = new Database(file); db.pragma('journal_mode = WAL'); migrate(db); - cached = db; + dbCache.set(file, db); return db; } diff --git a/src/capture/queue.ts b/src/capture/queue.ts index d677167..0dd2e82 100644 --- a/src/capture/queue.ts +++ b/src/capture/queue.ts @@ -2,6 +2,7 @@ import type { DB } from './db.js'; import type { IgUrl } from './sources.js'; export type QueueStatus = 'pending' | 'running' | 'done' | 'failed'; +export type QueueTable = 'ig_queue' | 'tt_queue' | 'yt_queue'; /** Per-platform resume queue. Survives mid-run aborts; pending rows resume. */ @@ -27,22 +28,29 @@ export function enqueueSimple(db: DB, table: 'tt_queue' | 'yt_queue', urls: stri return tx(urls); } -export function pendingCount(db: DB, table: string): number { +export function pendingCount(db: DB, table: QueueTable): number { const row = db.prepare(`SELECT COUNT(*) AS c FROM ${table} WHERE status='pending'`).get() as { c: number; }; return row.c; } -export function pendingUrls(db: DB, table: string, limit?: number): string[] { - const sql = - `SELECT url FROM ${table} WHERE status='pending'` + (limit ? ` LIMIT ${Number(limit)}` : ''); - return (db.prepare(sql).all() as Array<{ url: string }>).map((r) => r.url); +export function pendingUrls(db: DB, table: QueueTable, limit?: number): string[] { + if (limit !== undefined) { + return ( + db + .prepare(`SELECT url FROM ${table} WHERE status='pending' LIMIT ?`) + .all(limit) as Array<{ url: string }> + ).map((r) => r.url); + } + return ( + db.prepare(`SELECT url FROM ${table} WHERE status='pending'`).all() as Array<{ url: string }> + ).map((r) => r.url); } export function markStatus( db: DB, - table: string, + table: QueueTable, url: string, status: QueueStatus, error?: string, @@ -57,6 +65,6 @@ export function markStatus( * Recovery: any row left in 'running' from a killed process is reset to * 'pending' so the next run picks it back up. Call at queue startup. */ -export function recoverRunning(db: DB, table: string): number { +export function recoverRunning(db: DB, table: QueueTable): number { return db.prepare(`UPDATE ${table} SET status='pending' WHERE status='running'`).run().changes; } diff --git a/src/capture/run/instagram.ts b/src/capture/run/instagram.ts index 4f946d6..f589f4d 100644 --- a/src/capture/run/instagram.ts +++ b/src/capture/run/instagram.ts @@ -68,10 +68,18 @@ async function processBatch( spentUsd += run.costUsd; const byUrl = new Map>(); - for (const it of run.items) byUrl.set(String(it.url ?? '').replace(/\/?$/, '/'), it); + const byShortCode = new Map>(); + for (const it of run.items) { + byUrl.set(String(it.url ?? '').replace(/\/?$/, '/'), it); + const sc = String(it.shortCode ?? it.id ?? ''); + if (sc) byShortCode.set(sc, it); + } + + const scFromUrl = (u: string) => u.match(/\/(p|reel|tv)\/([A-Za-z0-9_-]+)/)?.[2] ?? null; for (const u of urls) { - const item = byUrl.get(u.replace(/\/?$/, '/')); + const sc = scFromUrl(u); + const item = byUrl.get(u.replace(/\/?$/, '/')) ?? (sc ? byShortCode.get(sc) : undefined); if (!item) { markStatus(db, 'ig_queue', u, 'failed', 'no_dataset_item'); failed++; @@ -132,21 +140,28 @@ export async function runInstagram(ctx: CommandContext): Promise } const { budgetAbortUsd: abortUsd, budgetHardCapUsd: hardCapUsd, canarySize, igBatch } = ctx.config.capture; - const ceiling = (ctx.opts.budgetCap as number | undefined) ?? hardCapUsd; + const rawCap = ctx.opts.budgetCap; + const ceiling = rawCap !== undefined ? parseFloat(String(rawCap)) : hardCapUsd; + const limitOpt = ctx.opts.limit !== undefined ? parseInt(String(ctx.opts.limit), 10) : undefined; const client = makeClient(); let processed = 0; let failed = 0; let collisions = 0; + let remaining = limitOpt; // Canary ALWAYS runs first; --yes only bypasses the abort on projection overage. - const sample = sampleRows(db, canarySize); + const canarycap = Math.min(canarySize, remaining ?? canarySize); + const sample = sampleRows(db, canarycap); + let ratePerUrl = 0; if (sample.length > 0) { const r = await processBatch(vault, db, client, sample); processed += r.processed; failed += r.failed; collisions += r.collisions; + if (remaining !== undefined) remaining -= sample.length; const projection = projectCost(sample.length, r.spentUsd, pending); + ratePerUrl = sample.length > 0 ? r.spentUsd / sample.length : 0; ctx.log.info( `Canary: ${sample.length} URLs cost $${r.spentUsd.toFixed(4)} → projected $${projection.projectedCostUsd.toFixed(2)} for ${pending}.`, ); @@ -154,13 +169,17 @@ export async function runInstagram(ctx: CommandContext): Promise } for (;;) { - assertWithinBudget(budgetState(db, abortUsd, hardCapUsd)); - const rows = sampleRows(db, igBatch); + if (remaining !== undefined && remaining <= 0) break; + const batchSize = Math.min(igBatch, remaining ?? igBatch); + const projectedBatch = batchSize * ratePerUrl; + assertWithinBudget(budgetState(db, abortUsd, hardCapUsd), projectedBatch); + const rows = sampleRows(db, batchSize); if (rows.length === 0) break; const r = await processBatch(vault, db, client, rows); processed += r.processed; failed += r.failed; collisions += r.collisions; + if (remaining !== undefined) remaining -= rows.length; ctx.log.info(`Instagram: ${processed} processed, $${igSpend(db).toFixed(2)} spent.`); } diff --git a/src/capture/run/tiktok.ts b/src/capture/run/tiktok.ts index cc1a44b..053c5b3 100644 --- a/src/capture/run/tiktok.ts +++ b/src/capture/run/tiktok.ts @@ -95,24 +95,31 @@ export async function runTikTok(ctx: CommandContext): Promise { } const { budgetAbortUsd: abortUsd, budgetHardCapUsd: hardCapUsd, canarySize, ttBatch } = ctx.config.capture; - const ceiling = (ctx.opts.budgetCap as number | undefined) ?? hardCapUsd; + const rawCap = ctx.opts.budgetCap; + const ceiling = rawCap !== undefined ? parseFloat(String(rawCap)) : hardCapUsd; + const limitOpt = ctx.opts.limit !== undefined ? parseInt(String(ctx.opts.limit), 10) : undefined; const client = makeClient(); let processed = 0; let failed = 0; let collisions = 0; let spentUsd = 0; + let remaining = limitOpt; // Mandatory canary for TikTok (pricing not stably published). Always runs; // --yes only bypasses the abort on projection overage. - const sample = pending(db, canarySize); + const canarycap = Math.min(canarySize, remaining ?? canarySize); + const sample = pending(db, canarycap); + let ratePerUrl = 0; if (sample.length > 0) { const r = await processBatch(vault, db, client, sample); processed += r.processed; failed += r.failed; collisions += r.collisions; spentUsd += r.spentUsd; + if (remaining !== undefined) remaining -= sample.length; const projection = projectCost(sample.length, r.spentUsd, total); + ratePerUrl = sample.length > 0 ? r.spentUsd / sample.length : 0; ctx.log.info( `Canary: ${sample.length} URLs cost $${r.spentUsd.toFixed(4)} → projected $${projection.projectedCostUsd.toFixed(2)} for ${total}.`, ); @@ -120,14 +127,18 @@ export async function runTikTok(ctx: CommandContext): Promise { } for (;;) { - assertWithinBudget(budgetState(db, abortUsd, hardCapUsd)); - const batch = pending(db, ttBatch); + if (remaining !== undefined && remaining <= 0) break; + const batchSize = Math.min(ttBatch, remaining ?? ttBatch); + const projectedBatch = batchSize * ratePerUrl; + assertWithinBudget(budgetState(db, abortUsd, hardCapUsd), projectedBatch); + const batch = pending(db, batchSize); if (batch.length === 0) break; const r = await processBatch(vault, db, client, batch); processed += r.processed; failed += r.failed; collisions += r.collisions; spentUsd += r.spentUsd; + if (remaining !== undefined) remaining -= batch.length; ctx.log.info(`TikTok: ${processed} processed, $${spentUsd.toFixed(2)} spent.`); } diff --git a/src/capture/run/youtube.ts b/src/capture/run/youtube.ts index 874377b..0363038 100644 --- a/src/capture/run/youtube.ts +++ b/src/capture/run/youtube.ts @@ -111,8 +111,10 @@ export async function runYouTube(ctx: CommandContext): Promise { return { platform: 'youtube', enqueued, processed: 0, failed: 0, spentUsd: 0, collisions: 0 }; } + const limitOpt = ctx.opts.limit !== undefined ? parseInt(String(ctx.opts.limit), 10) : undefined; + // Write the pending batch file and run yt-dlp (subs + info.json only, $0). - const batchUrls = pendingUrls(db, 'yt_queue'); + const batchUrls = pendingUrls(db, 'yt_queue', limitOpt); const batchFile = join(work, 'batch.txt'); mkdirSync(work, { recursive: true }); writeFileSync(batchFile, batchUrls.join('\n'), 'utf8'); @@ -132,6 +134,10 @@ export async function runYouTube(ctx: CommandContext): Promise { hint: 'Install yt-dlp; capture YouTube needs it on PATH.', }); } + throw new MagnusError(`yt-dlp failed to start: ${(err as Error).message}`, { + code: ExitCode.Network, + errorCode: 'E_YTDLP_ERROR', + }); } // Render each video from its info.json + best transcript. diff --git a/src/cli.ts b/src/cli.ts index 21a8e95..5d8c7e1 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -106,11 +106,12 @@ async function execute( * `magnus ` form and route to the default `download` command. */ function injectDefaultCommand(argv: string[]): string[] { + if (argv.length < 2) return argv; const rest = argv.slice(2); const firstNonFlag = rest.find((a) => !a.startsWith('-')); if (!firstNonFlag) return argv; if (findCommand(firstNonFlag)) return argv; - return [argv[0]!, argv[1]!, 'download', ...rest]; + return [argv[0] as string, argv[1] as string, 'download', ...rest]; } export async function main(argv = process.argv): Promise { @@ -142,5 +143,5 @@ export async function main(argv = process.argv): Promise { main().catch((err) => { process.stderr.write(`fatal: ${(err as Error).message}\n`); - process.exit(ExitCode.Error); + process.exitCode = ExitCode.Error; }); diff --git a/src/commands/config.ts b/src/commands/config.ts index b6aa7fc..54ab070 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -38,7 +38,6 @@ async function runConfig(ctx: CommandContext): Promise { case undefined: case 'list': { const cfg = loadConfig(override); - ctx.log.info(JSON.stringify(cfg, null, 2)); return { data: cfg, human: JSON.stringify(cfg, null, 2) }; } case 'path': { diff --git a/src/util/redact.ts b/src/util/redact.ts index de57f86..2d36777 100644 --- a/src/util/redact.ts +++ b/src/util/redact.ts @@ -5,10 +5,15 @@ const SECRET_KEYS = /^(apify_token|token|authorization|cookie|cookies|password|secret)$/i; const APIFY_TOKEN_RE = /apify_api_[A-Za-z0-9]+/g; +const BEARER_RE = /(Bearer\s+)\S+/gi; +const GENERIC_SECRET_RE = /\b(token|secret|password|api[-_]?key)\s*=\s*\S+/gi; /** Replace known secret patterns inside a free-text string. */ export function redactString(input: string): string { - return input.replace(APIFY_TOKEN_RE, 'apify_api_***'); + return input + .replace(APIFY_TOKEN_RE, 'apify_api_***') + .replace(BEARER_RE, '$1***') + .replace(GENERIC_SECRET_RE, (_, k: string) => `${k}=***`); } /** Deep-redact an object/array for safe serialization. */ From 5368c1722993a87b63d05fe5f49f8d4a2f8e3c31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 18:53:02 +0000 Subject: [PATCH 3/3] chore(deps): bump zod from 3.25.76 to 4.4.3 Bumps [zod](https://github.com/colinhacks/zod) from 3.25.76 to 4.4.3. - [Release notes](https://github.com/colinhacks/zod/releases) - [Commits](https://github.com/colinhacks/zod/compare/v3.25.76...v4.4.3) --- updated-dependencies: - dependency-name: zod dependency-version: 4.4.3 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 220a7b9..75008a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "execa": "^9.4.0", "picocolors": "^1.1.0", "yaml": "^2.5.1", - "zod": "^3.23.8" + "zod": "^4.4.3" }, "bin": { "magnus": "dist/cli.js" @@ -7224,9 +7224,9 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 39e51f8..3859113 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "execa": "^9.4.0", "picocolors": "^1.1.0", "yaml": "^2.5.1", - "zod": "^3.23.8" + "zod": "^4.4.3" }, "devDependencies": { "@changesets/cli": "^2.27.9",