@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/README.md +12 -0
- package/package.json +1 -1
- package/src/cli/commands/add.js +537 -30
- package/src/cli/commands/completions.js +514 -26
- package/src/cli/commands/index.js +1276 -16
- package/src/cli/commands/link.js +524 -17
- package/src/cli/commands/list.js +636 -9
- package/src/cli/commands/remove.js +493 -5
- package/src/cli/commands/sync.js +519 -12
- package/src/cli/commands/which.js +496 -8
- package/src/cli/index.js +1446 -10
- package/src/cli/layout.js +22 -6
- package/src/cli/prompt.js +0 -2
- package/src/cli/theme.js +0 -2
- package/src/cli/yargs.js +165 -8
- package/src/core/add.js +170 -14
- package/src/core/config.js +139 -115
- package/src/core/index.js +475 -13
- package/src/core/link.js +329 -3
- package/src/core/remove.js +164 -8
- package/src/core/sync.js +198 -33
- package/src/core/unlink.js +162 -6
|
@@ -1,12 +1,500 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var
|
|
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/
|
|
14
|
+
// src/core/config.ts
|
|
5
15
|
import fs from "fs";
|
|
6
16
|
import os from "os";
|
|
7
17
|
import path from "path";
|
|
8
|
-
import {
|
|
9
|
-
|
|
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/add.ts
|
|
157
|
+
import fs2 from "fs";
|
|
158
|
+
import path2 from "path";
|
|
159
|
+
function isUrl(value) {
|
|
160
|
+
return value.startsWith("http://") || value.startsWith("https://") || value.startsWith("git@") || value.startsWith("ssh://");
|
|
161
|
+
}
|
|
162
|
+
function nameFromGitRemote(dir) {
|
|
163
|
+
const result = Bun.spawnSync(["git", "remote", "get-url", "origin"], {
|
|
164
|
+
cwd: dir,
|
|
165
|
+
stdout: "pipe",
|
|
166
|
+
stderr: "pipe"
|
|
167
|
+
});
|
|
168
|
+
if (result.exitCode !== 0)
|
|
169
|
+
return null;
|
|
170
|
+
const url = result.stdout.toString().trim();
|
|
171
|
+
return nameFromUrl(url);
|
|
172
|
+
}
|
|
173
|
+
function nameFromUrl(url) {
|
|
174
|
+
const base = url.split("/").pop() ?? url;
|
|
175
|
+
return base.replace(/\.git$/, "");
|
|
176
|
+
}
|
|
177
|
+
async function add(uri, name, description) {
|
|
178
|
+
const desc = description ?? "";
|
|
179
|
+
if (isUrl(uri)) {
|
|
180
|
+
return cloneUrl(uri, name, desc);
|
|
181
|
+
}
|
|
182
|
+
return linkLocal(uri, name, desc);
|
|
183
|
+
}
|
|
184
|
+
async function cloneUrl(url, name, description) {
|
|
185
|
+
const resolved = name ?? nameFromUrl(url);
|
|
186
|
+
const store = Config.storeDir();
|
|
187
|
+
fs2.mkdirSync(store, { recursive: true });
|
|
188
|
+
const target = path2.join(store, resolved);
|
|
189
|
+
if (!fs2.existsSync(target)) {
|
|
190
|
+
const proc = Bun.spawn(["git", "clone", "--depth=1", url, target], {
|
|
191
|
+
stdout: "pipe",
|
|
192
|
+
stderr: "pipe"
|
|
193
|
+
});
|
|
194
|
+
const code = await proc.exited;
|
|
195
|
+
if (code !== 0) {
|
|
196
|
+
const msg = await new Response(proc.stderr).text();
|
|
197
|
+
return { ok: false, error: `git clone failed: ${msg.trim()}` };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const entry = { kind: "url", name: resolved, uri: url, description };
|
|
201
|
+
const global = Config.Global.read();
|
|
202
|
+
Config.Global.write(Config.Global.add(global, entry));
|
|
203
|
+
return { ok: true, entry, storePath: target };
|
|
204
|
+
}
|
|
205
|
+
function linkLocal(raw, name, description) {
|
|
206
|
+
const resolved = path2.resolve(raw);
|
|
207
|
+
if (!fs2.existsSync(resolved)) {
|
|
208
|
+
return { ok: false, error: `Path does not exist: ${resolved}` };
|
|
209
|
+
}
|
|
210
|
+
if (!fs2.statSync(resolved).isDirectory()) {
|
|
211
|
+
return { ok: false, error: `Not a directory: ${resolved}` };
|
|
212
|
+
}
|
|
213
|
+
const store = Config.storeDir();
|
|
214
|
+
fs2.mkdirSync(store, { recursive: true });
|
|
215
|
+
const finalName = name ?? nameFromGitRemote(resolved) ?? path2.basename(resolved);
|
|
216
|
+
const target = path2.join(store, finalName);
|
|
217
|
+
if (!fs2.existsSync(target)) {
|
|
218
|
+
fs2.symlinkSync(resolved, target, "dir");
|
|
219
|
+
}
|
|
220
|
+
const entry = { kind: "file", name: finalName, uri: resolved, description };
|
|
221
|
+
const global = Config.Global.read();
|
|
222
|
+
Config.Global.write(Config.Global.add(global, entry));
|
|
223
|
+
return { ok: true, entry, storePath: target };
|
|
224
|
+
}
|
|
225
|
+
var init_add = __esm(() => {
|
|
226
|
+
init_config();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// src/core/remove.ts
|
|
230
|
+
import fs3 from "fs";
|
|
231
|
+
import path3 from "path";
|
|
232
|
+
function remove(name) {
|
|
233
|
+
const global = Config.Global.read();
|
|
234
|
+
const found = Config.Global.find(global, name);
|
|
235
|
+
if (!found) {
|
|
236
|
+
return { ok: false, error: `No repo named "${name}" in registry` };
|
|
237
|
+
}
|
|
238
|
+
const target = path3.join(Config.storeDir(), found.name);
|
|
239
|
+
if (fs3.existsSync(target)) {
|
|
240
|
+
const stat = fs3.lstatSync(target);
|
|
241
|
+
if (stat.isSymbolicLink()) {
|
|
242
|
+
fs3.unlinkSync(target);
|
|
243
|
+
}
|
|
244
|
+
if (stat.isDirectory()) {
|
|
245
|
+
fs3.rmSync(target, { recursive: true, force: true });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
Config.Global.write(Config.Global.remove(global, found.name));
|
|
249
|
+
return { ok: true };
|
|
250
|
+
}
|
|
251
|
+
var init_remove = __esm(() => {
|
|
252
|
+
init_config();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// src/core/sync.ts
|
|
256
|
+
import fs4 from "fs";
|
|
257
|
+
import path4 from "path";
|
|
258
|
+
function shouldPull(storeDir, force) {
|
|
259
|
+
if (force)
|
|
260
|
+
return true;
|
|
261
|
+
const head = path4.join(storeDir, ".git", "HEAD");
|
|
262
|
+
const headStat = fs4.statSync(head, { throwIfNoEntry: false });
|
|
263
|
+
if (!headStat)
|
|
264
|
+
return true;
|
|
265
|
+
const age = Date.now() - headStat.mtimeMs;
|
|
266
|
+
if (age < FRESH_MS)
|
|
267
|
+
return false;
|
|
268
|
+
if (age > STALE_MS)
|
|
269
|
+
return true;
|
|
270
|
+
const dirStat = fs4.statSync(storeDir, { throwIfNoEntry: false });
|
|
271
|
+
if (!dirStat)
|
|
272
|
+
return true;
|
|
273
|
+
return dirStat.atimeMs > headStat.mtimeMs;
|
|
274
|
+
}
|
|
275
|
+
async function pull(names, options = {}) {
|
|
276
|
+
const force = options.force === true;
|
|
277
|
+
const global = Config.Global.read();
|
|
278
|
+
const states = await Promise.all(names.map(async (name) => {
|
|
279
|
+
const cwd = path4.join(Config.refDir(), name);
|
|
280
|
+
if (!fs4.existsSync(cwd)) {
|
|
281
|
+
return { kind: "failed", name, error: "reference directory missing" };
|
|
282
|
+
}
|
|
283
|
+
const repo = Config.Global.find(global, name);
|
|
284
|
+
if (repo && repo.kind === "file") {
|
|
285
|
+
return { kind: "skipped", name };
|
|
286
|
+
}
|
|
287
|
+
const storeDir = path4.join(Config.storeDir(), name);
|
|
288
|
+
const storeStat = fs4.lstatSync(storeDir, { throwIfNoEntry: false });
|
|
289
|
+
if (!storeStat || storeStat.isSymbolicLink()) {
|
|
290
|
+
return { kind: "skipped", name };
|
|
291
|
+
}
|
|
292
|
+
if (!fs4.existsSync(path4.join(storeDir, ".git"))) {
|
|
293
|
+
return { kind: "failed", name, error: "store directory is not a git repo" };
|
|
294
|
+
}
|
|
295
|
+
if (!shouldPull(storeDir, force)) {
|
|
296
|
+
return { kind: "skipped", name };
|
|
297
|
+
}
|
|
298
|
+
const fetch = Bun.spawn(["git", "fetch", "--depth=1", "origin", "HEAD"], {
|
|
299
|
+
cwd,
|
|
300
|
+
stdout: "pipe",
|
|
301
|
+
stderr: "pipe"
|
|
302
|
+
});
|
|
303
|
+
const [fetchCode, fetchOut, fetchErr] = await Promise.all([
|
|
304
|
+
fetch.exited,
|
|
305
|
+
new Response(fetch.stdout).text(),
|
|
306
|
+
new Response(fetch.stderr).text()
|
|
307
|
+
]);
|
|
308
|
+
if (fetchCode !== 0) {
|
|
309
|
+
const msg = `${fetchOut}
|
|
310
|
+
${fetchErr}`.trim();
|
|
311
|
+
return { kind: "failed", name, error: msg || "git fetch failed" };
|
|
312
|
+
}
|
|
313
|
+
const reset = Bun.spawn(["git", "reset", "--hard", "FETCH_HEAD"], {
|
|
314
|
+
cwd,
|
|
315
|
+
stdout: "pipe",
|
|
316
|
+
stderr: "pipe"
|
|
317
|
+
});
|
|
318
|
+
const [resetCode, resetOut, resetErr] = await Promise.all([
|
|
319
|
+
reset.exited,
|
|
320
|
+
new Response(reset.stdout).text(),
|
|
321
|
+
new Response(reset.stderr).text()
|
|
322
|
+
]);
|
|
323
|
+
if (resetCode !== 0) {
|
|
324
|
+
const msg = `${resetOut}
|
|
325
|
+
${resetErr}`.trim();
|
|
326
|
+
return { kind: "failed", name, error: msg || "git reset failed" };
|
|
327
|
+
}
|
|
328
|
+
return { kind: "ok" };
|
|
329
|
+
}));
|
|
330
|
+
const pulled = [];
|
|
331
|
+
const skipped = [];
|
|
332
|
+
const failed = [];
|
|
333
|
+
for (let i = 0;i < states.length; i++) {
|
|
334
|
+
const state = states[i];
|
|
335
|
+
if (state.kind === "ok")
|
|
336
|
+
pulled.push(names[i]);
|
|
337
|
+
if (state.kind === "skipped")
|
|
338
|
+
skipped.push(state.name);
|
|
339
|
+
if (state.kind === "failed")
|
|
340
|
+
failed.push({ name: state.name, error: state.error });
|
|
341
|
+
}
|
|
342
|
+
return { count: names.length, pulled, skipped, failed };
|
|
343
|
+
}
|
|
344
|
+
function sync() {
|
|
345
|
+
const local = Config.Local.read();
|
|
346
|
+
const global = Config.Global.read();
|
|
347
|
+
const merged = Object.values(local.refs).reduce((config, repo) => Config.Global.add(config, repo), global);
|
|
348
|
+
if (Object.keys(local.refs).length > 0) {
|
|
349
|
+
Config.Global.write(merged);
|
|
350
|
+
}
|
|
351
|
+
const refDir = Config.refDir();
|
|
352
|
+
fs4.mkdirSync(refDir, { recursive: true });
|
|
353
|
+
fs4.mkdirSync(Config.storeDir(), { recursive: true });
|
|
354
|
+
const linked = [];
|
|
355
|
+
const removed = [];
|
|
356
|
+
const missing = [];
|
|
357
|
+
const unchanged = [];
|
|
358
|
+
const wanted = new Set(Object.keys(local.refs));
|
|
359
|
+
for (const entry of fs4.readdirSync(refDir)) {
|
|
360
|
+
if (wanted.has(entry))
|
|
361
|
+
continue;
|
|
362
|
+
const target = path4.join(refDir, entry);
|
|
363
|
+
const stat = fs4.lstatSync(target);
|
|
364
|
+
if (stat.isSymbolicLink()) {
|
|
365
|
+
fs4.unlinkSync(target);
|
|
366
|
+
removed.push(entry);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
for (const [name, repo] of Object.entries(local.refs)) {
|
|
370
|
+
const store = path4.join(Config.storeDir(), name);
|
|
371
|
+
const storeBroken = repo.kind === "url" && fs4.existsSync(store) && !fs4.existsSync(path4.join(store, ".git"));
|
|
372
|
+
if (storeBroken) {
|
|
373
|
+
fs4.rmSync(store, { recursive: true, force: true });
|
|
374
|
+
}
|
|
375
|
+
if (!fs4.existsSync(store) && repo.kind === "url") {
|
|
376
|
+
const clone = Bun.spawnSync(["git", "clone", "--depth=1", repo.uri, store], {
|
|
377
|
+
stdout: "pipe",
|
|
378
|
+
stderr: "pipe"
|
|
379
|
+
});
|
|
380
|
+
if (clone.exitCode !== 0) {
|
|
381
|
+
missing.push(name);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (!fs4.existsSync(store) && repo.kind === "file") {
|
|
386
|
+
if (!fs4.existsSync(repo.uri)) {
|
|
387
|
+
missing.push(name);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (!fs4.statSync(repo.uri).isDirectory()) {
|
|
391
|
+
missing.push(name);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
fs4.symlinkSync(repo.uri, store, "dir");
|
|
395
|
+
}
|
|
396
|
+
if (!fs4.existsSync(store)) {
|
|
397
|
+
missing.push(name);
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const target = path4.join(refDir, name);
|
|
401
|
+
const stat = fs4.lstatSync(target, { throwIfNoEntry: false });
|
|
402
|
+
if (stat && !stat.isSymbolicLink()) {
|
|
403
|
+
unchanged.push(name);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (stat && fs4.readlinkSync(target) === store) {
|
|
407
|
+
unchanged.push(name);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (stat) {
|
|
411
|
+
fs4.unlinkSync(target);
|
|
412
|
+
}
|
|
413
|
+
fs4.symlinkSync(store, target, "dir");
|
|
414
|
+
linked.push(name);
|
|
415
|
+
}
|
|
416
|
+
return { linked, removed, missing, unchanged };
|
|
417
|
+
}
|
|
418
|
+
var FRESH_MS, STALE_MS;
|
|
419
|
+
var init_sync = __esm(() => {
|
|
420
|
+
init_config();
|
|
421
|
+
FRESH_MS = 24 * 60 * 60 * 1000;
|
|
422
|
+
STALE_MS = 7 * FRESH_MS;
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// src/core/link.ts
|
|
426
|
+
function link(names) {
|
|
427
|
+
const global = Config.Global.read();
|
|
428
|
+
const rows = names.map((name) => {
|
|
429
|
+
const repo = Config.Global.find(global, name);
|
|
430
|
+
if (!repo)
|
|
431
|
+
return null;
|
|
432
|
+
return [repo.name, repo];
|
|
433
|
+
}).filter((row) => row !== null);
|
|
434
|
+
Config.Local.write({ refs: Object.fromEntries(rows) });
|
|
435
|
+
return sync();
|
|
436
|
+
}
|
|
437
|
+
var init_link = __esm(() => {
|
|
438
|
+
init_config();
|
|
439
|
+
init_sync();
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// src/core/unlink.ts
|
|
443
|
+
import fs5 from "fs";
|
|
444
|
+
import path5 from "path";
|
|
445
|
+
function unlink(name) {
|
|
446
|
+
const local = Config.Local.read();
|
|
447
|
+
const found = Config.Local.find(local, name);
|
|
448
|
+
if (!found) {
|
|
449
|
+
return { ok: false, error: `"${name}" is not linked in local config` };
|
|
450
|
+
}
|
|
451
|
+
const target = path5.join(Config.refDir(), found.name);
|
|
452
|
+
if (fs5.lstatSync(target, { throwIfNoEntry: false })) {
|
|
453
|
+
fs5.unlinkSync(target);
|
|
454
|
+
}
|
|
455
|
+
Config.Local.write(Config.Local.remove(local, found.name));
|
|
456
|
+
return { ok: true };
|
|
457
|
+
}
|
|
458
|
+
var init_unlink = __esm(() => {
|
|
459
|
+
init_config();
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// src/core/index.ts
|
|
463
|
+
var init_core = __esm(() => {
|
|
464
|
+
init_config();
|
|
465
|
+
init_add();
|
|
466
|
+
init_remove();
|
|
467
|
+
init_link();
|
|
468
|
+
init_unlink();
|
|
469
|
+
init_sync();
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// src/cli/commands/completions.ts
|
|
473
|
+
init_core();
|
|
474
|
+
import fs6 from "fs";
|
|
475
|
+
import os2 from "os";
|
|
476
|
+
import path6 from "path";
|
|
477
|
+
|
|
478
|
+
// src/cli/theme.ts
|
|
479
|
+
function rgb(r, g, b) {
|
|
480
|
+
return (value) => `\x1B[38;2;${r};${g};${b}m${value}\x1B[39m`;
|
|
481
|
+
}
|
|
482
|
+
var gray = (value) => rgb(value, value, value);
|
|
483
|
+
var defaultTheme = {
|
|
484
|
+
primary: rgb(114, 161, 136),
|
|
485
|
+
link: rgb(114, 140, 212),
|
|
486
|
+
header: gray(128),
|
|
487
|
+
command: rgb(114, 161, 136),
|
|
488
|
+
arg: rgb(161, 212, 212),
|
|
489
|
+
option: rgb(212, 212, 161),
|
|
490
|
+
type: gray(128),
|
|
491
|
+
description: (value) => value,
|
|
492
|
+
dim: gray(128),
|
|
493
|
+
error: rgb(212, 114, 114),
|
|
494
|
+
success: rgb(114, 212, 136)
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// src/cli/commands/completions.ts
|
|
10
498
|
var BASH = `_dotllm() {
|
|
11
499
|
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
12
500
|
|
|
@@ -69,7 +557,7 @@ complete -c dotllm -n "__fish_use_subcommand" -a completions -d "Output shell co
|
|
|
69
557
|
complete -c dotllm -n "__fish_seen_subcommand_from which link" -a "(dotllm completions --names 2>/dev/null)"`;
|
|
70
558
|
var CANARY = "@dotllm_completions";
|
|
71
559
|
function completionFile(shell) {
|
|
72
|
-
return
|
|
560
|
+
return path6.join(Config.home(), `completions.${shell}`);
|
|
73
561
|
}
|
|
74
562
|
function hookSnippet(shell) {
|
|
75
563
|
const file = completionFile(shell);
|
|
@@ -97,18 +585,18 @@ test -f "${file}"; and source "${file}"
|
|
|
97
585
|
}
|
|
98
586
|
}
|
|
99
587
|
function rcPath(shell) {
|
|
100
|
-
const home =
|
|
588
|
+
const home = os2.homedir();
|
|
101
589
|
switch (shell) {
|
|
102
590
|
case "bash": {
|
|
103
|
-
const bashrc =
|
|
104
|
-
if (
|
|
591
|
+
const bashrc = path6.join(home, ".bashrc");
|
|
592
|
+
if (fs6.existsSync(bashrc))
|
|
105
593
|
return bashrc;
|
|
106
|
-
return
|
|
594
|
+
return path6.join(home, ".bash_profile");
|
|
107
595
|
}
|
|
108
596
|
case "zsh":
|
|
109
|
-
return
|
|
597
|
+
return path6.join(home, ".zshrc");
|
|
110
598
|
case "fish":
|
|
111
|
-
return
|
|
599
|
+
return path6.join(home, ".config", "fish", "config.fish");
|
|
112
600
|
}
|
|
113
601
|
}
|
|
114
602
|
var install = {
|
|
@@ -116,35 +604,35 @@ var install = {
|
|
|
116
604
|
summary: "Install completions",
|
|
117
605
|
handler: async (argv) => {
|
|
118
606
|
if (process.platform === "win32") {
|
|
119
|
-
console.log(
|
|
607
|
+
console.log(defaultTheme.dim("Shell completions are not supported on Windows."));
|
|
120
608
|
return;
|
|
121
609
|
}
|
|
122
610
|
const shell = detectShell();
|
|
123
611
|
const rc = rcPath(shell);
|
|
124
612
|
if (!rc) {
|
|
125
|
-
console.error(
|
|
613
|
+
console.error(defaultTheme.error(`Could not determine rc file for shell: "${shell}"`));
|
|
126
614
|
process.exit(1);
|
|
127
615
|
return;
|
|
128
616
|
}
|
|
129
617
|
const script = shellScript(shell);
|
|
130
618
|
if (!script) {
|
|
131
|
-
console.error(
|
|
619
|
+
console.error(defaultTheme.error(`Unknown shell: "${shell}"`));
|
|
132
620
|
process.exit(1);
|
|
133
621
|
return;
|
|
134
622
|
}
|
|
135
623
|
const cached = completionFile(shell);
|
|
136
|
-
|
|
137
|
-
|
|
624
|
+
fs6.mkdirSync(path6.dirname(cached), { recursive: true });
|
|
625
|
+
fs6.writeFileSync(cached, script + `
|
|
138
626
|
`);
|
|
139
|
-
const existing =
|
|
627
|
+
const existing = fs6.existsSync(rc) ? fs6.readFileSync(rc, "utf-8") : "";
|
|
140
628
|
if (existing.includes(CANARY)) {
|
|
141
|
-
console.log(
|
|
142
|
-
console.log(
|
|
629
|
+
console.log(defaultTheme.dim(`Updated ${cached}`));
|
|
630
|
+
console.log(defaultTheme.dim(`Already sourced from ${rc}`));
|
|
143
631
|
return;
|
|
144
632
|
}
|
|
145
|
-
|
|
146
|
-
console.log(`Installed completions in ${
|
|
147
|
-
console.log(
|
|
633
|
+
fs6.appendFileSync(rc, hookSnippet(shell));
|
|
634
|
+
console.log(`Installed completions in ${defaultTheme.link(rc)}`);
|
|
635
|
+
console.log(defaultTheme.dim(`Restart your shell or run: source ${rc}`));
|
|
148
636
|
}
|
|
149
637
|
};
|
|
150
638
|
var command = {
|
|
@@ -166,12 +654,12 @@ var command = {
|
|
|
166
654
|
},
|
|
167
655
|
handler: async (argv) => {
|
|
168
656
|
if (process.platform === "win32") {
|
|
169
|
-
console.log(
|
|
657
|
+
console.log(defaultTheme.dim("Shell completions are not supported on Windows."));
|
|
170
658
|
return;
|
|
171
659
|
}
|
|
172
660
|
if (argv.names) {
|
|
173
|
-
|
|
174
|
-
const global =
|
|
661
|
+
await Promise.resolve().then(() => init_core());
|
|
662
|
+
const global = Config.Global.read();
|
|
175
663
|
for (const r of global.repos)
|
|
176
664
|
console.log(r.name);
|
|
177
665
|
return;
|
|
@@ -188,7 +676,7 @@ var command = {
|
|
|
188
676
|
console.log(FISH);
|
|
189
677
|
break;
|
|
190
678
|
default:
|
|
191
|
-
console.error(
|
|
679
|
+
console.error(defaultTheme.error(`Unknown shell: "${shell}". Use bash, zsh, or fish.`));
|
|
192
680
|
process.exit(1);
|
|
193
681
|
}
|
|
194
682
|
}
|