markit-ai 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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +333 -0
  3. package/dist/commands/config.d.ts +4 -0
  4. package/dist/commands/config.js +133 -0
  5. package/dist/commands/convert.d.ts +5 -0
  6. package/dist/commands/convert.js +110 -0
  7. package/dist/commands/formats.d.ts +2 -0
  8. package/dist/commands/formats.js +56 -0
  9. package/dist/commands/init.d.ts +2 -0
  10. package/dist/commands/init.js +29 -0
  11. package/dist/commands/onboard.d.ts +2 -0
  12. package/dist/commands/onboard.js +61 -0
  13. package/dist/commands/plugin.d.ts +4 -0
  14. package/dist/commands/plugin.js +58 -0
  15. package/dist/config.d.ts +26 -0
  16. package/dist/config.js +42 -0
  17. package/dist/converters/audio.d.ts +7 -0
  18. package/dist/converters/audio.js +87 -0
  19. package/dist/converters/csv.d.ts +7 -0
  20. package/dist/converters/csv.js +83 -0
  21. package/dist/converters/docx.d.ts +6 -0
  22. package/dist/converters/docx.js +28 -0
  23. package/dist/converters/epub.d.ts +8 -0
  24. package/dist/converters/epub.js +110 -0
  25. package/dist/converters/html.d.ts +6 -0
  26. package/dist/converters/html.js +33 -0
  27. package/dist/converters/image.d.ts +6 -0
  28. package/dist/converters/image.js +94 -0
  29. package/dist/converters/ipynb.d.ts +6 -0
  30. package/dist/converters/ipynb.js +72 -0
  31. package/dist/converters/json.d.ts +6 -0
  32. package/dist/converters/json.js +21 -0
  33. package/dist/converters/pdf.d.ts +6 -0
  34. package/dist/converters/pdf.js +29 -0
  35. package/dist/converters/plain-text.d.ts +6 -0
  36. package/dist/converters/plain-text.js +41 -0
  37. package/dist/converters/pptx.d.ts +8 -0
  38. package/dist/converters/pptx.js +189 -0
  39. package/dist/converters/rss.d.ts +11 -0
  40. package/dist/converters/rss.js +134 -0
  41. package/dist/converters/wikipedia.d.ts +6 -0
  42. package/dist/converters/wikipedia.js +35 -0
  43. package/dist/converters/xlsx.d.ts +8 -0
  44. package/dist/converters/xlsx.js +139 -0
  45. package/dist/converters/xml.d.ts +6 -0
  46. package/dist/converters/xml.js +17 -0
  47. package/dist/converters/yaml.d.ts +6 -0
  48. package/dist/converters/yaml.js +16 -0
  49. package/dist/converters/zip.d.ts +8 -0
  50. package/dist/converters/zip.js +56 -0
  51. package/dist/index.d.ts +28 -0
  52. package/dist/index.js +24 -0
  53. package/dist/llm.d.ts +10 -0
  54. package/dist/llm.js +139 -0
  55. package/dist/main.d.ts +2 -0
  56. package/dist/main.js +182 -0
  57. package/dist/markit.d.ts +19 -0
  58. package/dist/markit.js +124 -0
  59. package/dist/mill.d.ts +18 -0
  60. package/dist/mill.js +123 -0
  61. package/dist/plugins/api.d.ts +7 -0
  62. package/dist/plugins/api.js +44 -0
  63. package/dist/plugins/index.d.ts +4 -0
  64. package/dist/plugins/index.js +3 -0
  65. package/dist/plugins/installer.d.ts +25 -0
  66. package/dist/plugins/installer.js +176 -0
  67. package/dist/plugins/loader.d.ts +6 -0
  68. package/dist/plugins/loader.js +61 -0
  69. package/dist/plugins/types.d.ts +25 -0
  70. package/dist/plugins/types.js +1 -0
  71. package/dist/providers/anthropic.d.ts +2 -0
  72. package/dist/providers/anthropic.js +47 -0
  73. package/dist/providers/index.d.ts +21 -0
  74. package/dist/providers/index.js +58 -0
  75. package/dist/providers/openai.d.ts +2 -0
  76. package/dist/providers/openai.js +65 -0
  77. package/dist/providers/types.d.ts +26 -0
  78. package/dist/providers/types.js +1 -0
  79. package/dist/types.d.ts +28 -0
  80. package/dist/types.js +1 -0
  81. package/dist/utils/exit-codes.d.ts +4 -0
  82. package/dist/utils/exit-codes.js +4 -0
  83. package/dist/utils/output.d.ts +22 -0
  84. package/dist/utils/output.js +31 -0
  85. package/package.json +70 -0
