@spader/dotllm 1.2.6 → 1.3.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spader/dotllm",
3
- "version": "1.2.6",
3
+ "version": "1.3.1",
4
4
  "description": "A simple CLI to clone, manage, and link repositories for LLM reference",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/cli/commands/add.ts
3
5
  import fs from "fs";
4
6
  import path from "path";
@@ -0,0 +1,216 @@
1
+ // @bun
2
+ var __require = import.meta.require;
3
+
4
+ // src/cli/commands/completions.ts
5
+ import fs from "fs";
6
+ import os from "os";
7
+ import path from "path";
8
+ import { Config } from "@spader/dotllm/core";
9
+ import { defaultTheme as t } from "@spader/dotllm/cli/theme";
10
+ var BASH = `_dotllm() {
11
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
12
+
13
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
14
+ COMPREPLY=( $(compgen -W "add remove list link sync which completions" -- "\${cur}") )
15
+ return
16
+ fi
17
+
18
+ case "\${COMP_WORDS[1]}" in
19
+ which|link)
20
+ local repos
21
+ repos=$(dotllm completions --names 2>/dev/null)
22
+ COMPREPLY=( $(compgen -W "\${repos}" -- "\${cur}") )
23
+ ;;
24
+ esac
25
+ }
26
+
27
+ complete -F _dotllm dotllm`;
28
+ var ZSH = `#compdef dotllm
29
+
30
+ _dotllm() {
31
+ local -a commands
32
+ commands=(
33
+ 'add:Register a new repo'
34
+ 'remove:Remove a repo from the registry'
35
+ 'list:Show the registry'
36
+ 'link:Link references'
37
+ 'sync:Sync linked repos'
38
+ 'which:Show repo store path'
39
+ 'completions:Output shell completions'
40
+ )
41
+
42
+ _arguments -C '1:command:->cmd' '*::arg:->args'
43
+
44
+ case "$state" in
45
+ cmd)
46
+ _describe 'command' commands
47
+ ;;
48
+ args)
49
+ case "\${words[1]}" in
50
+ which|link)
51
+ local -a repos
52
+ repos=(\${(f)"$(dotllm completions --names 2>/dev/null)"})
53
+ _describe 'repo' repos
54
+ ;;
55
+ esac
56
+ ;;
57
+ esac
58
+ }
59
+
60
+ _dotllm`;
61
+ var FISH = `complete -c dotllm -f
62
+ complete -c dotllm -n "__fish_use_subcommand" -a add -d "Register a new repo"
63
+ complete -c dotllm -n "__fish_use_subcommand" -a remove -d "Remove a repo from the registry"
64
+ complete -c dotllm -n "__fish_use_subcommand" -a list -d "Show the registry"
65
+ complete -c dotllm -n "__fish_use_subcommand" -a link -d "Link references"
66
+ complete -c dotllm -n "__fish_use_subcommand" -a sync -d "Sync linked repos"
67
+ complete -c dotllm -n "__fish_use_subcommand" -a which -d "Show repo store path"
68
+ complete -c dotllm -n "__fish_use_subcommand" -a completions -d "Output shell completions"
69
+ complete -c dotllm -n "__fish_seen_subcommand_from which link" -a "(dotllm completions --names 2>/dev/null)"`;
70
+ var CANARY = "@dotllm_completions";
71
+ function completionFile(shell) {
72
+ return path.join(path.dirname(Config.storeDir()), `completions.${shell}`);
73
+ }
74
+ function hookSnippet(shell) {
75
+ const file = completionFile(shell);
76
+ switch (shell) {
77
+ case "bash":
78
+ return `
79
+ # ${CANARY}
80
+ # Installed by the dotllm CLI
81
+ [ -f "${file}" ] && source "${file}"
82
+ `;
83
+ case "zsh":
84
+ return `
85
+ # ${CANARY}
86
+ # Installed by the dotllm CLI
87
+ [ -f "${file}" ] && source "${file}"
88
+ `;
89
+ case "fish":
90
+ return `
91
+ # ${CANARY}
92
+ # Installed by the dotllm CLI
93
+ test -f "${file}"; and source "${file}"
94
+ `;
95
+ default:
96
+ return "";
97
+ }
98
+ }
99
+ function rcPath(shell) {
100
+ const home = os.homedir();
101
+ switch (shell) {
102
+ case "bash": {
103
+ const bashrc = path.join(home, ".bashrc");
104
+ if (fs.existsSync(bashrc))
105
+ return bashrc;
106
+ return path.join(home, ".bash_profile");
107
+ }
108
+ case "zsh":
109
+ return path.join(home, ".zshrc");
110
+ case "fish":
111
+ return path.join(home, ".config", "fish", "config.fish");
112
+ }
113
+ }
114
+ var install = {
115
+ description: "Add completions to your shell rc file",
116
+ summary: "Install completions",
117
+ handler: async (argv) => {
118
+ if (process.platform === "win32") {
119
+ console.log(t.dim("Shell completions are not supported on Windows."));
120
+ return;
121
+ }
122
+ const shell = detectShell();
123
+ const rc = rcPath(shell);
124
+ if (!rc) {
125
+ console.error(t.error(`Could not determine rc file for shell: "${shell}"`));
126
+ process.exit(1);
127
+ return;
128
+ }
129
+ const script = shellScript(shell);
130
+ if (!script) {
131
+ console.error(t.error(`Unknown shell: "${shell}"`));
132
+ process.exit(1);
133
+ return;
134
+ }
135
+ const cached = completionFile(shell);
136
+ fs.mkdirSync(path.dirname(cached), { recursive: true });
137
+ fs.writeFileSync(cached, script + `
138
+ `);
139
+ const existing = fs.existsSync(rc) ? fs.readFileSync(rc, "utf-8") : "";
140
+ if (existing.includes(CANARY)) {
141
+ console.log(t.dim(`Updated ${cached}`));
142
+ console.log(t.dim(`Already sourced from ${rc}`));
143
+ return;
144
+ }
145
+ fs.appendFileSync(rc, hookSnippet(shell));
146
+ console.log(`Installed completions in ${t.link(rc)}`);
147
+ console.log(t.dim(`Restart your shell or run: source ${rc}`));
148
+ }
149
+ };
150
+ var command = {
151
+ description: "Output shell completion script for bash, zsh, or fish",
152
+ summary: "Shell completions",
153
+ options: {
154
+ shell: {
155
+ alias: "s",
156
+ type: "string",
157
+ description: "Shell type: bash, zsh, or fish"
158
+ },
159
+ names: {
160
+ type: "boolean",
161
+ description: "Print repo names (used internally by completions)"
162
+ }
163
+ },
164
+ commands: {
165
+ install
166
+ },
167
+ handler: async (argv) => {
168
+ if (process.platform === "win32") {
169
+ console.log(t.dim("Shell completions are not supported on Windows."));
170
+ return;
171
+ }
172
+ if (argv.names) {
173
+ const { Config: Config2 } = await import("@spader/dotllm/core");
174
+ const global = Config2.Global.read();
175
+ for (const r of global.repos)
176
+ console.log(r.name);
177
+ return;
178
+ }
179
+ const shell = typeof argv.shell === "string" && argv.shell.length > 0 ? argv.shell : detectShell();
180
+ switch (shell) {
181
+ case "bash":
182
+ console.log(BASH);
183
+ break;
184
+ case "zsh":
185
+ console.log(ZSH);
186
+ break;
187
+ case "fish":
188
+ console.log(FISH);
189
+ break;
190
+ default:
191
+ console.error(t.error(`Unknown shell: "${shell}". Use bash, zsh, or fish.`));
192
+ process.exit(1);
193
+ }
194
+ }
195
+ };
196
+ function shellScript(shell) {
197
+ switch (shell) {
198
+ case "bash":
199
+ return BASH;
200
+ case "zsh":
201
+ return ZSH;
202
+ case "fish":
203
+ return FISH;
204
+ }
205
+ }
206
+ function detectShell() {
207
+ const login = process.env.SHELL ?? "";
208
+ if (login.endsWith("/fish"))
209
+ return "fish";
210
+ if (login.endsWith("/zsh"))
211
+ return "zsh";
212
+ return "bash";
213
+ }
214
+ export {
215
+ command
216
+ };
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/cli/commands/index.ts
3
5
  import { command } from "@spader/dotllm/cli/commands/add";
