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