@@ -0,0 +1,35 @@
1
+ import TurndownService from "turndown";
2
+ const WIKIPEDIA_RE = /^https?:\/\/[a-zA-Z]{2,3}\.wikipedia\.org\//;
3
+ export class WikipediaConverter {
4
+ name = "wikipedia";
5
+ accepts(streamInfo) {
6
+ if (!streamInfo.url)
7
+ return false;
8
+ return WIKIPEDIA_RE.test(streamInfo.url);
9
+ }
10
+ async convert(input, streamInfo) {
11
+ const html = new TextDecoder(streamInfo.charset || "utf-8").decode(input);
12
+ // Extract the main content div
13
+ const contentMatch = html.match(/<div[^>]*id="mw-content-text"[^>]*>([\s\S]*?)<\/div>\s*(?:<\/div>|$)/i);
14
+ // Extract title
15
+ const titleMatch = html.match(/<span[^>]*class="mw-page-title-main"[^>]*>([\s\S]*?)<\/span>/i) ||
16
+ html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
17
+ const title = titleMatch ? titleMatch[1].replace(/ - Wikipedia$/, "").trim() : undefined;
18
+ const turndown = new TurndownService({
19
+ headingStyle: "atx",
20
+ codeBlockStyle: "fenced",
21
+ });
22
+ // Clean up Wikipedia-specific elements
23
+ let content = contentMatch ? contentMatch[1] : html;
24
+ content = content
25
+ .replace(/<script[\s\S]*?<\/script>/gi, "")
26
+ .replace(/<style[\s\S]*?<\/style>/gi, "")
27
+ .replace(/<div[^>]*class="[^"]*mw-editsection[^"]*"[\s\S]*?<\/div>/gi, "") // edit links
28
+ .replace(/<sup[^>]*class="[^"]*reference[^"]*"[\s\S]*?<\/sup>/gi, "") // reference numbers
29
+ .replace(/<div[^>]*class="[^"]*navbox[\s\S]*?<\/div>/gi, "") // navboxes
30
+ .replace(/<table[^>]*class="[^"]*sidebar[\s\S]*?<\/table>/gi, ""); // sidebars
31
+ const markdown = turndown.turndown(content).trim();
32
+ const result = title ? `# ${title}\n\n${markdown}` : markdown;
33
+ return { markdown: result, title };
34
+ }
35
+ }
@@ -0,0 +1,8 @@
1
+ import type { Converter, ConversionResult, StreamInfo } from "../types.js";
2
+ export declare class XlsxConverter implements Converter {
3
+ name: string;
4
+ accepts(streamInfo: StreamInfo): boolean;
5
+ convert(input: Buffer, _streamInfo: StreamInfo): Promise<ConversionResult>;
6
+ private getCellValue;
7
+ private getSharedString;
8
+ }
@@ -0,0 +1,139 @@
1
+ import JSZip from "jszip";
2
+ import { XMLParser } from "fast-xml-parser";
3
+ const EXTENSIONS = [".xlsx"];
4
+ const MIMETYPES = [
5
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
6
+ ];
7
+ export class XlsxConverter {
8
+ name = "xlsx";
9
+ accepts(streamInfo) {
10
+ if (streamInfo.extension && EXTENSIONS.includes(streamInfo.extension))
11
+ return true;
12
+ if (streamInfo.mimetype && MIMETYPES.some((m) => streamInfo.mimetype.startsWith(m)))
13
+ return true;
14
+ return false;
15
+ }
16
+ async convert(input, _streamInfo) {
17
+ const zip = await JSZip.loadAsync(input);
18
+ const parser = new XMLParser({
19
+ ignoreAttributes: false,
20
+ attributeNamePrefix: "@_",
21
+ textNodeName: "#text",
22
+ });
23
+ // Parse shared strings
24
+ const ssXml = await zip.file("xl/sharedStrings.xml")?.async("string");
25
+ const ss = ssXml ? parser.parse(ssXml) : null;
26
+ const siList = ss?.sst?.si;
27
+ const shared = toArray(siList);
28
+ // Parse workbook for sheet names
29
+ const wbXml = await zip.file("xl/workbook.xml")?.async("string");
30
+ if (!wbXml)
31
+ throw new Error("Invalid XLSX: missing workbook.xml");
32
+ const wb = parser.parse(wbXml);
33
+ const sheets = toArray(wb.workbook?.sheets?.sheet);
34
+ // Parse workbook rels to map rIds to sheet files
35
+ const relsXml = await zip.file("xl/_rels/workbook.xml.rels")?.async("string");
36
+ const rels = relsXml ? parser.parse(relsXml) : null;
37
+ const relList = toArray(rels?.Relationships?.Relationship);
38
+ const relMap = new Map();
39
+ for (const r of relList) {
40
+ relMap.set(r["@_Id"], r["@_Target"]);
41
+ }
42
+ const sections = [];
43
+ for (const sheet of sheets) {
44
+ const sheetName = sheet["@_name"];
45
+ const rId = sheet["@_r:id"];
46
+ const target = relMap.get(rId);
47
+ if (!target)
48
+ continue;
49
+ const sheetPath = target.startsWith("/") ? target.slice(1) : `xl/${target}`;
50
+ const sheetXml = await zip.file(sheetPath)?.async("string");
51
+ if (!sheetXml)
52
+ continue;
53
+ const parsed = parser.parse(sheetXml);
54
+ const rows = toArray(parsed.worksheet?.sheetData?.row);
55
+ if (rows.length === 0)
56
+ continue;
57
+ // Extract all rows as string arrays
58
+ const tableRows = [];
59
+ for (const row of rows) {
60
+ const cells = toArray(row.c);
61
+ const values = [];
62
+ for (const cell of cells) {
63
+ values.push(this.getCellValue(cell, shared));
64
+ }
65
+ tableRows.push(values);
66
+ }
67
+ if (tableRows.length === 0)
68
+ continue;
69
+ // Normalize column count
70
+ const maxCols = Math.max(...tableRows.map((r) => r.length));
71
+ for (const row of tableRows) {
72
+ while (row.length < maxCols)
73
+ row.push("");
74
+ }
75
+ sections.push(`## ${sheetName}`);
76
+ const [header, ...body] = tableRows;
77
+ const lines = [];
78
+ lines.push(`| ${header.join(" | ")} |`);
79
+ lines.push(`| ${header.map(() => "---").join(" | ")} |`);
80
+ for (const row of body) {
81
+ lines.push(`| ${row.join(" | ")} |`);
82
+ }
83
+ sections.push(lines.join("\n"));
84
+ }
85
+ return { markdown: sections.join("\n\n") };
86
+ }
87
+ getCellValue(cell, shared) {
88
+ // Shared string
89
+ if (cell["@_t"] === "s") {
90
+ return this.getSharedString(shared, Number(cell.v));
91
+ }
92
+ // Inline string
93
+ if (cell["@_t"] === "inlineStr") {
94
+ const is = cell.is;
95
+ if (!is)
96
+ return "";
97
+ if (is.t != null)
98
+ return textValue(is.t);
99
+ if (is.r)
100
+ return toArray(is.r).map((r) => textValue(r.t)).join("");
101
+ return "";
102
+ }
103
+ // Boolean
104
+ if (cell["@_t"] === "b") {
105
+ return cell.v === 1 || cell.v === "1" ? "TRUE" : "FALSE";
106
+ }
107
+ // Number or formula result
108
+ if (cell.v != null)
109
+ return String(cell.v);
110
+ return "";
111
+ }
112
+ getSharedString(shared, idx) {
113
+ const si = shared[idx];
114
+ if (!si)
115
+ return "";
116
+ // Simple text
117
+ if (si.t != null)
118
+ return textValue(si.t);
119
+ // Rich text runs
120
+ if (si.r) {
121
+ return toArray(si.r)
122
+ .map((r) => textValue(r.t))
123
+ .join("");
124
+ }
125
+ return "";
126
+ }
127
+ }
128
+ function textValue(t) {
129
+ if (t == null)
130
+ return "";
131
+ if (typeof t === "object")
132
+ return t["#text"] || "";
133
+ return String(t);
134
+ }
135
+ function toArray(val) {
136
+ if (!val)
137
+ return [];
138
+ return Array.isArray(val) ? val : [val];
139
+ }
@@ -0,0 +1,6 @@
1
+ import type { Converter, ConversionResult, StreamInfo } from "../types.js";
2
+ export declare class XmlConverter implements Converter {
3
+ name: string;
4
+ accepts(streamInfo: StreamInfo): boolean;
5
+ convert(input: Buffer, streamInfo: StreamInfo): Promise<ConversionResult>;
6
+ }
@@ -0,0 +1,17 @@
1
+ const EXTENSIONS = [".xml", ".svg"];
2
+ const MIMETYPES = ["text/xml", "application/xml"];
3
+ export class XmlConverter {
4
+ name = "xml";
5
+ accepts(streamInfo) {
6
+ if (streamInfo.extension && EXTENSIONS.includes(streamInfo.extension))
7
+ return true;
8
+ if (streamInfo.mimetype && MIMETYPES.some((m) => streamInfo.mimetype.startsWith(m)))
9
+ return true;
10
+ return false;
11
+ }
12
+ async convert(input, streamInfo) {
13
+ const text = new TextDecoder(streamInfo.charset || "utf-8").decode(input);
14
+ const ext = streamInfo.extension?.slice(1) || "xml";
15
+ return { markdown: `\`\`\`${ext}\n${text}\n\`\`\`` };
16
+ }
17
+ }
@@ -0,0 +1,6 @@
1
+ import type { Converter, ConversionResult, StreamInfo } from "../types.js";
2
+ export declare class YamlConverter implements Converter {
3
+ name: string;
4
+ accepts(streamInfo: StreamInfo): boolean;
5
+ convert(input: Buffer, streamInfo: StreamInfo): Promise<ConversionResult>;
6
+ }
@@ -0,0 +1,16 @@
1
+ const EXTENSIONS = [".yaml", ".yml"];
2
+ const MIMETYPES = ["text/yaml", "application/x-yaml"];
3
+ export class YamlConverter {
4
+ name = "yaml";
5
+ accepts(streamInfo) {
6
+ if (streamInfo.extension && EXTENSIONS.includes(streamInfo.extension))
7
+ return true;
8
+ if (streamInfo.mimetype && MIMETYPES.some((m) => streamInfo.mimetype.startsWith(m)))
9
+ return true;
10
+ return false;
11
+ }
12
+ async convert(input, streamInfo) {
13
+ const text = new TextDecoder(streamInfo.charset || "utf-8").decode(input);
14
+ return { markdown: `\`\`\`yaml\n${text}\n\`\`\`` };
15
+ }
16
+ }
@@ -0,0 +1,8 @@
1
+ import type { Converter, ConversionResult, StreamInfo } from "../types.js";
2
+ export declare class ZipConverter implements Converter {
3
+ name: string;
4
+ private parentConverters;
5
+ constructor(parentConverters: Converter[]);
6
+ accepts(streamInfo: StreamInfo): boolean;
7
+ convert(input: Buffer, streamInfo: StreamInfo): Promise<ConversionResult>;
8
+ }
@@ -0,0 +1,56 @@
1
+ import JSZip from "jszip";
2
+ import { extname, basename } from "node:path";
3
+ const EXTENSIONS = [".zip"];
4
+ const MIMETYPES = ["application/zip", "application/x-zip-compressed"];
5
+ export class ZipConverter {
6
+ name = "zip";
7
+ parentConverters;
8
+ constructor(parentConverters) {
9
+ this.parentConverters = parentConverters;
10
+ }
11
+ accepts(streamInfo) {
12
+ if (streamInfo.extension && EXTENSIONS.includes(streamInfo.extension))
13
+ return true;
14
+ if (streamInfo.mimetype && MIMETYPES.some((m) => streamInfo.mimetype.startsWith(m)))
15
+ return true;
16
+ return false;
17
+ }
18
+ async convert(input, streamInfo) {
19
+ const zip = await JSZip.loadAsync(input);
20
+ const label = streamInfo.localPath || streamInfo.filename || "archive.zip";
21
+ const sections = [`Content from \`${basename(label)}\`:`];
22
+ for (const [path, file] of Object.entries(zip.files)) {
23
+ if (file.dir)
24
+ continue;
25
+ const ext = extname(path).toLowerCase();
26
+ const fileInfo = {
27
+ extension: ext,
28
+ filename: basename(path),
29
+ };
30
+ const buffer = Buffer.from(await file.async("arraybuffer"));
31
+ // Try each converter
32
+ let converted = false;
33
+ for (const converter of this.parentConverters) {
34
+ if (converter.name === "zip")
35
+ continue; // avoid recursion loops
36
+ if (!converter.accepts(fileInfo))
37
+ continue;
38
+ try {
39
+ const result = await converter.convert(buffer, fileInfo);
40
+ if (result.markdown.trim()) {
41
+ sections.push(`## File: ${path}\n\n${result.markdown.trim()}`);
42
+ converted = true;
43
+ break;
44
+ }
45
+ }
46
+ catch {
47
+ // Try next converter
48
+ }
49
+ }
50
+ if (!converted) {
51
+ sections.push(`## File: ${path}\n\n*[binary file]*`);
52
+ }
53
+ }
54
+ return { markdown: sections.join("\n\n").trim() };
55
+ }
56
+ }
@@ -0,0 +1,28 @@
1
+ export { Markit } from "./markit.js";
2
+ export type { Converter, ConversionResult, StreamInfo, MarkitOptions } from "./types.js";
3
+ export type { MarkitConfig } from "./config.js";
4
+ export { createLlmFunctions, registerProvider, getProvider, listProviders, } from "./providers/index.js";
5
+ export type { Provider, ProviderConfig, ResolvedConfig } from "./providers/types.js";
6
+ export { openai } from "./providers/openai.js";
7
+ export { anthropic } from "./providers/anthropic.js";
8
+ export { PdfConverter } from "./converters/pdf.js";
9
+ export { DocxConverter } from "./converters/docx.js";
10
+ export { PptxConverter } from "./converters/pptx.js";
11
+ export { XlsxConverter } from "./converters/xlsx.js";
12
+ export { EpubConverter } from "./converters/epub.js";
13
+ export { IpynbConverter } from "./converters/ipynb.js";
14
+ export { HtmlConverter } from "./converters/html.js";
15
+ export { WikipediaConverter } from "./converters/wikipedia.js";
16
+ export { RssConverter } from "./converters/rss.js";
17
+ export { CsvConverter } from "./converters/csv.js";
18
+ export { JsonConverter } from "./converters/json.js";
19
+ export { YamlConverter } from "./converters/yaml.js";
20
+ export { XmlConverter } from "./converters/xml.js";
21
+ export { ZipConverter } from "./converters/zip.js";
22
+ export { ImageConverter } from "./converters/image.js";
23
+ export { AudioConverter } from "./converters/audio.js";
24
+ export { PlainTextConverter } from "./converters/plain-text.js";
25
+ export type { MarkitPluginAPI, PluginFunction, PluginDef } from "./plugins/types.js";
26
+ export { createPluginAPI, resolvePluginExport } from "./plugins/api.js";
27
+ export { loadPluginFromPath, loadAllPlugins } from "./plugins/loader.js";
28
+ export { installPlugin, removePlugin, listInstalled, } from "./plugins/installer.js";
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ export { Markit } from "./markit.js";
2
+ export { createLlmFunctions, registerProvider, getProvider, listProviders, } from "./providers/index.js";
3
+ export { openai } from "./providers/openai.js";
4
+ export { anthropic } from "./providers/anthropic.js";
5
+ export { PdfConverter } from "./converters/pdf.js";
6
+ export { DocxConverter } from "./converters/docx.js";
7
+ export { PptxConverter } from "./converters/pptx.js";
8
+ export { XlsxConverter } from "./converters/xlsx.js";
9
+ export { EpubConverter } from "./converters/epub.js";
10
+ export { IpynbConverter } from "./converters/ipynb.js";
11
+ export { HtmlConverter } from "./converters/html.js";
12
+ export { WikipediaConverter } from "./converters/wikipedia.js";
13
+ export { RssConverter } from "./converters/rss.js";
14
+ export { CsvConverter } from "./converters/csv.js";
15
+ export { JsonConverter } from "./converters/json.js";
16
+ export { YamlConverter } from "./converters/yaml.js";
17
+ export { XmlConverter } from "./converters/xml.js";
18
+ export { ZipConverter } from "./converters/zip.js";
19
+ export { ImageConverter } from "./converters/image.js";
20
+ export { AudioConverter } from "./converters/audio.js";
21
+ export { PlainTextConverter } from "./converters/plain-text.js";
22
+ export { createPluginAPI, resolvePluginExport } from "./plugins/api.js";
23
+ export { loadPluginFromPath, loadAllPlugins } from "./plugins/loader.js";
24
+ export { installPlugin, removePlugin, listInstalled, } from "./plugins/installer.js";
package/dist/llm.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { MarkitOptions } from "./types.js";
2
+ import type { MarkitConfig } from "./config.js";
3
+ /**
4
+ * Build describe/transcribe functions from .markit/config.json + env vars.
5
+ * Uses the OpenAI-compatible provider by default.
6
+ */
7
+ export declare function createLlmFunctions(config: MarkitConfig): MarkitOptions;
8
+ export declare function createOpenAIDescribe(config: MarkitConfig): (image: Buffer, mimetype: string) => Promise<string>;
9
+ export declare function createOpenAITranscribe(config: MarkitConfig): (audio: Buffer, mimetype: string) => Promise<string>;
10
+ export declare function createAnthropicDescribe(config: MarkitConfig): (image: Buffer, mimetype: string) => Promise<string>;
package/dist/llm.js ADDED
@@ -0,0 +1,139 @@
1
+ import { resolveApiKey, resolveApiBase, resolveModel, resolveTranscriptionModel, } from "./config.js";
2
+ /**
3
+ * Build describe/transcribe functions from .markit/config.json + env vars.
4
+ * Uses the OpenAI-compatible provider by default.
5
+ */
6
+ export function createLlmFunctions(config) {
7
+ const apiKey = resolveApiKey(config);
8
+ if (!apiKey)
9
+ return {};
10
+ const provider = config.llm?.provider || "openai";
11
+ if (provider === "anthropic") {
12
+ return {
13
+ describe: createAnthropicDescribe(config),
14
+ // Anthropic doesn't have a transcription API — leave undefined
15
+ };
16
+ }
17
+ return {
18
+ describe: createOpenAIDescribe(config),
19
+ transcribe: createOpenAITranscribe(config),
20
+ };
21
+ }
22
+ // ── OpenAI-compatible (also works with Groq, Together, Fireworks, Ollama) ──
23
+ export function createOpenAIDescribe(config) {
24
+ const apiKey = resolveApiKey(config);
25
+ const baseUrl = resolveApiBase(config).replace(/\/+$/, "");
26
+ const model = resolveModel(config);
27
+ return async (image, mimetype) => {
28
+ const base64 = image.toString("base64");
29
+ const res = await fetch(`${baseUrl}/chat/completions`, {
30
+ method: "POST",
31
+ headers: {
32
+ "Content-Type": "application/json",
33
+ Authorization: `Bearer ${apiKey}`,
34
+ },
35
+ body: JSON.stringify({
36
+ model,
37
+ messages: [
38
+ {
39
+ role: "user",
40
+ content: [
41
+ { type: "text", text: "Write a detailed description of this image." },
42
+ { type: "image_url", image_url: { url: `data:${mimetype};base64,${base64}` } },
43
+ ],
44
+ },
45
+ ],
46
+ max_tokens: 1024,
47
+ }),
48
+ });
49
+ if (!res.ok) {
50
+ const body = await res.text();
51
+ throw new Error(`OpenAI API error ${res.status}: ${body}`);
52
+ }
53
+ const data = await res.json();
54
+ return data.choices?.[0]?.message?.content ?? "";
55
+ };
56
+ }
57
+ export function createOpenAITranscribe(config) {
58
+ const apiKey = resolveApiKey(config);
59
+ const baseUrl = resolveApiBase(config).replace(/\/+$/, "");
60
+ const transcriptionModel = resolveTranscriptionModel(config);
61
+ return async (audio, mimetype) => {
62
+ const ext = mimeToExt(mimetype);
63
+ const file = new File([audio], `audio${ext}`, { type: mimetype });
64
+ const formData = new FormData();
65
+ formData.append("model", transcriptionModel);
66
+ formData.append("file", file);
67
+ const res = await fetch(`${baseUrl}/audio/transcriptions`, {
68
+ method: "POST",
69
+ headers: { Authorization: `Bearer ${apiKey}` },
70
+ body: formData,
71
+ });
72
+ if (!res.ok) {
73
+ const body = await res.text();
74
+ throw new Error(`Transcription API error ${res.status}: ${body}`);
75
+ }
76
+ const data = await res.json();
77
+ return data.text ?? "";
78
+ };
79
+ }
80
+ // ── Anthropic ───────────────────────────────────────────────────────────────
81
+ export function createAnthropicDescribe(config) {
82
+ const apiKey = process.env.ANTHROPIC_API_KEY ||
83
+ config.llm?.apiKey ||
84
+ resolveApiKey(config);
85
+ const baseUrl = (process.env.ANTHROPIC_BASE_URL ||
86
+ config.llm?.apiBase ||
87
+ "https://api.anthropic.com").replace(/\/+$/, "");
88
+ const model = resolveModel(config, undefined) || "claude-sonnet-4-20250514";
89
+ return async (image, mimetype) => {
90
+ const base64 = image.toString("base64");
91
+ const res = await fetch(`${baseUrl}/v1/messages`, {
92
+ method: "POST",
93
+ headers: {
94
+ "Content-Type": "application/json",
95
+ "x-api-key": apiKey,
96
+ "anthropic-version": "2023-06-01",
97
+ },
98
+ body: JSON.stringify({
99
+ model,
100
+ max_tokens: 1024,
101
+ messages: [
102
+ {
103
+ role: "user",
104
+ content: [
105
+ {
106
+ type: "image",
107
+ source: {
108
+ type: "base64",
109
+ media_type: mimetype,
110
+ data: base64,
111
+ },
112
+ },
113
+ { type: "text", text: "Write a detailed description of this image." },
114
+ ],
115
+ },
116
+ ],
117
+ }),
118
+ });
119
+ if (!res.ok) {
120
+ const body = await res.text();
121
+ throw new Error(`Anthropic API error ${res.status}: ${body}`);
122
+ }
123
+ const data = await res.json();
124
+ return data.content?.[0]?.text ?? "";
125
+ };
126
+ }
127
+ // ── Helpers ─────────────────────────────────────────────────────────────────
128
+ function mimeToExt(mime) {
129
+ const map = {
130
+ "audio/mpeg": ".mp3",
131
+ "audio/wav": ".wav",
132
+ "audio/mp4": ".m4a",
133
+ "video/mp4": ".mp4",
134
+ "audio/ogg": ".ogg",
135
+ "audio/flac": ".flac",
136
+ "audio/aac": ".aac",
137
+ };
138
+ return map[mime] || ".mp3";
139
+ }
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};