From 5d1db28882ff1aa67ece2e49778291b378380719 Mon Sep 17 00:00:00 2001 From: Jim Carucci <96802642+uncleJim21@users.noreply.github.com> Date: Thu, 2 Apr 2026 14:58:37 -0500 Subject: [PATCH] add skills folder and automation scripts --- .github/workflows/skills-ci.yml | 142 +++ .gitignore | 3 + dist/.DS_Store | Bin 0 -> 6148 bytes dist/smithery/pullthatupjamie/SKILL.md | 180 ++++ .../pullthatupjamie/references/create.md | 345 +++++++ .../pullthatupjamie/references/podcast-rra.md | 503 ++++++++++ package-lock.json | 937 ++++++++++++++++++ package.json | 9 +- scripts/.DS_Store | Bin 0 -> 6148 bytes scripts/skills/build-clawhub-zip.js | 84 ++ scripts/skills/build-smithery.js | 127 +++ .../pullthatupjamie/.smithery-overrides.yaml | 28 + 12 files changed, 2357 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/skills-ci.yml create mode 100644 dist/.DS_Store create mode 100644 dist/smithery/pullthatupjamie/SKILL.md create mode 100644 dist/smithery/pullthatupjamie/references/create.md create mode 100644 dist/smithery/pullthatupjamie/references/podcast-rra.md create mode 100644 scripts/.DS_Store create mode 100644 scripts/skills/build-clawhub-zip.js create mode 100644 scripts/skills/build-smithery.js create mode 100644 skills/pullthatupjamie/.smithery-overrides.yaml diff --git a/.github/workflows/skills-ci.yml b/.github/workflows/skills-ci.yml new file mode 100644 index 0000000..2bc6d2a --- /dev/null +++ b/.github/workflows/skills-ci.yml @@ -0,0 +1,142 @@ +name: Skills CI/CD + +on: + push: + branches: [master] + paths: + - "skills/**" + - "scripts/skills/**" + - ".github/workflows/skills-ci.yml" + pull_request: + paths: + - "skills/**" + - "scripts/skills/**" + - ".github/workflows/skills-ci.yml" + +jobs: + validate-and-build: + name: Validate & Build Skills + runs-on: ubuntu-latest + permissions: + contents: write # needed to commit dist/smithery/ back to repo + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install project dependencies + run: npm install --legacy-peer-deps + + - name: Install skills-ref CLI + run: | + if npm install -g skills-ref 2>/dev/null; then + echo "SKILLS_REF_AVAILABLE=true" >> $GITHUB_ENV + else + echo "SKILLS_REF_AVAILABLE=false" >> $GITHUB_ENV + echo "⚠️ skills-ref not found on npm — validation steps will be skipped" + fi + + # ── Step 1: Validate source SKILL.md (ClawHub format) ──────────────── + # Source may have ClawHub extensions that fail strict spec validation — non-blocking. + - name: "[Source] Validate against agentskills.io spec" + id: validate-source + run: | + if [ "$SKILLS_REF_AVAILABLE" = "true" ]; then + echo "### Source SKILL.md Validation" >> $GITHUB_STEP_SUMMARY + skills-ref validate ./skills/pullthatupjamie 2>&1 | tee -a $GITHUB_STEP_SUMMARY \ + && echo "✅ Source: valid" \ + || echo "⚠️ Source has spec issues (expected — ClawHub extensions present)" + else + echo "⚠️ Skipping source validation (skills-ref unavailable)" + fi + continue-on-error: true + + # ── Step 2: Build Smithery-compliant dist ───────────────────────────── + - name: "[Smithery] Build dist from source" + run: node scripts/skills/build-smithery.js + + # ── Step 3: Validate Smithery dist (strict — blocks CI on failure) ──── + - name: "[Smithery] Validate dist against agentskills.io spec" + run: | + if [ "$SKILLS_REF_AVAILABLE" = "true" ]; then + echo "### Smithery dist/ Validation" >> $GITHUB_STEP_SUMMARY + skills-ref validate ./dist/smithery/pullthatupjamie 2>&1 | tee -a $GITHUB_STEP_SUMMARY + else + echo "⚠️ Skipping dist validation (skills-ref unavailable)" + fi + + # ── Step 4: Build ClawHub zip ───────────────────────────────────────── + - name: "[ClawHub] Build zip" + run: node scripts/skills/build-clawhub-zip.js + + # ── Step 5: Upload artifacts ────────────────────────────────────────── + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: skill-builds-${{ github.sha }} + path: dist/ + retention-days: 30 + + # ── Step 6: Commit dist/smithery/ back to repo (master push only) ───── + # Smithery's gitUrl points to this committed path. + # Uses [skip ci] to prevent an infinite workflow loop. + - name: Commit dist/smithery/ to repo + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add dist/smithery/ + if git diff --cached --quiet; then + echo "No changes to dist/smithery/ — skipping commit" + else + git commit -m "ci: update dist/smithery from build [skip ci]" + git push + fi + + # ── Step 7: Print submission URLs ───────────────────────────────────── + - name: Print submission URLs + run: | + VERSION=$(node -e "const m=require('./skills/pullthatupjamie/_meta.json'); console.log(m.version)") + REPO="uncleJim21/pullthatupjamie-backend" + BRANCH="master" + GIT_URL="https://github.com/${REPO}/tree/${BRANCH}/dist/smithery/pullthatupjamie" + + cat >> $GITHUB_STEP_SUMMARY << EOF + + --- + + ## Skill Build: v${VERSION} + + ### Smithery + + Submit once (or on each new version) using your Smithery API key: + + \`\`\`bash + curl -X PUT "https://api.smithery.ai/skills/\${SMITHERY_NAMESPACE}/pullthatupjamie" \\ + -H "Authorization: Bearer \${SMITHERY_API_KEY}" \\ + -H "Content-Type: application/json" \\ + -d '{"gitUrl": "${GIT_URL}"}' + \`\`\` + + **Skill URL after registration:** + \`https://smithery.ai/skills/\${SMITHERY_NAMESPACE}/pullthatupjamie\` + + **gitUrl (what you pass to Smithery):** + \`${GIT_URL}\` + + --- + + ### ClawHub + + Download the \`skill-builds\` artifact from this run and upload the zip: + - File: \`dist/clawhub/pullthatupjamie-v${VERSION}.zip\` + - Upload at: https://clawhub.ai/publish + + EOF diff --git a/.gitignore b/.gitignore index 5debb41..4f8eac1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ scripts/seedPodcastFeeds.js /dev-helper-scripts test/cost-baseline-100.sh .l402-creds + +# Skills build artifacts — ClawHub zips are CI artifacts, not committed +dist/clawhub/ diff --git a/dist/.DS_Store b/dist/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..8af6dd6db916da8e7051873e8d069c7211430b7a GIT binary patch literal 6148 zcmeHK%}T>S5T3QwrWBzE1-&hJt(dAP#Y?F51&ruHr6#u6V9b{Om_sS#t}o<^_&m<+ zZop~|oHy$iBNU_*2$>6AD>j%==s5;3g)mIV!!RzG z=r5Y++beJnF$9ppr|(xGdfvie9E~Tg`z}^W<<+%{Q*mm}tv^tyKM5w&aXT1Y)96B} zIL!4Ryo~y3x4vZZU^fRKAq;?2nRIB@gJyv+`(0 zW`G%B2AF}RV!$2)PHm~G=3=0j;_z-FB6iWPHzc9Y0:. Used as the Authorization header for all paid requests.' + required: false + externalServices: + - url: https://www.pullthatupjamie.ai + description: Jamie API — podcast search, research sessions, corpus browsing, RSS feed ingestion, clip generation (all endpoints proxied for security) + externalTools: + - name: Lightning wallet (any) + description: 'For paid tier only: Any Lightning wallet (Zeus, BlueWallet, Phoenix, Alby extension, etc.) to pay BOLT-11 invoices. NO CLI tools are required or auto-executed by this skill.' + required: false + version: 1.6.0 + homepage: https://pullthatupjamie.ai +license: Proprietary +compatibility: Requires internet access to https://www.pullthatupjamie.ai. Free tier works immediately with no credentials. Paid tier requires a Lightning-compatible wallet (Zeus, BlueWallet, Phoenix, Alby, etc.) for L402 payments. lnget handles payment flow automatically. +--- + +# PullThatUpJamie — Podcast Intelligence + +Powered by [Pull That Up Jamie](https://pullthatupjamie.ai). + +## Why Use This + +Jamie is a **retrieval/vector DB as a service for podcasts**. Instead of: +- Transcribing hours of audio yourself (expensive, slow) +- Stuffing full transcripts into context (thousands of tokens wasted) +- Web searching and getting SEO spam, listicles, and low-quality summaries +- Multiple search iterations across unreliable sources + +You run a single semantic search ($0.002, returns in under 2s) and get the **exact clip** with timestamp, audio deeplink, and transcript. Every result is timestamped to the second — you're not handing users a 2-hour episode and saying "it's in there somewhere." You're linking them to the exact 30-second moment where the expert makes the point. 500 sats ($0.33) covers an entire deep research session of 150+ searches. + +**Don't see the podcast you need?** Use `POST /api/discover-podcasts` ($0.005) to search the full Podcast Index catalog, then submit episodes for transcription via `POST /api/on-demand/submitOnDemandRun` ($0.45). Once processed, episodes get timestamped chaptering and become searchable at paragraph level. + +**Your output is not a text wall.** Research sessions are interactive web experiences where users play audio clips inline, browse visually, and share with others. Every clip deeplinks to the exact moment in the source audio. The session link IS the deliverable. + +## Corpus Coverage (as of Feb 2026) + +109 feeds · ~7K episodes · ~1.9M indexed paragraphs · ~11.5K identified people/orgs. Growing. + +| Category | Notable Feeds | Feeds | Episodes | +|---|---|---|---| +| **Bitcoin/Crypto** | TFTC, Bitcoin Audible, Simply Bitcoin, Peter McCormack, What is Money, Once Bitten, Ungovernable Misfits | 41 | ~11,700 | +| **Finance/Markets** | Bloomberg Intelligence, Bloomberg Surveillance, Prof G Markets, Tim Ferriss, Diary of a CEO | 11 | ~5,700 | +| **Health/Wellness** | Huberman Lab, Peter Attia Drive, Meat Mafia, FoundMyFitness, Modern Wisdom | 7 | ~3,000 | +| **Politics/Culture** | Ron Paul Liberty Report, No Agenda, Tucker Carlson, Breaking Points, Pod Save America | 8+ | ~2,800 | +| **Tech/General** | Joe Rogan Experience, Lex Fridman, How I Built This, Kill Tony, Sean Carroll's Mindscape | 40+ | ~17,000 | + +**If a feed isn't indexed yet, you can ingest it on demand** from any RSS feed. See the Ingestion section in [references/podcast-rra.md](references/podcast-rra.md). + +**Free corpus browsing** (no auth required for reads): `GET /api/corpus/feeds`, `/api/corpus/stats`, `/api/corpus/people`. Check before you search. + +## Auth Flow (L402 Protocol) + +**Free tier available.** Corpus browsing (no auth needed) and IP-based search quota are available. To use the free tier on paid endpoints, add the header `X-Free-Tier: true` to your requests. Without this header, paid endpoints return an immediate 402 L402 challenge. The free tier has per-endpoint quotas (e.g., 50 searches/week) — once exhausted, you'll need to pay via L402 to continue. + +Jamie uses the [L402 protocol](https://docs.lightning.engineering/the-lightning-network/l402) for paid access. Compatible with [lnget](https://github.com/lightninglabs/lnget) and any L402-aware client. + +### How Paid Access Works + +Every paid request goes through the same L402 flow: hit an endpoint, receive a 402 with a Lightning invoice, pay the invoice, retry with `Authorization: L402 :`. Each payment gives you credits on pullthatupjamie.ai. Each API call deducts its cost from your balance. + +**You choose how to use it.** Pay per query if you want — that's just a credit you use once. Or deposit more upfront and reuse the same credential across all endpoints until the balance is depleted. The 402 response includes the full pricing table (`creditInfo.pricingMicroUsd`) so you can decide. + +**Key points:** +- **Credential works across all endpoints:** The same `macaroon:preimage` works on `/search-quotes`, `/search-chapters`, `/make-clip`, `/jamie-assist`, etc. until the balance is depleted. +- **Response headers on every paid request:** `X-Credits-Remaining-USD` and `X-Credits-Cost-USD` tell you your balance and what the call cost. +- **Custom amount:** The default invoice is 500 sats (~$0.33). To request a different amount, add `?amountSats=N` to any request (min 10, max 500,000 sats). The 402 response will contain an invoice for that amount. +- **Balance endpoint:** `GET /api/agent/balance` with your L402 credential returns your full balance breakdown. +- **When credits run out:** The next request returns a fresh 402 challenge. Pay it to continue. If using lnget, this is automatic. + +### Automatic Flow (lnget) + +If using [lnget](https://github.com/lightninglabs/lnget), everything happens automatically: +```bash +lnget "https://www.pullthatupjamie.ai/api/search-quotes" \ + -d '{"query": "bitcoin energy consumption"}' +``` +lnget handles the 402 challenge, pays the invoice, caches the credential per-domain, and retries. Subsequent requests to any pullthatupjamie.ai endpoint reuse the cached credential with no additional payment. + +### Manual Flow + +#### 1. Hit any protected endpoint +```bash +curl -s -D- -X POST -H "Content-Type: application/json" \ + -d '{"query": "bitcoin energy consumption"}' \ + "https://www.pullthatupjamie.ai/api/search-quotes" +``` +Returns `HTTP 402` with: +- `WWW-Authenticate` header: contains `macaroon` and `invoice` (BOLT-11) +- JSON body: contains `creditInfo` with pricing table, min/max deposit amounts, and balance endpoint + +#### 2. Pay the Invoice +**Pay using ANY Lightning wallet** (Zeus, BlueWallet, Phoenix, Alby browser extension, etc.). Save the **preimage** returned by your wallet — you need it for the credential. + +**NWC (programmatic):** If using [Alby CLI](https://github.com/getAlby/alby-cli) or any NWC-compatible wallet: +```bash +npx @getalby/cli pay-invoice -c "NWC_CONNECTION_STRING" -i "BOLT11_INVOICE" +``` +Returns `preimage`. + +#### 3. Retry with L402 credential +```bash +curl -s -X POST \ + -H "Authorization: L402 MACAROON:PREIMAGE" \ + -H "Content-Type: application/json" \ + -d '{"query": "bitcoin energy consumption"}' \ + "https://www.pullthatupjamie.ai/api/search-quotes" +``` +Credits are auto-activated on first use. **Reuse this same credential on all subsequent requests** to any pullthatupjamie.ai endpoint until depleted. + +#### Custom Credit Amount +To deposit more (or less) than the default 500 sats, add `?amountSats=N` to any request: +```bash +curl -s -D- -X POST -H "Content-Type: application/json" \ + -d '{"query": "bitcoin energy consumption"}' \ + "https://www.pullthatupjamie.ai/api/search-quotes?amountSats=5000" +``` +Returns a 402 challenge with a 5,000-sat invoice instead of the default. Min: 10 sats, Max: 500,000 sats. + +### Check Balance +```bash +curl -s -H "Authorization: L402 MACAROON:PREIMAGE" \ + "https://www.pullthatupjamie.ai/api/agent/balance" +``` + +## Modules + +### Available Now +- **Podcast RRA (Retrieve, Research, Analyze):** See [references/podcast-rra.md](references/podcast-rra.md) — search the corpus, build interactive research sessions, discover people/orgs, ingest new feeds. Now with **Smart Search** (`smartMode`) for handling vague or descriptive queries. +- **Podcast Discovery:** `POST /api/discover-podcasts` — LLM-assisted search across the full Podcast Index catalog. Returns matching podcasts with `transcriptAvailable` flags and actionable next-step endpoints. Use this to find podcasts not yet in the corpus, then submit them for transcription via on-demand processing. +- **Create:** See [references/create.md](references/create.md) — generate shareable MP4 clips with burned-in subtitles from any search result. Full pipeline: Search → Create clip → Share. + +### Coming Soon +- **Publish:** Cross-post to Twitter, Nostr, and more. Research a topic → generate a post → publish everywhere. + +## Credits Running Low +Check `X-Credits-Remaining-USD` in response headers during workflows, or call `GET /api/agent/balance`. If the balance is too low for the next call, that request will return a fresh 402 challenge with a new invoice. Pay it to continue. If using lnget, this top-up happens automatically. + +## Security & Trust + +**No Command Execution:** This skill does NOT execute arbitrary shell commands, install packages, or modify system state. All operations are HTTP API calls to `pullthatupjamie.ai`. The skill documentation references an optional CLI tool (`@getalby/cli`) for paying Lightning invoices, but this is: +- **Never auto-executed** — requires explicit operator approval +- **Completely optional** — operators can use any Lightning wallet instead +- **Not installed by this skill** — operators must manually install if desired + +**Free tier:** Corpus browsing (`/api/corpus/*`) works without any headers. For paid endpoints, add `X-Free-Tier: true` to get an IP-based quota allowance (e.g., 50 searches/week). Without this header, paid endpoints return a 402 immediately. You can evaluate the service before providing any payment info. + +**Paid tier credentials:** The L402 credential (macaroon + preimage) is a sensitive bearer token. It should be: +- Stored securely (env vars or encrypted config, not in plaintext logs) +- Never shared with untrusted agents or services +- Backed by a wallet with limited funds (e.g., 500-1000 sats) + +**All API calls proxied:** All operations route through `https://www.pullthatupjamie.ai`. RSS feed parsing, search, and ingestion are handled server-side. No direct external URL fetching by the agent. + +**No persistence or privilege escalation:** This skill has no install hooks, no `always: true`, and does not modify other skills or system config. + +## Gotchas +- API base: `https://www.pullthatupjamie.ai` (must include `www.` — bare domain redirects and breaks API calls) +- **Free tier requires `X-Free-Tier: true` header.** Without it, paid endpoints return 402 immediately. Corpus endpoints work without any headers. +- **Credit reuse is optional.** You can pay per query (each 402 = one credit used once) or deposit more and reuse the same `L402 macaroon:preimage` credential across all endpoints. Either way works. +- Custom amount: append `?amountSats=N` to any request (min 10, max 500,000). No separate purchase endpoint. +- Alby CLI: `pay-invoice` with `-i` flag (not `pay`) +- 500 sats gets ~150+ searches. Start there. +- Monitor balance via `X-Credits-Remaining-USD` response header or `GET /api/agent/balance`. +- Research session creation takes 30-45 seconds. Be patient. +- Clip creation takes 30-120 seconds. Poll `/api/clip-status/:lookupHash` every 5 seconds. diff --git a/dist/smithery/pullthatupjamie/references/create.md b/dist/smithery/pullthatupjamie/references/create.md new file mode 100644 index 0000000..c2721fa --- /dev/null +++ b/dist/smithery/pullthatupjamie/references/create.md @@ -0,0 +1,345 @@ +# Create Module — Audio Clip Generation + +Generate shareable MP4 clips with burned-in subtitles from podcast search results. + +## Quick Start + +```javascript +const BASE_URL = 'https://pullthatupjamie.ai'; + +// 1. Search for content +const searchRes = await fetch(`${BASE_URL}/api/search-quotes`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: 'Bitcoin as legal tender', limit: 5 }) +}); +const { results } = await searchRes.json(); +const clipId = results[0].shareLink; + +// 2. Create clip +const createRes = await fetch(`${BASE_URL}/api/make-clip`, { + method: 'POST', + headers: { + 'Authorization': 'L402 YOUR_MACAROON:YOUR_PREIMAGE', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ clipId }) +}); +const { status, lookupHash, url } = await createRes.json(); + +// 3. If cached, URL is immediate +if (status === 'completed') { + console.log('Clip ready:', url); +} else { + // 4. Poll for completion + const clipUrl = await pollClipStatus(lookupHash); + console.log('Clip ready:', clipUrl); +} +``` + +--- + +## Authentication + +### Option 1: Lightning Credits (Agent Access) + +**Cost:** $0.05 per clip (50,000 microUSD) + +Lightning auth uses the L402 protocol. Hit the endpoint, get a 402 challenge, pay the invoice, retry with the credential. + +#### Step 1 — Hit the endpoint (or purchase credits for a custom amount) + +```bash +curl -X POST https://www.pullthatupjamie.ai/api/make-clip \ + -H "Content-Type: application/json" \ + -d '{"clipId": "1015378_guid_p123"}' +``` + +Returns `HTTP 402` with a `macaroon` and `invoice` in the `WWW-Authenticate` header. For a custom credit amount, add `?amountSats=N` to the request (min 10, max 500,000 sats). + +#### Step 2 — Pay the Lightning invoice + +Pay `invoice` with any Lightning wallet. Returns `preimage`. + +#### Step 3 — Retry with L402 credential + +```bash +curl -X POST https://www.pullthatupjamie.ai/api/make-clip \ + -H "Authorization: L402 MACAROON:PREIMAGE" \ + -H "Content-Type: application/json" \ + -d '{"clipId": "1015378_guid_p123"}' +``` + +Credits are auto-activated on first use. The credential is reused for all subsequent requests. + +#### Checking balance + +```bash +curl https://www.pullthatupjamie.ai/api/agent/balance \ + -H "Authorization: L402 MACAROON:PREIMAGE" +``` + +Returns `balanceUsd`, `usedUsd`, `totalDepositedUsd`, and current `btcUsdRate`. + +### Option 2: Free Tier + +No auth required. Quotas tracked by IP (anonymous) or JWT (registered): + +| Tier | Clips | Period | +|------|-------|--------| +| Anonymous | 5 | Per week | +| Registered | 10 | Per month | +| Subscriber | 50 | Per month | + +Simply call `/api/make-clip` without credentials. + +--- + +## API Reference + +### `POST /api/make-clip` + +Create an audio clip from a search result. + +**Request:** + +```json +{ + "clipId": "1015378_38c8e3a2b0e94b2b80feb2e426fbf0b3_p1234", + "timestamps": [1234.5, 1289.0] +} +``` + +- `clipId` (required): The `shareLink` value from a search result. +- `timestamps` (optional): `[startSeconds, endSeconds]` to override the natural paragraph bounds. + +**Responses:** + +| Status | Meaning | Body | +|--------|---------|------| +| 200 | Cached (instant, no charge) | `{ status: 'completed', lookupHash, url }` | +| 202 | Queued for processing | `{ status: 'processing', lookupHash, pollUrl }` | +| 400 | Missing clipId | `{ error: 'clipId is required' }` | +| 404 | clipId not in corpus | `{ error: 'Clip not found', clipId }` | +| 429 | Quota exceeded | `{ error, code: 'QUOTA_EXCEEDED', used, max, resetDate, daysUntilReset, tier }` | +| 429 | Insufficient funds | `{ error, code: 'INSUFFICIENT_FUNDS', costUsd, balanceUsd, costUsdMicro, balanceUsdMicro }` | +| 500 | Server error | `{ error: 'Internal server error' }` | + +### `GET /api/clip-status/:lookupHash` + +Poll for clip processing completion. No authentication required. + +**Responses:** + +| Status | Meaning | Body | +|--------|---------|------| +| 200 | Completed | `{ status: 'completed', url }` | +| 200 | Still processing | `{ status: 'processing', queuePosition: { estimatedWaitSeconds }, lookupHash }` | +| 200 | Failed | `{ status: 'failed', error: '...', lookupHash }` | +| 404 | Not found | `{ status: 'not_found' }` | +| 500 | Server error | `{ error: 'Internal server error' }` | + +--- + +## Polling Strategy + +### Recommended approach + +```javascript +async function pollClipStatus(lookupHash, maxAttempts = 30, intervalMs = 5000) { + for (let i = 0; i < maxAttempts; i++) { + const res = await fetch( + `https://pullthatupjamie.ai/api/clip-status/${lookupHash}` + ); + const data = await res.json(); + + if (data.status === 'completed') return data.url; + if (data.status === 'failed') throw new Error(`Clip failed: ${data.error}`); + if (res.status === 404) throw new Error('Clip not found — retry creation'); + + await new Promise(r => setTimeout(r, intervalMs)); + } + throw new Error('Clip processing timeout after 2.5 minutes'); +} +``` + +### Timing + +- **Interval:** 5 seconds +- **Max attempts:** 30 (~2.5 minute timeout) +- **Typical completion:** <1 minute (10-12 polls) +- **Worst case:** ~2 minutes for complex clips + +### Status progression + +``` +queued → processing → completed + ↘ failed (rare) +``` + +--- + +## Custom Timestamps + +By default, clips use the natural paragraph boundaries from the search result. Override with `timestamps`: + +```javascript +// Natural bounds (uses timeContext from search result) +await makeClip({ clipId: 'abc_p123' }); + +// Custom bounds (exactly 60 seconds for social media) +await makeClip({ + clipId: 'abc_p123', + timestamps: [1234.5, 1294.5] +}); +``` + +Different timestamps produce a different `lookupHash`, which means new processing and a new charge. Even a 0.1-second difference creates a new clip. + +--- + +## Error Handling + +### Quota exhausted (free tier) + +```json +{ + "error": "Quota exceeded", + "code": "QUOTA_EXCEEDED", + "used": 10, + "max": 10, + "resetDate": "2026-04-01T00:00:00Z", + "daysUntilReset": 5, + "tier": "registered" +} +``` + +**Action:** Wait for reset or switch to Lightning credits. + +### Insufficient funds (Lightning) + +```json +{ + "error": "Insufficient funds", + "code": "INSUFFICIENT_FUNDS", + "costUsd": 0.05, + "balanceUsd": 0.02, + "costUsdMicro": 50000, + "balanceUsdMicro": 20000 +} +``` + +**Action:** Hit any paid endpoint to receive a 402 challenge, pay the invoice to top up, then retry. + +### Processing failure + +When polling returns `status: 'failed'`, the `error` field describes the cause. Possible causes: +- Audio URL inaccessible (source podcast CDN issue) +- Transcript data missing for episode +- FFmpeg processing error + +**Action:** Log the error. Optionally retry clip creation once. If persistent, the source episode may have an issue. + +--- + +## Cost Optimization + +1. **Cache lookupHash client-side.** The same `clipId` + `timestamps` always produces the same `lookupHash`. Re-requesting a completed clip returns instantly at no charge. +2. **Avoid timestamp micro-adjustments.** Even a 0.1-second difference creates a new clip with a new charge. +3. **Use natural bounds when possible.** Omit the `timestamps` parameter to use the pre-indexed paragraph boundaries. +4. **Batch in parallel.** Multiple clip requests can be made concurrently — each gets its own `lookupHash` for independent polling. + +--- + +## Complete Agent Example + +```javascript +async function createClipFromSearch(query) { + const BASE_URL = 'https://pullthatupjamie.ai'; + const AUTH = 'L402 YOUR_MACAROON:YOUR_PREIMAGE'; + + // 1. Search + const searchRes = await fetch(`${BASE_URL}/api/search-quotes`, { + method: 'POST', + headers: { + 'Authorization': AUTH, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ query, limit: 1 }) + }); + const { results } = await searchRes.json(); + if (!results.length) throw new Error('No results found'); + + const clipId = results[0].shareLink; + + // 2. Create clip + const createRes = await fetch(`${BASE_URL}/api/make-clip`, { + method: 'POST', + headers: { + 'Authorization': AUTH, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ clipId }) + }); + + const createData = await createRes.json(); + + if (createRes.status === 429) { + if (createData.code === 'QUOTA_EXCEEDED') { + throw new Error(`Quota exceeded. Resets in ${createData.daysUntilReset} days.`); + } + if (createData.code === 'INSUFFICIENT_FUNDS') { + throw new Error(`Insufficient funds. Need $${createData.costUsd}, have $${createData.balanceUsd}.`); + } + } + + if (createData.status === 'completed') { + return createData.url; + } + + // 3. Poll + const lookupHash = createData.lookupHash; + for (let i = 0; i < 30; i++) { + await new Promise(r => setTimeout(r, 5000)); + const statusRes = await fetch(`${BASE_URL}/api/clip-status/${lookupHash}`); + const statusData = await statusRes.json(); + + if (statusData.status === 'completed') return statusData.url; + if (statusData.status === 'failed') { + throw new Error(`Clip processing failed: ${statusData.error}`); + } + } + + throw new Error('Clip processing timeout after 2.5 minutes'); +} + +// Usage +const clipUrl = await createClipFromSearch('Bitcoin fixes this'); +console.log('Share this:', clipUrl); +``` + +--- + +## Clip Specifications + +| Property | Value | +|----------|-------| +| Format | MP4 (H.264 video, AAC audio) | +| Resolution | 1920x1080 (1080p) | +| Subtitles | Burned-in, white text with black outline | +| Audio | Original podcast audio | +| CDN | DigitalOcean Spaces | +| Cache lifetime | Indefinite (clips never expire) | + +--- + +## Troubleshooting + +| Error | Cause | Solution | +|-------|-------|----------| +| 400 "clipId is required" | Missing body parameter | Include `{ "clipId": "..." }` in request body | +| 404 "Clip not found" | Invalid clipId | Verify `shareLink` from a search result | +| 429 "Quota exceeded" | Free tier limit reached | Wait for reset or buy Lightning credits | +| 402 "Insufficient funds" | Lightning balance too low | Pay the new invoice in the 402 response to top up | +| `status: 'failed'` during poll | Processing error | Check `error` field, retry once, contact support if persistent | +| Timeout after 30 polls | Rare processing hang | Contact support with the `lookupHash` | diff --git a/dist/smithery/pullthatupjamie/references/podcast-rra.md b/dist/smithery/pullthatupjamie/references/podcast-rra.md new file mode 100644 index 0000000..4820940 --- /dev/null +++ b/dist/smithery/pullthatupjamie/references/podcast-rra.md @@ -0,0 +1,503 @@ +# RRA — Retrieve, Research, Analyze + +API base: `https://www.pullthatupjamie.ai` + +## Understanding Requests + +Users make natural language requests. Decompose them into composable atoms: + +| Atom | Signal Words | Action | +|---|---|---| +| **guest/org** | person's name, company name | Use People endpoint to find their episodes | +| **feed filter** | "from [show]", "on TFTC" | Use `feedIds` to restrict search | +| **episode filter** | "#716", "latest episode" | Find specific episode, search within it | +| **date filter** | "2026", "latest", "recent" | Use `minDate`/`maxDate` params or filter results | +| **count** | "8 clips", "top 5" | Target that many in final output | +| **topic** | "about mining", "energy FUD" | Primary semantic search query | +| **session** | "build a session", "share it" | Full workflow: search → curate → create session → return URL | +| **compare** | "X vs Y", "contrast" | Dual-angle search, contrasting clips | +| **ingest** | "add this podcast" | On-demand ingestion workflow (see Ingestion section) | +| **scan** | "what pods do you have" | Browse corpus feeds | +| **chain** | "ingest then search" | Sequential: complete first action, then proceed | +| **vague/descriptive** | "that time X told Y about Z" | Use **Smart Search** (`smartMode: true`) to let LLM triage handle entity extraction | + +### Example Decompositions + +| Request | Atoms | Strategy | +|---|---|---| +| "Clips from Jesse Shrader's latest TFTC appearance" | guest(Jesse Shrader) + feed(TFTC) + episode(latest) | People endpoint → find TFTC episode → search topics within it | +| "Build a session on energy FUD, 2026 only" | topic(energy FUD) + date(2026) + session | Multi-angle search with date filter → curate → session | +| "Compare what TFTC vs Simply Bitcoin say about mining" | compare + feed(TFTC, Simply Bitcoin) + topic(mining) | Search mining in each feed separately → contrast | +| "What does Amboss do?" | org(Amboss) | People endpoint with "Amboss" → find episodes → search topics | +| "That funny story Steve O told Joe Rogan" | vague/descriptive | Smart Search: extracts person=Steve-O, show=JRE, rewrites query for transcript matching | +| "When Trump explains the weave to Joe" | vague/descriptive | Smart Search: extracts person=Trump, show=JRE, topic=weave, searches within matched episodes | + +--- + +## Smart Search (`smartMode`) + +For vague, descriptive, or entity-rich queries, enable Smart Search by passing `smartMode: true` on any search endpoint. This adds an LLM-powered triage layer that: + +1. **Classifies intent** — `direct_quote`, `topical`, or `descriptive` +2. **Extracts entities** — person/org hints, show hints, topic keywords, date references +3. **Resolves against corpus metadata** — maps extracted names to actual feeds, episodes, and guests in the database +4. **Rewrites the query** — strips meta-language ("that time", "the episode where") and focuses on transcript-matching content +5. **Applies precision filters** — narrows Pinecone search to specific feeds or episodes when entities resolve + +### When to Use Smart Search + +| Scenario | Use `smartMode`? | Why | +|---|---|---| +| "that funny story Steve O told Joe Rogan" | **Yes** | Descriptive, needs entity extraction + show resolution | +| "Sagar talking about Israel lobby" | **Yes** | Person + topic, benefits from guest resolution | +| "Bitcoin Lightning Network scaling" | No | Direct topical query, standard semantic search handles this well | +| "four score and seven years ago" | No | Direct quote, no entity extraction needed | +| "Tucker talking to Greenwald about Israeli interests" | **Yes** | Multiple entities, benefits from feed + guest filtering | + +### Usage + +```bash +curl -s -X POST \ + -H "Authorization: L402 MACAROON:PREIMAGE" \ + -H "Content-Type: application/json" \ + -d '{"query": "that time Jim talked about the AI monolith", "smartMode": true, "limit": 10}' \ + "API_BASE/api/search-quotes" +``` + +### Response Additions + +When `smartMode` is enabled, the response includes: + +- `originalQuery` — the user's raw input before rewriting +- `query` — the rewritten query used for semantic search +- `triage` — full triage metadata: + - `intent` — `direct_quote`, `topical`, or `descriptive` + - `show_hint` / `person_hint` — extracted entity signals + - `person_variants` — spelling variants tried for guest resolution + - `topic_keywords` — extracted topic terms + - `rewrittenQuery` — the cleaned query + - `resolvedSignals` — what matched in the corpus (feed, guest, keywords) + - `filtersApplied` — whether triage narrowed the search + - `latencyMs` — triage overhead (typically 1-3 seconds) + +### How It Works + +The triage layer uses `gpt-4o-mini` to classify the query and extract structured entities. These entities are then resolved against MongoDB metadata: + +- **Show hints** ("Joe Rogan", "Tucker", "TFTC") → fuzzy-matched to feed names/abbreviations → `feedIds` filter +- **Person hints** ("Sagar", "Steve O", "Greenwald") → regex-matched against episode guest lists → `guids` filter (up to 10 episodes) +- **Date hints** ("last year", "in 2024", "recently") → resolved to `minDate`/`maxDate` ISO date ranges +- **Query rewrite** — strips conversational framing, focuses on transcript content + +Cost: ~$0.0001 per triage call (500 tokens × gpt-4o-mini pricing). Negligible even at high volume. + +### Backward Compatibility + +Smart Search is fully opt-in. Without `smartMode: true`, endpoints behave exactly as before. No client changes are required unless you want to enable it. + +--- + +## People & Organizations + +Find episodes featuring a person, company, or affiliation. Works for guests, creators, AND organizations. + +**Important:** The People endpoint tracks explicit guest **appearances** — people who were on the show. It does NOT find mentions or discussions about someone. For widely-discussed figures (e.g. Lyn Alden, Elon Musk, Satoshi), combine People (for direct appearances) with semantic search (for clips discussing their ideas). + +### List People +```bash +curl -s "API_BASE/api/corpus/people" +``` +Returns names, appearance counts, roles, and recent episodes. No auth required. + +### Find Episodes by Person/Org +```bash +curl -s -X POST -H "Content-Type: application/json" \ + -d '{"name": "Jesse Shrader"}' \ + "API_BASE/api/corpus/people/episodes" +``` +Works with person names ("Jesse Shrader") AND company/org names ("Amboss", "Bloomberg"). Returns episodes with guids, feedIds, dates, and roles. + +**Recommended workflow for guest queries:** People endpoint → get their episodes → search within those episodes for specific topics. For broadly-discussed figures, also run semantic searches across the full corpus to catch clips where others discuss their ideas. + +--- + +## Search Strategy: Choosing the Right Endpoint + +Before searching, **triage the request type**. The API has three search layers — use the most specific one first: + +| Request Type | Best Method | Endpoint(s) | Example | +|---|---|---|---| +| **Specific episode by title/number** | Episode title search | 1. `/corpus/feeds?search=`
2. `/corpus/feeds/:feedId/episodes?search=` | "Find NAG25: Berlin" | +| **Topic within known episode** | Chapter keywords | 1. Get episode guid<br>2. `/corpus/episodes/:guid/chapters`<br>3. Filter keywords client-side | "Privacy topics in NAG25" | +| **All episodes from feed** | List episodes | `/corpus/feeds/:feedId/episodes` (paginated) | "All Bitcoin Park episodes" | +| **Topic across corpus** | Semantic search | `/search-quotes` with optional filters | "What do podcasters say about tariffs?" | +| **Vague/descriptive request** | Smart Search | `/search-quotes` with `smartMode: true` | "That time Sagar talked about the Israel lobby" | +| **Hybrid** (episode + topic) | Episode search → chapters → semantic fallback | Try title/chapters first, semantic if needed | "Zaprite episode discussing payments" | + +### Why This Order Matters + +- **Episode title search** is instant and exact (no token cost) +- **Chapters** give structured navigation within episodes — headlines, keywords, timestamps +- **Semantic search** is powerful but searches 1.9M paragraphs — slower, costs tokens +- **Smart Search** adds ~1-3s of LLM triage overhead but dramatically improves results for vague queries + +**When in doubt:** If a user mentions an episode title/number, try title search first. If the query is vague or mentions people/shows by name in a conversational way, use Smart Search. Only fall back to unfiltered semantic search as a last resort. + +### Episode Title Search + +Find episodes by name/number across the corpus: + +```bash +# Step 1: Find the feed +curl -s "API_BASE/api/corpus/feeds?search=News+and+Guidance" +# Returns: feedId (e.g., 7648986) + +# Step 2: Search episode titles within that feed +curl -s "API_BASE/api/corpus/feeds/7648986/episodes?search=Berlin&limit=20" +``` + +**No auth required** for corpus browsing. Returns: +- `title` — episode name +- `guid` — episode identifier (use for chapters and clip search) +- `publishedDate`, `duration`, `creator`, `imageUrl` + +Search is fuzzy — "Berlin" matches "NAG25: Berlin, Germany". + +### Chapter Search (Episode-Level) + +Get structured topic navigation within an episode: + +```bash +curl -s "API_BASE/api/corpus/episodes/EPISODE_GUID/chapters" +``` + +Returns chapters with: +- `headline` — chapter title +- `keywords` — array of topic tags (e.g., `["privacy tools", "Lightning Network"]`) +- `summary` — brief description +- `startTime`, `endTime`, `duration` — timestamps for clip generation +- `pineconeId` — use for semantic search or session items + +**Use case:** "What privacy topics are in NAG25?" → get chapters → filter by keywords like "privacy", "encryption", "surveillance". + +### Chapter Search (Corpus-Wide, L402) + +Search chapter keywords across the entire corpus: + +```bash +curl -s -X POST \ + -H "Authorization: L402 MACAROON:PREIMAGE" \ + -H "Content-Type: application/json" \ + -d '{"search": "Lightning Network", "limit": 20}' \ + "API_BASE/api/search-chapters" +``` + +**Cost:** $0.008 per call. Matches against curated chapter keyword tags using exact match. + +**Parameters:** +- `search` (required) — keyword to match (e.g., "Lightning Network", "privacy", "eCash") +- `feedIds` (optional) — array of feed IDs to scope results +- `limit` (default: 20, max: 200) — results per page +- `page` (default: 1) + +**Returns** each result as `{ chapter, episode }` — chapter metadata (headline, keywords, summary, timestamps) paired with the parent episode (title, creator, date, feedId, imageUrl). + +**Use case:** "Find all discussions about Lightning Network across every podcast" → returns chapters tagged with that keyword, organized with episode context. No need to iterate episode by episode. + +--- + +## Retrieve: Semantic Search + +When title/chapter search doesn't fit, use semantic search across all transcribed content: + +```bash +curl -s -X POST \ + -H "Authorization: L402 MACAROON:PREIMAGE" \ + -H "Content-Type: application/json" \ + -d '{"query": "Bitcoin Lightning Network scaling", "limit": 10}' \ + "API_BASE/api/search-quotes" +``` + +### Parameters + +| Parameter | Type | Required | Description | +|---|---|---|---| +| `query` | `string` | **Yes** | Semantic search string | +| `limit` | `number` | No | Number of results (default 10, max 50) | +| `feedIds` | `number[]` | No | Array of feed IDs to filter by specific podcasts. Must be an array: `[123, 456]` | +| `guid` | `string` | No | Single episode GUID to filter results to one episode | +| `guids` | `string[]` | No | Array of episode GUIDs to search across multiple specific episodes in one request | +| `minDate` | `string` | No | ISO date string for minimum publish date filter | +| `maxDate` | `string` | No | ISO date string for maximum publish date filter | +| `episodeName` | `string` | No | Filter by exact episode title | +| `smartMode` | `boolean` | No | `true` to enable LLM-powered query triage (see [Smart Search](#smart-search-smartmode) section) | + +> **Common mistakes:** +> - `episodeGuids` does not exist. Use `guid` (single) or `guids` (array). +> - `guid` accepts only a string, not an array. For multiple episodes, use `guids`. +> - `feedId` (singular) does not exist on this endpoint. Use `feedIds` (array). + +### Response Fields +Each result contains: +- `shareLink` — unique clip ID (use as `pineconeId` for sessions) +- `quote` — transcript text +- `episode` — episode title +- `creator` — podcast name +- `audioUrl` — direct audio file link +- `date` — publish date +- `similarity.combined` — relevance score (0-1, aim for >0.84) +- `timeContext.start_time` / `end_time` — timestamp in seconds +- `shareUrl` — **deeplink to exact audio moment** (give these to users!) +- `listenLink` — original episode link +- `episodeImage` — artwork + +### Multi-Angle Search Strategy + +For thorough coverage, run 4-6 queries per topic from different angles: + +1. **Broad topic** — "lightning network privacy" +2. **Comparative** — "why lightning is more private than on-chain" +3. **Technical** — "onion routing payment channels" +4. **Contrarian** — "lightning surveillance risks" +5. **Adjacent** — "ecash lightning combined privacy" + +Deduplicate results by `shareLink` across all queries. + +### Cost +~$0.002 per search. A full research session (6 queries × 10 results) costs ~$0.012. Smart Search adds ~$0.0001 per query for LLM triage. + +--- + +## Research: Build Sessions + +Research sessions are **interactive visual artifacts** — not text dumps. Users can: +- Play each audio clip inline +- Browse clips visually +- Click deeplinks to exact audio moments +- Share the session with anyone + +**The session URL is your primary deliverable.** Supplement with a brief summary, but always lead with the link. + +### Create a Session +```bash +curl -s -X POST \ + -H "Authorization: L402 MACAROON:PREIMAGE" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Compelling Session Title", + "description": "🔥 Theme one\n⚡ Theme two\n💡 Theme three", + "pineconeIds": ["clip_id_1", "clip_id_2"], + "items": [ + { + "pineconeId": "clip_id_1", + "metadata": { + "text": "quote text", + "creator": "Podcast Name", + "episodeTitle": "Episode Title", + "audioUrl": "https://...", + "episodeImage": "https://...", + "listenLink": "https://...", + "date": "2025-01-01", + "start_time": 120.5, + "end_time": 180.3, + "shareUrl": "https://...", + "shareLink": "clip_id_1" + } + } + ] + }' \ + "API_BASE/api/research-sessions?clientId=PAYMENT_HASH" +``` + +Returns `{"data": {"id": "SESSION_ID"}}` — note: `data.id`, not `id`. + +**`clientId` is required.** Pass your `paymentHash` as `clientId` via query param, header, or body. Without it the API returns "Missing owner identifier". + +### Session URL +``` +https://www.pullthatupjamie.ai/app?researchSessionId=SESSION_ID +``` +NOT `pullthatupjamie.ai/researchSession/ID` (that 404s). + +### Critical: Always Include `items` with Full Metadata +The backend needs client-provided metadata. Without the `items` array, clips save with `metadata: null` and the session breaks. + +### Curation Standards +- **10-12 clips per session** (18+ causes server hangs) +- **3 emoji bullet points** in description, one theme each +- **Compelling title** — specific, not generic +- **Most compelling clips first** — users scroll and bounce +- **Cap 2 clips per episode**, 3 per creator for diversity +- **Filter ad reads:** "brought to you by", "use code", sponsor URLs +- **Similarity > 0.83** preferred +- Clips must be substantive — no hot takes without depth, no casual banter + +--- + +## Analyze: Run Analysis + +```bash +curl -s -X POST \ + -H "Authorization: L402 MACAROON:PREIMAGE" \ + "API_BASE/api/research-sessions/SESSION_ID/analyze" +``` +Returns AI-generated analysis of the session content. + +--- + +## Share Sessions + +Generate a public share link with 3D visualization: + +```bash +curl -s -X POST \ + -H "Authorization: L402 MACAROON:PREIMAGE" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Share Title", + "visibility": "public" + }' \ + "API_BASE/api/research-sessions/SESSION_ID/share?clientId=PAYMENT_HASH" +``` + +`nodes` array is **optional** — if omitted, the backend auto-generates a semantically meaningful 3D layout from stored embeddings (UMAP projection). Omit for best results. Only pass `nodes` if you need a custom layout. + +Shared URL: `https://www.pullthatupjamie.ai/app?sharedSession=SHARE_ID` + +--- + +## Corpus Exploration + +### Browse Feeds (no auth required) +```bash +curl -s "API_BASE/api/corpus/feeds?page=1" +``` +Paginated (50/page). **Always paginate** with `?page=N`. Response data under `data` key (not `feeds`). + +### Corpus Stats +```bash +curl -s "API_BASE/api/corpus/stats" +``` + +### Feed Episodes +```bash +curl -s -H "Authorization: L402 MACAROON:PREIMAGE" \ + "API_BASE/api/corpus/feeds/FEED_ID/episodes" +``` + +Use corpus exploration to answer: +- "What podcasts are available?" → paginate feeds +- "Is [show] indexed?" → search feeds by title +- "What's the latest episode?" → check feed episodes by date + +--- + +## Ingestion: Add New Podcasts + +If a podcast isn't in the corpus yet, ingest it on demand from any RSS feed. All endpoints proxied through the Jamie API for security. + +### Recommended: Use the Discovery Endpoint + +The fastest path is `POST /api/discover-podcasts` — it takes a natural language query, searches the Podcast Index catalog via LLM-assisted extraction, and returns structured results with `transcriptAvailable` flags and ready-to-use next-step endpoints (including `submitOnDemandRun` body templates with GUIDs pre-filled for the top results). + +```bash +curl -s -X POST \ + -H "Authorization: L402 MACAROON:PREIMAGE" \ + -H "Content-Type: application/json" \ + -d '{"query": "Lex Fridman AI safety episodes"}' \ + "API_BASE/api/discover-podcasts" +``` + +Each result includes `nextSteps.requestTranscription` with the exact body to submit for transcription. For untranscribed results, episode GUIDs are included inline. + +### Manual Alternative: Direct RSS Endpoints + +If you already know the podcast name or have specific feed details: + +### Step 1: Search for the Podcast +```bash +curl -s -X POST "https://www.pullthatupjamie.ai/api/rss/searchFeeds" \ + -H "Authorization: L402 MACAROON:PREIMAGE" \ + -H "Content-Type: application/json" \ + -d '{"podcastName": "Podcast Name"}' +``` +Returns `data.feeds[]` with `id` (feedId), `title`, `url` (RSS URL). + +### Step 2: Get Feed Episodes +```bash +curl -s -X POST "https://www.pullthatupjamie.ai/api/rss/getFeed" \ + -H "Authorization: L402 MACAROON:PREIMAGE" \ + -H "Content-Type: application/json" \ + -d '{ + "feedUrl": "https://feeds.example.com/feed.xml", + "feedId": "12345", + "limit": 50, + "skipCleanGuid": true + }' +``` +Returns: +- `episodes.feedInfo` — feed metadata (feedGuid, feedTitle, etc.) +- `episodes.episodes[]` — array of episodes with `episodeGUID`, `itemTitle`, `publishedDate`, `enclosureUrl` + +Extract `feedGuid` from `feedInfo.feedGuid`. + +### Step 3: Confirm with User +**Always show the episode list and get approval before ingesting.** Never auto-submit. Present episode titles and dates for review. + +### Step 4: Submit Ingestion +```bash +curl -s -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: L402 MACAROON:PREIMAGE" \ + -d '{ + "message": "Ingest episodes for [Podcast Name]", + "parameters": {}, + "episodes": [ + {"guid": "episode-guid", "feedGuid": "feed-guid", "feedId": 12345} + ] + }' \ + "https://www.pullthatupjamie.ai/api/on-demand/submitOnDemandRun" +``` +- `parameters` must be `{}` (required but empty) +- Response: `jobId` at top level +- `guid` comes from `episodeGUID` field in getFeed response +- `feedGuid` comes from `feedInfo.feedGuid` field + +### Step 5: Poll Status +```bash +curl -s "https://www.pullthatupjamie.ai/api/on-demand/getOnDemandJobStatus/JOB_ID" \ + -H "Authorization: L402 MACAROON:PREIMAGE" +``` +Poll every 30-60 seconds. Status: `pending` → `complete` or `failed`. Typical: 8 episodes in ~1 minute. + +--- + +## Known Feed IDs +| Feed | ID | +|---|---| +| TFTC | 226249 | +| Bitcoin Park | 5702105 | +| Thank God for Nostr | 6437926 | +| Stacker News Live | 4866432 | +| Stacker Sports Pod | 7050096 | +| No Agenda Show | 41504 | +| Convos On The Pedicab | 3498055 | + +Browse `GET /api/corpus/feeds` for the full list. + +--- + +## Footguns +- API base: `https://www.pullthatupjamie.ai` (must include `www.` — bare domain redirects and breaks API calls) +- `episodeCount` in feeds response caps at 1,000 per feed — not the true count for large feeds +- People/episodes responses have `feedTitle: null` — cross-reference `feedId` with corpus/feeds to get show names +- Session response: `data.id`, NOT `id` +- Feeds response: `data` key, NOT `feeds` +- Always include `items` array with metadata in session creation +- Share endpoint: `nodes` is optional (backend auto-layouts from embeddings if omitted) +- `shareLink` = `pineconeId` (interchangeable) +- RSS endpoints: Always use `/api/rss/*` (proxied through Jamie API for security) +- `submitOnDemandRun` needs `"parameters": {}` even if empty +- Always confirm episodes with user before ingesting +- If results look wrong, check the echoed `query` field in the response — it should match your input exactly +- Smart Search `triage` object is only present when `smartMode: true` — don't assume it exists in standard responses diff --git a/package-lock.json b/package-lock.json index 8756e21..25b395a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,10 @@ "wav": "^1.0.2", "web-streams-polyfill": "^4.0.0", "ws": "^8.18.3" + }, + "devDependencies": { + "archiver": "^7.0.1", + "js-yaml": "^4.1.1" } }, "node_modules/@aws-crypto/crc32": { @@ -1320,6 +1324,24 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@langchain/classic": { "version": "1.0.27", "resolved": "https://registry.npmjs.org/@langchain/classic/-/classic-1.0.27.tgz", @@ -2146,6 +2168,17 @@ "node": ">=18.0.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@redis/bloom": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", @@ -3114,6 +3147,19 @@ "node": ">= 8.0.0" } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3141,6 +3187,146 @@ "node": ">=0.2.6" } }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/node_modules/tar-stream": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", + "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3190,12 +3376,124 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/b4a": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", + "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.6.0.tgz", + "integrity": "sha512-2YkS7NuiJceSEbyEOdSNLE9tsGd+f4+f7C+Nik/MCk27SYdwIMPT/yRKvg++FZhQXgk0KWJKJyXX9RhVV0RGqA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz", + "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.12.0.tgz", + "integrity": "sha512-w28i8lkBgREV3rPXGbgK+BO66q+ZpKqRWrZLiCdmmUlLPrQ45CzkvRhN+7lnv00Gpi2zy5naRxnUFAxCECDm9g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz", + "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, "node_modules/base-x": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.1.tgz", @@ -3481,6 +3779,16 @@ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", "license": "MIT" }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -3667,6 +3975,40 @@ "node": ">= 0.8" } }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3763,6 +4105,50 @@ "node": ">= 0.4.0" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -3776,6 +4162,37 @@ "sha.js": "^2.4.0" } }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/crypto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", @@ -3988,6 +4405,13 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -4027,6 +4451,13 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -4132,6 +4563,26 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -4234,6 +4685,13 @@ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-xml-builder": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", @@ -4353,6 +4811,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -4521,6 +4996,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/handlebars": { "version": "4.7.9", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", @@ -4826,6 +5308,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", @@ -4859,6 +5364,22 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/js-tiktoken": { "version": "1.0.21", "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz", @@ -5088,6 +5609,19 @@ } } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -5136,6 +5670,13 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/macaroons.js": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/macaroons.js/-/macaroons.js-0.3.9.tgz", @@ -5296,6 +5837,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -5707,6 +6258,16 @@ "he": "1.2.0" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nostr-tools": { "version": "2.23.0", "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.23.0.tgz", @@ -5921,6 +6482,13 @@ "node": ">=8" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -5954,6 +6522,33 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/path-to-regexp": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", @@ -6165,6 +6760,16 @@ "node": ">=10" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -6302,6 +6907,39 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/redis": { "version": "4.7.1", "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz", @@ -6557,6 +7195,29 @@ "@img/sharp-win32-x64": "0.33.5" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -6635,6 +7296,19 @@ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", "license": "MIT" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -6749,6 +7423,18 @@ "debug": "2" } }, + "node_modules/streamx": { + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -6758,6 +7444,110 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "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-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -6877,6 +7667,26 @@ "node": ">= 6" } }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/to-buffer": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", @@ -7254,6 +8064,101 @@ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "license": "MIT" }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -7311,6 +8216,38 @@ "url": "https://github.com/sponsors/eemeli" } }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "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 e4a4cf6..af4cf77 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,10 @@ "setup-subtitle-test": "node setup-subtitle-test.js", "test-subtitles": "node test-subtitles.js", "test-server-subtitles": "node test-server-subtitles.js", - "generate-openapi": "node swagger.js" + "generate-openapi": "node swagger.js", + "skills:build": "node scripts/skills/build-smithery.js && node scripts/skills/build-clawhub-zip.js", + "skills:build:smithery": "node scripts/skills/build-smithery.js", + "skills:build:clawhub": "node scripts/skills/build-clawhub-zip.js" }, "author": "uj21", "license": "ISC", @@ -51,5 +54,9 @@ "wav": "^1.0.2", "web-streams-polyfill": "^4.0.0", "ws": "^8.18.3" + }, + "devDependencies": { + "archiver": "^7.0.1", + "js-yaml": "^4.1.1" } } diff --git a/scripts/.DS_Store b/scripts/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e87a7d088badc82f210d6c90814eb9de9d538ba2 GIT binary patch literal 6148 zcmeHKI|>3Z5S>vG!N$@uSMUZw^aOhW3qeFtP_*94b9pr1e41so(?;IF<Rz1N33<iN zj)>^;wq1zKL}UUtl!py{vwicP4KkuYIL<iA_1;`g$Mb2|%YGj)?pUsJkUiL4zU|Sd z02QDDRDcRlf$u7i^>s4+?y)?L3Q&PRP{6(q1#Vaq+d%(xVDJ_II6~MBbMGa9#R9;Z z*ajj3)1U%_s@Y;_&=D`0R}<U7po?bnp?R}rhoXKv&M%%WS_3&!0V>c{U>M7p)&Dj8 zL;v3;aYY5Fz+Wk#qgAt7;z?OsdylhPTi_eG<vihLm^%f7mt&xpV=Sy3&pj#fip{ZK V6Wc(iBkpt{e+En!8Ws4p0x#+b6}SKZ literal 0 HcmV?d00001 diff --git a/scripts/skills/build-clawhub-zip.js b/scripts/skills/build-clawhub-zip.js new file mode 100644 index 0000000..405c49d --- /dev/null +++ b/scripts/skills/build-clawhub-zip.js @@ -0,0 +1,84 @@ +#!/usr/bin/env node +'use strict'; + +/** + * build-clawhub-zip.js + * + * Packages the source skills/pullthatupjamie/ directory into a zip file + * at dist/clawhub/pullthatupjamie-v{version}.zip, suitable for manual + * upload to ClawHub at https://clawhub.ai/publish. + * + * Version is read from _meta.json first, then SKILL.md frontmatter. + * All files in the skill directory are included (including _meta.json, + * CHANGELOG.md, and references/), excluding .DS_Store and node_modules. + */ + +const fs = require('fs'); +const path = require('path'); +const yaml = require('js-yaml'); +const archiver = require('archiver'); + +const REPO_ROOT = path.resolve(__dirname, '../..'); +const SKILL_DIR = path.join(REPO_ROOT, 'skills/pullthatupjamie'); +const DIST_DIR = path.join(REPO_ROOT, 'dist/clawhub'); + +function getVersion() { + const metaPath = path.join(SKILL_DIR, '_meta.json'); + if (fs.existsSync(metaPath)) { + try { + const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); + if (meta.version) return meta.version; + } catch {} + } + const skillContent = fs.readFileSync(path.join(SKILL_DIR, 'SKILL.md'), 'utf8'); + const match = skillContent.match(/^---\r?\n([\s\S]*?)\r?\n---/); + if (match) { + const fm = yaml.load(match[1]); + if (fm && fm.version) return String(fm.version); + } + return '0.0.0'; +} + +async function main() { + const version = getVersion(); + fs.mkdirSync(DIST_DIR, { recursive: true }); + + const outputPath = path.join(DIST_DIR, `pullthatupjamie-v${version}.zip`); + if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath); + + const output = fs.createWriteStream(outputPath); + const archive = archiver('zip', { zlib: { level: 9 } }); + archive.pipe(output); + + // Add all visible files + archive.glob('**/*', { + cwd: SKILL_DIR, + ignore: ['node_modules/**', '.DS_Store', '.smithery-overrides.yaml'], + dot: false, + }); + + // _meta.json starts with _ so dot:false skips it — add explicitly + const metaPath = path.join(SKILL_DIR, '_meta.json'); + if (fs.existsSync(metaPath)) { + archive.file(metaPath, { name: '_meta.json' }); + } + + // .smithery-overrides.yaml is a build artifact, not needed in ClawHub zip + + await new Promise((resolve, reject) => { + output.on('close', resolve); + archive.on('error', reject); + archive.finalize(); + }); + + const relPath = path.relative(REPO_ROOT, outputPath); + const bytes = archive.pointer(); + console.log(`✅ ClawHub zip: ${relPath}`); + console.log(` Version: ${version} | Size: ${(bytes / 1024).toFixed(1)} KB`); + console.log(` Upload at: https://clawhub.ai/publish`); +} + +main().catch((err) => { + console.error('❌ Zip build failed:', err.message); + process.exit(1); +}); diff --git a/scripts/skills/build-smithery.js b/scripts/skills/build-smithery.js new file mode 100644 index 0000000..572486d --- /dev/null +++ b/scripts/skills/build-smithery.js @@ -0,0 +1,127 @@ +#!/usr/bin/env node +'use strict'; + +/** + * build-smithery.js + * + * Transforms the source skills/pullthatupjamie/SKILL.md (ClawHub format) into + * a dist/smithery/pullthatupjamie/SKILL.md that is fully agentskills.io-compliant, + * ready for Smithery registry submission via gitUrl. + * + * Transforms applied: + * - Moves `version` and `homepage` from top-level frontmatter into metadata + * - Adds `license` from overrides (default: Proprietary) + * - Adds `compatibility` from overrides if not already set + * - Replaces `description` with the override value (required: source exceeds 1024 chars) + * - Copies references/ directory alongside SKILL.md + * + * Config: skills/pullthatupjamie/.smithery-overrides.yaml + */ + +const fs = require('fs'); +const path = require('path'); +const yaml = require('js-yaml'); + +const REPO_ROOT = path.resolve(__dirname, '../..'); +const SKILL_DIR = path.join(REPO_ROOT, 'skills/pullthatupjamie'); +const DIST_DIR = path.join(REPO_ROOT, 'dist/smithery/pullthatupjamie'); +const OVERRIDES_PATH = path.join(SKILL_DIR, '.smithery-overrides.yaml'); +const MAX_DESCRIPTION_LENGTH = 1024; + +function parseFrontmatter(content) { + const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/); + if (!match) throw new Error('No YAML frontmatter found in SKILL.md'); + return { data: yaml.load(match[1]), body: match[2] }; +} + +function serializeFrontmatter(data) { + return `---\n${yaml.dump(data, { lineWidth: -1 })}---\n`; +} + +function copyDir(src, dest) { + fs.mkdirSync(dest, { recursive: true }); + for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + if (entry.isDirectory()) { + copyDir(srcPath, destPath); + } else { + fs.copyFileSync(srcPath, destPath); + } + } +} + +function main() { + const skillPath = path.join(SKILL_DIR, 'SKILL.md'); + if (!fs.existsSync(skillPath)) { + throw new Error(`SKILL.md not found at ${skillPath}`); + } + + const { data: fm, body } = parseFrontmatter(fs.readFileSync(skillPath, 'utf8')); + const overrides = fs.existsSync(OVERRIDES_PATH) + ? yaml.load(fs.readFileSync(OVERRIDES_PATH, 'utf8')) || {} + : {}; + + const out = { ...fm }; + + // Move non-spec top-level fields into metadata + out.metadata = { ...(out.metadata || {}) }; + if (out.version !== undefined) { + out.metadata.version = String(out.version); + delete out.version; + } + if (out.homepage !== undefined) { + out.metadata.homepage = String(out.homepage); + delete out.homepage; + } + + // Add license if missing + if (!out.license) { + out.license = overrides.license || 'Proprietary'; + } + + // Add compatibility if missing + if (!out.compatibility && overrides.compatibility) { + out.compatibility = overrides.compatibility; + } + + // Apply description override (required — source exceeds 1024 chars) + if (overrides.description) { + out.description = overrides.description; + } else if (out.description && out.description.length > MAX_DESCRIPTION_LENGTH) { + console.warn( + `⚠️ Description is ${out.description.length} chars (max ${MAX_DESCRIPTION_LENGTH}).\n` + + ` Add a 'description' key to .smithery-overrides.yaml to provide a shorter version.\n` + + ` Auto-truncating for now...` + ); + out.description = out.description.slice(0, MAX_DESCRIPTION_LENGTH - 3) + '...'; + } + + if (out.description && out.description.length > MAX_DESCRIPTION_LENGTH) { + console.error(`❌ Description still ${out.description.length} chars after transforms (max ${MAX_DESCRIPTION_LENGTH}).`); + process.exit(1); + } + + // Write SKILL.md to dist + fs.mkdirSync(DIST_DIR, { recursive: true }); + fs.writeFileSync(path.join(DIST_DIR, 'SKILL.md'), serializeFrontmatter(out) + body); + + // Copy references/ + const refsDir = path.join(SKILL_DIR, 'references'); + if (fs.existsSync(refsDir)) { + copyDir(refsDir, path.join(DIST_DIR, 'references')); + } + + const relDist = path.relative(REPO_ROOT, DIST_DIR); + console.log(`✅ Smithery dist written to ${relDist}/`); + console.log(` Description: ${out.description.length}/${MAX_DESCRIPTION_LENGTH} chars`); + console.log(` Fields moved to metadata: ${[fm.version && 'version', fm.homepage && 'homepage'].filter(Boolean).join(', ') || 'none'}`); + console.log(` License: ${out.license}`); +} + +try { + main(); +} catch (err) { + console.error('❌ Build failed:', err.message); + process.exit(1); +} diff --git a/skills/pullthatupjamie/.smithery-overrides.yaml b/skills/pullthatupjamie/.smithery-overrides.yaml new file mode 100644 index 0000000..ce4d649 --- /dev/null +++ b/skills/pullthatupjamie/.smithery-overrides.yaml @@ -0,0 +1,28 @@ +# Smithery/agentskills.io overrides for SKILL.md +# +# Applied by scripts/skills/build-smithery.js to produce +# dist/smithery/pullthatupjamie/SKILL.md — a spec-compliant version +# of the source skill for Smithery registry submission. +# +# The source SKILL.md (this directory) is the ClawHub source of truth. +# Smithery reads from dist/smithery/pullthatupjamie/ via gitUrl. + +license: "Proprietary" + +compatibility: >- + Requires internet access to https://www.pullthatupjamie.ai. + Free tier works immediately with no credentials. + Paid tier requires a Lightning-compatible wallet (Zeus, BlueWallet, Phoenix, Alby, etc.) + for L402 payments. lnget handles payment flow automatically. + +# Source description exceeds the 1024-char agentskills.io limit — this replaces it. +description: >- + PullThatUpJamie — Podcast Intelligence. Semantically indexed corpus: 109+ feeds, + ~7K episodes, ~1.9M paragraphs. Use when agents need to: (1) find what experts said + across major podcasts (Rogan, Huberman, Bloomberg, TFTC, Lex Fridman, etc.), + (2) build interactive research sessions with timestamped playable audio clips, + (3) generate shareable MP4 clips with burned-in subtitles, (4) discover people/orgs + and podcast appearances, (5) ingest new feeds on demand. Smart search (smartMode): + LLM triage for vague queries — entity extraction, corpus metadata resolution, query + rewriting. Three-tier strategy: title → chapter → semantic. Free tier: no credentials + needed. Paid tier via L402 Lightning: 500 sats (~$0.33) covers 150+ searches. lnget compatible.