@spader/dotllm 1.2.1 → 1.2.6
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 +18 -2
- package/package.json +2 -2
- package/src/cli/commands/add.js +39 -22
- package/src/cli/commands/link.js +5 -3
- package/src/cli/commands/sync.js +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dotllm
|
|
2
|
+
`dotllm` manages a system-wide cache of commonly referenced repositories and provides a beautiful command line for linking them anywhere on your system
|
|
3
|
+
|
|
4
|
+
```bash
|
|
5
|
+
> tree .llm/reference/
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
.llm/reference/
|
|
10
|
+
├── cloudflare-docs -> /home/spader/.local/share/dotllm/store/cloudflare-docs
|
|
11
|
+
├── cuda-toolkit -> /home/spader/.local/share/dotllm/store/cuda-toolkit
|
|
12
|
+
├── ghostty -> /home/spader/.local/share/dotllm/store/ghostty
|
|
13
|
+
├── node-llama-cpp -> /home/spader/.local/share/dotllm/store/node-llama-cpp
|
|
14
|
+
├── opencode -> /home/spader/.local/share/dotllm/store/opencode
|
|
15
|
+
└── sprites-docs -> /home/spader/.local/share/dotllm/store/sprites-docs
|
|
16
|
+
```
|
|
17
|
+
|
|
2
18
|
LLMs work best with references. If you're like me, every project has loose repositories lying around half a dozen places because some LLM needed it.
|
|
3
19
|
|
|
4
20
|
Instead, keep a cache of such repositories and use `dotllm` to link them to `.llm`. Check in `.llm/dotllm.json`, and a simple `dotllm sync` will restore all of your references.
|
|
@@ -9,7 +25,7 @@ bun install -g @spader/dotllm
|
|
|
9
25
|
```
|
|
10
26
|
|
|
11
27
|
# usage
|
|
12
|
-
Register a repository (by HTTPS, SSH, or path)
|
|
28
|
+
Register a repository (by HTTPS, SSH, or local path)
|
|
13
29
|
```bash
|
|
14
30
|
dotllm add https://github.com/tspader/dotllm.git
|
|
15
31
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spader/dotllm",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "A simple CLI to clone, manage, and link repositories for LLM reference",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
],
|
|
36
36
|
"license": "UNLICENSED",
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@clack/prompts": "^0.
|
|
38
|
+
"@clack/prompts": "^1.0.1",
|
|
39
39
|
"picocolors": "^1.1.1",
|
|
40
40
|
"yargs": "^17.7.2",
|
|
41
41
|
"zod": "^4.3.6"
|
package/src/cli/commands/add.js
CHANGED
|
@@ -35,20 +35,42 @@ function stem(value) {
|
|
|
35
35
|
return base;
|
|
36
36
|
}
|
|
37
37
|
function github(uri) {
|
|
38
|
-
|
|
38
|
+
return hosted(uri, "github.com");
|
|
39
|
+
}
|
|
40
|
+
function codeberg(uri) {
|
|
41
|
+
return hosted(uri, "codeberg.org");
|
|
42
|
+
}
|
|
43
|
+
function hosted(uri, host) {
|
|
44
|
+
const escaped = host.replace(/\./g, "\\.");
|
|
45
|
+
const https = uri.match(new RegExp(`^https?:\\/\\/${escaped}\\/([^/]+)\\/([^/]+?)(?:\\.git)?\\/?$`));
|
|
39
46
|
if (https) {
|
|
40
47
|
return { owner: https[1], repo: https[2] };
|
|
41
48
|
}
|
|
42
|
-
const ssh = uri.match(
|
|
49
|
+
const ssh = uri.match(new RegExp(`^ssh:\\/\\/git@${escaped}\\/([^/]+)\\/([^/]+?)(?:\\.git)?\\/?$`));
|
|
43
50
|
if (ssh) {
|
|
44
51
|
return { owner: ssh[1], repo: ssh[2] };
|
|
45
52
|
}
|
|
46
|
-
const scp = uri.match(
|
|
53
|
+
const scp = uri.match(new RegExp(`^git@${escaped}:([^/]+)\\/([^/]+?)(?:\\.git)?$`));
|
|
47
54
|
if (scp) {
|
|
48
55
|
return { owner: scp[1], repo: scp[2] };
|
|
49
56
|
}
|
|
50
57
|
return null;
|
|
51
58
|
}
|
|
59
|
+
async function apiDescription(url, accept) {
|
|
60
|
+
const response = await fetch(url, {
|
|
61
|
+
headers: {
|
|
62
|
+
accept,
|
|
63
|
+
"user-agent": "dotllm"
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok)
|
|
67
|
+
return "";
|
|
68
|
+
const raw = await response.json();
|
|
69
|
+
const result = RepoShape.safeParse(raw);
|
|
70
|
+
if (!result.success)
|
|
71
|
+
return "";
|
|
72
|
+
return result.data.description ?? "";
|
|
73
|
+
}
|
|
52
74
|
function gitName(uri) {
|
|
53
75
|
const dir = path.resolve(uri);
|
|
54
76
|
if (!fs.existsSync(dir))
|
|
@@ -68,23 +90,17 @@ function gitName(uri) {
|
|
|
68
90
|
return stem(out);
|
|
69
91
|
}
|
|
70
92
|
async function remoteDescription(uri) {
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return "";
|
|
83
|
-
const raw = await response.json();
|
|
84
|
-
const result = RepoShape.safeParse(raw);
|
|
85
|
-
if (!result.success)
|
|
86
|
-
return "";
|
|
87
|
-
return result.data.description ?? "";
|
|
93
|
+
const gh = github(uri);
|
|
94
|
+
if (gh) {
|
|
95
|
+
const url = `https://api.github.com/repos/${gh.owner}/${gh.repo}`;
|
|
96
|
+
return apiDescription(url, "application/vnd.github+json");
|
|
97
|
+
}
|
|
98
|
+
const cb = codeberg(uri);
|
|
99
|
+
if (cb) {
|
|
100
|
+
const url = `https://codeberg.org/api/v1/repos/${cb.owner}/${cb.repo}`;
|
|
101
|
+
return apiDescription(url, "application/json");
|
|
102
|
+
}
|
|
103
|
+
return "";
|
|
88
104
|
}
|
|
89
105
|
async function prefill(uri) {
|
|
90
106
|
const remote = isUrl(uri);
|
|
@@ -138,7 +154,7 @@ async function run(uri, name, description) {
|
|
|
138
154
|
const desc = description ?? seed.description;
|
|
139
155
|
const result = await add(uri, resolved || undefined, desc || undefined);
|
|
140
156
|
if (!result.ok) {
|
|
141
|
-
spinner2.stop(t.error(result.error)
|
|
157
|
+
spinner2.stop(t.error(result.error));
|
|
142
158
|
process.exit(1);
|
|
143
159
|
return;
|
|
144
160
|
}
|
|
@@ -181,5 +197,6 @@ var command = {
|
|
|
181
197
|
export {
|
|
182
198
|
stem,
|
|
183
199
|
github,
|
|
184
|
-
command
|
|
200
|
+
command,
|
|
201
|
+
codeberg
|
|
185
202
|
};
|
package/src/cli/commands/link.js
CHANGED
|
@@ -55,14 +55,16 @@ var command = {
|
|
|
55
55
|
}
|
|
56
56
|
const local = Config.Local.read();
|
|
57
57
|
const current = new Set(Object.keys(local.refs));
|
|
58
|
-
const
|
|
58
|
+
const repos = [...global.repos].sort((a, b) => a.name.localeCompare(b.name));
|
|
59
|
+
const initialValues = repos.filter((r) => current.has(r.name)).map((r) => r.name).sort((a, b) => b.localeCompare(a));
|
|
60
|
+
const selected = await prompts.autocompleteMultiselect({
|
|
59
61
|
message: "Select repos to link into .llm/reference/",
|
|
60
|
-
options:
|
|
62
|
+
options: repos.map((r) => ({
|
|
61
63
|
value: r.name,
|
|
62
64
|
label: r.name,
|
|
63
65
|
hint: r.uri
|
|
64
66
|
})),
|
|
65
|
-
initialValues
|
|
67
|
+
initialValues,
|
|
66
68
|
required: false
|
|
67
69
|
});
|
|
68
70
|
if (prompts.isCancel(selected)) {
|
package/src/cli/commands/sync.js
CHANGED
|
@@ -24,7 +24,7 @@ var command = {
|
|
|
24
24
|
spinner2.start(`Pulling ${refs.length} linked repo${refs.length === 1 ? "" : "s"}`);
|
|
25
25
|
const pulled = await pull(refs);
|
|
26
26
|
if (pulled.failed.length > 0) {
|
|
27
|
-
spinner2.stop(t.error(`pull failed for ${pulled.failed.length} repo${pulled.failed.length === 1 ? "" : "s"}`)
|
|
27
|
+
spinner2.stop(t.error(`pull failed for ${pulled.failed.length} repo${pulled.failed.length === 1 ? "" : "s"}`));
|
|
28
28
|
process.exit(1);
|
|
29
29
|
return;
|
|
30
30
|
}
|