4
6
  import { command as command2 } from "@spader/dotllm/cli/commands/remove";
@@ -6,13 +8,13 @@ import { command as command3 } from "@spader/dotllm/cli/commands/list";
6
8
  import { command as command4 } from "@spader/dotllm/cli/commands/link";
7
9
  import { command as command5 } from "@spader/dotllm/cli/commands/sync";
8
10
  import { command as command6 } from "@spader/dotllm/cli/commands/which";
9
- import { command as command7 } from "@spader/dotllm/cli/commands/cd";
11
+ import { command as command7 } from "@spader/dotllm/cli/commands/completions";
10
12
  export {
11
13
  command6 as which,
12
14
  command5 as sync,
13
15
  command2 as remove,
14
16
  command3 as list,
15
17
  command4 as link,
16
- command7 as cd,
18
+ command7 as completions,
17
19
  command as add
18
20
  };
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/cli/commands/link.ts
3
5
  import * as prompts from "@clack/prompts";
4
6
  import { Config, link, unlink, sync } from "@spader/dotllm/core";
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/cli/commands/list.ts
3
5
  import * as prompts from "@clack/prompts";
4
6
  import { Config } from "@spader/dotllm/core";
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/cli/commands/remove.ts
3
5
  import * as prompts from "@clack/prompts";
