@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/sync.js CHANGED
@@ -1,17 +1,166 @@
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/sync.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";
8
- var FRESH_MS = 24 * 60 * 60 * 1000;
9
- var STALE_MS = 7 * FRESH_MS;
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";
10
159
  function shouldPull(storeDir, force) {
11
160
  if (force)
12
161
  return true;
13
- const head = path.join(storeDir, ".git", "HEAD");
14
- const headStat = fs.statSync(head, { throwIfNoEntry: false });
162
+ const head = path2.join(storeDir, ".git", "HEAD");
163
+ const headStat = fs2.statSync(head, { throwIfNoEntry: false });
15
164
  if (!headStat)
16
165
  return true;
17
166
  const age = Date.now() - headStat.mtimeMs;
@@ -19,7 +168,7 @@ function shouldPull(storeDir, force) {
19
168
  return false;
20
169
  if (age > STALE_MS)
21
170
  return true;
22
- const dirStat = fs.statSync(storeDir, { throwIfNoEntry: false });
171
+ const dirStat = fs2.statSync(storeDir, { throwIfNoEntry: false });
23
172
  if (!dirStat)
24
173
  return true;
25
174
  return dirStat.atimeMs > headStat.mtimeMs;
@@ -28,20 +177,20 @@ async function pull(names, options = {}) {
28
177
  const force = options.force === true;
29
178
  const global = Config.Global.read();
30
179
  const states = await Promise.all(names.map(async (name) => {
31
- const cwd = path.join(Config.refDir(), name);
32
- if (!fs.existsSync(cwd)) {
180
+ const cwd = path2.join(Config.refDir(), name);
181
+ if (!fs2.existsSync(cwd)) {
33
182
  return { kind: "failed", name, error: "reference directory missing" };
34
183
  }
35
184
  const repo = Config.Global.find(global, name);
36
185
  if (repo && repo.kind === "file") {
37
186
  return { kind: "skipped", name };
38
187
  }
39
- const storeDir = path.join(Config.storeDir(), name);
40
- const storeStat = fs.lstatSync(storeDir, { throwIfNoEntry: false });
188
+ const storeDir = path2.join(Config.storeDir(), name);
189
+ const storeStat = fs2.lstatSync(storeDir, { throwIfNoEntry: false });
41
190
  if (!storeStat || storeStat.isSymbolicLink()) {
42
191
  return { kind: "skipped", name };
43
192
  }
44
- if (!fs.existsSync(path.join(storeDir, ".git"))) {
193
+ if (!fs2.existsSync(path2.join(storeDir, ".git"))) {
45
194
  return { kind: "failed", name, error: "store directory is not a git repo" };
46
195
  }
47
196
  if (!shouldPull(storeDir, force)) {
@@ -101,30 +250,30 @@ function sync() {
101
250
  Config.Global.write(merged);
102
251
  }
103
252
  const refDir = Config.refDir();
104
- fs.mkdirSync(refDir, { recursive: true });
105
- fs.mkdirSync(Config.storeDir(), { recursive: true });
253
+ fs2.mkdirSync(refDir, { recursive: true });
254
+ fs2.mkdirSync(Config.storeDir(), { recursive: true });
106
255
  const linked = [];
107
256
  const removed = [];
108
257
  const missing = [];
109
258
  const unchanged = [];
110
259
  const wanted = new Set(Object.keys(local.refs));
111
- for (const entry of fs.readdirSync(refDir)) {
260
+ for (const entry of fs2.readdirSync(refDir)) {
112
261
  if (wanted.has(entry))
113
262
  continue;
114
- const target = path.join(refDir, entry);
115
- const stat = fs.lstatSync(target);
263
+ const target = path2.join(refDir, entry);
264
+ const stat = fs2.lstatSync(target);
116
265
  if (stat.isSymbolicLink()) {
117
- fs.unlinkSync(target);
266
+ fs2.unlinkSync(target);
118
267
  removed.push(entry);
119
268
  }
120
269
  }
121
270
  for (const [name, repo] of Object.entries(local.refs)) {
122
- const store = path.join(Config.storeDir(), name);
123
- const storeBroken = repo.kind === "url" && fs.existsSync(store) && !fs.existsSync(path.join(store, ".git"));
271
+ const store = path2.join(Config.storeDir(), name);
272
+ const storeBroken = repo.kind === "url" && fs2.existsSync(store) && !fs2.existsSync(path2.join(store, ".git"));
124
273
  if (storeBroken) {
125
- fs.rmSync(store, { recursive: true, force: true });
274
+ fs2.rmSync(store, { recursive: true, force: true });
126
275
  }
127
- if (!fs.existsSync(store) && repo.kind === "url") {
276
+ if (!fs2.existsSync(store) && repo.kind === "url") {
128
277
  const clone = Bun.spawnSync(["git", "clone", "--depth=1", repo.uri, store], {
129
278
  stdout: "pipe",
130
279
  stderr: "pipe"
@@ -134,32 +283,48 @@ function sync() {
134
283
  continue;
135
284
  }
136
285
  }
137
- if (!fs.existsSync(store) && repo.kind === "file") {
138
- if (!fs.existsSync(repo.uri)) {
286
+ if (!fs2.existsSync(store) && repo.kind === "file") {
287
+ if (!fs2.existsSync(repo.uri)) {
139
288
  missing.push(name);
140
289
  continue;
141
290
  }
142
- if (!fs.statSync(repo.uri).isDirectory()) {
291
+ if (!fs2.statSync(repo.uri).isDirectory()) {
143
292
  missing.push(name);
144
293
  continue;
145
294
  }
146
- fs.symlinkSync(repo.uri, store, "dir");
295
+ fs2.symlinkSync(repo.uri, store, "dir");
147
296
  }
148
- if (!fs.existsSync(store)) {
297
+ if (!fs2.existsSync(store)) {
149
298
  missing.push(name);
150
299
  continue;
151
300
  }
152
- const target = path.join(refDir, name);
153
- if (fs.existsSync(target)) {
301
+ const target = path2.join(refDir, name);
302
+ const stat = fs2.lstatSync(target, { throwIfNoEntry: false });
303
+ if (stat && !stat.isSymbolicLink()) {
154
304
  unchanged.push(name);
155
305
  continue;
156
306
  }
157
- fs.symlinkSync(store, target, "dir");
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");
158
315
  linked.push(name);
159
316
  }
160
317
  return { linked, removed, missing, unchanged };
161
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
+ });
325
+ init_sync();
326
+
162
327
  export {
163
- sync,
164
- pull
328
+ pull,
329
+ sync
165
330
  };
@@ -1,23 +1,179 @@
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/unlink.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/unlink.ts
157
+ import fs2 from "fs";
158
+ import path2 from "path";
8
159
  function unlink(name) {
9
160
  const local = Config.Local.read();
10
161
  const found = Config.Local.find(local, name);
11
162
  if (!found) {
12
163
  return { ok: false, error: `"${name}" is not linked in local config` };
13
164
  }
14
- const target = path.join(Config.refDir(), found.name);
15
- if (fs.existsSync(target)) {
16
- fs.unlinkSync(target);
165
+ const target = path2.join(Config.refDir(), found.name);
166
+ if (fs2.lstatSync(target, { throwIfNoEntry: false })) {
167
+ fs2.unlinkSync(target);
17
168
  }
18
169
  Config.Local.write(Config.Local.remove(local, found.name));
19
170
  return { ok: true };
20
171
  }
172
+ var init_unlink = __esm(() => {
173
+ init_config();
174
+ });
175
+ init_unlink();
176
+
21
177
  export {
22
178
  unlink
23
179
  };