Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a8e0442
feat(examples): website browse/media pages, CLI TUI, and meta provide…
hexxt-git Jun 18, 2026
6892076
feat(scaffold): Phase 0 — internal layout + empty 2.0 scaffolding
hexxt-git Jun 18, 2026
d89061b
feat(dom): Phase 1 — bundled DOM parser (already implemented)
hexxt-git Jun 18, 2026
db83c04
feat(types): Phase 2 — value types, AniError, SdkOptions
hexxt-git Jun 18, 2026
4ffb74f
feat(ids): Phase 3 — opaque base64url ID encode/decode
hexxt-git Jun 18, 2026
1a2cf3c
feat(registry): Phase 4 — Source interface, registry, health tracker,…
hexxt-git Jun 18, 2026
844e716
feat(progressive): Phase 5 — ProgressiveResult + AbortSignal plumbing
hexxt-git Jun 18, 2026
876bd08
feat(sources): Phase 6 — migrate all 11 providers to Source interface
hexxt-git Jun 18, 2026
93f8da2
feat(sdk): Phase 7 — Sdk class, createSdk(), public surface
hexxt-git Jun 18, 2026
f42b7fb
feat(server): Phase 8 — thin server v2, routes, CLI entry
hexxt-git Jun 18, 2026
5a6fc4f
feat(examples): Phase 9 — rewrite examples against 2.0 SDK
hexxt-git Jun 18, 2026
e2e28fa
feat(cleanup): Phase 10 — trim public surface, delete obsolete tests
hexxt-git Jun 18, 2026
c2c96d7
docs(2.0): Phase 11 — README, CLAUDE.md, changeset for v2.0
hexxt-git Jun 18, 2026
329c607
fix(registry): wire resolveMediaId() for cross-source episode lookups
hexxt-git Jun 18, 2026
9647bd4
feat(content): update marketing website, CLI, and Ink TUI to 2.0 API
hexxt-git Jun 19, 2026
edd59a9
docs: rewrite all marketing + documentation content for 2.0 API
hexxt-git Jun 19, 2026
350792a
chore: drop 1.x compat surface; rename startServerV2 → startServer
hexxt-git Jun 23, 2026
a40e0e6
feat: proxy, download module, fuzzy title resolution, and browser UA …
hexxt-git Jun 25, 2026
3b383f6
feat: transition to progressive multi-source stream resolution
hexxt-git Jun 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changeset/sdk-2-0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'anime-sdk': major
---

# anime-sdk 2.0

A complete redesign around a single `createSdk()` factory and nine verbs.

## Highlights

- **`createSdk()`**: zero-config factory. All 12 sources, built-in DOM parser, sane rate limits.
- **Unified `Media`/`Episode`/`Chapter` types**: plain POJOs, `JSON.stringify`-safe, opaque `id` fields.
- **`ProgressiveResult<T>`**: `AsyncIterable<T>` + `PromiseLike<T[]>` for search results.
- **`Stream.adjacent`**: prev/next episode IDs without a second fetch.
- **`Stream.origin`**: `{ host, url, proxied }` — no URL parsing on the consumer side.
- **`Score { value, scale }`**: units carried.
- **`sdk.sources(media)`**: ranked list of playable providers — safe "Watch via" dropdown.
- **`npx anime-sdk`**: zero-install server. `PORT`, `SOURCES_DISABLED`, `PROXY_*` env vars.
- **`startServer({ proxy })`**: optional `/proxy` endpoint with SSRF allowlist + HMAC signing. Every `Stream`/`Pages` URL the server emits gets rewritten to be browser-playable.
- **`downloadVideo` / `downloadMangaChapter` / `downloadMangaPage`**: built-in MP4 (via ffmpeg mux) and ZIP downloaders that work on `Stream`/`Pages` directly.
- **Manga has no language axis**: `sdk.pages(chapter)` — no `language` argument.
- **`AniError` + `AniErrorCode`**: structured error type for branching without string matching.
- **AbortSignal on every async call**.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ dist/
references/
.DS_Store
*.log
scratch
scratch
examples/website/tsconfig.tsbuildinfo
*.tsbuildinfo
119 changes: 57 additions & 62 deletions CLAUDE.md

Large diffs are not rendered by default.

