@tforgach/axi-fetch 0.1.0

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/dist/cache.d.ts +8 -0
  4. package/dist/cache.d.ts.map +1 -0
  5. package/dist/cache.js +47 -0
  6. package/dist/cache.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +112 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/extractors/article.d.ts +12 -0
  12. package/dist/extractors/article.d.ts.map +1 -0
  13. package/dist/extractors/article.js +40 -0
  14. package/dist/extractors/article.js.map +1 -0
  15. package/dist/extractors/generic.d.ts +13 -0
  16. package/dist/extractors/generic.d.ts.map +1 -0
  17. package/dist/extractors/generic.js +46 -0
  18. package/dist/extractors/generic.js.map +1 -0
  19. package/dist/extractors/shared.d.ts +26 -0
  20. package/dist/extractors/shared.d.ts.map +1 -0
  21. package/dist/extractors/shared.js +164 -0
  22. package/dist/extractors/shared.js.map +1 -0
  23. package/dist/fetcher.d.ts +26 -0
  24. package/dist/fetcher.d.ts.map +1 -0
  25. package/dist/fetcher.js +170 -0
  26. package/dist/fetcher.js.map +1 -0
  27. package/dist/index.d.ts +18 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +81 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/output.d.ts +14 -0
  32. package/dist/output.d.ts.map +1 -0
  33. package/dist/output.js +54 -0
  34. package/dist/output.js.map +1 -0
  35. package/dist/typeDetector.d.ts +14 -0
  36. package/dist/typeDetector.d.ts.map +1 -0
  37. package/dist/typeDetector.js +98 -0
  38. package/dist/typeDetector.js.map +1 -0
  39. package/dist/types.d.ts +83 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +2 -0
  42. package/dist/types.js.map +1 -0
  43. package/package.json +67 -0
