foxmind 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +308 -2
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +6 -0
- package/dist/browser/gliner2-model.d.ts +57 -0
- package/dist/browser/gliner2-model.js +233 -0
- package/dist/browser/gliner2.d.ts +13 -0
- package/dist/browser/gliner2.js +50 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.js +6 -0
- package/dist/browser/runtime.d.ts +61 -0
- package/dist/browser/runtime.js +205 -0
- package/dist/browser/toolcalls.d.ts +6 -0
- package/dist/browser/toolcalls.js +20 -0
- package/dist/browser/transformers.d.ts +18 -0
- package/dist/browser/transformers.js +81 -0
- package/dist/browser/trialml.d.ts +32 -0
- package/dist/browser/trialml.js +196 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +46 -0
- package/dist/doctor.d.ts +23 -0
- package/dist/doctor.js +58 -0
- package/dist/errors.d.ts +48 -0
- package/dist/errors.js +36 -0
- package/dist/http.d.ts +46 -0
- package/dist/http.js +164 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/mind.d.ts +60 -0
- package/dist/mind.js +122 -0
- package/dist/providers/anthropic.d.ts +16 -0
- package/dist/providers/anthropic.js +200 -0
- package/dist/providers/openai.d.ts +39 -0
- package/dist/providers/openai.js +177 -0
- package/dist/providers/presets.d.ts +33 -0
- package/dist/providers/presets.js +89 -0
- package/dist/reply.d.ts +10 -0
- package/dist/reply.js +60 -0
- package/dist/sse.d.ts +6 -0
- package/dist/sse.js +38 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.js +3 -0
- package/package.json +59 -4
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Ready-made settings for the local servers people run on their own machine.
|
|
2
|
+
import { FoxmindError } from "../errors.js";
|
|
3
|
+
import { openaiCompatible } from "./openai.js";
|
|
4
|
+
/** Underdog Saluki 27B 1.0, from its Hugging Face model card (checked 2026-10-08). */
|
|
5
|
+
export const SALUKI = {
|
|
6
|
+
repo: "ConwayResearch/Underdog-Saluki-27B-1.0",
|
|
7
|
+
file: "Underdog-Saluki-27B-1.0-IQ2-mix.gguf",
|
|
8
|
+
sizeGB: 7.89,
|
|
9
|
+
license: "Apache-2.0",
|
|
10
|
+
download: "huggingface-cli download ConwayResearch/Underdog-Saluki-27B-1.0 Underdog-Saluki-27B-1.0-IQ2-mix.gguf --local-dir .",
|
|
11
|
+
serve: "llama-server -m Underdog-Saluki-27B-1.0-IQ2-mix.gguf --jinja -ngl 99 -fa on -c 32768",
|
|
12
|
+
};
|
|
13
|
+
function escape(text) {
|
|
14
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15
|
+
}
|
|
16
|
+
/** Ollama's OpenAI-compatible API. "llama3.2" matches "llama3.2:latest". */
|
|
17
|
+
export function ollama(options) {
|
|
18
|
+
const tagged = options.model.includes(":") ? escape(options.model) : `${escape(options.model)}(:latest)?`;
|
|
19
|
+
const provider = openaiCompatible({
|
|
20
|
+
name: "ollama",
|
|
21
|
+
baseURL: "http://127.0.0.1:11434/v1",
|
|
22
|
+
checkModel: new RegExp(`^${tagged}$`),
|
|
23
|
+
hint: `Start Ollama with "ollama serve", then run "ollama pull ${options.model}".`,
|
|
24
|
+
...options,
|
|
25
|
+
});
|
|
26
|
+
// Ollama answers 403 to an origin it does not allow, such as moz-extension://.
|
|
27
|
+
const explain = (error) => {
|
|
28
|
+
if (!(error instanceof FoxmindError) || error.status !== 403)
|
|
29
|
+
return error;
|
|
30
|
+
return new FoxmindError("auth", 'Ollama refused this origin (HTTP 403). Start it with OLLAMA_ORIGINS="moz-extension://*" to allow Firefox extensions.', { provider: provider.name, tier: provider.tier, status: 403 });
|
|
31
|
+
};
|
|
32
|
+
const { chat, embed, probe } = provider;
|
|
33
|
+
const root = (options.baseURL ?? "http://127.0.0.1:11434/v1").replace(/\/v1\/?$/, "");
|
|
34
|
+
return {
|
|
35
|
+
...provider,
|
|
36
|
+
// Ollama can run a model on a remote host with a name that does not say so.
|
|
37
|
+
// /api/tags lists that host; a local-tier provider must not use such a model.
|
|
38
|
+
async probe(probeOptions) {
|
|
39
|
+
const result = await probe(probeOptions);
|
|
40
|
+
if (!result.ok || provider.tier === "cloud")
|
|
41
|
+
return result;
|
|
42
|
+
const tags = await fetch(`${root}/api/tags`, { signal: AbortSignal.timeout(3000), redirect: "manual" })
|
|
43
|
+
.then((response) => (response.ok ? response.json() : undefined))
|
|
44
|
+
.catch(() => undefined);
|
|
45
|
+
// Fail closed: when we cannot see where the model runs, a local-tier provider does not use it.
|
|
46
|
+
if (!tags)
|
|
47
|
+
return { ok: false, code: "unreachable", where: result.where, reason: `Cannot read ${root}/api/tags to check that ${options.model} runs on this machine.` };
|
|
48
|
+
const entry = tags.models?.find((model) => model.name === options.model || model.name === `${options.model}:latest`);
|
|
49
|
+
if (!entry?.remote_host)
|
|
50
|
+
return result;
|
|
51
|
+
return { ok: false, code: "remote_model", where: result.where, reason: `Ollama runs ${options.model} on ${entry.remote_host}, not on this machine. Pass tier: "cloud" to use it as a cloud model.` };
|
|
52
|
+
},
|
|
53
|
+
chat: (messages, chatOptions) => chat(messages, chatOptions).catch((error) => { throw explain(error); }),
|
|
54
|
+
embed: (texts, embedOptions) => embed(texts, embedOptions).catch((error) => { throw explain(error); }),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** llama.cpp llama-server. It serves one model, so the name is not checked. */
|
|
58
|
+
export function llamaServer(options = {}) {
|
|
59
|
+
return openaiCompatible({
|
|
60
|
+
name: "llama-server",
|
|
61
|
+
baseURL: "http://127.0.0.1:8080/v1",
|
|
62
|
+
checkModel: false,
|
|
63
|
+
hint: 'Start it with "llama-server -m <model.gguf> --jinja".',
|
|
64
|
+
...options,
|
|
65
|
+
model: options.model ?? "default",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** LM Studio's local server. */
|
|
69
|
+
export function lmStudio(options) {
|
|
70
|
+
return openaiCompatible({ name: "lm-studio", baseURL: "http://127.0.0.1:1234/v1", hint: 'Start it with "lms server start".', ...options });
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Underdog Saluki 27B on llama-server: the default planner for private mode.
|
|
74
|
+
* Thinking is off by default with temperature 0, the model card's settings
|
|
75
|
+
* for direct tool calls. `thinking: true` uses its settings for reasoning.
|
|
76
|
+
*/
|
|
77
|
+
export function saluki(options = {}) {
|
|
78
|
+
const { thinking = false, ...rest } = options;
|
|
79
|
+
const sampling = thinking ? { temperature: 0.6, top_p: 0.95, top_k: 20 } : { temperature: 0 };
|
|
80
|
+
return openaiCompatible({
|
|
81
|
+
name: "saluki",
|
|
82
|
+
baseURL: "http://127.0.0.1:8080/v1",
|
|
83
|
+
checkModel: /saluki/i,
|
|
84
|
+
hint: `Download it with "${SALUKI.download}", then start it with "${SALUKI.serve}".`,
|
|
85
|
+
...rest,
|
|
86
|
+
model: SALUKI.file,
|
|
87
|
+
body: { ...sampling, chat_template_kwargs: { enable_thinking: thinking }, ...rest.body },
|
|
88
|
+
});
|
|
89
|
+
}
|
package/dist/reply.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Origin } from "./http.js";
|
|
2
|
+
import type { ChatReply, Message } from "./types.js";
|
|
3
|
+
/** Move a leading <think>…</think> block out of the answer, as Qwen3 servers often send it. */
|
|
4
|
+
export declare function splitThinking(message: Message): Message;
|
|
5
|
+
/**
|
|
6
|
+
* Throws `bad_tool_call` when a tool call's arguments are not a JSON object,
|
|
7
|
+
* and `bad_json` when the caller asked for JSON and got something else.
|
|
8
|
+
* Empty arguments become "{}", which is what servers send for a tool with no parameters.
|
|
9
|
+
*/
|
|
10
|
+
export declare function checkReply(origin: Origin, reply: ChatReply, json: boolean | undefined): ChatReply;
|
package/dist/reply.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Checks every provider runs on a reply before foxmind returns it.
|
|
2
|
+
import { failure } from "./http.js";
|
|
3
|
+
/** Move a leading <think>…</think> block out of the answer, as Qwen3 servers often send it. */
|
|
4
|
+
export function splitThinking(message) {
|
|
5
|
+
const match = message.content?.match(/^\s*<think>([\s\S]*?)<\/think>\s*/);
|
|
6
|
+
if (!match || message.content == null)
|
|
7
|
+
return message;
|
|
8
|
+
const reasoning = [message.reasoning, match[1].trim()].filter(Boolean).join("\n");
|
|
9
|
+
return { ...message, content: message.content.slice(match[0].length), ...(reasoning ? { reasoning } : {}) };
|
|
10
|
+
}
|
|
11
|
+
/** The JSON text in a reply: the whole reply, or the one ```json block in it. */
|
|
12
|
+
function jsonText(content) {
|
|
13
|
+
for (const candidate of [content.trim(), content.match(/```(?:json)?\s*([\s\S]*?)```/)?.[1]?.trim()]) {
|
|
14
|
+
if (!candidate)
|
|
15
|
+
continue;
|
|
16
|
+
try {
|
|
17
|
+
JSON.parse(candidate);
|
|
18
|
+
return candidate;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Try the next candidate.
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Throws `bad_tool_call` when a tool call's arguments are not a JSON object,
|
|
28
|
+
* and `bad_json` when the caller asked for JSON and got something else.
|
|
29
|
+
* Empty arguments become "{}", which is what servers send for a tool with no parameters.
|
|
30
|
+
*/
|
|
31
|
+
export function checkReply(origin, reply, json) {
|
|
32
|
+
const message = splitThinking(reply.message);
|
|
33
|
+
const calls = message.tool_calls?.map((toolCall) => {
|
|
34
|
+
const text = toolCall.function.arguments.trim() || "{}";
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = JSON.parse(text);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
throw failure(origin, "bad_tool_call", `The arguments of tool call "${toolCall.function.name}" are not valid JSON.`, { raw: toolCall.function.arguments });
|
|
41
|
+
}
|
|
42
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
43
|
+
throw failure(origin, "bad_tool_call", `The arguments of tool call "${toolCall.function.name}" are not a JSON object.`, { raw: text });
|
|
44
|
+
}
|
|
45
|
+
// Only the OpenAI fields: some servers add their own (Ollama adds "index").
|
|
46
|
+
return { id: toolCall.id, type: "function", function: { name: toolCall.function.name, arguments: text } };
|
|
47
|
+
});
|
|
48
|
+
if (calls?.length)
|
|
49
|
+
message.tool_calls = calls;
|
|
50
|
+
else
|
|
51
|
+
delete message.tool_calls;
|
|
52
|
+
if (json && !calls?.length) {
|
|
53
|
+
const text = jsonText(message.content ?? "");
|
|
54
|
+
if (text === undefined)
|
|
55
|
+
throw failure(origin, "bad_json", "The reply is not JSON, but the call asked for JSON.", { raw: message.content ?? "" });
|
|
56
|
+
message.content = text;
|
|
57
|
+
}
|
|
58
|
+
const finishReason = calls?.length && reply.finishReason === "stop" ? "tool_calls" : reply.finishReason;
|
|
59
|
+
return { ...reply, message: message, finishReason };
|
|
60
|
+
}
|
package/dist/sse.d.ts
ADDED
package/dist/sse.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Server-sent events from a fetch body, as OpenAI and Anthropic stream them.
|
|
2
|
+
function parse(block) {
|
|
3
|
+
let event;
|
|
4
|
+
const data = [];
|
|
5
|
+
for (const line of block.split(/\r?\n/)) {
|
|
6
|
+
if (line.startsWith("event:"))
|
|
7
|
+
event = line.slice(6).trim();
|
|
8
|
+
else if (line.startsWith("data:"))
|
|
9
|
+
data.push(line.slice(5).replace(/^ /, ""));
|
|
10
|
+
}
|
|
11
|
+
return data.length ? { ...(event ? { event } : {}), data: data.join("\n") } : undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Yield each event. A read error (a dropped connection, an abort) is thrown to the caller. */
|
|
14
|
+
export async function* events(body) {
|
|
15
|
+
const reader = body.getReader();
|
|
16
|
+
const decoder = new TextDecoder();
|
|
17
|
+
let buffer = "";
|
|
18
|
+
try {
|
|
19
|
+
for (;;) {
|
|
20
|
+
const { value, done } = await reader.read();
|
|
21
|
+
if (done)
|
|
22
|
+
break;
|
|
23
|
+
buffer += decoder.decode(value, { stream: true });
|
|
24
|
+
for (let end = buffer.search(/\r?\n\r?\n/); end >= 0; end = buffer.search(/\r?\n\r?\n/)) {
|
|
25
|
+
const event = parse(buffer.slice(0, end));
|
|
26
|
+
buffer = buffer.slice(end).replace(/^\r?\n\r?\n/, "");
|
|
27
|
+
if (event)
|
|
28
|
+
yield event;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const last = parse(buffer);
|
|
32
|
+
if (last)
|
|
33
|
+
yield last;
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await reader.cancel().catch(() => { });
|
|
37
|
+
}
|
|
38
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** Where a provider runs: in the browser, on a server on this machine, or in a cloud you pay with your key. */
|
|
2
|
+
export type Tier = "browser" | "local" | "cloud";
|
|
3
|
+
export type Capability = "chat" | "embed" | "extract" | "classify";
|
|
4
|
+
export type Role = "system" | "user" | "assistant" | "tool";
|
|
5
|
+
export interface ToolCall {
|
|
6
|
+
id: string;
|
|
7
|
+
type: "function";
|
|
8
|
+
/** `arguments` is a JSON string. foxmind checks that it parses before it returns it. */
|
|
9
|
+
function: {
|
|
10
|
+
name: string;
|
|
11
|
+
arguments: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export interface Message {
|
|
15
|
+
role: Role;
|
|
16
|
+
content: string | null;
|
|
17
|
+
name?: string;
|
|
18
|
+
tool_calls?: ToolCall[];
|
|
19
|
+
tool_call_id?: string;
|
|
20
|
+
/** The model's thinking, when the server sends it apart from the answer. */
|
|
21
|
+
reasoning?: string;
|
|
22
|
+
/** Provider-specific data to send back unchanged on the next turn (for example Anthropic thinking blocks). */
|
|
23
|
+
provider_data?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
export interface Tool {
|
|
26
|
+
type: "function";
|
|
27
|
+
function: {
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
parameters?: Record<string, unknown>;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface CallOptions {
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
/**
|
|
36
|
+
* The longest wait, in milliseconds, for the response headers and then for
|
|
37
|
+
* each next piece of the body. A stream that keeps sending is never cut.
|
|
38
|
+
*/
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ChatOptions extends CallOptions {
|
|
42
|
+
tools?: Tool[];
|
|
43
|
+
/** Ask for one JSON object, and fail with `bad_json` when the reply is not JSON. */
|
|
44
|
+
json?: boolean;
|
|
45
|
+
temperature?: number;
|
|
46
|
+
maxTokens?: number;
|
|
47
|
+
/** Stream the reply. Called with each new piece of text. */
|
|
48
|
+
onDelta?: (text: string) => void;
|
|
49
|
+
}
|
|
50
|
+
export type FinishReason = "stop" | "length" | "tool_calls" | "content_filter";
|
|
51
|
+
export interface ChatReply {
|
|
52
|
+
message: Message & {
|
|
53
|
+
role: "assistant";
|
|
54
|
+
};
|
|
55
|
+
finishReason: FinishReason;
|
|
56
|
+
usage?: {
|
|
57
|
+
inputTokens: number;
|
|
58
|
+
outputTokens: number;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export interface Entity {
|
|
62
|
+
text: string;
|
|
63
|
+
confidence: number;
|
|
64
|
+
start: number;
|
|
65
|
+
end: number;
|
|
66
|
+
}
|
|
67
|
+
/** Label name to an optional description, as GLiNER2 takes them. */
|
|
68
|
+
export type Labels = Record<string, string | undefined>;
|
|
69
|
+
export interface ExtractOptions extends CallOptions {
|
|
70
|
+
/** Keep spans at or above this confidence. Default 0.5. */
|
|
71
|
+
threshold?: number;
|
|
72
|
+
}
|
|
73
|
+
export interface Probe {
|
|
74
|
+
ok: boolean;
|
|
75
|
+
/** Why the provider cannot run, as an error code. */
|
|
76
|
+
code?: string;
|
|
77
|
+
reason?: string;
|
|
78
|
+
/** Where it runs, for example "webgpu", "wasm" or a server URL. */
|
|
79
|
+
where?: string;
|
|
80
|
+
}
|
|
81
|
+
export interface ProviderStatus {
|
|
82
|
+
name: string;
|
|
83
|
+
tier: Tier;
|
|
84
|
+
model: string;
|
|
85
|
+
capabilities: readonly Capability[];
|
|
86
|
+
state: "idle" | "loading" | "ready" | "unavailable" | "error";
|
|
87
|
+
where?: string;
|
|
88
|
+
/** Download progress from 0 to 1 while a model loads. */
|
|
89
|
+
progress?: number;
|
|
90
|
+
reason?: string;
|
|
91
|
+
}
|
|
92
|
+
export interface Provider {
|
|
93
|
+
readonly name: string;
|
|
94
|
+
readonly tier: Tier;
|
|
95
|
+
readonly model: string;
|
|
96
|
+
readonly capabilities: readonly Capability[];
|
|
97
|
+
/** Check, without a model call, that the provider can run now. */
|
|
98
|
+
probe(options?: CallOptions): Promise<Probe>;
|
|
99
|
+
status(): ProviderStatus;
|
|
100
|
+
/** Download and start the model now, not at the first call. */
|
|
101
|
+
load?(options?: CallOptions): Promise<void>;
|
|
102
|
+
chat?(messages: Message[], options: ChatOptions): Promise<ChatReply>;
|
|
103
|
+
embed?(texts: string[], options: CallOptions): Promise<number[][]>;
|
|
104
|
+
extract?(text: string, labels: Labels, options: ExtractOptions): Promise<Record<string, Entity[]>>;
|
|
105
|
+
classify?(texts: string[], prompt: string, labels: Labels, options: CallOptions): Promise<Record<string, number>[]>;
|
|
106
|
+
}
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foxmind",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "One API for on-device AI models in the browser and for your own model key.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Pooria Arab",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/pooriaarab/foxmind.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/pooriaarab/foxmind#readme",
|
|
13
|
+
"packageManager": "pnpm@11.10.0",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=24"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./browser": {
|
|
23
|
+
"types": "./dist/browser/index.d.ts",
|
|
24
|
+
"default": "./dist/browser/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.build.json",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"lint": "oxlint --deny-warnings",
|
|
34
|
+
"test": "vitest run --passWithNoTests",
|
|
35
|
+
"ci:local": "pnpm lint && pnpm typecheck && pnpm test && pnpm build && pnpm build:ext && pnpm lint:ext",
|
|
36
|
+
"build:ext": "node scripts/build-ext.mjs",
|
|
37
|
+
"lint:ext": "web-ext lint -s dist-ext",
|
|
38
|
+
"e2e": "pnpm build && node scripts/build-ext.mjs --e2e && node e2e/run.mjs"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@huggingface/transformers": "^4.3.1",
|
|
42
|
+
"@types/node": "^24.19.1",
|
|
43
|
+
"create-foxkit": "^0.1.0",
|
|
44
|
+
"esbuild": "^0.28.2",
|
|
45
|
+
"oxlint": "^1.87.0",
|
|
46
|
+
"typescript": "^7.0.2",
|
|
47
|
+
"vitest": "^5.0.3",
|
|
48
|
+
"web-ext": "^10.7.0"
|
|
49
|
+
},
|
|
50
|
+
"bin": {
|
|
51
|
+
"foxmind": "dist/bin.js"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@huggingface/transformers": "^4.3.1"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"@huggingface/transformers": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|