371 changes: 132 additions & 239 deletions README.md

Large diffs are not rendered by default.

89 changes: 47 additions & 42 deletions examples/cli.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { createInterface } from 'node:readline/promises';
import { stdin, stdout } from 'node:process';
import { HttpClient, GogoanimeProvider, GoyabuProvider, AllmangaProvider } from '../dist/index.js';
import { stdin } from 'node:process';
import { createSdk } from '../dist/index.js';

const io = createInterface({ input: stdin, output: stdout });

const http = new HttpClient({ timeoutMs: 30000 });
const PROVIDERS = [
new GogoanimeProvider(http),
new GoyabuProvider(http),
new AllmangaProvider(http),
];
const io = createInterface({ input: stdin, output: process.stdout });
const sdk = createSdk();

async function pick(items, label) {
items.forEach((item, i) => console.log(` ${i + 1}. ${item}`));
Expand All @@ -19,58 +13,69 @@ async function pick(items, label) {

console.log('\n═══ anime-sdk CLI ═══\n');

const pi = await pick(
PROVIDERS.map((p) => p.id),
'Provider',
);
const provider = PROVIDERS[pi];

const query = await io.question('\nSearch: ');
const kind = (await io.question('Kind (anime/manga) [anime]: ')).trim() || 'anime';
const query = await io.question('Search: ');
process.stdout.write('...\n');

const results = await provider.search(query);
const results = await sdk.search(query, { kind });
if (!results.length) {
console.log('No results.');
process.exit(0);
}

console.log('');
const ri = await pick(
results.map((r) => `${r.title} [${r.catalogType}]`),
results.map((r) => `${r.title.preferred} [${r.kind}]`),
'Select title',
);
const media = results[ri];

process.stdout.write('...\n');
const units = await provider.fetchContentUnits(media.id);
if (!units.length) {
console.log('No episodes.');
process.exit(0);
}

console.log('');
const ui = await pick(
units.map((u) => `EP.${String(u.number).padStart(3, '0')} ${u.title} (${u.language})`),
'Select episode',
);
const unit = units[ui];
if (kind === 'manga') {
const { items: chapters } = await sdk.chapters(media);
if (!chapters.length) {
console.log('No chapters.');
process.exit(0);
}

process.stdout.write('...\n');
const stream = await provider.resolveStream(unit.id);
console.log('');
const ci = await pick(
chapters.map((c) => `Ch.${String(c.number).padStart(3, '0')} ${c.title ?? ''}`),
'Select chapter',
);
const chapter = chapters[ci];
process.stdout.write('...\n');
const pages = await sdk.pages(chapter);
console.log(`\n─── PAGES (${pages.pages.length}) ───`);
pages.pages.slice(0, 3).forEach((p) => console.log(p.url));
if (pages.pages.length > 3) console.log(`... (${pages.pages.length - 3} more)`);
} else {
const { items: episodes } = await sdk.episodes(media);
if (!episodes.length) {
console.log('No episodes.');
process.exit(0);
}

console.log('');
const ei = await pick(
episodes.map(
(e) =>
`EP.${String(e.number).padStart(3, '0')} ${e.title ?? ''} (${(e.languages ?? ['sub']).join('/')})`,
),
'Select episode',
);
const episode = episodes[ei];
process.stdout.write('...\n');
const streams = await sdk.stream(episode);

console.log('\n─── STREAM ───');
if (stream.type === 'video') {
for (const s of stream.streams) {
console.log(`\n─── STREAMS (${streams.length}) ───`);
for (const s of streams) {
console.log(
`\n[${s.isHLS ? 'HLS' : 'MP4'}] ${s.quality}${s.language ? ' ' + s.language : ''}`,
`[${s.isHls ? 'HLS' : 'MP4'}] ${s.language} ${s.quality} server: ${s.server} source: ${s.source}`,
);
console.log(s.sourceUrl);
if (s.headers && Object.keys(s.headers).length)
console.log('headers:', JSON.stringify(s.headers));
console.log(` ${s.url}`);
}
} else if (stream.type === 'manga') {
console.log(`${stream.pages.imageUrls.length} pages`);
stream.pages.imageUrls.slice(0, 3).forEach((u) => console.log(u));
}

io.close();
Loading