4
6
  import { remove } from "@spader/dotllm/core";
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/cli/commands/sync.ts
3
5
  import * as prompts from "@clack/prompts";
4
6
  import { pull, sync } from "@spader/dotllm/core";
@@ -1,28 +1,42 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/cli/commands/which.ts
3
5
  import path from "path";
4
6
  import { Config } from "@spader/dotllm/core";
5
7
  import { defaultTheme as t } from "@spader/dotllm/cli/theme";
6
8
  var command = {
7
- description: "Print the absolute path to a repo in the store",
9
+ description: "Print the absolute path to a repo in the store (prefix match, shortest wins)",
8
10
  summary: "Show repo store path",
9
11
  positionals: {
10
12
  name: {
11
13
  type: "string",
12
- description: "Name of the repo",
14
+ description: "Name (or prefix) of the repo",
13
15
  required: true
14
16
  }
15
17
  },
16
18
  handler: (argv) => {
17
19
  const name = String(argv.name);
18
20
  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`));
21
+ const exact = Config.Global.find(global, name);
22
+ if (exact) {
23
+ console.log(path.join(Config.storeDir(), exact.name));
24
+ return;
25
+ }
26
+ const lower = name.toLowerCase();
27
+ const matches = global.repos.filter((r) => r.name.toLowerCase().startsWith(lower)).sort((a, b) => a.name.length - b.name.length);
28
+ if (matches.length === 0) {
29
+ console.error(t.error(`No repo matching "${name}" in registry`));
30
+ process.exit(1);
31
+ return;
32
+ }
33
+ if (matches.length > 1 && matches[0].name.length === matches[1].name.length) {
34
+ const names = matches.filter((m) => m.name.length === matches[0].name.length).map((m) => m.name);
35
+ console.error(t.error(`Ambiguous prefix "${name}": ${names.join(", ")}`));
22
36
  process.exit(1);
23
37
  return;
24
38
  }
25
- console.log(path.join(Config.storeDir(), name));
39
+ console.log(path.join(Config.storeDir(), matches[0].name));
26
40
  }
27
41
  };
28
42
  export {
package/src/cli/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
+ var __require = import.meta.require;
3
4
 
4
5
  // src/cli/index.ts
5
6
  import { build } from "@spader/dotllm/cli/yargs";
6
- import { add, remove, list, link, sync, which, cd } from "@spader/dotllm/cli/commands/index";
7
+ import { add, remove, list, link, sync, which, completions } from "@spader/dotllm/cli/commands/index";
7
8
  var DotLlmCli;
8
9
  ((DotLlmCli) => {
9
10
  async function run() {
@@ -20,7 +21,7 @@ var DotLlmCli;
20
21
  link,
21
22
  sync,
22
23
  which,
23
- cd
24
+ completions
24
25
  }
25
26
  };
26
27
  build(def).parse();
package/src/cli/layout.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/cli/layout.ts
3
5
  import { defaultTheme as theme } from "@spader/dotllm/cli/theme";
4
6
  var ANSI_RE = /\x1b\[[0-9;]*m/g;
package/src/cli/prompt.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/cli/prompt.ts
3
5
  import * as prompts from "@clack/prompts";
4
6
  var Prompt;
package/src/cli/theme.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/cli/theme.ts
3
5
  function rgb(r, g, b) {
4
6
  return (value) => `\x1B[38;2;${r};${g};${b}m${value}\x1B[39m`;
package/src/cli/yargs.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/cli/yargs.ts
3
5
  import yargs from "yargs";
4
6
  import { hideBin } from "yargs/helpers";
@@ -145,7 +147,10 @@ function configure(y, def, root, path) {
145
147
  for (const [k, v] of Object.entries(def.commands)) {
146
148
  command(y, k, v, root, path);
147
149
  }
148
- y.demandCommand(1, "You must specify a command");
150
+ const hasHandler = "handler" in def && typeof def.handler === "function";
151
+ if (!hasHandler) {
152
+ y.demandCommand(1, "You must specify a command");
153
+ }
149
154
  }
150
155
  y.help(false).option("help", { alias: "h", type: "boolean", describe: "Show help" }).check(check(def, root, path)).fail(fail(def, root, path));
151
156
  if (path.length === 0 && "version" in def && def.version) {
package/src/core/add.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/core/add.ts
3
5
  import fs from "fs";
4
6
  import path from "path";
@@ -1,13 +1,20 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/core/config.ts
3
5
  import fs from "fs";
6
+ import os from "os";
4
7
  import path from "path";
5
8
  import { z } from "zod";
6
9
  var LOCAL_DIR = ".llm";
7
10
  var LOCAL_FILE = path.join(LOCAL_DIR, "dotllm.json");
8
11
  var REF_DIR = path.join(LOCAL_DIR, "reference");
9
12
  function home() {
10
- return path.join(process.env.HOME ?? "", ".local", "share", "dotllm");
13
+ if (process.platform === "win32") {
14
+ const appData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local");
15
+ return path.join(appData, "dotllm");
16
+ }
17
+ return path.join(process.env.HOME ?? os.homedir(), ".local", "share", "dotllm");
11
18
  }
12
19
  var RepoEntry = z.object({
13
20
  kind: z.enum(["url", "file"]),
@@ -51,16 +58,19 @@ var Config;
51
58
  }
52
59
  Global.write = write;
53
60
  function find(config, name) {
54
- return config.repos.find((r) => r.name === name);
61
+ const lower = name.toLowerCase();
62
+ return config.repos.find((r) => r.name.toLowerCase() === lower);
55
63
  }
56
64
  Global.find = find;
57
65
  function add(config, entry) {
58
- const filtered = config.repos.filter((r) => r.name !== entry.name);
66
+ const lower = entry.name.toLowerCase();
67
+ const filtered = config.repos.filter((r) => r.name.toLowerCase() !== lower);
59
68
  return { repos: [...filtered, entry] };
60
69
  }
61
70
  Global.add = add;
62
71
  function remove(config, name) {
63
- return { repos: config.repos.filter((r) => r.name !== name) };
72
+ const lower = name.toLowerCase();
73
+ return { repos: config.repos.filter((r) => r.name.toLowerCase() !== lower) };
64
74
  }
65
75
  Global.remove = remove;
66
76
  })(Global = Config.Global ||= {});
@@ -82,16 +92,29 @@ var Config;
82
92
  `);
83
93
  }
84
94
  Local.write = write;
95
+ function find(config, name) {
96
+ const lower = name.toLowerCase();
97
+ for (const [key, value] of Object.entries(config.refs)) {
98
+ if (key.toLowerCase() === lower)
99
+ return value;
100
+ }
101
+ return;
102
+ }
103
+ Local.find = find;
85
104
  function has(config, name) {
86
- return Object.prototype.hasOwnProperty.call(config.refs, name);
105
+ return find(config, name) !== undefined;
87
106
  }
88
107
  Local.has = has;
89
108
  function add(config, repo) {
90
- return { refs: { ...config.refs, [repo.name]: repo } };
109
+ const lower = repo.name.toLowerCase();
110
+ const refs = Object.fromEntries(Object.entries(config.refs).filter(([key]) => key.toLowerCase() !== lower));
111
+ refs[repo.name] = repo;
112
+ return { refs };
91
113
  }
92
114
  Local.add = add;
93
115
  function remove(config, name) {
94
- const refs = Object.fromEntries(Object.entries(config.refs).filter(([key]) => key !== name));
116
+ const lower = name.toLowerCase();
117
+ const refs = Object.fromEntries(Object.entries(config.refs).filter(([key]) => key.toLowerCase() !== lower));
95
118
  return { refs };
96
119
  }
97
120
  Local.remove = remove;
package/src/core/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/core/index.ts
3
5
  import { Config } from "@spader/dotllm/core/config";
4
6
  import { add } from "@spader/dotllm/core/add";
package/src/core/link.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/core/link.ts
3
5
  import { Config } from "@spader/dotllm/core/config";
4
6
  import { sync } from "@spader/dotllm/core/sync";
@@ -8,7 +10,7 @@ function link(names) {
8
10
  const repo = Config.Global.find(global, name);
9
11
  if (!repo)
10
12
  return null;
11
- return [name, repo];
13
+ return [repo.name, repo];
12
14
  }).filter((row) => row !== null);
13
15
  Config.Local.write({ refs: Object.fromEntries(rows) });
14
16
  return sync();
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/core/remove.ts
3
5
  import fs from "fs";
4
6
  import path from "path";
@@ -9,7 +11,7 @@ function remove(name) {
9
11
  if (!found) {
10
12
  return { ok: false, error: `No repo named "${name}" in registry` };
11
13
  }
12
- const target = path.join(Config.storeDir(), name);
14
+ const target = path.join(Config.storeDir(), found.name);
13
15
  if (fs.existsSync(target)) {
14
16
  const stat = fs.lstatSync(target);
15
17
  if (stat.isSymbolicLink()) {
@@ -19,7 +21,7 @@ function remove(name) {
19
21
  fs.rmSync(target, { recursive: true, force: true });
20
22
  }
21
23
  }
22
- Config.Global.write(Config.Global.remove(global, name));
24
+ Config.Global.write(Config.Global.remove(global, found.name));
23
25
  return { ok: true };
24
26
  }
25
27
  export {
package/src/core/sync.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/core/sync.ts
3
5
  import fs from "fs";
4
6
  import path from "path";
@@ -1,18 +1,21 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // src/core/unlink.ts
3
5
  import fs from "fs";
4
6
  import path from "path";
5
7
  import { Config } from "@spader/dotllm/core/config";
6
8
  function unlink(name) {
7
9
  const local = Config.Local.read();
8
- if (!Config.Local.has(local, name)) {
10
+ const found = Config.Local.find(local, name);
11
+ if (!found) {
9
12
  return { ok: false, error: `"${name}" is not linked in local config` };
10
13
  }
11
- const target = path.join(Config.refDir(), name);
14
+ const target = path.join(Config.refDir(), found.name);
12
15
  if (fs.existsSync(target)) {
13
16
  fs.unlinkSync(target);
14
17
  }
15
- Config.Local.write(Config.Local.remove(local, name));
18
+ Config.Local.write(Config.Local.remove(local, found.name));
16
19
  return { ok: true };
17
20
  }
18
21
  export {
@@ -1,45 +0,0 @@
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
- };