@spader/dotllm 1.3.2 → 1.3.4

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/src/core/link.js CHANGED
@@ -1,9 +1,329 @@
1
1
  // @bun
2
- var __require = import.meta.require;
2
+ var __esm = (fn, res, err) => () => {
3
+ if (fn)
4
+ try {
5
+ res = fn(fn = 0);
6
+ } catch (e) {
7
+ err = [e];
8
+ }
9
+ if (err)
10
+ throw err[0];
11
+ return res;
12
+ };
13
+
14
+ // src/core/config.ts
15
+ import fs from "fs";
16
+ import os from "os";
17
+ import path from "path";
18
+ import { z } from "zod";
19
+ function readJson(filepath) {
20
+ if (!fs.existsSync(filepath))
21
+ return null;
22
+ const raw = fs.readFileSync(filepath, "utf-8");
23
+ try {
24
+ return JSON.parse(raw);
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+ var LOCAL_DIR = ".llm", LOCAL_FILE, REF_DIR, RepoEntry, GlobalShape, LocalShape, Config;
30
+ var init_config = __esm(() => {
31
+ LOCAL_FILE = path.join(LOCAL_DIR, "dotllm.json");
32
+ REF_DIR = path.join(LOCAL_DIR, "reference");
33
+ RepoEntry = z.object({
34
+ kind: z.enum(["url", "file"]),
35
+ name: z.string(),
36
+ uri: z.string(),
37
+ description: z.string()
38
+ });
39
+ GlobalShape = z.object({
40
+ store: z.string().optional(),
41
+ repos: z.array(RepoEntry)
42
+ });
43
+ LocalShape = z.object({
44
+ refs: z.record(z.string(), RepoEntry)
45
+ });
46
+ ((Config) => {
47
+ function home() {
48
+ if (process.platform === "win32") {
49
+ const appData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local");
50
+ return path.join(appData, "dotllm");
51
+ }
52
+ return path.join(process.env.HOME ?? os.homedir(), ".local", "share", "dotllm");
53
+ }
54
+ Config.home = home;
55
+ function storeDir() {
56
+ const store = Global.read().store;
57
+ if (!store)
58
+ return path.join(home(), "store");
59
+ return expand(store);
60
+ }
61
+ Config.storeDir = storeDir;
62
+ function refDir() {
63
+ return REF_DIR;
64
+ }
65
+ Config.refDir = refDir;
66
+ let Global;
67
+ ((Global) => {
68
+ function read() {
69
+ const raw = readJson(path.join(home(), "dotllm.json"));
70
+ if (!raw)
71
+ return { repos: [] };
72
+ const result = GlobalShape.safeParse(raw);
73
+ if (!result.success)
74
+ return { repos: [] };
75
+ return result.data;
76
+ }
77
+ Global.read = read;
78
+ function write(config) {
79
+ const dir = home();
80
+ fs.mkdirSync(dir, { recursive: true });
81
+ fs.writeFileSync(path.join(dir, "dotllm.json"), JSON.stringify(config, null, 2) + `
82
+ `);
83
+ }
84
+ Global.write = write;
85
+ function find(config, name) {
86
+ const lower = name.toLowerCase();
87
+ return config.repos.find((r) => r.name.toLowerCase() === lower);
88
+ }
89
+ Global.find = find;
90
+ function add(config, entry) {
91
+ const lower = entry.name.toLowerCase();
92
+ const filtered = config.repos.filter((r) => r.name.toLowerCase() !== lower);
93
+ return { ...config, repos: [...filtered, entry] };
94
+ }
95
+ Global.add = add;
96
+ function remove(config, name) {
97
+ const lower = name.toLowerCase();
98
+ return { ...config, repos: config.repos.filter((r) => r.name.toLowerCase() !== lower) };
99
+ }
100
+ Global.remove = remove;
101
+ })(Global = Config.Global ||= {});
102
+ let Local;
103
+ ((Local) => {
104
+ function read() {
105
+ const raw = readJson(LOCAL_FILE);
106
+ if (!raw)
107
+ return { refs: {} };
108
+ const result = LocalShape.safeParse(raw);
109
+ if (!result.success)
110
+ return { refs: {} };
111
+ return result.data;
112
+ }
113
+ Local.read = read;
114
+ function write(config) {
115
+ fs.mkdirSync(LOCAL_DIR, { recursive: true });
116
+ fs.writeFileSync(LOCAL_FILE, JSON.stringify(config, null, 2) + `
117
+ `);
118
+ }
119
+ Local.write = write;
120
+ function find(config, name) {
121
+ const lower = name.toLowerCase();
122
+ for (const [key, value] of Object.entries(config.refs)) {
123
+ if (key.toLowerCase() === lower)
124
+ return value;
125
+ }
126
+ return;
127
+ }
128
+ Local.find = find;
129
+ function has(config, name) {
130
+ return find(config, name) !== undefined;
131
+ }
132
+ Local.has = has;
133
+ function add(config, repo) {
134
+ const lower = repo.name.toLowerCase();
135
+ const refs = Object.fromEntries(Object.entries(config.refs).filter(([key]) => key.toLowerCase() !== lower));
136
+ refs[repo.name] = repo;
137
+ return { refs };
138
+ }
139
+ Local.add = add;
140
+ function remove(config, name) {
141
+ const lower = name.toLowerCase();
142
+ const refs = Object.fromEntries(Object.entries(config.refs).filter(([key]) => key.toLowerCase() !== lower));
143
+ return { refs };
144
+ }
145
+ Local.remove = remove;
146
+ })(Local = Config.Local ||= {});
147
+ function expand(dir) {
148
+ const tilde = dir === "~" || dir.startsWith("~/") || dir.startsWith("~\\");
149
+ if (!tilde)
150
+ return path.resolve(home(), dir);
151
+ return path.join(os.homedir(), dir.slice(1));
152
+ }
153
+ })(Config ||= {});
154
+ });
155
+
156
+ // src/core/sync.ts
157
+ import fs2 from "fs";
158
+ import path2 from "path";
159
+ function shouldPull(storeDir, force) {
160
+ if (force)
161
+ return true;
162
+ const head = path2.join(storeDir, ".git", "HEAD");
163
+ const headStat = fs2.statSync(head, { throwIfNoEntry: false });
164
+ if (!headStat)
165
+ return true;
166
+ const age = Date.now() - headStat.mtimeMs;
167
+ if (age < FRESH_MS)
168
+ return false;
169
+ if (age > STALE_MS)
170
+ return true;
171
+ const dirStat = fs2.statSync(storeDir, { throwIfNoEntry: false });
172
+ if (!dirStat)
173
+ return true;
174
+ return dirStat.atimeMs > headStat.mtimeMs;
175
+ }
176
+ async function pull(names, options = {}) {
177
+ const force = options.force === true;
178
+ const global = Config.Global.read();
179
+ const states = await Promise.all(names.map(async (name) => {
180
+ const cwd = path2.join(Config.refDir(), name);
181
+ if (!fs2.existsSync(cwd)) {
182
+ return { kind: "failed", name, error: "reference directory missing" };
183
+ }
184
+ const repo = Config.Global.find(global, name);
185
+ if (repo && repo.kind === "file") {
186
+ return { kind: "skipped", name };
187
+ }
188
+ const storeDir = path2.join(Config.storeDir(), name);
189
+ const storeStat = fs2.lstatSync(storeDir, { throwIfNoEntry: false });
190
+ if (!storeStat || storeStat.isSymbolicLink()) {
191
+ return { kind: "skipped", name };
192
+ }
193
+ if (!fs2.existsSync(path2.join(storeDir, ".git"))) {
194
+ return { kind: "failed", name, error: "store directory is not a git repo" };
195
+ }
196
+ if (!shouldPull(storeDir, force)) {
197
+ return { kind: "skipped", name };
198
+ }
199
+ const fetch = Bun.spawn(["git", "fetch", "--depth=1", "origin", "HEAD"], {
200
+ cwd,
201
+ stdout: "pipe",
202
+ stderr: "pipe"
203
+ });
204
+ const [fetchCode, fetchOut, fetchErr] = await Promise.all([
205
+ fetch.exited,
206
+ new Response(fetch.stdout).text(),
207
+ new Response(fetch.stderr).text()
208
+ ]);
209
+ if (fetchCode !== 0) {
210
+ const msg = `${fetchOut}
211
+ ${fetchErr}`.trim();
212
+ return { kind: "failed", name, error: msg || "git fetch failed" };
213
+ }
214
+ const reset = Bun.spawn(["git", "reset", "--hard", "FETCH_HEAD"], {
215
+ cwd,
216
+ stdout: "pipe",
217
+ stderr: "pipe"
218
+ });
219
+ const [resetCode, resetOut, resetErr] = await Promise.all([
220
+ reset.exited,
221
+ new Response(reset.stdout).text(),
222
+ new Response(reset.stderr).text()
223
+ ]);
224
+ if (resetCode !== 0) {
225
+ const msg = `${resetOut}
226
+ ${resetErr}`.trim();
227
+ return { kind: "failed", name, error: msg || "git reset failed" };
228
+ }
229
+ return { kind: "ok" };
230
+ }));
231
+ const pulled = [];
232
+ const skipped = [];
233
+ const failed = [];
234
+ for (let i = 0;i < states.length; i++) {
235
+ const state = states[i];
236
+ if (state.kind === "ok")
237
+ pulled.push(names[i]);
238
+ if (state.kind === "skipped")
239
+ skipped.push(state.name);
240
+ if (state.kind === "failed")
241
+ failed.push({ name: state.name, error: state.error });
242
+ }
243
+ return { count: names.length, pulled, skipped, failed };
244
+ }
245
+ function sync() {
246
+ const local = Config.Local.read();
247
+ const global = Config.Global.read();
248
+ const merged = Object.values(local.refs).reduce((config, repo) => Config.Global.add(config, repo), global);
249
+ if (Object.keys(local.refs).length > 0) {
250
+ Config.Global.write(merged);
251
+ }
252
+ const refDir = Config.refDir();
253
+ fs2.mkdirSync(refDir, { recursive: true });
254
+ fs2.mkdirSync(Config.storeDir(), { recursive: true });
255
+ const linked = [];
256
+ const removed = [];
257
+ const missing = [];
258
+ const unchanged = [];
259
+ const wanted = new Set(Object.keys(local.refs));
260
+ for (const entry of fs2.readdirSync(refDir)) {
261
+ if (wanted.has(entry))
262
+ continue;
263
+ const target = path2.join(refDir, entry);
264
+ const stat = fs2.lstatSync(target);
265
+ if (stat.isSymbolicLink()) {
266
+ fs2.unlinkSync(target);
267
+ removed.push(entry);
268
+ }
269
+ }
270
+ for (const [name, repo] of Object.entries(local.refs)) {
271
+ const store = path2.join(Config.storeDir(), name);
272
+ const storeBroken = repo.kind === "url" && fs2.existsSync(store) && !fs2.existsSync(path2.join(store, ".git"));
273
+ if (storeBroken) {
274
+ fs2.rmSync(store, { recursive: true, force: true });
275
+ }
276
+ if (!fs2.existsSync(store) && repo.kind === "url") {
277
+ const clone = Bun.spawnSync(["git", "clone", "--depth=1", repo.uri, store], {
278
+ stdout: "pipe",
279
+ stderr: "pipe"
280
+ });
281
+ if (clone.exitCode !== 0) {
282
+ missing.push(name);
283
+ continue;
284
+ }
285
+ }
286
+ if (!fs2.existsSync(store) && repo.kind === "file") {
287
+ if (!fs2.existsSync(repo.uri)) {
288
+ missing.push(name);
289
+ continue;
290
+ }
291
+ if (!fs2.statSync(repo.uri).isDirectory()) {
292
+ missing.push(name);
293
+ continue;
294
+ }
295
+ fs2.symlinkSync(repo.uri, store, "dir");
296
+ }
297
+ if (!fs2.existsSync(store)) {
298
+ missing.push(name);
299
+ continue;
300
+ }
301
+ const target = path2.join(refDir, name);
302
+ const stat = fs2.lstatSync(target, { throwIfNoEntry: false });
303
+ if (stat && !stat.isSymbolicLink()) {
304
+ unchanged.push(name);
305
+ continue;
306
+ }
307
+ if (stat && fs2.readlinkSync(target) === store) {
308
+ unchanged.push(name);
309
+ continue;
310
+ }
311
+ if (stat) {
312
+ fs2.unlinkSync(target);
313
+ }
314
+ fs2.symlinkSync(store, target, "dir");
315
+ linked.push(name);
316
+ }
317
+ return { linked, removed, missing, unchanged };
318
+ }
319
+ var FRESH_MS, STALE_MS;
320
+ var init_sync = __esm(() => {
321
+ init_config();
322
+ FRESH_MS = 24 * 60 * 60 * 1000;
323
+ STALE_MS = 7 * FRESH_MS;
324
+ });
3
325
 
4
326
  // src/core/link.ts
5
- import { Config } from "@spader/dotllm/core/config";
6
- import { sync } from "@spader/dotllm/core/sync";
7
327
  function link(names) {
8
328
  const global = Config.Global.read();
9
329
  const rows = names.map((name) => {
@@ -15,6 +335,12 @@ function link(names) {
15
335
  Config.Local.write({ refs: Object.fromEntries(rows) });
16
336
  return sync();
17
337
  }
338
+ var init_link = __esm(() => {
339
+ init_config();
340
+ init_sync();
341
+ });
342
+ init_link();
343
+
18
344
  export {
19
345
  link
20
346
  };
@@ -1,29 +1,185 @@
1
1
  // @bun
2
- var __require = import.meta.require;
2
+ var __esm = (fn, res, err) => () => {
3
+ if (fn)
4
+ try {
5
+ res = fn(fn = 0);
6
+ } catch (e) {
7
+ err = [e];
8
+ }
9
+ if (err)
10
+ throw err[0];
11
+ return res;
12
+ };
3
13
 
4
- // src/core/remove.ts
14
+ // src/core/config.ts
5
15
  import fs from "fs";
16
+ import os from "os";
6
17
  import path from "path";
7
- import { Config } from "@spader/dotllm/core/config";
18
+ import { z } from "zod";
19
+ function readJson(filepath) {
20
+ if (!fs.existsSync(filepath))
21
+ return null;
22
+ const raw = fs.readFileSync(filepath, "utf-8");
23
+ try {
24
+ return JSON.parse(raw);
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+ var LOCAL_DIR = ".llm", LOCAL_FILE, REF_DIR, RepoEntry, GlobalShape, LocalShape, Config;
30
+ var init_config = __esm(() => {
31
+ LOCAL_FILE = path.join(LOCAL_DIR, "dotllm.json");
32
+ REF_DIR = path.join(LOCAL_DIR, "reference");
33
+ RepoEntry = z.object({
34
+ kind: z.enum(["url", "file"]),
35
+ name: z.string(),
36
+ uri: z.string(),
37
+ description: z.string()
38
+ });
39
+ GlobalShape = z.object({
40
+ store: z.string().optional(),
41
+ repos: z.array(RepoEntry)
42
+ });
43
+ LocalShape = z.object({
44
+ refs: z.record(z.string(), RepoEntry)
45
+ });
46
+ ((Config) => {
47
+ function home() {
48
+ if (process.platform === "win32") {
49
+ const appData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local");
50
+ return path.join(appData, "dotllm");
51
+ }
52
+ return path.join(process.env.HOME ?? os.homedir(), ".local", "share", "dotllm");
53
+ }
54
+ Config.home = home;
55
+ function storeDir() {
56
+ const store = Global.read().store;
57
+ if (!store)
58
+ return path.join(home(), "store");
59
+ return expand(store);
60
+ }
61
+ Config.storeDir = storeDir;
62
+ function refDir() {
63
+ return REF_DIR;
64
+ }
65
+ Config.refDir = refDir;
66
+ let Global;
67
+ ((Global) => {
68
+ function read() {
69
+ const raw = readJson(path.join(home(), "dotllm.json"));
70
+ if (!raw)
71
+ return { repos: [] };
72
+ const result = GlobalShape.safeParse(raw);
73
+ if (!result.success)
74
+ return { repos: [] };
75
+ return result.data;
76
+ }
77
+ Global.read = read;
78
+ function write(config) {
79
+ const dir = home();
80
+ fs.mkdirSync(dir, { recursive: true });
81
+ fs.writeFileSync(path.join(dir, "dotllm.json"), JSON.stringify(config, null, 2) + `
82
+ `);
83
+ }
84
+ Global.write = write;
85
+ function find(config, name) {
86
+ const lower = name.toLowerCase();
87
+ return config.repos.find((r) => r.name.toLowerCase() === lower);
88
+ }
89
+ Global.find = find;
90
+ function add(config, entry) {
91
+ const lower = entry.name.toLowerCase();
92
+ const filtered = config.repos.filter((r) => r.name.toLowerCase() !== lower);
93
+ return { ...config, repos: [...filtered, entry] };
94
+ }
95
+ Global.add = add;
96
+ function remove(config, name) {
97
+ const lower = name.toLowerCase();
98
+ return { ...config, repos: config.repos.filter((r) => r.name.toLowerCase() !== lower) };
99
+ }
100
+ Global.remove = remove;
101
+ })(Global = Config.Global ||= {});
102
+ let Local;
103
+ ((Local) => {
104
+ function read() {
105
+ const raw = readJson(LOCAL_FILE);
106
+ if (!raw)
107
+ return { refs: {} };
108
+ const result = LocalShape.safeParse(raw);
109
+ if (!result.success)
110
+ return { refs: {} };
111
+ return result.data;
112
+ }
113
+ Local.read = read;
114
+ function write(config) {
115
+ fs.mkdirSync(LOCAL_DIR, { recursive: true });
116
+ fs.writeFileSync(LOCAL_FILE, JSON.stringify(config, null, 2) + `
117
+ `);
118
+ }
119
+ Local.write = write;
120
+ function find(config, name) {
121
+ const lower = name.toLowerCase();
122
+ for (const [key, value] of Object.entries(config.refs)) {
123
+ if (key.toLowerCase() === lower)
124
+ return value;
125
+ }
126
+ return;
127
+ }
128
+ Local.find = find;
129
+ function has(config, name) {
130
+ return find(config, name) !== undefined;
131
+ }
132
+ Local.has = has;
133
+ function add(config, repo) {
134
+ const lower = repo.name.toLowerCase();
135
+ const refs = Object.fromEntries(Object.entries(config.refs).filter(([key]) => key.toLowerCase() !== lower));
136
+ refs[repo.name] = repo;
137
+ return { refs };
138
+ }
139
+ Local.add = add;
140
+ function remove(config, name) {
141
+ const lower = name.toLowerCase();
142
+ const refs = Object.fromEntries(Object.entries(config.refs).filter(([key]) => key.toLowerCase() !== lower));
143
+ return { refs };
144
+ }
145
+ Local.remove = remove;
146
+ })(Local = Config.Local ||= {});
147
+ function expand(dir) {
148
+ const tilde = dir === "~" || dir.startsWith("~/") || dir.startsWith("~\\");
149
+ if (!tilde)
150
+ return path.resolve(home(), dir);
151
+ return path.join(os.homedir(), dir.slice(1));
152
+ }
153
+ })(Config ||= {});
154
+ });
155
+
156
+ // src/core/remove.ts
157
+ import fs2 from "fs";
158
+ import path2 from "path";
8
159
  function remove(name) {
9
160
  const global = Config.Global.read();
10
161
  const found = Config.Global.find(global, name);
11
162
  if (!found) {
12
163
  return { ok: false, error: `No repo named "${name}" in registry` };
13
164
  }
14
- const target = path.join(Config.storeDir(), found.name);
15
- if (fs.existsSync(target)) {
16
- const stat = fs.lstatSync(target);
165
+ const target = path2.join(Config.storeDir(), found.name);
166
+ if (fs2.existsSync(target)) {
167
+ const stat = fs2.lstatSync(target);
17
168
  if (stat.isSymbolicLink()) {
18
- fs.unlinkSync(target);
169
+ fs2.unlinkSync(target);
19
170
  }
20
171
  if (stat.isDirectory()) {
21
- fs.rmSync(target, { recursive: true, force: true });
172
+ fs2.rmSync(target, { recursive: true, force: true });
22
173
  }
23
174
  }
24
175
  Config.Global.write(Config.Global.remove(global, found.name));
25
176
  return { ok: true };
26
177
  }
178
+ var init_remove = __esm(() => {
179
+ init_config();
180
+ });
181
+ init_remove();
182
+
27
183
  export {
28
184
  remove
29
185
  };