@jeffober/thatch 0.1.43 → 0.1.45
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/README.md +84 -9
- package/bin/thatch +307 -95
- package/docs/dev/README.md +12 -3
- package/docs/dev/features/README.md +1 -0
- package/docs/dev/features/cli.md +15 -17
- package/docs/dev/features/commands.md +74 -0
- package/docs/dev/features/cross-session-chat.md +128 -45
- package/docs/dev/features/database.md +7 -3
- package/docs/dev/features/extraction.md +5 -0
- package/docs/dev/features/multi-host.md +1 -1
- package/docs/dev/features/session-lifecycle.md +1 -1
- package/docs/dev/gotchas.md +20 -0
- package/docs/dev/mcp-parity.md +2 -0
- package/docs/dev/setup-and-hooks.md +20 -8
- package/docs/plans/thatch-actions.md +179 -0
- package/docs/user/README.md +10 -4
- package/docs/user/cli.md +45 -44
- package/docs/user/commands.md +50 -0
- package/docs/user/cross-session-chat.md +94 -22
- package/docs/user/setup.md +9 -3
- package/package.json +1 -1
- package/src/chat-names.ts +2 -2
- package/src/chat.ts +372 -185
- package/src/commands.ts +138 -19
- package/src/db.ts +46 -0
- package/src/debug.ts +44 -0
- package/src/embeddings.ts +42 -1
- package/src/index.ts +262 -39
- package/src/mcp.ts +78 -3
- package/src/prompts.ts +57 -3
- package/src/setup.ts +212 -33
- package/src/tool-defs.ts +115 -30
- package/src/watchers.ts +51 -29
package/src/commands.ts
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
defragCore,
|
|
5
|
+
extractionCore,
|
|
6
|
+
hygieneCore,
|
|
7
|
+
mcpToolName,
|
|
8
|
+
opencodeToolName,
|
|
9
|
+
reflectCore,
|
|
10
|
+
type ToolNamer,
|
|
11
|
+
} from "./prompts";
|
|
3
12
|
|
|
4
13
|
/**
|
|
5
|
-
* Thatch's
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* so
|
|
14
|
+
* Thatch's slash commands, shipped as command markdown files synced into each
|
|
15
|
+
* host's command directory:
|
|
16
|
+
*
|
|
17
|
+
* - opencode: ~/.config/opencode/command/thatch/ - synced by the plugin on
|
|
18
|
+
* every load, so template updates self-heal without a reinstall. Config is
|
|
19
|
+
* loaded before plugins during server startup, so a first-ever install
|
|
20
|
+
* becomes visible on the next start.
|
|
21
|
+
* - Claude Code: ~/.claude/commands/thatch/ (or the project's .claude/
|
|
22
|
+
* commands/thatch/) - synced by `thatch setup --claude`, which has no
|
|
23
|
+
* plugin to do it on load.
|
|
24
|
+
*
|
|
25
|
+
* Two command families live here:
|
|
26
|
+
*
|
|
27
|
+
* - Actions (/thatch/defrag, extract, hygiene, reflect): user-invoked
|
|
28
|
+
* wrappers around a behavior that the nudges otherwise only run on their
|
|
29
|
+
* own schedule. Each action body is a prompt core from src/prompts.ts, so
|
|
30
|
+
* nudge and command wording cannot drift.
|
|
31
|
+
* - Wrap-ups (/thatch/compact, exit): greenlight-token commands that need
|
|
32
|
+
* the opencode plugin's TUI control routes, so they are opencode-only.
|
|
10
33
|
*/
|
|
11
34
|
|
|
12
35
|
/** Greenlight token ending /thatch/compact's assistant response when compaction is safe. */
|
|
@@ -24,19 +47,92 @@ export const EXIT_READY_TOKEN = "THATCH_EXIT_READY";
|
|
|
24
47
|
*/
|
|
25
48
|
|
|
26
49
|
const sharedChecklist = `1. Flush pending persistence. Call thatch_get_extraction_payload; if it returns buffered tool interactions, process them now (write any memories worth keeping), then call thatch_extraction_done to mark them complete. Complete any memory writes you promised earlier but have not made.
|
|
27
|
-
2. Check for loose ends. Review the conversation for todos, follow-ups, or open questions raised earlier that were never addressed, and surface anything the user should address before the wrap-up completes
|
|
50
|
+
2. Check for loose ends. Review the conversation for todos, follow-ups, or open questions raised earlier that were never addressed, and surface anything the user should address before the wrap-up completes.`;
|
|
28
51
|
|
|
29
|
-
|
|
52
|
+
const userMessageSection = `# User Message
|
|
53
|
+
|
|
54
|
+
$ARGUMENTS
|
|
55
|
+
|
|
56
|
+
(That section carries the text typed after the command. When it is empty, treat the user message as n/a - the command was run bare.)`;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* One on-demand action: the command name (minus the thatch/ prefix), the
|
|
60
|
+
* description shown in the command menu, and the instruction body (a prompt
|
|
61
|
+
* core). opencodeOnly marks actions that need a session identity or other
|
|
62
|
+
* plugin-only capability; those are excluded from Claude Code command files
|
|
63
|
+
* and MCP prompts.
|
|
64
|
+
*/
|
|
65
|
+
export interface ActionDef {
|
|
66
|
+
name: string;
|
|
67
|
+
description: string;
|
|
68
|
+
body: string;
|
|
69
|
+
opencodeOnly?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The on-demand action set, shared by every host that renders actions. The
|
|
74
|
+
* bodies come from prompt cores so a wording change lands in the nudge and
|
|
75
|
+
* the command at once. `tool` renders tool names in the host's spelling.
|
|
76
|
+
*/
|
|
77
|
+
export function actionDefs(tool: ToolNamer): ActionDef[] {
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
name: "defrag",
|
|
81
|
+
description: "Consolidate duplicate and near-duplicate memories in the store",
|
|
82
|
+
body: defragCore(tool),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "extract",
|
|
86
|
+
description: "Drain the extraction queue now",
|
|
87
|
+
// Needs the invoking session's ID (via get_session_info, opencode-only)
|
|
88
|
+
// and the plugin's extraction payload provider.
|
|
89
|
+
opencodeOnly: true,
|
|
90
|
+
body: `First call thatch_get_session_info to learn this session's ID. Below, replace SESSION_ID with that value.
|
|
91
|
+
|
|
92
|
+
Dispatch a task with background: true and subagent_type: "general" (required for thatch tool access) with this prompt:
|
|
93
|
+
|
|
94
|
+
"${extractionCore("thatch_get_extraction_payload", "thatch_extraction_done", ' with session_id \\"SESSION_ID\\"')}"
|
|
95
|
+
|
|
96
|
+
After dispatching, call thatch_extraction_done to acknowledge.`,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "hygiene",
|
|
100
|
+
description: "Tend the memory store: stale entries, orphans, pending dedup pairs",
|
|
101
|
+
body: hygieneCore(),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "reflect",
|
|
105
|
+
description: "Persist what this session learned as memories",
|
|
106
|
+
body: reflectCore(tool),
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Renders one action as a full command markdown file. */
|
|
112
|
+
export function renderActionCommand(action: ActionDef): string {
|
|
113
|
+
return `---
|
|
114
|
+
description: ${action.description}
|
|
115
|
+
---
|
|
116
|
+
${userMessageSection}
|
|
117
|
+
|
|
118
|
+
# ${action.name.charAt(0).toUpperCase() + action.name.slice(1)}
|
|
119
|
+
|
|
120
|
+
${action.body}`;
|
|
121
|
+
}
|
|
30
122
|
|
|
31
123
|
const COMPACT_TEMPLATE = `---
|
|
32
124
|
description: Flush thatch persistence, check for loose ends, then compact if clear
|
|
33
125
|
---
|
|
34
|
-
$
|
|
126
|
+
${userMessageSection}
|
|
127
|
+
|
|
128
|
+
# Pre-compact wrap-up
|
|
35
129
|
|
|
36
|
-
|
|
130
|
+
Work through this checklist before responding:
|
|
37
131
|
|
|
38
132
|
${sharedChecklist}
|
|
39
133
|
|
|
134
|
+
Do not start new work beyond this checklist. If every item is handled and nothing needs the user's attention first, end your final response with exactly this token as the very last line, with no formatting around it:
|
|
135
|
+
|
|
40
136
|
${COMPACT_READY_TOKEN}
|
|
41
137
|
|
|
42
138
|
If anything is outstanding, list the items concisely so the user can address them, and do NOT include the token. The session will only be compacted when the token is present.`;
|
|
@@ -44,9 +140,11 @@ If anything is outstanding, list the items concisely so the user can address the
|
|
|
44
140
|
const EXIT_TEMPLATE = `---
|
|
45
141
|
description: Flush thatch persistence, check for loose ends, then exit opencode if clear
|
|
46
142
|
---
|
|
47
|
-
$
|
|
143
|
+
${userMessageSection}
|
|
144
|
+
|
|
145
|
+
# Pre-exit wrap-up
|
|
48
146
|
|
|
49
|
-
|
|
147
|
+
Work through this checklist before responding:
|
|
50
148
|
|
|
51
149
|
${sharedChecklist}
|
|
52
150
|
3. Leave the chat directory. Call thatch_chat_unregister for this session - the session is exiting, so other sessions must stop addressing mail to it.
|
|
@@ -57,29 +155,40 @@ ${EXIT_READY_TOKEN}
|
|
|
57
155
|
|
|
58
156
|
If anything is outstanding, list the items concisely so the user can address them, and do NOT include the token. opencode will only exit when the token is present.`;
|
|
59
157
|
|
|
60
|
-
/** One
|
|
158
|
+
/** One command: the command name (minus the thatch/ prefix) and its full markdown file content. */
|
|
61
159
|
export interface CommandDef {
|
|
62
160
|
name: string;
|
|
63
161
|
content: string;
|
|
64
162
|
}
|
|
65
163
|
|
|
66
|
-
/** The wrap-
|
|
164
|
+
/** The opencode command set: wrap-ups plus every action, as full file contents. */
|
|
67
165
|
export function opencodeCommandDefs(): CommandDef[] {
|
|
68
166
|
return [
|
|
69
167
|
{ name: "compact", content: COMPACT_TEMPLATE },
|
|
70
168
|
{ name: "exit", content: EXIT_TEMPLATE },
|
|
169
|
+
...actionDefs(opencodeToolName).map((a) => ({ name: a.name, content: renderActionCommand(a) })),
|
|
71
170
|
];
|
|
72
171
|
}
|
|
73
172
|
|
|
74
173
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
174
|
+
* The Claude Code command set: the actions minus the opencode-only ones.
|
|
175
|
+
* The wrap-up commands are excluded too - their greenlight check needs the
|
|
176
|
+
* plugin's command.execute.before arming and TUI control routes.
|
|
78
177
|
*/
|
|
79
|
-
export function
|
|
80
|
-
|
|
178
|
+
export function claudeCommandDefs(): CommandDef[] {
|
|
179
|
+
return actionDefs(mcpToolName)
|
|
180
|
+
.filter((a) => !a.opencodeOnly)
|
|
181
|
+
.map((a) => ({ name: a.name, content: renderActionCommand(a) }));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Sync command files into dir. Idempotent: each file is written only when
|
|
186
|
+
* its on-disk content differs. Returns the paths that were written (empty
|
|
187
|
+
* when everything was current).
|
|
188
|
+
*/
|
|
189
|
+
function syncCommandFiles(dir: string, defs: CommandDef[]): string[] {
|
|
81
190
|
const written: string[] = [];
|
|
82
|
-
for (const def of
|
|
191
|
+
for (const def of defs) {
|
|
83
192
|
const path = join(dir, `${def.name}.md`);
|
|
84
193
|
let current: string | null = null;
|
|
85
194
|
try {
|
|
@@ -95,3 +204,13 @@ export function installOpencodeCommands(configHome: string): string[] {
|
|
|
95
204
|
}
|
|
96
205
|
return written;
|
|
97
206
|
}
|
|
207
|
+
|
|
208
|
+
/** Sync opencode's commands into <configHome>/opencode/command/thatch/. */
|
|
209
|
+
export function installOpencodeCommands(configHome: string): string[] {
|
|
210
|
+
return syncCommandFiles(join(configHome, "opencode", "command", "thatch"), opencodeCommandDefs());
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Sync Claude Code's commands into <claudeDir>/commands/thatch/ (claudeDir is ~/.claude or <project>/.claude). */
|
|
214
|
+
export function installClaudeCommands(claudeDir: string): string[] {
|
|
215
|
+
return syncCommandFiles(join(claudeDir, "commands", "thatch"), claudeCommandDefs());
|
|
216
|
+
}
|
package/src/db.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
2
4
|
import { blobToVector, cosineSimilarity } from "./vector-math";
|
|
3
5
|
import {
|
|
4
6
|
PredictionEngine,
|
|
@@ -58,6 +60,13 @@ export class ThatchDB {
|
|
|
58
60
|
#chat: ChatStore;
|
|
59
61
|
|
|
60
62
|
constructor(path: string) {
|
|
63
|
+
// bun:sqlite creates the file but not its parent directory. On a fresh
|
|
64
|
+
// machine the default path (~/.config/thatch/thatch.db) has no dir yet, so
|
|
65
|
+
// create it here - otherwise every command fails with SQLITE_CANTOPEN.
|
|
66
|
+
// `:memory:` and other special paths have no meaningful parent to create.
|
|
67
|
+
if (path !== ":memory:" && !path.startsWith("file:")) {
|
|
68
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
69
|
+
}
|
|
61
70
|
this.#db = new Database(path, { create: true });
|
|
62
71
|
this.#db.run("PRAGMA journal_mode = WAL");
|
|
63
72
|
this.#db.run("PRAGMA busy_timeout = 5000");
|
|
@@ -284,6 +293,20 @@ export class ThatchDB {
|
|
|
284
293
|
)
|
|
285
294
|
`);
|
|
286
295
|
|
|
296
|
+
// Hook-parent-process -> session mapping (Claude Code identity anchor):
|
|
297
|
+
// hooks learn the true session id from the host's hook payload and run
|
|
298
|
+
// as children of the same process that spawned the MCP server, so the
|
|
299
|
+
// server resolves its own parent pid against this table - an identity
|
|
300
|
+
// the model cannot claim. Rows are timestamped; stale rows are ignored
|
|
301
|
+
// (pid reuse) rather than deleted.
|
|
302
|
+
this.#db.run(`
|
|
303
|
+
CREATE TABLE IF NOT EXISTS chat_host_pids (
|
|
304
|
+
ppid INTEGER PRIMARY KEY,
|
|
305
|
+
session_id TEXT NOT NULL,
|
|
306
|
+
seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
|
|
307
|
+
)
|
|
308
|
+
`);
|
|
309
|
+
|
|
287
310
|
this.#migrateColumns();
|
|
288
311
|
this.#migrateChatNameCollation();
|
|
289
312
|
this.#migrateChatTopic();
|
|
@@ -291,6 +314,7 @@ export class ThatchDB {
|
|
|
291
314
|
this.#migrateChatBroadcastFlag();
|
|
292
315
|
this.#migrateChatAuto();
|
|
293
316
|
this.#migrateChatWorktree();
|
|
317
|
+
this.#migrateChatHostPid();
|
|
294
318
|
}
|
|
295
319
|
|
|
296
320
|
// chat_sessions tables created before the auto column lack it; the ALTER
|
|
@@ -322,6 +346,16 @@ export class ThatchDB {
|
|
|
322
346
|
}
|
|
323
347
|
}
|
|
324
348
|
|
|
349
|
+
// The released schema has no host_pid column (liveness is heartbeat age;
|
|
350
|
+
// see chatLiveness). A development database may carry one; drop it so
|
|
351
|
+
// every database matches a fresh install.
|
|
352
|
+
#migrateChatHostPid(): void {
|
|
353
|
+
const cols = (this.#db.query("PRAGMA table_info(chat_sessions)").all() as any[]).map((r) => r.name);
|
|
354
|
+
if (cols.includes("host_pid")) {
|
|
355
|
+
this.#db.run("ALTER TABLE chat_sessions DROP COLUMN host_pid");
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
325
359
|
// chat_messages tables created before the via_broadcast column lack it;
|
|
326
360
|
// the ALTER adds it, and existing rows read as direct sends (which they
|
|
327
361
|
// were).
|
|
@@ -935,6 +969,18 @@ export class ThatchDB {
|
|
|
935
969
|
return this.#chat.unreadCount(sessionID);
|
|
936
970
|
}
|
|
937
971
|
|
|
972
|
+
recordChatHostPid(ppid: number, sessionID: string) {
|
|
973
|
+
return this.#chat.recordHostPid(ppid, sessionID);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
findChatSessionByHostPid(ppid: number, maxAgeSeconds: number): string | null {
|
|
977
|
+
return this.#chat.findSessionByHostPid(ppid, maxAgeSeconds);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
continueChatSession(oldSessionID: string, newSessionID: string) {
|
|
981
|
+
return this.#chat.continueSession(oldSessionID, newSessionID);
|
|
982
|
+
}
|
|
983
|
+
|
|
938
984
|
heartbeatChatSessions(sessionIDs: string[]) {
|
|
939
985
|
return this.#chat.heartbeat(sessionIDs);
|
|
940
986
|
}
|
package/src/debug.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { appendFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
// Opt-in diagnostic logging for questions a live harness cannot otherwise
|
|
5
|
+
// answer ("what did the plugin actually see at init?"). Lines go to
|
|
6
|
+
// debug.log beside the database, never to the host's stdout or stderr:
|
|
7
|
+
// the plugin runs inside opencode's TUI process, where console output is
|
|
8
|
+
// invisible or garbles the screen, and test sandboxes point the database at
|
|
9
|
+
// a tmp dir so their debug output never reaches the quality gate.
|
|
10
|
+
//
|
|
11
|
+
// Off unless THATCH_DEBUG is set. THATCH_DEBUG=1 (or "all" / "*") logs every
|
|
12
|
+
// tag. Otherwise it is a comma-separated list of tag filters: a filter
|
|
13
|
+
// matches a tag when it equals the whole tag ("chat:startup") or the
|
|
14
|
+
// feature part before the colon ("chat" matches every chat:* tag).
|
|
15
|
+
//
|
|
16
|
+
// Tags are `feature:aspect`, rendered in square brackets, so a grep on
|
|
17
|
+
// "[chat:" pulls one feature's story out of a shared file.
|
|
18
|
+
|
|
19
|
+
export type DebugLog = (tag: string, msg: string) => void;
|
|
20
|
+
|
|
21
|
+
// Decide, from a THATCH_DEBUG value, whether a tag is enabled. Exported for
|
|
22
|
+
// tests; callers use createDebugLog.
|
|
23
|
+
export function debugEnabled(spec: string | undefined, tag: string): boolean {
|
|
24
|
+
if (!spec) return false;
|
|
25
|
+
const s = spec.trim();
|
|
26
|
+
if (s === "1" || s === "all" || s === "*") return true;
|
|
27
|
+
const feature = tag.split(":")[0];
|
|
28
|
+
return s.split(",").map((f) => f.trim()).filter(Boolean).some((f) => f === tag || f === feature);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Build the logger for a database location. Every line: ISO timestamp,
|
|
32
|
+
// bracketed tag, message. Writes never throw: diagnostics must not break
|
|
33
|
+
// the host, so a missing directory or a full disk simply drops the line.
|
|
34
|
+
export function createDebugLog(dbPath: string, spec: string | undefined = process.env.THATCH_DEBUG): DebugLog {
|
|
35
|
+
const file = join(dirname(dbPath), "debug.log");
|
|
36
|
+
return (tag, msg) => {
|
|
37
|
+
if (!debugEnabled(spec, tag)) return;
|
|
38
|
+
try {
|
|
39
|
+
appendFileSync(file, `${new Date().toISOString()} [${tag}] ${msg}\n`);
|
|
40
|
+
} catch {
|
|
41
|
+
// Intentionally silent; see above.
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
package/src/embeddings.ts
CHANGED
|
@@ -1,9 +1,48 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
1
4
|
// BGE-small-en-v1.5 requires a query prefix for asymmetric search.
|
|
2
5
|
// Passage (memory content) gets no prefix - the model was trained
|
|
3
6
|
// to encode passages without instruction.
|
|
4
7
|
const QUERY_PREFIX =
|
|
5
8
|
"Represent this sentence for searching relevant passages: ";
|
|
6
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Writable directory for the downloaded model files.
|
|
12
|
+
*
|
|
13
|
+
* transformers.js defaults its filesystem cache to a `.cache` folder *inside*
|
|
14
|
+
* its own package directory (`node_modules/@huggingface/transformers/.cache`).
|
|
15
|
+
* That breaks any read-only install - a global `bun install -g`, and Nix store
|
|
16
|
+
* paths in particular - and gets wiped on every reinstall. Anchor the cache in
|
|
17
|
+
* the platform's per-user cache location instead so it survives upgrades and
|
|
18
|
+
* works from an immutable store path.
|
|
19
|
+
*
|
|
20
|
+
* THATCH_MODEL_CACHE explicit override (highest priority)
|
|
21
|
+
* XDG_CACHE_HOME explicit base dir -> $XDG_CACHE_HOME/thatch/models
|
|
22
|
+
* macOS ~/Library/Caches/thatch/models
|
|
23
|
+
* Windows %LOCALAPPDATA%\thatch\models
|
|
24
|
+
* Linux / other ~/.cache/thatch/models
|
|
25
|
+
*
|
|
26
|
+
* `platform` and `home` are injectable for testing; they default to the real
|
|
27
|
+
* runtime values.
|
|
28
|
+
*/
|
|
29
|
+
export function modelCacheDir(
|
|
30
|
+
platform: NodeJS.Platform = process.platform,
|
|
31
|
+
home: string = homedir(),
|
|
32
|
+
): string {
|
|
33
|
+
const env = process.env;
|
|
34
|
+
if (env.THATCH_MODEL_CACHE) return env.THATCH_MODEL_CACHE;
|
|
35
|
+
// Respect an explicit XDG override on any platform (some macOS users set it).
|
|
36
|
+
if (env.XDG_CACHE_HOME) return join(env.XDG_CACHE_HOME, "thatch", "models");
|
|
37
|
+
const base =
|
|
38
|
+
platform === "darwin"
|
|
39
|
+
? join(home, "Library", "Caches")
|
|
40
|
+
: platform === "win32"
|
|
41
|
+
? (env.LOCALAPPDATA ?? join(home, "AppData", "Local"))
|
|
42
|
+
: join(home, ".cache");
|
|
43
|
+
return join(base, "thatch", "models");
|
|
44
|
+
}
|
|
45
|
+
|
|
7
46
|
/**
|
|
8
47
|
* Interface for an embedding model so tests can supply a mock.
|
|
9
48
|
* `name` is the tag stored alongside each entry so a future reader can tell
|
|
@@ -90,7 +129,9 @@ export function resetBackendForTests(): void {
|
|
|
90
129
|
|
|
91
130
|
const defaultPipelineFactory: PipelineFactory = async (modelName) => {
|
|
92
131
|
const mode = await configureBackend();
|
|
93
|
-
const { pipeline } = await import("@huggingface/transformers");
|
|
132
|
+
const { pipeline, env } = await import("@huggingface/transformers");
|
|
133
|
+
// Redirect the model cache out of the (possibly read-only) package tree.
|
|
134
|
+
env.cacheDir = modelCacheDir();
|
|
94
135
|
if (mode === "native") {
|
|
95
136
|
return pipeline("feature-extraction", modelName);
|
|
96
137
|
}
|