@@ -0,0 +1,26 @@
1
+ export interface FetchedPage {
2
+ html: string;
3
+ /** URL after any redirects. */
4
+ finalUrl: string;
5
+ status: number;
6
+ contentType: string;
7
+ }
8
+ export interface FetchUrlOptions {
9
+ timeout?: number;
10
+ userAgent?: string;
11
+ /** Use the on-disk response cache. Default: true. */
12
+ cache?: boolean;
13
+ /** Cache freshness window in ms. */
14
+ cacheTtl?: number;
15
+ }
16
+ /** Validate and normalize a user-supplied URL, defaulting the scheme to https. */
17
+ export declare function normalizeUrl(input: string): URL;
18
+ /** Find an HTML `<meta http-equiv="refresh">` target, resolved against `base`. */
19
+ export declare function metaRefreshTarget(html: string, base: string): string | null;
20
+ /**
21
+ * Fetch a URL as HTML. Follows HTTP redirects (native) and HTML meta-refresh
22
+ * redirects (manually, up to MAX_META_HOPS), decodes by charset, enforces a
23
+ * timeout, and fails loud with structured errors.
24
+ */
25
+ export declare function fetchUrl(input: string, options?: FetchUrlOptions): Promise<FetchedPage>;
26
+ //# sourceMappingURL=fetcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetcher.d.ts","sourceRoot":"","sources":["../src/fetcher.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAyB/C;AA2BD,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc3E;AA4FD;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,WAAW,CAAC,CAuBtB"}
@@ -0,0 +1,170 @@
1
+ import { AxiError } from "axi-sdk-js";
2
+ import { readCache, writeCache } from "./cache.js";
3
+ const DEFAULT_TIMEOUT = 10_000;
4
+ const DEFAULT_USER_AGENT = "axi-fetch/0.1 (+https://github.com/travisforgach/axi-fetch)";
5
+ // How many HTML meta-refresh hops to follow (native fetch handles HTTP 3xx).
6
+ const MAX_META_HOPS = 3;
7
+ // Retry transient failures (network blips, 429, 5xx) with exponential backoff.
8
+ const MAX_RETRIES = 2;
9
+ const RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);
10
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
11
+ /** Validate and normalize a user-supplied URL, defaulting the scheme to https. */
12
+ export function normalizeUrl(input) {
13
+ const trimmed = input.trim();
14
+ // Only default the scheme when none is present; an explicit non-http(s)
15
+ // scheme must fall through to the protocol check below and be rejected.
16
+ const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed);
17
+ const withScheme = hasScheme ? trimmed : `https://${trimmed}`;
18
+ let url;
19
+ try {
20
+ url = new URL(withScheme);
21
+ }
22
+ catch {
23
+ throw new AxiError(`Invalid URL: ${input}`, "VALIDATION_ERROR", [
24
+ "Pass a valid http(s) URL, e.g. `axi-fetch https://example.com/article`",
25
+ ]);
26
+ }
27
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
28
+ throw new AxiError(`Unsupported URL scheme: ${url.protocol}`, "VALIDATION_ERROR", ["Only http and https URLs are supported"]);
29
+ }
30
+ return url;
31
+ }
32
+ /**
33
+ * Decode a response body honoring its charset. Native `Response.text()` assumes
34
+ * UTF-8; real-world pages are sometimes windows-1252, iso-8859-1, shift_jis, etc.
35
+ * Charset comes from the content-type header, or a `<meta charset>` in the head.
36
+ */
37
+ function decodeBody(buffer, contentType) {
38
+ let charset = /charset=([^;]+)/i.exec(contentType)?.[1]?.trim().toLowerCase();
39
+ if (!charset) {
40
+ // Peek at the head bytes (ASCII-safe) for a <meta charset>.
41
+ const head = new TextDecoder("latin1").decode(buffer.slice(0, 2048));
42
+ charset =
43
+ /<meta[^>]+charset=["']?\s*([\w-]+)/i.exec(head)?.[1]?.toLowerCase() ??
44
+ undefined;
45
+ }
46
+ if (!charset || charset === "utf-8" || charset === "utf8") {
47
+ return new TextDecoder("utf-8").decode(buffer);
48
+ }
49
+ try {
50
+ return new TextDecoder(charset).decode(buffer);
51
+ }
52
+ catch {
53
+ // Unknown label (TextDecoder throws on unsupported encodings) — fall back.
54
+ return new TextDecoder("utf-8").decode(buffer);
55
+ }
56
+ }
57
+ /** Find an HTML `<meta http-equiv="refresh">` target, resolved against `base`. */
58
+ export function metaRefreshTarget(html, base) {
59
+ for (const tag of html.match(/<meta\b[^>]*>/gi) ?? []) {
60
+ if (!/http-equiv\s*=\s*["']?\s*refresh/i.test(tag))
61
+ continue;
62
+ // Backreference so inner quotes (e.g. content="0; url='...'") don't truncate.
63
+ const content = /content\s*=\s*(["'])(.*?)\1/is.exec(tag)?.[2] ?? "";
64
+ const urlPart = /url\s*=\s*(.+)$/i.exec(content.trim())?.[1];
65
+ if (!urlPart)
66
+ continue;
67
+ try {
68
+ return new URL(urlPart.trim().replace(/^['"]|['"]$/g, ""), base).href;
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ /** Perform a single fetch, validate it, and decode the body by charset. */
77
+ async function fetchOnce(input, timeout, userAgent) {
78
+ const url = normalizeUrl(input);
79
+ const controller = new AbortController();
80
+ const timer = setTimeout(() => controller.abort(), timeout);
81
+ let response;
82
+ try {
83
+ response = await fetch(url, {
84
+ redirect: "follow",
85
+ signal: controller.signal,
86
+ headers: {
87
+ "User-Agent": userAgent,
88
+ Accept: "text/html,application/xhtml+xml",
89
+ },
90
+ });
91
+ }
92
+ catch (error) {
93
+ if (error instanceof Error && error.name === "AbortError") {
94
+ throw new AxiError(`Request timed out after ${timeout}ms: ${url.href}`, "TIMEOUT", ["Increase the timeout with `--timeout <ms>`"]);
95
+ }
96
+ const reason = error instanceof Error ? error.message : String(error);
97
+ throw new AxiError(`Failed to fetch ${url.href}: ${reason}`, "FETCH_FAILED");
98
+ }
99
+ finally {
100
+ clearTimeout(timer);
101
+ }
102
+ if (!response.ok) {
103
+ const where = `${response.status} ${response.statusText} for ${url.href}`;
104
+ if (response.status === 401 || response.status === 403) {
105
+ throw new AxiError(`HTTP ${where} — the site appears to be blocking automated requests`, "FORBIDDEN", [
106
+ "Some sites block bots; this usually can't be worked around from a plain fetch",
107
+ ]);
108
+ }
109
+ // 429/5xx are transient — code them so the retry wrapper knows to retry.
110
+ const code = RETRYABLE_STATUS.has(response.status) ? "SERVER_ERROR" : "HTTP_ERROR";
111
+ throw new AxiError(`HTTP ${where}`, code);
112
+ }
113
+ const contentType = response.headers.get("content-type") ?? "";
114
+ if (contentType && !/text\/html|application\/xhtml\+xml/i.test(contentType)) {
115
+ throw new AxiError(`Unsupported content type "${contentType}" for ${url.href}`, "NOT_HTML", ["axi-fetch only handles HTML pages in the MVP"]);
116
+ }
117
+ const html = decodeBody(await response.arrayBuffer(), contentType);
118
+ return { html, finalUrl: response.url || url.href, status: response.status, contentType };
119
+ }
120
+ /** Transient failures worth another attempt: network blips and 429/5xx. */
121
+ function isRetryable(error) {
122
+ return (error instanceof AxiError &&
123
+ (error.code === "FETCH_FAILED" || error.code === "SERVER_ERROR"));
124
+ }
125
+ /** fetchOnce with exponential backoff on transient failures. */
126
+ async function fetchWithRetry(input, timeout, userAgent) {
127
+ let lastError;
128
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
129
+ try {
130
+ return await fetchOnce(input, timeout, userAgent);
131
+ }
132
+ catch (error) {
133
+ lastError = error;
134
+ if (!isRetryable(error) || attempt === MAX_RETRIES)
135
+ break;
136
+ await sleep(250 * 2 ** attempt);
137
+ }
138
+ }
139
+ throw lastError;
140
+ }
141
+ /**
142
+ * Fetch a URL as HTML. Follows HTTP redirects (native) and HTML meta-refresh
143
+ * redirects (manually, up to MAX_META_HOPS), decodes by charset, enforces a
144
+ * timeout, and fails loud with structured errors.
145
+ */
146
+ export async function fetchUrl(input, options = {}) {
147
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
148
+ const userAgent = options.userAgent ?? DEFAULT_USER_AGENT;
149
+ const useCache = options.cache ?? true;
150
+ // Canonical cache key (also validates the URL up front).
151
+ const key = normalizeUrl(input).href;
152
+ if (useCache) {
153
+ const cached = await readCache(key, options.cacheTtl);
154
+ if (cached)
155
+ return cached;
156
+ }
157
+ let current = input;
158
+ for (let hop = 0;; hop++) {
159
+ const page = await fetchWithRetry(current, timeout, userAgent);
160
+ const refresh = metaRefreshTarget(page.html, page.finalUrl);
161
+ if (refresh && refresh !== page.finalUrl && hop < MAX_META_HOPS) {
162
+ current = refresh;
163
+ continue;
164
+ }
165
+ if (useCache)
166
+ await writeCache(key, page);
167
+ return page;
168
+ }
169
+ }
170
+ //# sourceMappingURL=fetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../src/fetcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { AxiFetchOptions, FetchResult } from "./types.js";
2
+ /**
3
+ * Fetch a URL and return an agent-ready AXI response plus its TOON encoding.
4
+ *
5
+ * Pipeline: fetch -> detect type -> extract -> truncate -> build next steps.
6
+ * Never throws for expected failures upstream throw structured `AxiError`s.
7
+ */
8
+ export declare function axiFetch(url: string, options?: AxiFetchOptions): Promise<FetchResult>;
9
+ /**
10
+ * Build an AXI response from already-fetched HTML (no network). Useful for
11
+ * offline processing, fixtures, and benchmarking the same HTML across formats.
12
+ */
13
+ export declare function extractFromHtml(html: string, url: string, options?: AxiFetchOptions): FetchResult;
14
+ export { toStructured, toToon } from "./output.js";
15
+ export { fetchUrl, normalizeUrl } from "./fetcher.js";
16
+ export { detectType } from "./typeDetector.js";
17
+ export * from "./types.js";
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,eAAe,EAGf,WAAW,EACZ,MAAM,YAAY,CAAC;AAIpB;;;;;GAKG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,WAAW,CAAC,CAQtB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,eAAoB,GAC5B,WAAW,CAyCb;AAeD,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,cAAc,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,81 @@
1
+ import { fetchUrl } from "./fetcher.js";
2
+ import { detectType } from "./typeDetector.js";
3
+ import { extractArticle } from "./extractors/article.js";
4
+ import { extractGeneric } from "./extractors/generic.js";
5
+ import { toToon } from "./output.js";
6
+ const DEFAULT_MAX_CONTENT_LENGTH = 1500;
7
+ /**
8
+ * Fetch a URL and return an agent-ready AXI response plus its TOON encoding.
9
+ *
10
+ * Pipeline: fetch -> detect type -> extract -> truncate -> build next steps.
11
+ * Never throws for expected failures upstream throw structured `AxiError`s.
12
+ */
13
+ export async function axiFetch(url, options = {}) {
14
+ const page = await fetchUrl(url, {
15
+ timeout: options.timeout,
16
+ userAgent: options.userAgent,
17
+ cache: options.cache,
18
+ cacheTtl: options.cacheTtl,
19
+ });
20
+ return extractFromHtml(page.html, page.finalUrl, options);
21
+ }
22
+ /**
23
+ * Build an AXI response from already-fetched HTML (no network). Useful for
24
+ * offline processing, fixtures, and benchmarking the same HTML across formats.
25
+ */
26
+ export function extractFromHtml(html, url, options = {}) {
27
+ const includeLinks = options.includeLinks ?? true;
28
+ const maxContentLength = options.maxContentLength ?? DEFAULT_MAX_CONTENT_LENGTH;
29
+ const detection = detectType(html, url);
30
+ let title;
31
+ let content;
32
+ if (detection.type === "article") {
33
+ const article = extractArticle(html, url, includeLinks);
34
+ title = article.title;
35
+ content = article.content;
36
+ }
37
+ else {
38
+ const generic = extractGeneric(html, url, includeLinks);
39
+ title = generic.title;
40
+ content = generic.content;
41
+ }
42
+ const fullLength = content.main.length;
43
+ if (!options.full && fullLength > maxContentLength) {
44
+ content = {
45
+ ...content,
46
+ main: `${content.main.slice(0, maxContentLength)}…`,
47
+ truncated: true,
48
+ };
49
+ }
50
+ const axiResponse = {
51
+ metadata: {
52
+ url,
53
+ title,
54
+ type: detection.type,
55
+ confidence: detection.confidence,
56
+ fetchedAt: new Date().toISOString(),
57
+ contentLength: fullLength,
58
+ },
59
+ content,
60
+ nextSteps: buildNextSteps(content, includeLinks),
61
+ };
62
+ return { axiResponse, toonOutput: toToon(axiResponse) };
63
+ }
64
+ function buildNextSteps(content, includeLinks) {
65
+ const steps = [];
66
+ if (content.truncated) {
67
+ steps.push("Re-run with `--full` to get the complete content");
68
+ }
69
+ if (!includeLinks) {
70
+ steps.push("Re-run without `--no-links` to include outbound links");
71
+ }
72
+ else if (content.links.length > 0) {
73
+ steps.push("Follow a link above with `axi-fetch <url>` to go deeper");
74
+ }
75
+ return steps;
76
+ }
77
+ export { toStructured, toToon } from "./output.js";
78
+ export { fetchUrl, normalizeUrl } from "./fetcher.js";
79
+ export { detectType } from "./typeDetector.js";
80
+ export * from "./types.js";
81
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAgB,MAAM,EAAE,MAAM,aAAa,CAAC;AAQnD,MAAM,0BAA0B,GAAG,IAAI,CAAC;AAExC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,GAAW,EACX,UAA2B,EAAE;IAE7B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE;QAC/B,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC,CAAC;IACH,OAAO,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAY,EACZ,GAAW,EACX,UAA2B,EAAE;IAE7B,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC;IAClD,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,0BAA0B,CAAC;IAEhF,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAExC,IAAI,KAAa,CAAC;IAClB,IAAI,OAAgB,CAAC;IACrB,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC;QACxD,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QACtB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAC5B,CAAC;SAAM,CAAC;QACN,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC;QACxD,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QACtB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAC5B,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;IACvC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,UAAU,GAAG,gBAAgB,EAAE,CAAC;QACnD,OAAO,GAAG;YACR,GAAG,OAAO;YACV,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG;YACnD,SAAS,EAAE,IAAI;SAChB,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAgB;QAC/B,QAAQ,EAAE;YACR,GAAG;YACH,KAAK;YACL,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,UAAU,EAAE,SAAS,CAAC,UAAU;YAChC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,aAAa,EAAE,UAAU;SAC1B;QACD,OAAO;QACP,SAAS,EAAE,cAAc,CAAC,OAAO,EAAE,YAAY,CAAC;KACjD,CAAC;IAEF,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED,SAAS,cAAc,CAAC,OAAgB,EAAE,YAAqB;IAC7D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,KAAK,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;IACtE,CAAC;SAAM,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,cAAc,YAAY,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { AxiResponse } from "./types.js";
2
+ /** A plain object destined for TOON encoding (mirrors the SDK's shape). */
3
+ export type AxiStructuredOutput = Record<string, unknown>;
4
+ /**
5
+ * Map an AxiResponse to the flat-ish structure we hand to the TOON encoder.
6
+ * Field order is deliberate: identity first, then content, then aggregates and
7
+ * next steps. Low-value and empty fields are omitted to save tokens — `fetchedAt`
8
+ * is dropped entirely (rarely actionable), `truncated` only shows when true, and
9
+ * trivially small pages get a bare envelope.
10
+ */
11
+ export declare function toStructured(response: AxiResponse): AxiStructuredOutput;
12
+ /** Encode an AxiResponse as a TOON string (the agent-facing payload). */
13
+ export declare function toToon(response: AxiResponse): string;
14
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,2EAA2E;AAC3E,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAM1D;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,mBAAmB,CA2CvE;AAED,yEAAyE;AACzE,wBAAgB,MAAM,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAEpD"}
package/dist/output.js ADDED
@@ -0,0 +1,54 @@
1
+ import { encode } from "@toon-format/toon";
2
+ // Below this content size the fixed envelope (confidence, contentLength, …)
3
+ // costs more than it's worth, so we emit only the essentials.
4
+ const SMALL_PAGE_CHARS = 200;
5
+ /**
6
+ * Map an AxiResponse to the flat-ish structure we hand to the TOON encoder.
7
+ * Field order is deliberate: identity first, then content, then aggregates and
8
+ * next steps. Low-value and empty fields are omitted to save tokens — `fetchedAt`
9
+ * is dropped entirely (rarely actionable), `truncated` only shows when true, and
10
+ * trivially small pages get a bare envelope.
11
+ */
12
+ export function toStructured(response) {
13
+ const { metadata, content, nextSteps } = response;
14
+ const hasStructure = content.sections.length > 0 ||
15
+ content.codeBlocks.length > 0 ||
16
+ content.tables.length > 0 ||
17
+ content.links.length > 0;
18
+ const isSmall = metadata.contentLength < SMALL_PAGE_CHARS && !hasStructure;
19
+ const output = {
20
+ url: metadata.url,
21
+ title: metadata.title,
22
+ type: metadata.type,
23
+ };
24
+ // Quality/aggregate fields aren't worth the tokens on trivially small pages.
25
+ if (!isSmall) {
26
+ output.confidence = Number(metadata.confidence.toFixed(2));
27
+ output.contentLength = metadata.contentLength;
28
+ }
29
+ if (content.truncated) {
30
+ output.truncated = true;
31
+ }
32
+ output.content = content.main;
33
+ if (content.sections.length > 0) {
34
+ output.sections = content.sections;
35
+ }
36
+ if (content.codeBlocks.length > 0) {
37
+ output.codeBlocks = content.codeBlocks;
38
+ }
39
+ if (content.tables.length > 0) {
40
+ output.tables = content.tables;
41
+ }
42
+ if (content.links.length > 0) {
43
+ output.links = content.links;
44
+ }
45
+ if (nextSteps.length > 0) {
46
+ output.help = nextSteps;
47
+ }
48
+ return output;
49
+ }
50
+ /** Encode an AxiResponse as a TOON string (the agent-facing payload). */
51
+ export function toToon(response) {
52
+ return encode(toStructured(response));
53
+ }
54
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAM3C,4EAA4E;AAC5E,8DAA8D;AAC9D,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,QAAqB;IAChD,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,QAAQ,CAAC;IAElD,MAAM,YAAY,GAChB,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QAC3B,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC;QAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QACzB,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3B,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,GAAG,gBAAgB,IAAI,CAAC,YAAY,CAAC;IAE3E,MAAM,MAAM,GAAwB;QAClC,GAAG,EAAE,QAAQ,CAAC,GAAG;QACjB,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,IAAI,EAAE,QAAQ,CAAC,IAAI;KACpB,CAAC;IAEF,6EAA6E;IAC7E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,CAAC,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAC;IAChD,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC;IAC1B,CAAC;IACD,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAE9B,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IACrC,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IACzC,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACjC,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC/B,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,GAAG,SAAS,CAAC;IAC1B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,MAAM,CAAC,QAAqB;IAC1C,OAAO,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { PageType } from "./types.js";
2
+ export interface TypeDetection {
3
+ type: PageType;
4
+ /** 0-1 confidence in the detected type. */
5
+ confidence: number;
6
+ /** Human-readable signals that drove the decision (for debugging). */
7
+ hints: string[];
8
+ }
9
+ /**
10
+ * Rules-based (no LLM) detection of documentation, article, or generic pages.
11
+ * Signals are additive; confidence reflects how many fired.
12
+ */
13
+ export declare function detectType(html: string, url: string): TypeDetection;
14
+ //# sourceMappingURL=typeDetector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typeDetector.d.ts","sourceRoot":"","sources":["../src/typeDetector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,CAAC;IACf,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AA6CD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CA+DnE"}
@@ -0,0 +1,98 @@
1
+ import * as cheerio from "cheerio";
2
+ const ARTICLE_PATH = /\/(blog|article|articles|news|post|posts|story|p)\//i;
3
+ const ARTICLE_SCHEMA = /(News|Blog|Tech|Scholarly)?Article|BlogPosting/i;
4
+ const DOC_HOST = /^(docs?|developer|devcenter|devdocs|api|readthedocs)\./i;
5
+ const DOC_PATH = /\/(docs?|documentation|reference|guide|guides|manual|handbook|api)(\/|$)/i;
6
+ /**
7
+ * Documentation detection runs first: docs pages are structurally distinct
8
+ * (URL conventions + code-heavy) and shouldn't be mislabeled as generic.
9
+ */
10
+ function detectDocumentation($, url) {
11
+ const hints = [];
12
+ let score = 0;
13
+ try {
14
+ const parsed = new URL(url);
15
+ if (DOC_HOST.test(parsed.hostname)) {
16
+ score += 2;
17
+ hints.push("docs-host");
18
+ }
19
+ if (DOC_PATH.test(parsed.pathname)) {
20
+ score += 2;
21
+ hints.push("docs-path");
22
+ }
23
+ }
24
+ catch {
25
+ // ignore malformed url
26
+ }
27
+ // Code-heavy pages are a strong documentation signal.
28
+ const codeBlocks = $("pre").length;
29
+ if (codeBlocks >= 3) {
30
+ score += 2;
31
+ hints.push("code-heavy");
32
+ }
33
+ else if (codeBlocks >= 1) {
34
+ score += 1;
35
+ hints.push("has-code");
36
+ }
37
+ if (score >= 3) {
38
+ return { type: "documentation", confidence: Math.min(0.5 + score * 0.1, 0.97), hints };
39
+ }
40
+ return null;
41
+ }
42
+ /**
43
+ * Rules-based (no LLM) detection of documentation, article, or generic pages.
44
+ * Signals are additive; confidence reflects how many fired.
45
+ */
46
+ export function detectType(html, url) {
47
+ const $ = cheerio.load(html);
48
+ const docs = detectDocumentation($, url);
49
+ if (docs)
50
+ return docs;
51
+ const hints = [];
52
+ let score = 0;
53
+ // URL path signal.
54
+ try {
55
+ if (ARTICLE_PATH.test(new URL(url).pathname)) {
56
+ score += 2;
57
+ hints.push("url-path");
58
+ }
59
+ }
60
+ catch {
61
+ // ignore malformed url, other signals still apply
62
+ }
63
+ // Open Graph type.
64
+ if ($('meta[property="og:type"]').attr("content")?.toLowerCase() === "article") {
65
+ score += 3;
66
+ hints.push("og:type=article");
67
+ }
68
+ // Semantic <article> element.
69
+ if ($("article").length > 0) {
70
+ score += 2;
71
+ hints.push("article-tag");
72
+ }
73
+ // schema.org markup (JSON-LD @type or microdata itemtype).
74
+ const ldTypes = $('script[type="application/ld+json"]')
75
+ .map((_, el) => $(el).text())
76
+ .get()
77
+ .join(" ");
78
+ if (ARTICLE_SCHEMA.test(ldTypes) ||
79
+ $('[itemtype*="Article" i], [itemtype*="BlogPosting" i]').length > 0) {
80
+ score += 3;
81
+ hints.push("schema.org-article");
82
+ }
83
+ // Published-date signals.
84
+ if ($("time[datetime]").length > 0 ||
85
+ $('meta[property="article:published_time"]').length > 0) {
86
+ score += 1;
87
+ hints.push("published-date");
88
+ }
89
+ // Map the additive score to a type + confidence.
90
+ if (score >= 3) {
91
+ return { type: "article", confidence: Math.min(0.5 + score * 0.1, 0.98), hints };
92
+ }
93
+ if (score > 0) {
94
+ return { type: "article", confidence: 0.4 + score * 0.05, hints };
95
+ }
96
+ return { type: "generic", confidence: 0.6, hints: ["no-article-signals"] };
97
+ }
98
+ //# sourceMappingURL=typeDetector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typeDetector.js","sourceRoot":"","sources":["../src/typeDetector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Page categories the MVP can extract. Kept intentionally small (AXI principle 2:
3
+ * minimal schemas). More types (pricing, product) are Phase 2.
4
+ */
5
+ export type PageType = "article" | "documentation" | "generic";
6
+ export interface AxiFetchOptions {
7
+ /** Include outbound links in the response. Default: true. */
8
+ includeLinks?: boolean;
9
+ /** Network timeout in milliseconds. Default: 10000. */
10
+ timeout?: number;
11
+ /**
12
+ * Max characters of main content before truncation (AXI principle 3).
13
+ * Default: 1500. Ignored when `full` is true.
14
+ */
15
+ maxContentLength?: number;
16
+ /** Bypass truncation and return full content (the `--full` escape hatch). */
17
+ full?: boolean;
18
+ /** Override the User-Agent header. */
19
+ userAgent?: string;
20
+ /** Use the on-disk response cache. Default: true. */
21
+ cache?: boolean;
22
+ /** Cache freshness window in ms. Default: 15 minutes. */
23
+ cacheTtl?: number;
24
+ }
25
+ export interface Metadata {
26
+ url: string;
27
+ title: string;
28
+ type: PageType;
29
+ /** Type-detection confidence, 0-1. */
30
+ confidence: number;
31
+ /** ISO timestamp of when the fetch completed. */
32
+ fetchedAt: string;
33
+ /** Length (chars) of the full extracted main content, before truncation. */
34
+ contentLength: number;
35
+ }
36
+ export interface Link {
37
+ text: string;
38
+ url: string;
39
+ kind: "internal" | "external";
40
+ }
41
+ /** A heading in the content outline. Text lives in `Content.main`, not here. */
42
+ export interface Section {
43
+ heading: string;
44
+ level: number;
45
+ }
46
+ /** A fenced/`<pre>` code block pulled out of the prose. */
47
+ export interface CodeBlock {
48
+ /** Detected language (from class hints), if any. */
49
+ language?: string;
50
+ code: string;
51
+ }
52
+ /** A tabular block pulled out of the prose (strong TOON fit). */
53
+ export interface Table {
54
+ headers: string[];
55
+ rows: string[][];
56
+ }
57
+ export interface Content {
58
+ /** Primary extracted prose, possibly truncated (see `truncated`). */
59
+ main: string;
60
+ /** True when `main` was truncated to `maxContentLength`. */
61
+ truncated: boolean;
62
+ /** Heading outline of the page, if any. */
63
+ sections: Section[];
64
+ /** Code blocks lifted out of the prose. */
65
+ codeBlocks: CodeBlock[];
66
+ /** Data tables lifted out of the prose. */
67
+ tables: Table[];
68
+ /** Relevant outbound links, if `includeLinks` was set. */
69
+ links: Link[];
70
+ }
71
+ export interface AxiResponse {
72
+ metadata: Metadata;
73
+ content: Content;
74
+ /** Contextual next-step suggestions for the agent (AXI principle 9). */
75
+ nextSteps: string[];
76
+ }
77
+ export interface FetchResult {
78
+ /** Structured response object (keep internal logic on plain objects). */
79
+ axiResponse: AxiResponse;
80
+ /** TOON-encoded rendering of the response (the agent-facing payload). */
81
+ toonOutput: string;
82
+ }
83
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,eAAe,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,GAAG,UAAU,CAAC;CAC/B;AAED,gFAAgF;AAChF,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,2DAA2D;AAC3D,MAAM,WAAW,SAAS;IACxB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,iEAAiE;AACjE,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,OAAO;IACtB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,2CAA2C;IAC3C,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,2CAA2C;IAC3C,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,0DAA0D;IAC1D,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,WAAW,EAAE,WAAW,CAAC;IACzB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;CACpB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}