@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/src/cli/layout.ts DELETED
@@ -1,200 +0,0 @@
1
- import { defaultTheme as theme } from "@spader/dotllm/cli/theme";
2
-
3
- const ANSI_RE = /\x1b\[[0-9;]*m/g;
4
-
5
- type TruncateMode = "start" | "middle" | "end";
6
-
7
- export type TableOptions = {
8
- maxWidth?: number;
9
- flex?: number[];
10
- noTruncate?: boolean[];
11
- truncate?: TruncateMode[];
12
- format?: Array<(value: string, row: number, col: number) => string>;
13
- maxRows?: number;
14
- };
15
-
16
- function clean(value: string): string {
17
- return value.replace(/[\t\n]/g, " ").replace(ANSI_RE, "");
18
- }
19
-
20
- function truncateMiddle(value: string, width: number): string {
21
- if (width <= 0) return "";
22
- if (value.length <= width) return value;
23
- if (width <= 3) return value.slice(0, width);
24
-
25
- const tail = Math.floor((width - 3) / 2);
26
- const head = width - 3 - tail;
27
- return `${value.slice(0, head)}...${value.slice(value.length - tail)}`;
28
- }
29
-
30
- function truncateStart(value: string, width: number): string {
31
- if (width <= 0) return "";
32
- if (value.length <= width) return value;
33
- if (width <= 3) return "...".slice(0, width);
34
-
35
- return `...${value.slice(value.length - (width - 3))}`;
36
- }
37
-
38
- function truncateEnd(value: string, width: number): string {
39
- if (width <= 0) return "";
40
- if (value.length <= width) return value;
41
- if (width <= 3) return "...".slice(0, width);
42
-
43
- return `${value.slice(0, width - 3)}...`;
44
- }
45
-
46
- function truncate(value: string, width: number, mode: TruncateMode): string {
47
- if (mode === "start") return truncateStart(value, width);
48
- if (mode === "end") return truncateEnd(value, width);
49
- return truncateMiddle(value, width);
50
- }
51
-
52
- function fitWidths(
53
- natural: number[],
54
- available: number,
55
- flex: number[],
56
- noTruncate: boolean[],
57
- ): number[] {
58
- const widths = [...natural];
59
- const totalNatural = natural.reduce((sum, width) => sum + width, 0);
60
-
61
- if (totalNatural <= available) {
62
- return widths;
63
- }
64
-
65
- let fixedWidth = 0;
66
- const dynamic: Array<{ index: number; weight: number }> = [];
67
-
68
- for (let index = 0; index < widths.length; index++) {
69
- if (noTruncate[index] || flex[index] === 0) {
70
- fixedWidth += widths[index] ?? 0;
71
- continue;
72
- }
73
-
74
- dynamic.push({ index, weight: flex[index] ?? 1 });
75
- }
76
-
77
- const remaining = Math.max(0, available - fixedWidth);
78
-
79
- if (dynamic.length === 0) {
80
- return widths;
81
- }
82
-
83
- const totalWeight = dynamic.reduce((sum, item) => sum + item.weight, 0);
84
- let used = 0;
85
-
86
- for (const item of dynamic) {
87
- const share = Math.floor((remaining * item.weight) / totalWeight);
88
- const width = Math.max(1, share);
89
- widths[item.index] = width;
90
- used += width;
91
- }
92
-
93
- let extra = remaining - used;
94
- let cursor = 0;
95
- while (extra > 0) {
96
- const item = dynamic[cursor % dynamic.length]!;
97
- widths[item.index] = (widths[item.index] ?? 0) + 1;
98
- extra--;
99
- cursor++;
100
- }
101
-
102
- return widths;
103
- }
104
-
105
- export function table(
106
- headers: string[],
107
- columns: string[][],
108
- options: TableOptions = {},
109
- ): void {
110
- if (headers.length === 0) return;
111
-
112
- const count = headers.length;
113
- const gap = 2;
114
- const rows = columns.reduce((max, column) => Math.max(max, column.length), 0);
115
- const visibleRows = Math.min(rows, options.maxRows ?? rows);
116
-
117
- const natural: number[] = [];
118
-
119
- for (let col = 0; col < count; col++) {
120
- const headerWidth = clean(headers[col] ?? "").length;
121
- let width = headerWidth;
122
-
123
- for (let row = 0; row < visibleRows; row++) {
124
- const value = clean(columns[col]?.[row] ?? "");
125
- width = Math.max(width, value.length);
126
- }
127
-
128
- natural[col] = width;
129
- }
130
-
131
- const maxWidth =
132
- options.maxWidth ??
133
- (process.stdout.columns == null ? 120 : process.stdout.columns);
134
- const available = Math.max(0, maxWidth - gap * (count - 1) - 1);
135
- const widths = fitWidths(
136
- natural,
137
- available,
138
- options.flex ?? [],
139
- options.noTruncate ?? [],
140
- );
141
-
142
- const header = headers
143
- .map((title, col) =>
144
- truncateMiddle(clean(title), widths[col] ?? 0).padEnd(widths[col] ?? 0),
145
- )
146
- .join(" ");
147
-
148
- process.stdout.write(`${theme.dim(header)}\n`);
149
-
150
- for (let row = 0; row < visibleRows; row++) {
151
- const cells: string[] = [];
152
-
153
- for (let col = 0; col < count; col++) {
154
- const width = widths[col] ?? 0;
155
- const source = clean(columns[col]?.[row] ?? "");
156
- const mode = options.truncate?.[col] ?? "middle";
157
- const value = options.noTruncate?.[col]
158
- ? source
159
- : truncate(source, width, mode);
160
- const padded = width > 0 ? value.padEnd(width) : value;
161
- const formatted = options.format?.[col]?.(padded, row, col) ?? padded;
162
-
163
- cells.push(formatted);
164
- }
165
-
166
- process.stdout.write(`${cells.join(" ")}\n`);
167
- }
168
-
169
- if (rows > visibleRows) {
170
- process.stdout.write(`${theme.dim("(...truncated)")}\n`);
171
- }
172
- }
173
-
174
- export function cols(
175
- rows: string[][],
176
- colorFns?: Array<(value: string) => string>,
177
- ): void {
178
- if (rows.length === 0) return;
179
-
180
- const widths = rows[0]!.map((_, col) => {
181
- let width = 0;
182
-
183
- for (const row of rows) {
184
- width = Math.max(width, clean(row[col] ?? "").length);
185
- }
186
-
187
- return width;
188
- });
189
-
190
- for (const row of rows) {
191
- const line = row
192
- .map((value, col) => {
193
- const padded = clean(value).padEnd(widths[col] ?? 0);
194
- return colorFns?.[col]?.(padded) ?? padded;
195
- })
196
- .join(" ");
197
-
198
- process.stdout.write(`${line}\n`);
199
- }
200
- }
package/src/cli/theme.ts DELETED
@@ -1,35 +0,0 @@
1
- type Formatter = (value: string) => string;
2
-
3
- export type Theme = {
4
- primary: Formatter;
5
- link: Formatter;
6
- header: Formatter;
7
- command: Formatter;
8
- arg: Formatter;
9
- option: Formatter;
10
- type: Formatter;
11
- description: Formatter;
12
- dim: Formatter;
13
- error: Formatter;
14
- success: Formatter;
15
- };
16
-
17
- function rgb(r: number, g: number, b: number): Formatter {
18
- return (value: string) => `\x1b[38;2;${r};${g};${b}m${value}\x1b[39m`;
19
- }
20
-
21
- const gray = (value: number) => rgb(value, value, value);
22
-
23
- export const defaultTheme: Theme = {
24
- primary: rgb(114, 161, 136),
25
- link: rgb(114, 140, 212),
26
- header: gray(128),
27
- command: rgb(114, 161, 136),
28
- arg: rgb(161, 212, 212),
29
- option: rgb(212, 212, 161),
30
- type: gray(128),
31
- description: (value) => value,
32
- dim: gray(128),
33
- error: rgb(212, 114, 114),
34
- success: rgb(114, 212, 136),
35
- };
@@ -1,126 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { z } from "zod";
4
-
5
- const GLOBAL_DIR = path.join(
6
- process.env.HOME ?? "",
7
- ".local",
8
- "share",
9
- "dotllm",
10
- );
11
- const STORE_DIR = path.join(GLOBAL_DIR, "store");
12
- const GLOBAL_FILE = path.join(GLOBAL_DIR, "dotllm.json");
13
- const LOCAL_DIR = ".llm";
14
- const LOCAL_FILE = path.join(LOCAL_DIR, "dotllm.json");
15
- const REF_DIR = path.join(LOCAL_DIR, "reference");
16
-
17
- const RepoEntry = z.object({
18
- kind: z.enum(["url", "file"]),
19
- name: z.string(),
20
- uri: z.string(),
21
- description: z.string(),
22
- });
23
-
24
- export type RepoEntry = z.infer<typeof RepoEntry>;
25
-
26
- const GlobalShape = z.object({
27
- repos: z.array(RepoEntry),
28
- });
29
-
30
- const LocalShape = z.object({
31
- refs: z.record(z.string(), RepoEntry),
32
- });
33
-
34
- const LocalLegacyShape = z.object({
35
- refs: z.array(z.string()),
36
- });
37
-
38
- export namespace Config {
39
- export function storeDir(): string {
40
- return STORE_DIR;
41
- }
42
-
43
- export function refDir(): string {
44
- return REF_DIR;
45
- }
46
-
47
- export namespace Global {
48
- export type Shape = z.infer<typeof GlobalShape>;
49
-
50
- export function read(): Shape {
51
- const raw = readJson(GLOBAL_FILE);
52
- if (!raw) return { repos: [] };
53
- const result = GlobalShape.safeParse(raw);
54
- if (!result.success) return { repos: [] };
55
- return result.data;
56
- }
57
-
58
- export function write(config: Shape): void {
59
- fs.mkdirSync(GLOBAL_DIR, { recursive: true });
60
- fs.writeFileSync(GLOBAL_FILE, JSON.stringify(config, null, 2) + "\n");
61
- }
62
-
63
- export function find(config: Shape, name: string): RepoEntry | undefined {
64
- return config.repos.find((r) => r.name === name);
65
- }
66
-
67
- export function add(config: Shape, entry: RepoEntry): Shape {
68
- const filtered = config.repos.filter((r) => r.name !== entry.name);
69
- return { repos: [...filtered, entry] };
70
- }
71
-
72
- export function remove(config: Shape, name: string): Shape {
73
- return { repos: config.repos.filter((r) => r.name !== name) };
74
- }
75
- }
76
-
77
- export namespace Local {
78
- export type Shape = z.infer<typeof LocalShape>;
79
-
80
- export function read(): Shape {
81
- const raw = readJson(LOCAL_FILE);
82
- if (!raw) return { refs: {} };
83
- const result = LocalShape.safeParse(raw);
84
- if (result.success) return result.data;
85
- const legacy = LocalLegacyShape.safeParse(raw);
86
- if (!legacy.success) return { refs: {} };
87
-
88
- const global = Global.read();
89
- const rows = legacy.data.refs
90
- .map((name) => {
91
- const repo = Global.find(global, name);
92
- if (!repo) return null;
93
- return [name, repo] as const;
94
- })
95
- .filter((row): row is readonly [string, RepoEntry] => row !== null);
96
-
97
- return { refs: Object.fromEntries(rows) };
98
- }
99
-
100
- export function write(config: Shape): void {
101
- fs.mkdirSync(LOCAL_DIR, { recursive: true });
102
- fs.writeFileSync(LOCAL_FILE, JSON.stringify(config, null, 2) + "\n");
103
- }
104
-
105
- export function has(config: Shape, name: string): boolean {
106
- return Object.prototype.hasOwnProperty.call(config.refs, name);
107
- }
108
-
109
- export function add(config: Shape, repo: RepoEntry): Shape {
110
- return { refs: { ...config.refs, [repo.name]: repo } };
111
- }
112
-
113
- export function remove(config: Shape, name: string): Shape {
114
- const refs = Object.fromEntries(
115
- Object.entries(config.refs).filter(([key]) => key !== name),
116
- );
117
- return { refs };
118
- }
119
- }
120
- }
121
-
122
- function readJson(filepath: string): unknown {
123
- if (!fs.existsSync(filepath)) return null;
124
- const raw = fs.readFileSync(filepath, "utf-8");
125
- return JSON.parse(raw);
126
- }
package/src/core/index.ts DELETED
@@ -1,6 +0,0 @@
1
- export { Config, type RepoEntry } from "@spader/dotllm/core/config";
2
- export { add, type AddResult } from "@spader/dotllm/core/add";
3
- export { remove, type RemoveResult } from "@spader/dotllm/core/remove";
4
- export { link } from "@spader/dotllm/core/link";
5
- export { unlink, type UnlinkResult } from "@spader/dotllm/core/unlink";
6
- export { pull, sync, type PullError, type PullResult, type SyncResult } from "@spader/dotllm/core/sync";
package/src/core/link.ts DELETED
@@ -1,16 +0,0 @@
1
- import { Config } from "@spader/dotllm/core/config";
2
- import { sync, type SyncResult } from "@spader/dotllm/core/sync";
3
-
4
- export function link(names: string[]): SyncResult {
5
- const global = Config.Global.read();
6
- const rows = names
7
- .map((name) => {
8
- const repo = Config.Global.find(global, name);
9
- if (!repo) return null;
10
- return [name, repo] as const;
11
- })
12
- .filter((row): row is readonly [string, (typeof global.repos)[number]] => row !== null);
13
-
14
- Config.Local.write({ refs: Object.fromEntries(rows) });
15
- return sync();
16
- }