@spader/dotllm 1.0.0 → 1.2.0
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/package.json +14 -18
- package/src/cli/commands/{add.ts → add.js} +89 -81
- package/src/cli/commands/cd.js +45 -0
- package/src/cli/commands/index.js +18 -0
- package/src/cli/commands/link.js +78 -0
- package/src/cli/commands/list.js +42 -0
- package/src/cli/commands/{remove.ts → remove.js} +13 -11
- package/src/cli/commands/{sync.ts → sync.js} +14 -23
- package/src/cli/commands/which.js +30 -0
- package/src/cli/index.js +30 -0
- package/src/cli/layout.js +145 -0
- package/src/cli/prompt.js +23 -0
- package/src/cli/theme.js +22 -0
- package/src/cli/{yargs.ts → yargs.js} +46 -124
- package/src/core/{add.ts → add.js} +19 -45
- package/src/core/config.js +112 -0
- package/src/core/index.js +17 -0
- package/src/core/link.js +18 -0
- package/src/core/{remove.ts → remove.js} +6 -12
- package/src/core/{sync.ts → sync.js} +39 -75
- package/src/core/{unlink.ts → unlink.js} +6 -12
- package/src/cli/commands/index.ts +0 -5
- package/src/cli/commands/link.ts +0 -54
- package/src/cli/commands/list.ts +0 -44
- package/src/cli/index.ts +0 -24
- package/src/cli/layout.ts +0 -200
- package/src/cli/theme.ts +0 -35
- package/src/core/config.ts +0 -126
- package/src/core/index.ts +0 -6
- package/src/core/link.ts +0 -16
package/package.json
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spader/dotllm",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A simple CLI to clone, manage, and link repositories for LLM reference",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/tspader/dotllm"
|
|
8
9
|
},
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"dotllm": "src/cli/index.js"
|
|
12
15
|
},
|
|
13
16
|
"files": [
|
|
14
|
-
"src/**/*.
|
|
17
|
+
"src/**/*.js",
|
|
15
18
|
"README.md"
|
|
16
19
|
],
|
|
17
20
|
"exports": {
|
|
18
|
-
"./cli": "./src/cli/index.
|
|
19
|
-
"./cli/*": "./src/cli/*.
|
|
20
|
-
"./core": "./src/core/index.
|
|
21
|
-
"./core/*": "./src/core/*.
|
|
21
|
+
"./cli": "./src/cli/index.js",
|
|
22
|
+
"./cli/*": "./src/cli/*.js",
|
|
23
|
+
"./core": "./src/core/index.js",
|
|
24
|
+
"./core/*": "./src/core/*.js"
|
|
22
25
|
},
|
|
23
26
|
"engines": {
|
|
24
27
|
"bun": ">=1.1.0"
|
|
25
28
|
},
|
|
26
|
-
"publishConfig": {
|
|
27
|
-
"access": "public"
|
|
28
|
-
},
|
|
29
29
|
"keywords": [
|
|
30
30
|
"cli",
|
|
31
31
|
"bun",
|
|
@@ -39,9 +39,5 @@
|
|
|
39
39
|
"picocolors": "^1.1.1",
|
|
40
40
|
"yargs": "^17.7.2",
|
|
41
41
|
"zod": "^4.3.6"
|
|
42
|
-
},
|
|
43
|
-
"devDependencies": {
|
|
44
|
-
"@types/bun": "latest",
|
|
45
|
-
"@types/yargs": "^17.0.33"
|
|
46
42
|
}
|
|
47
43
|
}
|
|
@@ -1,177 +1,185 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/cli/commands/add.ts
|
|
1
3
|
import fs from "fs";
|
|
2
4
|
import path from "path";
|
|
3
5
|
import * as prompts from "@clack/prompts";
|
|
4
6
|
import { z } from "zod";
|
|
5
|
-
import { add } from "@spader/dotllm/core";
|
|
7
|
+
import { add, Config, sync } from "@spader/dotllm/core";
|
|
6
8
|
import { defaultTheme as t } from "@spader/dotllm/cli/theme";
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
description: z.string().nullable().optional(),
|
|
9
|
+
import { Prompt } from "@spader/dotllm/cli/prompt";
|
|
10
|
+
var RepoShape = z.object({
|
|
11
|
+
description: z.string().nullable().optional()
|
|
11
12
|
});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return value.startsWith("http://") ||
|
|
15
|
-
value.startsWith("https://") ||
|
|
16
|
-
value.startsWith("git@") ||
|
|
17
|
-
value.startsWith("ssh://");
|
|
13
|
+
function isUrl(value) {
|
|
14
|
+
return value.startsWith("http://") || value.startsWith("https://") || value.startsWith("git@") || value.startsWith("ssh://");
|
|
18
15
|
}
|
|
19
|
-
|
|
20
|
-
function stem(value: string): string {
|
|
16
|
+
function stem(value) {
|
|
21
17
|
const clean = value.trim().replace(/\/+$/, "");
|
|
22
|
-
if (clean.length === 0)
|
|
23
|
-
|
|
18
|
+
if (clean.length === 0)
|
|
19
|
+
return "";
|
|
24
20
|
if (clean.startsWith("git@")) {
|
|
25
21
|
const raw = clean.split(":").slice(1).join(":");
|
|
26
22
|
const seg = raw.split("/").filter(Boolean).pop() ?? raw;
|
|
27
23
|
return seg.replace(/\.git$/, "");
|
|
28
24
|
}
|
|
29
|
-
|
|
30
25
|
if (isUrl(clean)) {
|
|
31
26
|
const seg = clean.split("/").filter(Boolean).pop() ?? clean;
|
|
32
27
|
const raw = seg.split("?")[0] ?? seg;
|
|
33
28
|
const full = raw.split("#")[0] ?? raw;
|
|
34
29
|
return full.replace(/\.git$/, "");
|
|
35
30
|
}
|
|
36
|
-
|
|
37
31
|
const base = path.basename(clean);
|
|
38
32
|
const parsed = path.parse(base);
|
|
39
|
-
if (parsed.name.length > 0)
|
|
33
|
+
if (parsed.name.length > 0)
|
|
34
|
+
return parsed.name;
|
|
40
35
|
return base;
|
|
41
36
|
}
|
|
42
|
-
|
|
43
|
-
function github(uri: string): { owner: string; repo: string } | null {
|
|
37
|
+
function github(uri) {
|
|
44
38
|
const https = uri.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
45
39
|
if (https) {
|
|
46
|
-
return { owner: https[1]
|
|
40
|
+
return { owner: https[1], repo: https[2] };
|
|
47
41
|
}
|
|
48
|
-
|
|
49
42
|
const ssh = uri.match(/^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
50
43
|
if (ssh) {
|
|
51
|
-
return { owner: ssh[1]
|
|
44
|
+
return { owner: ssh[1], repo: ssh[2] };
|
|
52
45
|
}
|
|
53
|
-
|
|
54
46
|
const scp = uri.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
55
47
|
if (scp) {
|
|
56
|
-
return { owner: scp[1]
|
|
48
|
+
return { owner: scp[1], repo: scp[2] };
|
|
57
49
|
}
|
|
58
|
-
|
|
59
50
|
return null;
|
|
60
51
|
}
|
|
61
|
-
|
|
62
|
-
function gitName(uri: string): string {
|
|
52
|
+
function gitName(uri) {
|
|
63
53
|
const dir = path.resolve(uri);
|
|
64
|
-
if (!fs.existsSync(dir))
|
|
65
|
-
|
|
66
|
-
|
|
54
|
+
if (!fs.existsSync(dir))
|
|
55
|
+
return "";
|
|
56
|
+
if (!fs.statSync(dir).isDirectory())
|
|
57
|
+
return "";
|
|
67
58
|
const proc = Bun.spawnSync(["git", "remote", "get-url", "origin"], {
|
|
68
59
|
cwd: dir,
|
|
69
60
|
stdout: "pipe",
|
|
70
|
-
stderr: "pipe"
|
|
61
|
+
stderr: "pipe"
|
|
71
62
|
});
|
|
72
|
-
if (proc.exitCode !== 0)
|
|
73
|
-
|
|
63
|
+
if (proc.exitCode !== 0)
|
|
64
|
+
return "";
|
|
74
65
|
const out = proc.stdout.toString().trim();
|
|
75
|
-
if (out.length === 0)
|
|
66
|
+
if (out.length === 0)
|
|
67
|
+
return "";
|
|
76
68
|
return stem(out);
|
|
77
69
|
}
|
|
78
|
-
|
|
79
|
-
async function remoteDescription(uri: string): Promise<string> {
|
|
70
|
+
async function remoteDescription(uri) {
|
|
80
71
|
const repo = github(uri);
|
|
81
|
-
if (!repo)
|
|
82
|
-
|
|
72
|
+
if (!repo)
|
|
73
|
+
return "";
|
|
83
74
|
const url = `https://api.github.com/repos/${repo.owner}/${repo.repo}`;
|
|
84
75
|
const response = await fetch(url, {
|
|
85
76
|
headers: {
|
|
86
77
|
accept: "application/vnd.github+json",
|
|
87
|
-
"user-agent": "dotllm"
|
|
88
|
-
}
|
|
78
|
+
"user-agent": "dotllm"
|
|
79
|
+
}
|
|
89
80
|
});
|
|
90
|
-
if (!response.ok)
|
|
91
|
-
|
|
81
|
+
if (!response.ok)
|
|
82
|
+
return "";
|
|
92
83
|
const raw = await response.json();
|
|
93
84
|
const result = RepoShape.safeParse(raw);
|
|
94
|
-
if (!result.success)
|
|
85
|
+
if (!result.success)
|
|
86
|
+
return "";
|
|
95
87
|
return result.data.description ?? "";
|
|
96
88
|
}
|
|
97
|
-
|
|
98
|
-
async function prefill(uri: string): Promise<{ name: string; description: string }> {
|
|
89
|
+
async function prefill(uri) {
|
|
99
90
|
const remote = isUrl(uri);
|
|
100
91
|
const git = remote ? "" : gitName(uri);
|
|
101
92
|
const name = git.length > 0 ? git : stem(uri);
|
|
102
93
|
const description = remote ? await remoteDescription(uri) : "";
|
|
103
94
|
return { name, description };
|
|
104
95
|
}
|
|
105
|
-
|
|
106
|
-
async function interactive(): Promise<void> {
|
|
96
|
+
async function interactive(namePrefill, descPrefill) {
|
|
107
97
|
const uri = await prompts.text({
|
|
108
|
-
message: "URL or local path to a git repo"
|
|
98
|
+
message: "URL or local path to a git repo"
|
|
109
99
|
});
|
|
110
|
-
if (prompts.isCancel(uri))
|
|
111
|
-
|
|
100
|
+
if (prompts.isCancel(uri))
|
|
101
|
+
return;
|
|
112
102
|
const input = uri.trim();
|
|
113
103
|
const seed = await prefill(input);
|
|
114
|
-
|
|
115
104
|
const name = await prompts.text({
|
|
116
|
-
message: "Name
|
|
117
|
-
|
|
105
|
+
message: "Name",
|
|
106
|
+
initialValue: namePrefill ?? seed.name
|
|
118
107
|
});
|
|
119
|
-
if (prompts.isCancel(name))
|
|
120
|
-
|
|
108
|
+
if (prompts.isCancel(name))
|
|
109
|
+
return;
|
|
121
110
|
const description = await prompts.text({
|
|
122
111
|
message: "Description",
|
|
123
|
-
|
|
112
|
+
initialValue: descPrefill ?? seed.description
|
|
124
113
|
});
|
|
125
|
-
if (prompts.isCancel(description))
|
|
126
|
-
|
|
114
|
+
if (prompts.isCancel(description))
|
|
115
|
+
return;
|
|
127
116
|
await run(input, name || undefined, description || undefined);
|
|
128
117
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
118
|
+
function autoLink(name) {
|
|
119
|
+
const localFile = path.join(".llm", "dotllm.json");
|
|
120
|
+
if (!fs.existsSync(localFile))
|
|
121
|
+
return;
|
|
122
|
+
const local = Config.Local.read();
|
|
123
|
+
if (Config.Local.has(local, name))
|
|
124
|
+
return;
|
|
125
|
+
const global = Config.Global.read();
|
|
126
|
+
const repo = Config.Global.find(global, name);
|
|
127
|
+
if (!repo)
|
|
128
|
+
return;
|
|
129
|
+
Config.Local.write(Config.Local.add(local, repo));
|
|
130
|
+
Prompt.sync(sync());
|
|
131
|
+
}
|
|
132
|
+
async function run(uri, name, description) {
|
|
133
|
+
const spinner2 = prompts.spinner();
|
|
134
|
+
spinner2.start(`Adding ${uri}`);
|
|
135
|
+
const needSeed = !name || !description;
|
|
136
|
+
const seed = needSeed ? await prefill(uri) : { name: "", description: "" };
|
|
137
|
+
const resolved = name ?? seed.name;
|
|
138
|
+
const desc = description ?? seed.description;
|
|
139
|
+
const result = await add(uri, resolved || undefined, desc || undefined);
|
|
135
140
|
if (!result.ok) {
|
|
136
|
-
|
|
141
|
+
spinner2.stop(t.error(result.error), 1);
|
|
137
142
|
process.exit(1);
|
|
138
143
|
return;
|
|
139
144
|
}
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
spinner2.stop(`${t.success("added")} ${t.primary(result.entry.name)} ${t.link(result.storePath)}`);
|
|
146
|
+
autoLink(result.entry.name);
|
|
142
147
|
}
|
|
143
|
-
|
|
144
|
-
export const command: CommandDef = {
|
|
148
|
+
var command = {
|
|
145
149
|
description: "Register a git repo as a reference",
|
|
146
150
|
summary: "Add a repo to the registry",
|
|
147
151
|
positionals: {
|
|
148
152
|
uri: {
|
|
149
153
|
type: "string",
|
|
150
|
-
description: "URL or local path to a git repo"
|
|
151
|
-
}
|
|
154
|
+
description: "URL or local path to a git repo"
|
|
155
|
+
}
|
|
152
156
|
},
|
|
153
157
|
options: {
|
|
154
158
|
name: {
|
|
155
159
|
alias: "n",
|
|
156
160
|
type: "string",
|
|
157
|
-
description: "Name override (defaults to repo name from git)"
|
|
161
|
+
description: "Name override (defaults to repo name from git)"
|
|
158
162
|
},
|
|
159
163
|
description: {
|
|
160
164
|
alias: "d",
|
|
161
165
|
type: "string",
|
|
162
|
-
description: "Description of the reference"
|
|
163
|
-
}
|
|
166
|
+
description: "Description of the reference"
|
|
167
|
+
}
|
|
164
168
|
},
|
|
165
169
|
handler: async (argv) => {
|
|
170
|
+
prompts.intro("dotllm add");
|
|
166
171
|
const uri = typeof argv.uri === "string" && argv.uri.length > 0 ? argv.uri : undefined;
|
|
167
|
-
|
|
172
|
+
const name = typeof argv.name === "string" && argv.name.length > 0 ? argv.name : undefined;
|
|
173
|
+
const description = typeof argv.description === "string" && argv.description.length > 0 ? argv.description : undefined;
|
|
168
174
|
if (!uri) {
|
|
169
|
-
await interactive();
|
|
175
|
+
await interactive(name, description);
|
|
170
176
|
return;
|
|
171
177
|
}
|
|
172
|
-
|
|
173
|
-
const name = typeof argv.name === "string" && argv.name.length > 0 ? argv.name : undefined;
|
|
174
|
-
const description = typeof argv.description === "string" ? argv.description : undefined;
|
|
175
178
|
await run(uri, name, description);
|
|
176
|
-
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
export {
|
|
182
|
+
stem,
|
|
183
|
+
github,
|
|
184
|
+
command
|
|
177
185
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/cli/commands/cd.ts
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import { Config } from "@spader/dotllm/core";
|
|
6
|
+
import { defaultTheme as t } from "@spader/dotllm/cli/theme";
|
|
7
|
+
var command = {
|
|
8
|
+
description: "Open a subshell in a repo's store directory",
|
|
9
|
+
summary: "cd into a repo",
|
|
10
|
+
positionals: {
|
|
11
|
+
name: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Name of the repo",
|
|
14
|
+
required: true
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
handler: async (argv) => {
|
|
18
|
+
const name = String(argv.name);
|
|
19
|
+
const global = Config.Global.read();
|
|
20
|
+
const repo = Config.Global.find(global, name);
|
|
21
|
+
if (!repo) {
|
|
22
|
+
console.error(t.error(`No repo named "${name}" in registry`));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const dir = path.join(Config.storeDir(), name);
|
|
27
|
+
if (!fs.existsSync(dir)) {
|
|
28
|
+
console.error(t.error(`Store path does not exist: ${dir}`));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const shell = process.env.SHELL ?? "/bin/sh";
|
|
33
|
+
const proc = Bun.spawn([shell], {
|
|
34
|
+
cwd: dir,
|
|
35
|
+
stdin: "inherit",
|
|
36
|
+
stdout: "inherit",
|
|
37
|
+
stderr: "inherit"
|
|
38
|
+
});
|
|
39
|
+
const code = await proc.exited;
|
|
40
|
+
process.exit(code);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export {
|
|
44
|
+
command
|
|
45
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/cli/commands/index.ts
|
|
3
|
+
import { command } from "@spader/dotllm/cli/commands/add";
|
|
4
|
+
import { command as command2 } from "@spader/dotllm/cli/commands/remove";
|
|
5
|
+
import { command as command3 } from "@spader/dotllm/cli/commands/list";
|
|
6
|
+
import { command as command4 } from "@spader/dotllm/cli/commands/link";
|
|
7
|
+
import { command as command5 } from "@spader/dotllm/cli/commands/sync";
|
|
8
|
+
import { command as command6 } from "@spader/dotllm/cli/commands/which";
|
|
9
|
+
import { command as command7 } from "@spader/dotllm/cli/commands/cd";
|
|
10
|
+
export {
|
|
11
|
+
command6 as which,
|
|
12
|
+
command5 as sync,
|
|
13
|
+
command2 as remove,
|
|
14
|
+
command3 as list,
|
|
15
|
+
command4 as link,
|
|
16
|
+
command7 as cd,
|
|
17
|
+
command as add
|
|
18
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/cli/commands/link.ts
|
|
3
|
+
import * as prompts from "@clack/prompts";
|
|
4
|
+
import { Config, link, unlink, sync } from "@spader/dotllm/core";
|
|
5
|
+
import { defaultTheme as t } from "@spader/dotllm/cli/theme";
|
|
6
|
+
import { Prompt } from "@spader/dotllm/cli/prompt";
|
|
7
|
+
var command = {
|
|
8
|
+
description: "Interactively pick repos to link into .llm/reference/, or add/remove one by name",
|
|
9
|
+
summary: "Link references",
|
|
10
|
+
positionals: {
|
|
11
|
+
name: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Name of the repo to link, or omit for interactive"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
options: {
|
|
17
|
+
remove: {
|
|
18
|
+
alias: "r",
|
|
19
|
+
type: "boolean",
|
|
20
|
+
description: "Remove the link instead of adding it"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
handler: async (argv) => {
|
|
24
|
+
prompts.intro("dotllm link");
|
|
25
|
+
const name = typeof argv.name === "string" && argv.name.length > 0 ? argv.name : undefined;
|
|
26
|
+
const shouldRemove = argv.remove === true;
|
|
27
|
+
if (name) {
|
|
28
|
+
if (shouldRemove) {
|
|
29
|
+
const result = unlink(name);
|
|
30
|
+
if (!result.ok) {
|
|
31
|
+
prompts.log.error(t.error(result.error));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
prompts.log.step(`unlinked ${t.primary(name)}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const global2 = Config.Global.read();
|
|
39
|
+
const repo = Config.Global.find(global2, name);
|
|
40
|
+
if (!repo) {
|
|
41
|
+
prompts.log.error(t.error(`No repo named "${name}" in registry`));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const local2 = Config.Local.read();
|
|
46
|
+
Config.Local.write(Config.Local.add(local2, repo));
|
|
47
|
+
Prompt.sync(sync());
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const global = Config.Global.read();
|
|
51
|
+
if (global.repos.length === 0) {
|
|
52
|
+
console.log(t.dim("(no repos registered)"));
|
|
53
|
+
console.log(t.dim("use `dotllm add <path>` to register one"));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const local = Config.Local.read();
|
|
57
|
+
const current = new Set(Object.keys(local.refs));
|
|
58
|
+
const selected = await prompts.multiselect({
|
|
59
|
+
message: "Select repos to link into .llm/reference/",
|
|
60
|
+
options: global.repos.map((r) => ({
|
|
61
|
+
value: r.name,
|
|
62
|
+
label: r.name,
|
|
63
|
+
hint: r.uri
|
|
64
|
+
})),
|
|
65
|
+
initialValues: global.repos.filter((r) => current.has(r.name)).map((r) => r.name),
|
|
66
|
+
required: false
|
|
67
|
+
});
|
|
68
|
+
if (prompts.isCancel(selected)) {
|
|
69
|
+
prompts.cancel("cancelled");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const names = Array.isArray(selected) ? selected.filter((value) => typeof value === "string") : [];
|
|
73
|
+
Prompt.sync(link(names));
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
export {
|
|
77
|
+
command
|
|
78
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/cli/commands/list.ts
|
|
3
|
+
import * as prompts from "@clack/prompts";
|
|
4
|
+
import { Config } from "@spader/dotllm/core";
|
|
5
|
+
import { table } from "@spader/dotllm/cli/layout";
|
|
6
|
+
import { defaultTheme as t } from "@spader/dotllm/cli/theme";
|
|
7
|
+
var command = {
|
|
8
|
+
description: "List all registered repos",
|
|
9
|
+
summary: "Show the registry",
|
|
10
|
+
handler: () => {
|
|
11
|
+
prompts.intro("dotllm list");
|
|
12
|
+
const global = Config.Global.read();
|
|
13
|
+
if (global.repos.length === 0) {
|
|
14
|
+
console.log(t.dim("(no repos registered)"));
|
|
15
|
+
console.log(t.dim("use `dotllm add <path>` to register one"));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const local = Config.Local.read();
|
|
19
|
+
const linked = new Set(Object.keys(local.refs));
|
|
20
|
+
table(["name", "kind", "uri", "description", "linked"], [
|
|
21
|
+
global.repos.map((r) => r.name),
|
|
22
|
+
global.repos.map((r) => r.kind),
|
|
23
|
+
global.repos.map((r) => r.uri),
|
|
24
|
+
global.repos.map((r) => r.description),
|
|
25
|
+
global.repos.map((r) => linked.has(r.name) ? "yes" : "no")
|
|
26
|
+
], {
|
|
27
|
+
flex: [0, 0, 1, 1, 0],
|
|
28
|
+
noTruncate: [true, true, false, false, true],
|
|
29
|
+
truncate: ["end", "end", "start", "end", "end"],
|
|
30
|
+
format: [
|
|
31
|
+
(s) => t.primary(s),
|
|
32
|
+
(s) => s,
|
|
33
|
+
(s) => t.link(s),
|
|
34
|
+
(s) => s,
|
|
35
|
+
(s) => s.trim() === "yes" ? t.success(s) : s
|
|
36
|
+
]
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
export {
|
|
41
|
+
command
|
|
42
|
+
};
|
|
@@ -1,28 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
// @bun
|
|
2
|
+
// src/cli/commands/remove.ts
|
|
3
|
+
import * as prompts from "@clack/prompts";
|
|
2
4
|
import { remove } from "@spader/dotllm/core";
|
|
3
5
|
import { defaultTheme as t } from "@spader/dotllm/cli/theme";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export const command: CommandDef = {
|
|
6
|
+
var command = {
|
|
7
7
|
description: "Remove a repo from the registry",
|
|
8
8
|
summary: "Remove a registered repo",
|
|
9
9
|
positionals: {
|
|
10
10
|
name: {
|
|
11
11
|
type: "string",
|
|
12
12
|
description: "Name of the reference to remove",
|
|
13
|
-
required: true
|
|
14
|
-
}
|
|
13
|
+
required: true
|
|
14
|
+
}
|
|
15
15
|
},
|
|
16
16
|
handler: (argv) => {
|
|
17
|
+
prompts.intro("dotllm remove");
|
|
17
18
|
const name = String(argv.name);
|
|
18
|
-
|
|
19
19
|
const result = remove(name);
|
|
20
20
|
if (!result.ok) {
|
|
21
|
-
log.error(t.error(result.error));
|
|
21
|
+
prompts.log.error(t.error(result.error));
|
|
22
22
|
process.exit(1);
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
prompts.log.step(`removed ${t.primary(name)}`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export {
|
|
29
|
+
command
|
|
28
30
|
};
|
|
@@ -1,45 +1,36 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/cli/commands/sync.ts
|
|
1
3
|
import * as prompts from "@clack/prompts";
|
|
2
4
|
import { pull, sync } from "@spader/dotllm/core";
|
|
3
5
|
import { defaultTheme as t } from "@spader/dotllm/cli/theme";
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
export const command: CommandDef = {
|
|
6
|
+
import { Prompt } from "@spader/dotllm/cli/prompt";
|
|
7
|
+
var command = {
|
|
7
8
|
description: "Re-create symlinks from .llm/dotllm.json",
|
|
8
9
|
summary: "Sync symlinks from local config",
|
|
9
10
|
handler: async () => {
|
|
11
|
+
prompts.intro("dotllm sync");
|
|
10
12
|
const result = sync();
|
|
11
|
-
|
|
12
13
|
if (result.linked.length === 0 && result.removed.length === 0 && result.missing.length === 0 && result.unchanged.length === 0) {
|
|
13
14
|
console.log(t.dim("(no refs in local config)"));
|
|
14
15
|
console.log(t.dim("use `dotllm link` to select repos"));
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
-
for (const name of result.removed) {
|
|
19
|
-
prompts.log.step(`removed stale ${t.primary(name)}`);
|
|
20
|
-
}
|
|
21
|
-
for (const name of result.missing) {
|
|
22
|
-
prompts.log.warn(`${t.primary(name)} missing cache entry`);
|
|
23
|
-
}
|
|
24
|
-
for (const name of result.linked) {
|
|
25
|
-
prompts.log.step(`linked ${t.primary(name)} -> ${t.link(`.llm/reference/${name}`)}`);
|
|
26
|
-
}
|
|
27
|
-
|
|
18
|
+
Prompt.sync(result);
|
|
28
19
|
const refs = [...new Set([...result.unchanged, ...result.linked])];
|
|
29
20
|
if (refs.length === 0) {
|
|
30
21
|
return;
|
|
31
22
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
spinner.start(`Pulling ${refs.length} linked repo${refs.length === 1 ? "" : "s"}`);
|
|
35
|
-
|
|
23
|
+
const spinner2 = prompts.spinner();
|
|
24
|
+
spinner2.start(`Pulling ${refs.length} linked repo${refs.length === 1 ? "" : "s"}`);
|
|
36
25
|
const pulled = await pull(refs);
|
|
37
26
|
if (pulled.failed.length > 0) {
|
|
38
|
-
|
|
27
|
+
spinner2.stop(t.error(`pull failed for ${pulled.failed.length} repo${pulled.failed.length === 1 ? "" : "s"}`), 1);
|
|
39
28
|
process.exit(1);
|
|
40
29
|
return;
|
|
41
30
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
31
|
+
spinner2.stop(`${t.success("pulled")} ${pulled.count} repo${pulled.count === 1 ? "" : "s"}`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
export {
|
|
35
|
+
command
|
|
45
36
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/cli/commands/which.ts
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { Config } from "@spader/dotllm/core";
|
|
5
|
+
import { defaultTheme as t } from "@spader/dotllm/cli/theme";
|
|
6
|
+
var command = {
|
|
7
|
+
description: "Print the absolute path to a repo in the store",
|
|
8
|
+
summary: "Show repo store path",
|
|
9
|
+
positionals: {
|
|
10
|
+
name: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Name of the repo",
|
|
13
|
+
required: true
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
handler: (argv) => {
|
|
17
|
+
const name = String(argv.name);
|
|
18
|
+
const global = Config.Global.read();
|
|
19
|
+
const repo = Config.Global.find(global, name);
|
|
20
|
+
if (!repo) {
|
|
21
|
+
console.error(t.error(`No repo named "${name}" in registry`));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
console.log(path.join(Config.storeDir(), name));
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export {
|
|
29
|
+
command
|
|
30
|
+
};
|
package/src/cli/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/cli/index.ts
|
|
5
|
+
import { build } from "@spader/dotllm/cli/yargs";
|
|
6
|
+
import { add, remove, list, link, sync, which, cd } from "@spader/dotllm/cli/commands/index";
|
|
7
|
+
var DotLlmCli;
|
|
8
|
+
((DotLlmCli) => {
|
|
9
|
+
DotLlmCli.def = {
|
|
10
|
+
name: "dotllm",
|
|
11
|
+
description: "Manage git repo references symlinked into .llm/reference/",
|
|
12
|
+
commands: {
|
|
13
|
+
add,
|
|
14
|
+
remove,
|
|
15
|
+
list,
|
|
16
|
+
link,
|
|
17
|
+
sync,
|
|
18
|
+
which,
|
|
19
|
+
cd
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
function run() {
|
|
23
|
+
build(DotLlmCli.def).parse();
|
|
24
|
+
}
|
|
25
|
+
DotLlmCli.run = run;
|
|
26
|
+
})(DotLlmCli ||= {});
|
|
27
|
+
DotLlmCli.run();
|
|
28
|
+
export {
|
|
29
|
+
DotLlmCli
|
|
30
|
+
};
|