@diffdelta/client 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cursor.ts","../src/models.ts","../src/client.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nconst DEFAULT_CURSOR_DIR = join(homedir(), \".diffdelta\");\nconst DEFAULT_CURSOR_FILE = \"cursors.json\";\n\n/**\n * Persists cursors to a local JSON file so bots survive restarts.\n *\n * By default, cursors are saved to ~/.diffdelta/cursors.json.\n * Each feed key gets its own cursor entry.\n */\nexport class CursorStore {\n private path: string;\n private cursors: Record<string, string> = {};\n\n constructor(path?: string) {\n this.path =\n path ||\n process.env.DD_CURSOR_PATH ||\n join(DEFAULT_CURSOR_DIR, DEFAULT_CURSOR_FILE);\n this.load();\n }\n\n /** Get the stored cursor for a feed key. */\n get(key: string): string | undefined {\n return this.cursors[key];\n }\n\n /** Save a cursor and persist to disk. */\n set(key: string, cursor: string): void {\n this.cursors[key] = cursor;\n this.save();\n }\n\n /** Clear cursor(s). If key is undefined, clears all cursors. */\n clear(key?: string): void {\n if (key) {\n delete this.cursors[key];\n } else {\n this.cursors = {};\n }\n this.save();\n }\n\n private load(): void {\n try {\n if (existsSync(this.path)) {\n const raw = readFileSync(this.path, \"utf-8\");\n const data = JSON.parse(raw);\n if (typeof data === \"object\" && data !== null) {\n this.cursors = data;\n }\n }\n } catch {\n // Corrupted file — start fresh\n this.cursors = {};\n }\n }\n\n private save(): void {\n try {\n const dir = dirname(this.path);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(this.path, JSON.stringify(this.cursors, null, 2));\n } catch {\n // Can't write — silently continue (in-memory only)\n }\n }\n}\n\n/**\n * In-memory-only cursor store (no file I/O).\n * Useful for serverless, browser, or Deno environments.\n */\nexport class MemoryCursorStore {\n private cursors: Record<string, string> = {};\n\n get(key: string): string | undefined {\n return this.cursors[key];\n }\n\n set(key: string, cursor: string): void {\n this.cursors[key] = cursor;\n }\n\n clear(key?: string): void {\n if (key) {\n delete this.cursors[key];\n } else {\n this.cursors = {};\n }\n }\n}\n","/** A single item from a DiffDelta feed. */\nexport interface FeedItem {\n /** Source identifier (e.g. \"cisa_kev\", \"nist_nvd\"). */\n source: string;\n /** Unique item ID within the source. */\n id: string;\n /** Human/agent-readable headline. */\n headline: string;\n /** Link to the original source. */\n url: string;\n /** Summary text extracted from the source. */\n excerpt: string;\n /** When the item was originally published. */\n publishedAt: string | null;\n /** When the item was last updated. */\n updatedAt: string | null;\n /** Which change bucket: \"new\", \"updated\", or \"removed\". */\n bucket: \"new\" | \"updated\" | \"removed\";\n /** Risk score 0–10, or null if not scored. */\n riskScore: number | null;\n /** Raw provenance data (fetched_at, evidence_urls, content_hash). */\n provenance: Record<string, unknown>;\n /** The full raw item object from the feed. */\n raw: Record<string, unknown>;\n}\n\n/** The lightweight head pointer for change detection. */\nexport interface Head {\n /** Opaque cursor string for change detection. */\n cursor: string;\n /** Content hash of the latest feed. */\n hash: string;\n /** Whether content has changed since last generation. */\n changed: boolean;\n /** When this head was generated. */\n generatedAt: string;\n /** Recommended polling interval in seconds. */\n ttlSec: number;\n}\n\n/** A full DiffDelta feed response. */\nexport interface Feed {\n /** The new cursor (save this for next poll). */\n cursor: string;\n /** The previous cursor. */\n prevCursor: string;\n /** Source ID (if per-source feed) or \"global\". */\n sourceId: string;\n /** When this feed was generated. */\n generatedAt: string;\n /** All items across all buckets. */\n items: FeedItem[];\n /** Items in the \"new\" bucket. */\n new: FeedItem[];\n /** Items in the \"updated\" bucket. */\n updated: FeedItem[];\n /** Items in the \"removed\" bucket. */\n removed: FeedItem[];\n /** Human-readable summary of what changed. */\n narrative: string;\n /** The full raw feed object. */\n raw: Record<string, unknown>;\n}\n\n/** Metadata about an available DiffDelta source. */\nexport interface SourceInfo {\n /** Unique source identifier (e.g. \"cisa_kev\"). */\n sourceId: string;\n /** Human-readable display name. */\n name: string;\n /** List of tags (e.g. [\"security\"]). */\n tags: string[];\n /** Brief description of the source. */\n description: string;\n /** URL of the source's homepage. */\n homepage: string;\n /** Whether the source is currently active. */\n enabled: boolean;\n /** Health status (\"ok\", \"degraded\", \"error\"). */\n status: string;\n /** Path to the source's head.json. */\n headUrl: string;\n /** Path to the source's latest.json. */\n latestUrl: string;\n}\n\n// ── Parsing helpers ──\n\n/** Parse a raw feed item into a typed FeedItem. */\nexport function parseFeedItem(\n data: Record<string, unknown>,\n bucket: \"new\" | \"updated\" | \"removed\" = \"new\"\n): FeedItem {\n const content = data.content as Record<string, unknown> | string | undefined;\n let excerpt = \"\";\n if (typeof content === \"object\" && content !== null) {\n excerpt =\n (content.excerpt_text as string) ||\n (content.summary as string) ||\n \"\";\n } else if (typeof content === \"string\") {\n excerpt = content;\n }\n\n // Extract risk_score from top-level or nested summary\n let riskScore: number | null = (data.risk_score as number) ?? null;\n if (riskScore === null) {\n const summary = data.summary as Record<string, unknown> | undefined;\n if (typeof summary === \"object\" && summary !== null) {\n riskScore = (summary.risk_score as number) ?? null;\n }\n }\n\n return {\n source: (data.source as string) || \"\",\n id: (data.id as string) || \"\",\n headline: (data.headline as string) || \"\",\n url: (data.url as string) || \"\",\n excerpt,\n publishedAt: (data.published_at as string) || null,\n updatedAt: (data.updated_at as string) || null,\n bucket,\n riskScore,\n provenance: (data.provenance as Record<string, unknown>) || {},\n raw: data,\n };\n}\n\n/** Parse a raw head.json response into a typed Head. */\nexport function parseHead(data: Record<string, unknown>): Head {\n return {\n cursor: (data.cursor as string) || \"\",\n hash: (data.hash as string) || \"\",\n changed: (data.changed as boolean) || false,\n generatedAt: (data.generated_at as string) || \"\",\n ttlSec: (data.ttl_sec as number) || 900,\n };\n}\n\n/** Parse a raw latest.json response into a typed Feed. */\nexport function parseFeed(data: Record<string, unknown>): Feed {\n const buckets = (data.buckets as Record<string, unknown[]>) || {};\n const newItems = (buckets.new || []).map((i) =>\n parseFeedItem(i as Record<string, unknown>, \"new\")\n );\n const updatedItems = (buckets.updated || []).map((i) =>\n parseFeedItem(i as Record<string, unknown>, \"updated\")\n );\n const removedItems = (buckets.removed || []).map((i) =>\n parseFeedItem(i as Record<string, unknown>, \"removed\")\n );\n\n return {\n cursor: (data.cursor as string) || \"\",\n prevCursor: (data.prev_cursor as string) || \"\",\n sourceId: (data.source_id as string) || \"\",\n generatedAt: (data.generated_at as string) || \"\",\n items: [...newItems, ...updatedItems, ...removedItems],\n new: newItems,\n updated: updatedItems,\n removed: removedItems,\n narrative: (data.batch_narrative as string) || \"\",\n raw: data,\n };\n}\n\n/** Parse a raw source object into a typed SourceInfo. */\nexport function parseSourceInfo(data: Record<string, unknown>): SourceInfo {\n return {\n sourceId: (data.source_id as string) || \"\",\n name: (data.name as string) || \"\",\n tags: (data.tags as string[]) || [],\n description: (data.description as string) || \"\",\n homepage: (data.homepage as string) || \"\",\n enabled: (data.enabled as boolean) ?? true,\n status: (data.status as string) || \"ok\",\n headUrl: (data.head_url as string) || \"\",\n latestUrl: (data.latest_url as string) || \"\",\n };\n}\n","/**\n * DiffDelta TypeScript client — agent-ready intelligence feeds.\n *\n * @example\n * ```ts\n * import { DiffDelta } from \"diffdelta\";\n *\n * const dd = new DiffDelta();\n *\n * // Poll for new items across all sources\n * const items = await dd.poll();\n * items.forEach(i => console.log(`${i.source}: ${i.headline}`));\n *\n * // Poll only security sources\n * const sec = await dd.poll({ tags: [\"security\"] });\n *\n * // Continuous monitoring\n * dd.watch(item => console.log(\"🚨\", item.headline), { tags: [\"security\"] });\n * ```\n */\n\nimport { CursorStore, MemoryCursorStore } from \"./cursor.js\";\nimport type { FeedItem, Feed, Head, SourceInfo } from \"./models.js\";\nimport { parseFeedItem, parseFeed, parseHead, parseSourceInfo } from \"./models.js\";\n\nconst VERSION = \"0.1.0\";\nconst DEFAULT_BASE_URL = \"https://diffdelta.io\";\nconst DEFAULT_TIMEOUT = 15_000; // ms\n\nexport interface DiffDeltaOptions {\n /** DiffDelta API base URL. Defaults to https://diffdelta.io. */\n baseUrl?: string;\n /** Pro/Enterprise API key (dd_live_...). */\n apiKey?: string;\n /**\n * Path to cursor file. Defaults to ~/.diffdelta/cursors.json.\n * Set to `null` to disable file persistence (in-memory only).\n * Set to `\"memory\"` for explicit in-memory mode (serverless, edge).\n */\n cursorPath?: string | null;\n /** HTTP timeout in milliseconds. Defaults to 15000. */\n timeout?: number;\n}\n\nexport interface PollOptions {\n /** Filter items to these tags (e.g. [\"security\"]). */\n tags?: string[];\n /** Filter items to these source IDs (e.g. [\"cisa_kev\", \"nist_nvd\"]). */\n sources?: string[];\n /**\n * Which buckets to return.\n * Defaults to [\"new\", \"updated\"].\n * Use [\"new\", \"updated\", \"removed\"] to include removals.\n */\n buckets?: Array<\"new\" | \"updated\" | \"removed\">;\n}\n\nexport interface WatchOptions extends PollOptions {\n /** Seconds between polls. Defaults to feed TTL (usually 900s). */\n interval?: number;\n /** If provided, an AbortSignal to stop watching. */\n signal?: AbortSignal;\n}\n\ninterface CursorStoreInterface {\n get(key: string): string | undefined;\n set(key: string, cursor: string): void;\n clear(key?: string): void;\n}\n\nexport class DiffDelta {\n readonly baseUrl: string;\n readonly apiKey?: string;\n readonly timeout: number;\n\n private cursors: CursorStoreInterface;\n private sourceTagsCache: Record<string, string[]> | null = null;\n\n constructor(options: DiffDeltaOptions = {}) {\n this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.apiKey = options.apiKey;\n this.timeout = options.timeout || DEFAULT_TIMEOUT;\n\n // Cursor persistence\n if (options.cursorPath === null || options.cursorPath === \"memory\") {\n this.cursors = new MemoryCursorStore();\n } else {\n try {\n this.cursors = new CursorStore(options.cursorPath || undefined);\n } catch {\n // Fallback to memory if file system not available\n this.cursors = new MemoryCursorStore();\n }\n }\n }\n\n // ── Core polling ──\n\n /**\n * Poll the global feed for new items since last poll.\n *\n * Checks head.json first (~400 bytes). Only fetches the full feed\n * if the cursor has changed. Automatically saves the new cursor.\n */\n async poll(options: PollOptions = {}): Promise<FeedItem[]> {\n const { tags, sources, buckets = [\"new\", \"updated\"] } = options;\n\n return this.pollFeed({\n headUrl: `${this.baseUrl}/diff/head.json`,\n latestUrl: `${this.baseUrl}/diff/latest.json`,\n cursorKey: \"global\",\n tags,\n sources,\n buckets,\n });\n }\n\n /**\n * Poll a specific source for new items since last poll.\n *\n * More efficient than `poll({ sources: [...] })` if you only\n * care about one source — fetches a smaller payload.\n */\n async pollSource(\n sourceId: string,\n options: Omit<PollOptions, \"sources\"> = {}\n ): Promise<FeedItem[]> {\n const { tags, buckets = [\"new\", \"updated\"] } = options;\n\n return this.pollFeed({\n headUrl: `${this.baseUrl}/diff/source/${sourceId}/head.json`,\n latestUrl: `${this.baseUrl}/diff/source/${sourceId}/latest.json`,\n cursorKey: `source:${sourceId}`,\n tags,\n sources: undefined,\n buckets,\n });\n }\n\n // ── Low-level fetch ──\n\n /**\n * Fetch a head.json pointer.\n * @param url Full URL to head.json. Defaults to global head.\n */\n async head(url?: string): Promise<Head> {\n const data = await this.fetchJson(url || `${this.baseUrl}/diff/head.json`);\n return parseHead(data);\n }\n\n /**\n * Fetch a full latest.json feed.\n * @param url Full URL to latest.json. Defaults to global latest.\n */\n async fetchFeed(url?: string): Promise<Feed> {\n const data = await this.fetchJson(\n url || `${this.baseUrl}/diff/latest.json`\n );\n return parseFeed(data);\n }\n\n /** List all available DiffDelta sources. */\n async sources(): Promise<SourceInfo[]> {\n const data = await this.fetchJson(`${this.baseUrl}/diff/sources.json`);\n const raw = (data.sources || []) as Record<string, unknown>[];\n return raw.map(parseSourceInfo);\n }\n\n // ── Continuous monitoring ──\n\n /**\n * Continuously poll and call a function for each new item.\n *\n * @param callback - Async or sync function called for each new FeedItem.\n * @param options - Watch options (tags, sources, interval, signal).\n *\n * @example\n * ```ts\n * dd.watch(item => {\n * console.log(`🚨 ${item.source}: ${item.headline}`);\n * }, { tags: [\"security\"] });\n * ```\n *\n * @example\n * ```ts\n * // Stop with AbortController\n * const ac = new AbortController();\n * dd.watch(handler, { signal: ac.signal });\n * setTimeout(() => ac.abort(), 60_000); // stop after 1 minute\n * ```\n */\n async watch(\n callback: (item: FeedItem) => void | Promise<void>,\n options: WatchOptions = {}\n ): Promise<void> {\n const { tags, sources, buckets, signal } = options;\n let interval = options.interval;\n\n // Determine interval from feed TTL if not specified\n if (!interval) {\n try {\n const h = await this.head();\n interval = Math.max(h.ttlSec, 60);\n } catch {\n interval = 900; // default 15 minutes\n }\n }\n\n console.log(`[diffdelta] Watching for changes every ${interval}s...`);\n\n while (!signal?.aborted) {\n try {\n const items = await this.poll({ tags, sources, buckets });\n if (items.length > 0) {\n console.log(`[diffdelta] ${items.length} new item(s) found.`);\n for (const item of items) {\n await callback(item);\n }\n } else {\n console.log(`[diffdelta] No changes.`);\n }\n } catch (err) {\n if (signal?.aborted) break;\n console.error(`[diffdelta] Error: ${err}. Retrying in ${interval}s...`);\n }\n\n // Sleep with abort support\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, interval! * 1000);\n signal?.addEventListener(\"abort\", () => {\n clearTimeout(timer);\n resolve();\n }, { once: true });\n });\n }\n\n console.log(\"[diffdelta] Stopped.\");\n }\n\n // ── Cursor management ──\n\n /** Reset stored cursors so the next poll returns all current items. */\n resetCursors(sourceId?: string): void {\n if (sourceId) {\n this.cursors.clear(`source:${sourceId}`);\n } else {\n this.cursors.clear();\n }\n }\n\n // ── Internal ──\n\n private async pollFeed(params: {\n headUrl: string;\n latestUrl: string;\n cursorKey: string;\n tags?: string[];\n sources?: string[];\n buckets: string[];\n }): Promise<FeedItem[]> {\n const { headUrl, latestUrl, cursorKey, tags, sources, buckets } = params;\n\n // Step 1: Fetch head.json (~400 bytes)\n const head = await this.head(headUrl);\n\n // Step 2: Compare cursor\n const storedCursor = this.cursors.get(cursorKey);\n if (storedCursor && storedCursor === head.cursor) {\n return []; // Nothing changed\n }\n\n // Step 3: Fetch full feed\n const feed = await this.fetchFeed(latestUrl);\n\n // Step 4: Save new cursor\n if (feed.cursor) {\n this.cursors.set(cursorKey, feed.cursor);\n }\n\n // Step 5: Filter and return\n let items = feed.items;\n\n // Filter by bucket\n items = items.filter((i) => buckets.includes(i.bucket));\n\n // Filter by source\n if (sources?.length) {\n items = items.filter((i) => sources.includes(i.source));\n }\n\n // Filter by tags\n if (tags?.length) {\n const tagMap = await this.getSourceTags();\n items = items.filter((i) => {\n const itemTags = tagMap[i.source] || [];\n return tags.some((t) => itemTags.includes(t));\n });\n }\n\n return items;\n }\n\n private async getSourceTags(): Promise<Record<string, string[]>> {\n if (this.sourceTagsCache) return this.sourceTagsCache;\n try {\n const allSources = await this.sources();\n this.sourceTagsCache = Object.fromEntries(\n allSources.map((s) => [s.sourceId, s.tags])\n );\n } catch {\n this.sourceTagsCache = {};\n }\n return this.sourceTagsCache;\n }\n\n private async fetchJson(url: string): Promise<Record<string, unknown>> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const headers: Record<string, string> = {\n \"User-Agent\": `diffdelta-js/${VERSION}`,\n Accept: \"application/json\",\n };\n if (this.apiKey) {\n headers[\"X-DiffDelta-Key\"] = this.apiKey;\n }\n\n const res = await fetch(url, { headers, signal: controller.signal });\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}: ${res.statusText} (${url})`);\n }\n return (await res.json()) as Record<string, unknown>;\n } finally {\n clearTimeout(timer);\n }\n }\n\n toString(): string {\n const tier = this.apiKey ? \"pro\" : \"free\";\n return `DiffDelta(baseUrl=${this.baseUrl}, tier=${tier})`;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cursor.ts","../src/models.ts","../src/client.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nconst DEFAULT_CURSOR_DIR = join(homedir(), \".diffdelta\");\nconst DEFAULT_CURSOR_FILE = \"cursors.json\";\n\n/**\n * Persists cursors to a local JSON file so bots survive restarts.\n *\n * By default, cursors are saved to ~/.diffdelta/cursors.json.\n * Each feed key gets its own cursor entry.\n */\nexport class CursorStore {\n private path: string;\n private cursors: Record<string, string> = {};\n\n constructor(path?: string) {\n this.path =\n path ||\n process.env.DD_CURSOR_PATH ||\n join(DEFAULT_CURSOR_DIR, DEFAULT_CURSOR_FILE);\n this.load();\n }\n\n /** Get the stored cursor for a feed key. */\n get(key: string): string | undefined {\n return this.cursors[key];\n }\n\n /** Save a cursor and persist to disk. */\n set(key: string, cursor: string): void {\n this.cursors[key] = cursor;\n this.save();\n }\n\n /** Clear cursor(s). If key is undefined, clears all cursors. */\n clear(key?: string): void {\n if (key) {\n delete this.cursors[key];\n } else {\n this.cursors = {};\n }\n this.save();\n }\n\n private load(): void {\n try {\n if (existsSync(this.path)) {\n const raw = readFileSync(this.path, \"utf-8\");\n const data = JSON.parse(raw);\n if (typeof data === \"object\" && data !== null) {\n this.cursors = data;\n }\n }\n } catch {\n // Corrupted file — start fresh\n this.cursors = {};\n }\n }\n\n private save(): void {\n try {\n const dir = dirname(this.path);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(this.path, JSON.stringify(this.cursors, null, 2));\n } catch {\n // Can't write — silently continue (in-memory only)\n }\n }\n}\n\n/**\n * In-memory-only cursor store (no file I/O).\n * Useful for serverless, browser, or Deno environments.\n */\nexport class MemoryCursorStore {\n private cursors: Record<string, string> = {};\n\n get(key: string): string | undefined {\n return this.cursors[key];\n }\n\n set(key: string, cursor: string): void {\n this.cursors[key] = cursor;\n }\n\n clear(key?: string): void {\n if (key) {\n delete this.cursors[key];\n } else {\n this.cursors = {};\n }\n }\n}\n","// ── Signal types ──\n\n/** Severity signal extracted from security advisories. */\nexport interface SeveritySignal {\n level: \"critical\" | \"high\" | \"medium\" | \"low\" | string;\n source: string;\n cvss?: number;\n cwes?: string[];\n packages?: string[];\n exploited?: boolean;\n provenance?: SignalProvenance;\n}\n\n/** Release signal extracted from changelogs/GitHub releases. */\nexport interface ReleaseSignal {\n version: string;\n prerelease?: boolean;\n security_patch?: boolean;\n provenance?: SignalProvenance;\n}\n\n/** Incident signal extracted from status pages. */\nexport interface IncidentSignal {\n status: \"investigating\" | \"identified\" | \"monitoring\" | \"resolved\" | string;\n impact?: \"minor\" | \"major\" | \"critical\" | string;\n provenance?: SignalProvenance;\n}\n\n/** Deprecation signal extracted from changelogs/advisories. */\nexport interface DeprecationSignal {\n type: \"breaking_change\" | \"end_of_life\" | \"removal\" | \"deprecated\" | string;\n affects?: string[];\n confidence: \"high\" | \"medium\" | \"low\" | string;\n source: string;\n provenance?: SignalProvenance;\n}\n\n/** Provenance chain for a signal — traces it to an authoritative source. */\nexport interface SignalProvenance {\n method: string;\n authority: string;\n authority_url: string;\n evidence_url: string;\n}\n\n/** Action codes telling a bot exactly what to do. */\nexport type SuggestedAction =\n | \"PATCH_IMMEDIATELY\"\n | \"PATCH_SOON\"\n | \"VERSION_PIN\"\n | \"REVIEW_CHANGELOG\"\n | \"MONITOR_STATUS\"\n | \"ACKNOWLEDGE\"\n | \"NO_ACTION\";\n\n/** All structured signals on an item. */\nexport interface Signals {\n severity?: SeveritySignal;\n release?: ReleaseSignal;\n incident?: IncidentSignal;\n deprecation?: DeprecationSignal;\n suggested_action?: SuggestedAction;\n [key: string]: unknown;\n}\n\n// ── Core types ──\n\n/** A single item from a DiffDelta feed. */\nexport interface FeedItem {\n /** Source identifier (e.g. \"cisa_kev\", \"github_advisories\"). */\n source: string;\n /** Unique item ID within the source. */\n id: string;\n /** Human/agent-readable headline. */\n headline: string;\n /** Link to the original source. */\n url: string;\n /** Summary text extracted from the source. */\n excerpt: string;\n /** When the item was originally published. */\n publishedAt: string | null;\n /** When the item was last updated. */\n updatedAt: string | null;\n /** Which change bucket: \"new\", \"updated\", \"removed\", or \"flagged\". */\n bucket: \"new\" | \"updated\" | \"removed\" | \"flagged\";\n /** Structured signals: severity, release, incident, deprecation, suggested_action. */\n signals: Signals;\n /** Shortcut: the suggested_action code, if any. */\n suggestedAction: SuggestedAction | null;\n /** Risk score 0.0–1.0, or null if not scored. */\n riskScore: number | null;\n /** Item-level provenance (fetched_at, evidence_urls, content_hash). */\n provenance: Record<string, unknown>;\n /** The full raw item object from the feed. */\n raw: Record<string, unknown>;\n}\n\n/** The lightweight head pointer for change detection. */\nexport interface Head {\n /** Opaque cursor string for change detection. */\n cursor: string;\n /** Whether content has changed since last generation. */\n changed: boolean;\n /** When this head was generated. */\n generatedAt: string;\n /** Recommended polling interval in seconds. */\n ttlSec: number;\n /** URL to the full latest.json feed. */\n latestUrl: string;\n /** URL to the digest (global head only). */\n digestUrl: string | null;\n /** Item counts: new, updated, removed, flagged. */\n counts: { new: number; updated: number; removed: number; flagged: number };\n /** Number of sources checked this cycle (Verified Silence). */\n sourcesChecked: number;\n /** Number of sources healthy this cycle. */\n sourcesOk: number;\n /** True if nothing changed AND all sources are healthy. */\n allClear: boolean;\n /** 0.0–1.0 confidence that allClear is trustworthy. */\n allClearConfidence: number | null;\n /** Pipeline freshness: oldest data age, stale count. */\n freshness: {\n oldest_data_age_sec: number;\n mean_data_age_sec: number;\n stale_count: number;\n all_fresh: boolean;\n } | null;\n /** The full raw head.json object. */\n raw: Record<string, unknown>;\n}\n\n/** A full DiffDelta feed response. */\nexport interface Feed {\n /** The new cursor (save this for next poll). */\n cursor: string;\n /** The previous cursor. */\n prevCursor: string;\n /** Source ID (if per-source feed) or \"global\". */\n sourceId: string;\n /** When this feed was generated. */\n generatedAt: string;\n /** All items across all buckets. */\n items: FeedItem[];\n /** Items in the \"new\" bucket. */\n new: FeedItem[];\n /** Items in the \"updated\" bucket. */\n updated: FeedItem[];\n /** Items in the \"removed\" bucket. */\n removed: FeedItem[];\n /** Items in the \"flagged\" bucket. */\n flagged: FeedItem[];\n /** Human-readable summary of what changed. */\n narrative: string;\n /** The full raw feed object. */\n raw: Record<string, unknown>;\n}\n\n/** Metadata about an available DiffDelta source. */\nexport interface SourceInfo {\n /** Unique source identifier (e.g. \"cisa_kev\"). */\n sourceId: string;\n /** Human-readable display name. */\n name: string;\n /** List of tags (e.g. [\"security\"]). */\n tags: string[];\n /** Brief description of the source. */\n description: string;\n /** URL of the source's homepage. */\n homepage: string;\n /** Whether the source is currently active. */\n enabled: boolean;\n /** Health status (\"ok\", \"degraded\", \"error\"). */\n status: string;\n /** Path to the source's head.json. */\n headUrl: string;\n /** Path to the source's latest.json. */\n latestUrl: string;\n}\n\n/** Health check response from /healthz.json. */\nexport interface HealthCheck {\n ok: boolean;\n service: string;\n time: string;\n sourcesChecked: number;\n sourcesOk: number;\n engineVersion: string;\n}\n\n// ── Parsing helpers ──\n\n/** Parse a raw feed item into a typed FeedItem. */\nexport function parseFeedItem(\n data: Record<string, unknown>,\n bucket: \"new\" | \"updated\" | \"removed\" | \"flagged\" = \"new\"\n): FeedItem {\n const content = data.content as Record<string, unknown> | string | undefined;\n let excerpt = \"\";\n if (typeof content === \"object\" && content !== null) {\n excerpt =\n (content.excerpt_text as string) ||\n (content.summary as string) ||\n \"\";\n } else if (typeof content === \"string\") {\n excerpt = content;\n }\n\n // Extract signals\n const signals = (data.signals as Signals) || {};\n const suggestedAction = (signals.suggested_action as SuggestedAction) || null;\n\n // Extract risk score from risk.score or legacy risk_score\n let riskScore: number | null = null;\n const risk = data.risk as Record<string, unknown> | undefined;\n if (typeof risk === \"object\" && risk !== null) {\n riskScore = (risk.score as number) ?? null;\n }\n if (riskScore === null) {\n riskScore = (data.risk_score as number) ?? null;\n }\n\n return {\n source: (data.source as string) || \"\",\n id: (data.id as string) || \"\",\n headline: (data.headline as string) || \"\",\n url: (data.url as string) || \"\",\n excerpt,\n publishedAt: (data.published_at as string) || null,\n updatedAt: (data.updated_at as string) || null,\n bucket,\n signals,\n suggestedAction,\n riskScore,\n provenance: (data.provenance as Record<string, unknown>) || {},\n raw: data,\n };\n}\n\n/** Parse a raw head.json response into a typed Head. */\nexport function parseHead(data: Record<string, unknown>): Head {\n const counts = (data.counts as Record<string, number>) || {};\n const freshness = data.freshness as Head[\"freshness\"] | undefined;\n\n return {\n cursor: (data.cursor as string) || \"\",\n changed: (data.changed as boolean) || false,\n generatedAt: (data.generated_at as string) || \"\",\n ttlSec: (data.ttl_sec as number) || 60,\n latestUrl: (data.latest_url as string) || \"\",\n digestUrl: (data.digest_url as string) || null,\n counts: {\n new: counts.new || 0,\n updated: counts.updated || 0,\n removed: counts.removed || 0,\n flagged: counts.flagged || 0,\n },\n sourcesChecked: (data.sources_checked as number) || 0,\n sourcesOk: (data.sources_ok as number) || 0,\n allClear: (data.all_clear as boolean) || false,\n allClearConfidence:\n (data.all_clear_confidence as number) ??\n (data.confidence as number) ??\n null,\n freshness: freshness || null,\n raw: data,\n };\n}\n\n/** Parse a raw latest.json response into a typed Feed. */\nexport function parseFeed(data: Record<string, unknown>): Feed {\n const buckets = (data.buckets as Record<string, unknown[]>) || {};\n const newItems = (buckets.new || []).map((i) =>\n parseFeedItem(i as Record<string, unknown>, \"new\")\n );\n const updatedItems = (buckets.updated || []).map((i) =>\n parseFeedItem(i as Record<string, unknown>, \"updated\")\n );\n const removedItems = (buckets.removed || []).map((i) =>\n parseFeedItem(i as Record<string, unknown>, \"removed\")\n );\n const flaggedItems = (buckets.flagged || []).map((i) =>\n parseFeedItem(i as Record<string, unknown>, \"flagged\")\n );\n\n return {\n cursor: (data.cursor as string) || \"\",\n prevCursor: (data.prev_cursor as string) || \"\",\n sourceId: (data.source_id as string) || \"\",\n generatedAt: (data.generated_at as string) || \"\",\n items: [...newItems, ...updatedItems, ...removedItems, ...flaggedItems],\n new: newItems,\n updated: updatedItems,\n removed: removedItems,\n flagged: flaggedItems,\n narrative: (data.batch_narrative as string) || \"\",\n raw: data,\n };\n}\n\n/** Parse a raw source object into a typed SourceInfo. */\nexport function parseSourceInfo(data: Record<string, unknown>): SourceInfo {\n return {\n sourceId: (data.source_id as string) || \"\",\n name: (data.name as string) || \"\",\n tags: (data.tags as string[]) || [],\n description: (data.description as string) || \"\",\n homepage: (data.homepage as string) || \"\",\n enabled: (data.enabled as boolean) ?? true,\n status: (data.status as string) || \"ok\",\n headUrl: (data.head_url as string) || \"\",\n latestUrl: (data.latest_url as string) || \"\",\n };\n}\n\n/** Parse a raw healthz.json response into a typed HealthCheck. */\nexport function parseHealthCheck(data: Record<string, unknown>): HealthCheck {\n return {\n ok: (data.ok as boolean) || false,\n service: (data.service as string) || \"\",\n time: (data.time as string) || \"\",\n sourcesChecked: (data.sources_checked as number) || 0,\n sourcesOk: (data.sources_ok as number) || 0,\n engineVersion: (data.engine_version as string) || \"\",\n };\n}\n","/**\n * DiffDelta TypeScript client — agent-ready intelligence feeds.\n *\n * @example\n * ```ts\n * import { DiffDelta } from \"diffdelta\";\n *\n * const dd = new DiffDelta();\n *\n * // Quick health check — is the pipeline alive?\n * const health = await dd.checkHealth();\n * console.log(`Pipeline: ${health.ok ? \"healthy\" : \"degraded\"}, last run: ${health.time}`);\n *\n * // Poll for new items across all sources\n * const items = await dd.poll();\n * items.forEach(i => console.log(`${i.source}: ${i.headline}`));\n *\n * // Poll only security sources\n * const sec = await dd.poll({ tags: [\"security\"] });\n *\n * // Check what's relevant to your stack\n * const sources = await dd.discoverSources([\"openai\", \"langchain\", \"pinecone\"]);\n * console.log(\"Watch these:\", sources);\n *\n * // Continuous monitoring\n * dd.watch(item => console.log(\"🚨\", item.headline), { tags: [\"security\"] });\n * ```\n */\n\nimport { CursorStore, MemoryCursorStore } from \"./cursor.js\";\nimport type { FeedItem, Feed, Head, SourceInfo, HealthCheck } from \"./models.js\";\nimport { parseFeedItem, parseFeed, parseHead, parseSourceInfo, parseHealthCheck } from \"./models.js\";\n\nconst VERSION = \"0.1.1\";\nconst DEFAULT_BASE_URL = \"https://diffdelta.io\";\nconst DEFAULT_TIMEOUT = 15_000; // ms\n\nexport interface DiffDeltaOptions {\n /** DiffDelta API base URL. Defaults to https://diffdelta.io. */\n baseUrl?: string;\n /** Pro/Enterprise API key (dd_live_...). */\n apiKey?: string;\n /**\n * Path to cursor file. Defaults to ~/.diffdelta/cursors.json.\n * Set to `null` to disable file persistence (in-memory only).\n * Set to `\"memory\"` for explicit in-memory mode (serverless, edge).\n */\n cursorPath?: string | null;\n /** HTTP timeout in milliseconds. Defaults to 15000. */\n timeout?: number;\n}\n\nexport interface PollOptions {\n /** Filter items to these tags (e.g. [\"security\"]). */\n tags?: string[];\n /** Filter items to these source IDs (e.g. [\"cisa_kev\", \"github_advisories\"]). */\n sources?: string[];\n /**\n * Which buckets to return.\n * Defaults to [\"new\", \"updated\", \"flagged\"].\n * Use [\"new\", \"updated\", \"removed\", \"flagged\"] to include removals.\n */\n buckets?: Array<\"new\" | \"updated\" | \"removed\" | \"flagged\">;\n}\n\nexport interface WatchOptions extends PollOptions {\n /** Seconds between polls. Defaults to feed TTL (usually 60s). */\n interval?: number;\n /** If provided, an AbortSignal to stop watching. */\n signal?: AbortSignal;\n}\n\ninterface CursorStoreInterface {\n get(key: string): string | undefined;\n set(key: string, cursor: string): void;\n clear(key?: string): void;\n}\n\nexport class DiffDelta {\n readonly baseUrl: string;\n readonly apiKey?: string;\n readonly timeout: number;\n\n private cursors: CursorStoreInterface;\n private sourceTagsCache: Record<string, string[]> | null = null;\n\n constructor(options: DiffDeltaOptions = {}) {\n this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.apiKey = options.apiKey;\n this.timeout = options.timeout || DEFAULT_TIMEOUT;\n\n // Cursor persistence\n if (options.cursorPath === null || options.cursorPath === \"memory\") {\n this.cursors = new MemoryCursorStore();\n } else {\n try {\n this.cursors = new CursorStore(options.cursorPath || undefined);\n } catch {\n // Fallback to memory if file system not available\n this.cursors = new MemoryCursorStore();\n }\n }\n }\n\n // ── Core polling ──\n\n /**\n * Poll the global feed for new items since last poll.\n *\n * Checks head.json first (~200 bytes). Only fetches the full feed\n * if the cursor has changed. Automatically saves the new cursor.\n */\n async poll(options: PollOptions = {}): Promise<FeedItem[]> {\n const { tags, sources, buckets = [\"new\", \"updated\", \"flagged\"] } = options;\n\n return this.pollFeed({\n headUrl: `${this.baseUrl}/diff/head.json`,\n latestUrl: `${this.baseUrl}/diff/latest.json`,\n cursorKey: \"global\",\n tags,\n sources,\n buckets,\n });\n }\n\n /**\n * Poll a specific source for new items since last poll.\n *\n * More efficient than `poll({ sources: [...] })` if you only\n * care about one source — fetches a smaller payload.\n */\n async pollSource(\n sourceId: string,\n options: Omit<PollOptions, \"sources\"> = {}\n ): Promise<FeedItem[]> {\n const { tags, buckets = [\"new\", \"updated\", \"flagged\"] } = options;\n\n return this.pollFeed({\n headUrl: `${this.baseUrl}/diff/${sourceId}/head.json`,\n latestUrl: `${this.baseUrl}/diff/${sourceId}/latest.json`,\n cursorKey: `source:${sourceId}`,\n tags,\n sources: undefined,\n buckets,\n });\n }\n\n // ── Low-level fetch ──\n\n /**\n * Fetch a head.json pointer. Cheapest call (~200 bytes).\n * @param url Full URL to head.json. Defaults to global head.\n */\n async head(url?: string): Promise<Head> {\n const data = await this.fetchJson(url || `${this.baseUrl}/diff/head.json`);\n return parseHead(data);\n }\n\n /**\n * Fetch a full latest.json feed.\n * @param url Full URL to latest.json. Defaults to global latest.\n */\n async fetchFeed(url?: string): Promise<Feed> {\n const data = await this.fetchJson(\n url || `${this.baseUrl}/diff/latest.json`\n );\n return parseFeed(data);\n }\n\n /** List all available DiffDelta sources. */\n async sources(): Promise<SourceInfo[]> {\n const data = await this.fetchJson(`${this.baseUrl}/diff/sources.json`);\n const raw = (data.sources || []) as Record<string, unknown>[];\n return raw.map(parseSourceInfo);\n }\n\n // ── Discovery & Health ──\n\n /**\n * Check pipeline health. Returns when the engine last ran and whether\n * all sources are healthy. A stale timestamp means the pipeline is down.\n */\n async checkHealth(): Promise<HealthCheck> {\n const data = await this.fetchJson(`${this.baseUrl}/healthz.json`);\n return parseHealthCheck(data);\n }\n\n /**\n * Given a list of dependency names your bot uses, returns the source IDs\n * you should monitor. Uses the static stacks.json mapping — no API call,\n * pure local lookup after one fetch.\n *\n * @example\n * ```ts\n * const sources = await dd.discoverSources([\"openai\", \"langchain\", \"pinecone\"]);\n * // → [\"openai_sdk_releases\", \"openai_api_changelog\", \"langchain_releases\", \"pinecone_status\"]\n * ```\n */\n async discoverSources(dependencies: string[]): Promise<string[]> {\n const data = await this.fetchJson(`${this.baseUrl}/diff/stacks.json`);\n // Support both formats: { dependencies: { x: { sources: [...] } } }\n // and legacy { dependency_map: { x: [...] } }\n const depsObj = (data.dependencies || data.dependency_map || {}) as Record<\n string,\n { sources?: string[] } | string[]\n >;\n const sourceIds = new Set<string>();\n for (const dep of dependencies) {\n const entry = depsObj[dep.toLowerCase()];\n if (!entry) continue;\n const sources = Array.isArray(entry) ? entry : entry.sources;\n if (Array.isArray(sources)) {\n for (const s of sources) {\n sourceIds.add(s);\n }\n }\n }\n return [...sourceIds];\n }\n\n // ── Continuous monitoring ──\n\n /**\n * Continuously poll and call a function for each new item.\n *\n * @param callback - Async or sync function called for each new FeedItem.\n * @param options - Watch options (tags, sources, interval, signal).\n *\n * @example\n * ```ts\n * dd.watch(item => {\n * console.log(`🚨 ${item.source}: ${item.headline}`);\n * if (item.suggestedAction === \"PATCH_IMMEDIATELY\") {\n * triggerAlert(item);\n * }\n * }, { tags: [\"security\"] });\n * ```\n *\n * @example\n * ```ts\n * // Stop with AbortController\n * const ac = new AbortController();\n * dd.watch(handler, { signal: ac.signal });\n * setTimeout(() => ac.abort(), 60_000); // stop after 1 minute\n * ```\n */\n async watch(\n callback: (item: FeedItem) => void | Promise<void>,\n options: WatchOptions = {}\n ): Promise<void> {\n const { tags, sources, buckets, signal } = options;\n let interval = options.interval;\n\n // Determine interval from feed TTL if not specified\n if (!interval) {\n try {\n const h = await this.head();\n interval = Math.max(h.ttlSec, 60);\n } catch {\n interval = 60; // default 1 minute\n }\n }\n\n console.log(`[diffdelta] Watching for changes every ${interval}s...`);\n\n while (!signal?.aborted) {\n try {\n const items = await this.poll({ tags, sources, buckets });\n if (items.length > 0) {\n console.log(`[diffdelta] ${items.length} new item(s) found.`);\n for (const item of items) {\n await callback(item);\n }\n }\n } catch (err) {\n if (signal?.aborted) break;\n console.error(`[diffdelta] Error: ${err}. Retrying in ${interval}s...`);\n }\n\n // Sleep with abort support\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, interval! * 1000);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true }\n );\n });\n }\n\n console.log(\"[diffdelta] Stopped.\");\n }\n\n // ── Cursor management ──\n\n /** Reset stored cursors so the next poll returns all current items. */\n resetCursors(sourceId?: string): void {\n if (sourceId) {\n this.cursors.clear(`source:${sourceId}`);\n } else {\n this.cursors.clear();\n }\n }\n\n // ── Internal ──\n\n private async pollFeed(params: {\n headUrl: string;\n latestUrl: string;\n cursorKey: string;\n tags?: string[];\n sources?: string[];\n buckets: string[];\n }): Promise<FeedItem[]> {\n const { headUrl, latestUrl, cursorKey, tags, sources, buckets } = params;\n\n // Step 1: Fetch head.json (~200 bytes)\n const head = await this.head(headUrl);\n\n // Step 2: Compare cursor — if unchanged, nothing to do\n const storedCursor = this.cursors.get(cursorKey);\n if (storedCursor && storedCursor === head.cursor) {\n return []; // Nothing changed\n }\n\n // Step 3: Fetch full feed\n const feed = await this.fetchFeed(latestUrl);\n\n // Step 4: Save new cursor\n if (feed.cursor) {\n this.cursors.set(cursorKey, feed.cursor);\n }\n\n // Step 5: Filter and return\n let items = feed.items;\n\n // Filter by bucket\n items = items.filter((i) => buckets.includes(i.bucket));\n\n // Filter by source\n if (sources?.length) {\n items = items.filter((i) => sources.includes(i.source));\n }\n\n // Filter by tags\n if (tags?.length) {\n const tagMap = await this.getSourceTags();\n items = items.filter((i) => {\n const itemTags = tagMap[i.source] || [];\n return tags.some((t) => itemTags.includes(t));\n });\n }\n\n return items;\n }\n\n private async getSourceTags(): Promise<Record<string, string[]>> {\n if (this.sourceTagsCache) return this.sourceTagsCache;\n try {\n const allSources = await this.sources();\n this.sourceTagsCache = Object.fromEntries(\n allSources.map((s) => [s.sourceId, s.tags])\n );\n } catch {\n this.sourceTagsCache = {};\n }\n return this.sourceTagsCache;\n }\n\n private async fetchJson(url: string): Promise<Record<string, unknown>> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const headers: Record<string, string> = {\n \"User-Agent\": `diffdelta-js/${VERSION}`,\n Accept: \"application/json\",\n };\n if (this.apiKey) {\n headers[\"X-DiffDelta-Key\"] = this.apiKey;\n }\n\n const res = await fetch(url, { headers, signal: controller.signal });\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}: ${res.statusText} (${url})`);\n }\n return (await res.json()) as Record<string, unknown>;\n } finally {\n clearTimeout(timer);\n }\n }\n\n toString(): string {\n const tier = this.apiKey ? \"pro\" : \"free\";\n return `DiffDelta(baseUrl=${this.baseUrl}, tier=${tier})`;\n }\n}\n"],"mappings":";AAAA,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,SAAS,YAAY;AAC9B,SAAS,eAAe;AAExB,IAAM,qBAAqB,KAAK,QAAQ,GAAG,YAAY;AACvD,IAAM,sBAAsB;AAQrB,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA,UAAkC,CAAC;AAAA,EAE3C,YAAY,MAAe;AACzB,SAAK,OACH,QACA,QAAQ,IAAI,kBACZ,KAAK,oBAAoB,mBAAmB;AAC9C,SAAK,KAAK;AAAA,EACZ;AAAA;AAAA,EAGA,IAAI,KAAiC;AACnC,WAAO,KAAK,QAAQ,GAAG;AAAA,EACzB;AAAA;AAAA,EAGA,IAAI,KAAa,QAAsB;AACrC,SAAK,QAAQ,GAAG,IAAI;AACpB,SAAK,KAAK;AAAA,EACZ;AAAA;AAAA,EAGA,MAAM,KAAoB;AACxB,QAAI,KAAK;AACP,aAAO,KAAK,QAAQ,GAAG;AAAA,IACzB,OAAO;AACL,WAAK,UAAU,CAAC;AAAA,IAClB;AACA,SAAK,KAAK;AAAA,EACZ;AAAA,EAEQ,OAAa;AACnB,QAAI;AACF,UAAI,WAAW,KAAK,IAAI,GAAG;AACzB,cAAM,MAAM,aAAa,KAAK,MAAM,OAAO;AAC3C,cAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,YAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,eAAK,UAAU;AAAA,QACjB;AAAA,MACF;AAAA,IACF,QAAQ;AAEN,WAAK,UAAU,CAAC;AAAA,IAClB;AAAA,EACF;AAAA,EAEQ,OAAa;AACnB,QAAI;AACF,YAAM,MAAM,QAAQ,KAAK,IAAI;AAC7B,UAAI,CAAC,WAAW,GAAG,GAAG;AACpB,kBAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,MACpC;AACA,oBAAc,KAAK,MAAM,KAAK,UAAU,KAAK,SAAS,MAAM,CAAC,CAAC;AAAA,IAChE,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAMO,IAAM,oBAAN,MAAwB;AAAA,EACrB,UAAkC,CAAC;AAAA,EAE3C,IAAI,KAAiC;AACnC,WAAO,KAAK,QAAQ,GAAG;AAAA,EACzB;AAAA,EAEA,IAAI,KAAa,QAAsB;AACrC,SAAK,QAAQ,GAAG,IAAI;AAAA,EACtB;AAAA,EAEA,MAAM,KAAoB;AACxB,QAAI,KAAK;AACP,aAAO,KAAK,QAAQ,GAAG;AAAA,IACzB,OAAO;AACL,WAAK,UAAU,CAAC;AAAA,IAClB;AAAA,EACF;AACF;;;ACiGO,SAAS,cACd,MACA,SAAoD,OAC1C;AACV,QAAM,UAAU,KAAK;AACrB,MAAI,UAAU;AACd,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,cACG,QAAQ,gBACR,QAAQ,WACT;AAAA,EACJ,WAAW,OAAO,YAAY,UAAU;AACtC,cAAU;AAAA,EACZ;AAGA,QAAM,UAAW,KAAK,WAAuB,CAAC;AAC9C,QAAM,kBAAmB,QAAQ,oBAAwC;AAGzE,MAAI,YAA2B;AAC/B,QAAM,OAAO,KAAK;AAClB,MAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,gBAAa,KAAK,SAAoB;AAAA,EACxC;AACA,MAAI,cAAc,MAAM;AACtB,gBAAa,KAAK,cAAyB;AAAA,EAC7C;AAEA,SAAO;AAAA,IACL,QAAS,KAAK,UAAqB;AAAA,IACnC,IAAK,KAAK,MAAiB;AAAA,IAC3B,UAAW,KAAK,YAAuB;AAAA,IACvC,KAAM,KAAK,OAAkB;AAAA,IAC7B;AAAA,IACA,aAAc,KAAK,gBAA2B;AAAA,IAC9C,WAAY,KAAK,cAAyB;AAAA,IAC1C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAa,KAAK,cAA0C,CAAC;AAAA,IAC7D,KAAK;AAAA,EACP;AACF;AAGO,SAAS,UAAU,MAAqC;AAC7D,QAAM,SAAU,KAAK,UAAqC,CAAC;AAC3D,QAAM,YAAY,KAAK;AAEvB,SAAO;AAAA,IACL,QAAS,KAAK,UAAqB;AAAA,IACnC,SAAU,KAAK,WAAuB;AAAA,IACtC,aAAc,KAAK,gBAA2B;AAAA,IAC9C,QAAS,KAAK,WAAsB;AAAA,IACpC,WAAY,KAAK,cAAyB;AAAA,IAC1C,WAAY,KAAK,cAAyB;AAAA,IAC1C,QAAQ;AAAA,MACN,KAAK,OAAO,OAAO;AAAA,MACnB,SAAS,OAAO,WAAW;AAAA,MAC3B,SAAS,OAAO,WAAW;AAAA,MAC3B,SAAS,OAAO,WAAW;AAAA,IAC7B;AAAA,IACA,gBAAiB,KAAK,mBAA8B;AAAA,IACpD,WAAY,KAAK,cAAyB;AAAA,IAC1C,UAAW,KAAK,aAAyB;AAAA,IACzC,oBACG,KAAK,wBACL,KAAK,cACN;AAAA,IACF,WAAW,aAAa;AAAA,IACxB,KAAK;AAAA,EACP;AACF;AAGO,SAAS,UAAU,MAAqC;AAC7D,QAAM,UAAW,KAAK,WAAyC,CAAC;AAChE,QAAM,YAAY,QAAQ,OAAO,CAAC,GAAG;AAAA,IAAI,CAAC,MACxC,cAAc,GAA8B,KAAK;AAAA,EACnD;AACA,QAAM,gBAAgB,QAAQ,WAAW,CAAC,GAAG;AAAA,IAAI,CAAC,MAChD,cAAc,GAA8B,SAAS;AAAA,EACvD;AACA,QAAM,gBAAgB,QAAQ,WAAW,CAAC,GAAG;AAAA,IAAI,CAAC,MAChD,cAAc,GAA8B,SAAS;AAAA,EACvD;AACA,QAAM,gBAAgB,QAAQ,WAAW,CAAC,GAAG;AAAA,IAAI,CAAC,MAChD,cAAc,GAA8B,SAAS;AAAA,EACvD;AAEA,SAAO;AAAA,IACL,QAAS,KAAK,UAAqB;AAAA,IACnC,YAAa,KAAK,eAA0B;AAAA,IAC5C,UAAW,KAAK,aAAwB;AAAA,IACxC,aAAc,KAAK,gBAA2B;AAAA,IAC9C,OAAO,CAAC,GAAG,UAAU,GAAG,cAAc,GAAG,cAAc,GAAG,YAAY;AAAA,IACtE,KAAK;AAAA,IACL,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT,WAAY,KAAK,mBAA8B;AAAA,IAC/C,KAAK;AAAA,EACP;AACF;AAGO,SAAS,gBAAgB,MAA2C;AACzE,SAAO;AAAA,IACL,UAAW,KAAK,aAAwB;AAAA,IACxC,MAAO,KAAK,QAAmB;AAAA,IAC/B,MAAO,KAAK,QAAqB,CAAC;AAAA,IAClC,aAAc,KAAK,eAA0B;AAAA,IAC7C,UAAW,KAAK,YAAuB;AAAA,IACvC,SAAU,KAAK,WAAuB;AAAA,IACtC,QAAS,KAAK,UAAqB;AAAA,IACnC,SAAU,KAAK,YAAuB;AAAA,IACtC,WAAY,KAAK,cAAyB;AAAA,EAC5C;AACF;AAGO,SAAS,iBAAiB,MAA4C;AAC3E,SAAO;AAAA,IACL,IAAK,KAAK,MAAkB;AAAA,IAC5B,SAAU,KAAK,WAAsB;AAAA,IACrC,MAAO,KAAK,QAAmB;AAAA,IAC/B,gBAAiB,KAAK,mBAA8B;AAAA,IACpD,WAAY,KAAK,cAAyB;AAAA,IAC1C,eAAgB,KAAK,kBAA6B;AAAA,EACpD;AACF;;;ACpSA,IAAM,UAAU;AAChB,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AA2CjB,IAAM,YAAN,MAAgB;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EAED;AAAA,EACA,kBAAmD;AAAA,EAE3D,YAAY,UAA4B,CAAC,GAAG;AAC1C,SAAK,WAAW,QAAQ,WAAW,kBAAkB,QAAQ,QAAQ,EAAE;AACvE,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ,WAAW;AAGlC,QAAI,QAAQ,eAAe,QAAQ,QAAQ,eAAe,UAAU;AAClE,WAAK,UAAU,IAAI,kBAAkB;AAAA,IACvC,OAAO;AACL,UAAI;AACF,aAAK,UAAU,IAAI,YAAY,QAAQ,cAAc,MAAS;AAAA,MAChE,QAAQ;AAEN,aAAK,UAAU,IAAI,kBAAkB;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,KAAK,UAAuB,CAAC,GAAwB;AACzD,UAAM,EAAE,MAAM,SAAS,UAAU,CAAC,OAAO,WAAW,SAAS,EAAE,IAAI;AAEnE,WAAO,KAAK,SAAS;AAAA,MACnB,SAAS,GAAG,KAAK,OAAO;AAAA,MACxB,WAAW,GAAG,KAAK,OAAO;AAAA,MAC1B,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,WACJ,UACA,UAAwC,CAAC,GACpB;AACrB,UAAM,EAAE,MAAM,UAAU,CAAC,OAAO,WAAW,SAAS,EAAE,IAAI;AAE1D,WAAO,KAAK,SAAS;AAAA,MACnB,SAAS,GAAG,KAAK,OAAO,SAAS,QAAQ;AAAA,MACzC,WAAW,GAAG,KAAK,OAAO,SAAS,QAAQ;AAAA,MAC3C,WAAW,UAAU,QAAQ;AAAA,MAC7B;AAAA,MACA,SAAS;AAAA,MACT;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,KAAK,KAA6B;AACtC,UAAM,OAAO,MAAM,KAAK,UAAU,OAAO,GAAG,KAAK,OAAO,iBAAiB;AACzE,WAAO,UAAU,IAAI;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAAU,KAA6B;AAC3C,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB,OAAO,GAAG,KAAK,OAAO;AAAA,IACxB;AACA,WAAO,UAAU,IAAI;AAAA,EACvB;AAAA;AAAA,EAGA,MAAM,UAAiC;AACrC,UAAM,OAAO,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,oBAAoB;AACrE,UAAM,MAAO,KAAK,WAAW,CAAC;AAC9B,WAAO,IAAI,IAAI,eAAe;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,cAAoC;AACxC,UAAM,OAAO,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,eAAe;AAChE,WAAO,iBAAiB,IAAI;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,gBAAgB,cAA2C;AAC/D,UAAM,OAAO,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,mBAAmB;AAGpE,UAAM,UAAW,KAAK,gBAAgB,KAAK,kBAAkB,CAAC;AAI9D,UAAM,YAAY,oBAAI,IAAY;AAClC,eAAW,OAAO,cAAc;AAC9B,YAAM,QAAQ,QAAQ,IAAI,YAAY,CAAC;AACvC,UAAI,CAAC,MAAO;AACZ,YAAM,UAAU,MAAM,QAAQ,KAAK,IAAI,QAAQ,MAAM;AACrD,UAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,mBAAW,KAAK,SAAS;AACvB,oBAAU,IAAI,CAAC;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AACA,WAAO,CAAC,GAAG,SAAS;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BA,MAAM,MACJ,UACA,UAAwB,CAAC,GACV;AACf,UAAM,EAAE,MAAM,SAAS,SAAS,OAAO,IAAI;AAC3C,QAAI,WAAW,QAAQ;AAGvB,QAAI,CAAC,UAAU;AACb,UAAI;AACF,cAAM,IAAI,MAAM,KAAK,KAAK;AAC1B,mBAAW,KAAK,IAAI,EAAE,QAAQ,EAAE;AAAA,MAClC,QAAQ;AACN,mBAAW;AAAA,MACb;AAAA,IACF;AAEA,YAAQ,IAAI,0CAA0C,QAAQ,MAAM;AAEpE,WAAO,CAAC,QAAQ,SAAS;AACvB,UAAI;AACF,cAAM,QAAQ,MAAM,KAAK,KAAK,EAAE,MAAM,SAAS,QAAQ,CAAC;AACxD,YAAI,MAAM,SAAS,GAAG;AACpB,kBAAQ,IAAI,eAAe,MAAM,MAAM,qBAAqB;AAC5D,qBAAW,QAAQ,OAAO;AACxB,kBAAM,SAAS,IAAI;AAAA,UACrB;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,QAAQ,QAAS;AACrB,gBAAQ,MAAM,sBAAsB,GAAG,iBAAiB,QAAQ,MAAM;AAAA,MACxE;AAGA,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,cAAM,QAAQ,WAAW,SAAS,WAAY,GAAI;AAClD,gBAAQ;AAAA,UACN;AAAA,UACA,MAAM;AACJ,yBAAa,KAAK;AAClB,oBAAQ;AAAA,UACV;AAAA,UACA,EAAE,MAAM,KAAK;AAAA,QACf;AAAA,MACF,CAAC;AAAA,IACH;AAEA,YAAQ,IAAI,sBAAsB;AAAA,EACpC;AAAA;AAAA;AAAA,EAKA,aAAa,UAAyB;AACpC,QAAI,UAAU;AACZ,WAAK,QAAQ,MAAM,UAAU,QAAQ,EAAE;AAAA,IACzC,OAAO;AACL,WAAK,QAAQ,MAAM;AAAA,IACrB;AAAA,EACF;AAAA;AAAA,EAIA,MAAc,SAAS,QAOC;AACtB,UAAM,EAAE,SAAS,WAAW,WAAW,MAAM,SAAS,QAAQ,IAAI;AAGlE,UAAM,OAAO,MAAM,KAAK,KAAK,OAAO;AAGpC,UAAM,eAAe,KAAK,QAAQ,IAAI,SAAS;AAC/C,QAAI,gBAAgB,iBAAiB,KAAK,QAAQ;AAChD,aAAO,CAAC;AAAA,IACV;AAGA,UAAM,OAAO,MAAM,KAAK,UAAU,SAAS;AAG3C,QAAI,KAAK,QAAQ;AACf,WAAK,QAAQ,IAAI,WAAW,KAAK,MAAM;AAAA,IACzC;AAGA,QAAI,QAAQ,KAAK;AAGjB,YAAQ,MAAM,OAAO,CAAC,MAAM,QAAQ,SAAS,EAAE,MAAM,CAAC;AAGtD,QAAI,SAAS,QAAQ;AACnB,cAAQ,MAAM,OAAO,CAAC,MAAM,QAAQ,SAAS,EAAE,MAAM,CAAC;AAAA,IACxD;AAGA,QAAI,MAAM,QAAQ;AAChB,YAAM,SAAS,MAAM,KAAK,cAAc;AACxC,cAAQ,MAAM,OAAO,CAAC,MAAM;AAC1B,cAAM,WAAW,OAAO,EAAE,MAAM,KAAK,CAAC;AACtC,eAAO,KAAK,KAAK,CAAC,MAAM,SAAS,SAAS,CAAC,CAAC;AAAA,MAC9C,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,gBAAmD;AAC/D,QAAI,KAAK,gBAAiB,QAAO,KAAK;AACtC,QAAI;AACF,YAAM,aAAa,MAAM,KAAK,QAAQ;AACtC,WAAK,kBAAkB,OAAO;AAAA,QAC5B,WAAW,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC;AAAA,MAC5C;AAAA,IACF,QAAQ;AACN,WAAK,kBAAkB,CAAC;AAAA,IAC1B;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,UAAU,KAA+C;AACrE,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO;AAE/D,QAAI;AACF,YAAM,UAAkC;AAAA,QACtC,cAAc,gBAAgB,OAAO;AAAA,QACrC,QAAQ;AAAA,MACV;AACA,UAAI,KAAK,QAAQ;AACf,gBAAQ,iBAAiB,IAAI,KAAK;AAAA,MACpC;AAEA,YAAM,MAAM,MAAM,MAAM,KAAK,EAAE,SAAS,QAAQ,WAAW,OAAO,CAAC;AACnE,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI,MAAM,QAAQ,IAAI,MAAM,KAAK,IAAI,UAAU,KAAK,GAAG,GAAG;AAAA,MAClE;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,WAAmB;AACjB,UAAM,OAAO,KAAK,SAAS,QAAQ;AACnC,WAAO,qBAAqB,KAAK,OAAO,UAAU,IAAI;AAAA,EACxD;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diffdelta/client",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "TypeScript client for DiffDelta — agent-ready intelligence feeds",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",