opencode-snippets 1.4.2 → 1.4.3
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/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/src/arg-parser.d.ts +16 -0
- package/dist/src/arg-parser.d.ts.map +1 -0
- package/dist/src/arg-parser.js +94 -0
- package/dist/src/arg-parser.js.map +1 -0
- package/dist/src/commands.d.ts +30 -0
- package/dist/src/commands.d.ts.map +1 -0
- package/dist/src/commands.js +315 -0
- package/dist/src/commands.js.map +1 -0
- package/dist/src/constants.d.ts +26 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +28 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/expander.d.ts +36 -0
- package/dist/src/expander.d.ts.map +1 -0
- package/dist/src/expander.js +187 -0
- package/dist/src/expander.js.map +1 -0
- package/dist/src/loader.d.ts +46 -0
- package/dist/src/loader.d.ts.map +1 -0
- package/dist/src/loader.js +223 -0
- package/dist/src/loader.js.map +1 -0
- package/dist/src/logger.d.ts +15 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +107 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/notification.d.ts +11 -0
- package/dist/src/notification.d.ts.map +1 -0
- package/dist/src/notification.js +26 -0
- package/dist/src/notification.js.map +1 -0
- package/dist/src/shell.d.ts +18 -0
- package/dist/src/shell.d.ts.map +1 -0
- package/dist/src/shell.js +30 -0
- package/dist/src/shell.js.map +1 -0
- package/dist/src/types.d.ts +65 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +8 -5
- package/index.ts +0 -81
- package/src/arg-parser.test.ts +0 -177
- package/src/arg-parser.ts +0 -87
- package/src/commands.test.ts +0 -188
- package/src/commands.ts +0 -414
- package/src/constants.ts +0 -32
- package/src/expander.test.ts +0 -846
- package/src/expander.ts +0 -225
- package/src/loader.test.ts +0 -352
- package/src/loader.ts +0 -268
- package/src/logger.test.ts +0 -136
- package/src/logger.ts +0 -121
- package/src/notification.ts +0 -30
- package/src/shell.ts +0 -50
- package/src/types.ts +0 -71
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ExpansionResult, ParsedSnippetContent, SnippetRegistry } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parses snippet content to extract inline text and prepend/append blocks
|
|
4
|
+
*
|
|
5
|
+
* Uses a lenient stack-based parser:
|
|
6
|
+
* - Unclosed tags → treat rest of content as block
|
|
7
|
+
* - Nesting → log error, return null (skip expansion)
|
|
8
|
+
* - Multiple blocks → collected in document order
|
|
9
|
+
*
|
|
10
|
+
* @param content - The raw snippet content to parse
|
|
11
|
+
* @returns Parsed content with inline, prepend, and append parts, or null on error
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseSnippetBlocks(content: string): ParsedSnippetContent | null;
|
|
14
|
+
/**
|
|
15
|
+
* Expands hashtags in text recursively with loop detection
|
|
16
|
+
*
|
|
17
|
+
* Returns an ExpansionResult containing the inline-expanded text plus
|
|
18
|
+
* collected prepend/append blocks from all expanded snippets.
|
|
19
|
+
*
|
|
20
|
+
* @param text - The text containing hashtags to expand
|
|
21
|
+
* @param registry - The snippet registry to look up hashtags
|
|
22
|
+
* @param expansionCounts - Map tracking how many times each snippet has been expanded
|
|
23
|
+
* @returns ExpansionResult with text and collected blocks
|
|
24
|
+
*/
|
|
25
|
+
export declare function expandHashtags(text: string, registry: SnippetRegistry, expansionCounts?: Map<string, number>): ExpansionResult;
|
|
26
|
+
/**
|
|
27
|
+
* Assembles the final message from an expansion result
|
|
28
|
+
*
|
|
29
|
+
* Joins: prepend blocks + inline text + append blocks
|
|
30
|
+
* with double newlines between non-empty sections.
|
|
31
|
+
*
|
|
32
|
+
* @param result - The expansion result to assemble
|
|
33
|
+
* @returns The final assembled message
|
|
34
|
+
*/
|
|
35
|
+
export declare function assembleMessage(result: ExpansionResult): string;
|
|
36
|
+
//# sourceMappingURL=expander.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expander.d.ts","sourceRoot":"","sources":["../../src/expander.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAYzF;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CA4E/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,eAAe,EACzB,eAAe,sBAA4B,GAC1C,eAAe,CA4EjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAmB/D"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { PATTERNS } from "./constants.js";
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
/**
|
|
4
|
+
* Maximum number of times a snippet can be expanded to prevent infinite loops
|
|
5
|
+
*/
|
|
6
|
+
const MAX_EXPANSION_COUNT = 15;
|
|
7
|
+
/**
|
|
8
|
+
* Parses snippet content to extract inline text and prepend/append blocks
|
|
9
|
+
*
|
|
10
|
+
* Uses a lenient stack-based parser:
|
|
11
|
+
* - Unclosed tags → treat rest of content as block
|
|
12
|
+
* - Nesting → log error, return null (skip expansion)
|
|
13
|
+
* - Multiple blocks → collected in document order
|
|
14
|
+
*
|
|
15
|
+
* @param content - The raw snippet content to parse
|
|
16
|
+
* @returns Parsed content with inline, prepend, and append parts, or null on error
|
|
17
|
+
*/
|
|
18
|
+
export function parseSnippetBlocks(content) {
|
|
19
|
+
const prepend = [];
|
|
20
|
+
const append = [];
|
|
21
|
+
let inline = "";
|
|
22
|
+
// Regex to find opening and closing tags
|
|
23
|
+
const tagPattern = /<(\/?)(?<tagName>prepend|append)>/gi;
|
|
24
|
+
let lastIndex = 0;
|
|
25
|
+
let currentBlock = null;
|
|
26
|
+
let match = tagPattern.exec(content);
|
|
27
|
+
while (match !== null) {
|
|
28
|
+
const isClosing = match[1] === "/";
|
|
29
|
+
const tagName = match.groups?.tagName?.toLowerCase();
|
|
30
|
+
const tagStart = match.index;
|
|
31
|
+
const tagEnd = tagStart + match[0].length;
|
|
32
|
+
if (isClosing) {
|
|
33
|
+
// Closing tag
|
|
34
|
+
if (currentBlock === null) {
|
|
35
|
+
// Closing tag without opening - ignore it, treat as inline content
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (currentBlock.type !== tagName) {
|
|
39
|
+
// Mismatched closing tag - this is a nesting error
|
|
40
|
+
logger.warn(`Mismatched closing tag: expected </${currentBlock.type}>, found </${tagName}>`);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// Extract block content
|
|
44
|
+
const blockContent = content.slice(currentBlock.contentStart, tagStart).trim();
|
|
45
|
+
if (blockContent) {
|
|
46
|
+
if (currentBlock.type === "prepend") {
|
|
47
|
+
prepend.push(blockContent);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
append.push(blockContent);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
lastIndex = tagEnd;
|
|
54
|
+
currentBlock = null;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Opening tag
|
|
58
|
+
if (currentBlock !== null) {
|
|
59
|
+
// Nested opening tag - error
|
|
60
|
+
logger.warn(`Nested tags not allowed: found <${tagName}> inside <${currentBlock.type}>`);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
// Add any inline content before this tag
|
|
64
|
+
const inlinePart = content.slice(lastIndex, tagStart);
|
|
65
|
+
inline += inlinePart;
|
|
66
|
+
currentBlock = { type: tagName, startIndex: tagStart, contentStart: tagEnd };
|
|
67
|
+
}
|
|
68
|
+
match = tagPattern.exec(content);
|
|
69
|
+
}
|
|
70
|
+
// Handle unclosed tag (lenient: treat rest as block content)
|
|
71
|
+
if (currentBlock !== null) {
|
|
72
|
+
const blockContent = content.slice(currentBlock.contentStart).trim();
|
|
73
|
+
if (blockContent) {
|
|
74
|
+
if (currentBlock.type === "prepend") {
|
|
75
|
+
prepend.push(blockContent);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
append.push(blockContent);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Add any remaining inline content
|
|
84
|
+
inline += content.slice(lastIndex);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
inline: inline.trim(),
|
|
88
|
+
prepend,
|
|
89
|
+
append,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Expands hashtags in text recursively with loop detection
|
|
94
|
+
*
|
|
95
|
+
* Returns an ExpansionResult containing the inline-expanded text plus
|
|
96
|
+
* collected prepend/append blocks from all expanded snippets.
|
|
97
|
+
*
|
|
98
|
+
* @param text - The text containing hashtags to expand
|
|
99
|
+
* @param registry - The snippet registry to look up hashtags
|
|
100
|
+
* @param expansionCounts - Map tracking how many times each snippet has been expanded
|
|
101
|
+
* @returns ExpansionResult with text and collected blocks
|
|
102
|
+
*/
|
|
103
|
+
export function expandHashtags(text, registry, expansionCounts = new Map()) {
|
|
104
|
+
const collectedPrepend = [];
|
|
105
|
+
const collectedAppend = [];
|
|
106
|
+
let expanded = text;
|
|
107
|
+
let hasChanges = true;
|
|
108
|
+
// Keep expanding until no more hashtags are found
|
|
109
|
+
while (hasChanges) {
|
|
110
|
+
const previous = expanded;
|
|
111
|
+
let loopDetected = false;
|
|
112
|
+
// Reset regex state (global flag requires this)
|
|
113
|
+
PATTERNS.HASHTAG.lastIndex = 0;
|
|
114
|
+
// We need to collect blocks during replacement, so we track them here
|
|
115
|
+
const roundPrepend = [];
|
|
116
|
+
const roundAppend = [];
|
|
117
|
+
expanded = expanded.replace(PATTERNS.HASHTAG, (match, name) => {
|
|
118
|
+
const key = name.toLowerCase();
|
|
119
|
+
const snippet = registry.get(key);
|
|
120
|
+
if (snippet === undefined) {
|
|
121
|
+
// Unknown snippet - leave as-is
|
|
122
|
+
return match;
|
|
123
|
+
}
|
|
124
|
+
// Track expansion count to prevent infinite loops
|
|
125
|
+
const count = (expansionCounts.get(key) || 0) + 1;
|
|
126
|
+
if (count > MAX_EXPANSION_COUNT) {
|
|
127
|
+
// Loop detected! Leave the hashtag as-is and stop expanding
|
|
128
|
+
logger.warn(`Loop detected: snippet '#${key}' expanded ${count} times (max: ${MAX_EXPANSION_COUNT})`);
|
|
129
|
+
loopDetected = true;
|
|
130
|
+
return match; // Leave as-is instead of error message
|
|
131
|
+
}
|
|
132
|
+
expansionCounts.set(key, count);
|
|
133
|
+
// Parse the snippet content for blocks
|
|
134
|
+
const parsed = parseSnippetBlocks(snippet.content);
|
|
135
|
+
if (parsed === null) {
|
|
136
|
+
// Parse error - leave hashtag unchanged
|
|
137
|
+
logger.warn(`Failed to parse snippet '${key}', leaving hashtag unchanged`);
|
|
138
|
+
return match;
|
|
139
|
+
}
|
|
140
|
+
// Collect prepend/append blocks
|
|
141
|
+
roundPrepend.push(...parsed.prepend);
|
|
142
|
+
roundAppend.push(...parsed.append);
|
|
143
|
+
// Recursively expand any hashtags in the inline content
|
|
144
|
+
const nestedResult = expandHashtags(parsed.inline, registry, expansionCounts);
|
|
145
|
+
// Collect blocks from nested expansion
|
|
146
|
+
roundPrepend.push(...nestedResult.prepend);
|
|
147
|
+
roundAppend.push(...nestedResult.append);
|
|
148
|
+
return nestedResult.text;
|
|
149
|
+
});
|
|
150
|
+
// Add this round's blocks to collected blocks
|
|
151
|
+
collectedPrepend.push(...roundPrepend);
|
|
152
|
+
collectedAppend.push(...roundAppend);
|
|
153
|
+
// Only continue if the text actually changed AND no loop was detected
|
|
154
|
+
hasChanges = expanded !== previous && !loopDetected;
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
text: expanded,
|
|
158
|
+
prepend: collectedPrepend,
|
|
159
|
+
append: collectedAppend,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Assembles the final message from an expansion result
|
|
164
|
+
*
|
|
165
|
+
* Joins: prepend blocks + inline text + append blocks
|
|
166
|
+
* with double newlines between non-empty sections.
|
|
167
|
+
*
|
|
168
|
+
* @param result - The expansion result to assemble
|
|
169
|
+
* @returns The final assembled message
|
|
170
|
+
*/
|
|
171
|
+
export function assembleMessage(result) {
|
|
172
|
+
const parts = [];
|
|
173
|
+
// Add prepend blocks
|
|
174
|
+
if (result.prepend.length > 0) {
|
|
175
|
+
parts.push(result.prepend.join("\n\n"));
|
|
176
|
+
}
|
|
177
|
+
// Add main text
|
|
178
|
+
if (result.text.trim()) {
|
|
179
|
+
parts.push(result.text);
|
|
180
|
+
}
|
|
181
|
+
// Add append blocks
|
|
182
|
+
if (result.append.length > 0) {
|
|
183
|
+
parts.push(result.append.join("\n\n"));
|
|
184
|
+
}
|
|
185
|
+
return parts.join("\n\n");
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=expander.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expander.js","sourceRoot":"","sources":["../../src/expander.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { SnippetInfo, SnippetRegistry } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Loads all snippets from global and project directories
|
|
4
|
+
*
|
|
5
|
+
* @param projectDir - Optional project directory path (from ctx.directory)
|
|
6
|
+
* @param globalDir - Optional global snippets directory (for testing)
|
|
7
|
+
* @returns A map of snippet keys (lowercase) to their SnippetInfo
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadSnippets(projectDir?: string, globalDir?: string): Promise<SnippetRegistry>;
|
|
10
|
+
/**
|
|
11
|
+
* Lists all unique snippets (by name) from the registry
|
|
12
|
+
*
|
|
13
|
+
* @param registry - The snippet registry
|
|
14
|
+
* @returns Array of unique snippet info objects
|
|
15
|
+
*/
|
|
16
|
+
export declare function listSnippets(registry: SnippetRegistry): SnippetInfo[];
|
|
17
|
+
/**
|
|
18
|
+
* Ensures the snippets directory exists
|
|
19
|
+
*/
|
|
20
|
+
export declare function ensureSnippetsDir(projectDir?: string): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new snippet file
|
|
23
|
+
*
|
|
24
|
+
* @param name - The snippet name (without extension)
|
|
25
|
+
* @param content - The snippet content
|
|
26
|
+
* @param options - Optional metadata (aliases, description)
|
|
27
|
+
* @param projectDir - If provided, creates in project directory; otherwise global
|
|
28
|
+
* @returns The path to the created snippet file
|
|
29
|
+
*/
|
|
30
|
+
export declare function createSnippet(name: string, content: string, options?: {
|
|
31
|
+
aliases?: string[];
|
|
32
|
+
description?: string;
|
|
33
|
+
}, projectDir?: string): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Deletes a snippet file
|
|
36
|
+
*
|
|
37
|
+
* @param name - The snippet name (without extension)
|
|
38
|
+
* @param projectDir - If provided, looks in project directory first; otherwise global
|
|
39
|
+
* @returns The path of the deleted file, or null if not found
|
|
40
|
+
*/
|
|
41
|
+
export declare function deleteSnippet(name: string, projectDir?: string): Promise<string | null>;
|
|
42
|
+
/**
|
|
43
|
+
* Reloads snippets into the registry from disk
|
|
44
|
+
*/
|
|
45
|
+
export declare function reloadSnippets(registry: SnippetRegistry, projectDir?: string): Promise<void>;
|
|
46
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/loader.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAsB,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEnF;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,CAAC,CAc1B;AAqHD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,GAAG,WAAW,EAAE,CAYrE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI5E;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,EAC1D,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAyBjB;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4B7F;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,eAAe,EACzB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
import { CONFIG, PATHS } from "./constants.js";
|
|
5
|
+
import { logger } from "./logger.js";
|
|
6
|
+
/**
|
|
7
|
+
* Loads all snippets from global and project directories
|
|
8
|
+
*
|
|
9
|
+
* @param projectDir - Optional project directory path (from ctx.directory)
|
|
10
|
+
* @param globalDir - Optional global snippets directory (for testing)
|
|
11
|
+
* @returns A map of snippet keys (lowercase) to their SnippetInfo
|
|
12
|
+
*/
|
|
13
|
+
export async function loadSnippets(projectDir, globalDir) {
|
|
14
|
+
const snippets = new Map();
|
|
15
|
+
// Load from global directory first (use provided or default)
|
|
16
|
+
const globalSnippetsDir = globalDir ?? PATHS.SNIPPETS_DIR;
|
|
17
|
+
await loadFromDirectory(globalSnippetsDir, snippets, "global");
|
|
18
|
+
// Load from project directory if provided (overrides global)
|
|
19
|
+
if (projectDir) {
|
|
20
|
+
const projectSnippetsDir = join(projectDir, ".opencode", "snippet");
|
|
21
|
+
await loadFromDirectory(projectSnippetsDir, snippets, "project");
|
|
22
|
+
}
|
|
23
|
+
return snippets;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Loads snippets from a specific directory
|
|
27
|
+
*
|
|
28
|
+
* @param dir - Directory to load snippets from
|
|
29
|
+
* @param registry - Registry to populate
|
|
30
|
+
* @param source - Source label for logging
|
|
31
|
+
*/
|
|
32
|
+
async function loadFromDirectory(dir, registry, source) {
|
|
33
|
+
try {
|
|
34
|
+
const files = await readdir(dir);
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
if (!file.endsWith(CONFIG.SNIPPET_EXTENSION))
|
|
37
|
+
continue;
|
|
38
|
+
const snippet = await loadSnippetFile(dir, file, source);
|
|
39
|
+
if (snippet) {
|
|
40
|
+
registerSnippet(registry, snippet);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
logger.debug(`Loaded snippets from ${source} directory`, {
|
|
44
|
+
path: dir,
|
|
45
|
+
fileCount: files.length,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
// Snippets directory doesn't exist or can't be read - that's fine
|
|
50
|
+
logger.debug(`${source} snippets directory not found or unreadable`, {
|
|
51
|
+
path: dir,
|
|
52
|
+
error: error instanceof Error ? error.message : String(error),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Loads and parses a single snippet file
|
|
58
|
+
*
|
|
59
|
+
* @param dir - Directory containing the snippet file
|
|
60
|
+
* @param filename - The filename to load (e.g., "my-snippet.md")
|
|
61
|
+
* @param source - Whether this is a global or project snippet
|
|
62
|
+
* @returns The parsed snippet info, or null if parsing failed
|
|
63
|
+
*/
|
|
64
|
+
async function loadSnippetFile(dir, filename, source) {
|
|
65
|
+
try {
|
|
66
|
+
const name = basename(filename, CONFIG.SNIPPET_EXTENSION);
|
|
67
|
+
const filePath = join(dir, filename);
|
|
68
|
+
const fileContent = await readFile(filePath, "utf-8");
|
|
69
|
+
const parsed = matter(fileContent);
|
|
70
|
+
const content = parsed.content.trim();
|
|
71
|
+
const frontmatter = parsed.data;
|
|
72
|
+
// Handle aliases: accept both 'aliases' (plural) and 'alias' (singular)
|
|
73
|
+
// Prefer 'aliases' if both are present
|
|
74
|
+
let aliases = [];
|
|
75
|
+
const aliasSource = frontmatter.aliases ?? frontmatter.alias;
|
|
76
|
+
if (aliasSource) {
|
|
77
|
+
if (Array.isArray(aliasSource)) {
|
|
78
|
+
aliases = aliasSource;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
aliases = [aliasSource];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
name,
|
|
86
|
+
content,
|
|
87
|
+
aliases,
|
|
88
|
+
description: frontmatter.description,
|
|
89
|
+
filePath,
|
|
90
|
+
source,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// Failed to read or parse this snippet - skip it
|
|
95
|
+
logger.warn("Failed to load snippet file", {
|
|
96
|
+
filename,
|
|
97
|
+
error: error instanceof Error ? error.message : String(error),
|
|
98
|
+
});
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Registers a snippet and its aliases in the registry
|
|
104
|
+
*
|
|
105
|
+
* @param registry - The registry to add the snippet to
|
|
106
|
+
* @param snippet - The snippet info to register
|
|
107
|
+
*/
|
|
108
|
+
function registerSnippet(registry, snippet) {
|
|
109
|
+
const key = snippet.name.toLowerCase();
|
|
110
|
+
// If snippet with same name exists, remove its old aliases first
|
|
111
|
+
const existing = registry.get(key);
|
|
112
|
+
if (existing) {
|
|
113
|
+
for (const alias of existing.aliases) {
|
|
114
|
+
registry.delete(alias.toLowerCase());
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Register the snippet under its name
|
|
118
|
+
registry.set(key, snippet);
|
|
119
|
+
// Register under all aliases (pointing to the same snippet info)
|
|
120
|
+
for (const alias of snippet.aliases) {
|
|
121
|
+
registry.set(alias.toLowerCase(), snippet);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Lists all unique snippets (by name) from the registry
|
|
126
|
+
*
|
|
127
|
+
* @param registry - The snippet registry
|
|
128
|
+
* @returns Array of unique snippet info objects
|
|
129
|
+
*/
|
|
130
|
+
export function listSnippets(registry) {
|
|
131
|
+
const seen = new Set();
|
|
132
|
+
const snippets = [];
|
|
133
|
+
for (const snippet of registry.values()) {
|
|
134
|
+
if (!seen.has(snippet.name)) {
|
|
135
|
+
seen.add(snippet.name);
|
|
136
|
+
snippets.push(snippet);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return snippets;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Ensures the snippets directory exists
|
|
143
|
+
*/
|
|
144
|
+
export async function ensureSnippetsDir(projectDir) {
|
|
145
|
+
const dir = projectDir ? join(projectDir, ".opencode", "snippet") : PATHS.SNIPPETS_DIR;
|
|
146
|
+
await mkdir(dir, { recursive: true });
|
|
147
|
+
return dir;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Creates a new snippet file
|
|
151
|
+
*
|
|
152
|
+
* @param name - The snippet name (without extension)
|
|
153
|
+
* @param content - The snippet content
|
|
154
|
+
* @param options - Optional metadata (aliases, description)
|
|
155
|
+
* @param projectDir - If provided, creates in project directory; otherwise global
|
|
156
|
+
* @returns The path to the created snippet file
|
|
157
|
+
*/
|
|
158
|
+
export async function createSnippet(name, content, options = {}, projectDir) {
|
|
159
|
+
const dir = await ensureSnippetsDir(projectDir);
|
|
160
|
+
const filePath = join(dir, `${name}${CONFIG.SNIPPET_EXTENSION}`);
|
|
161
|
+
// Build frontmatter if we have metadata
|
|
162
|
+
const frontmatter = {};
|
|
163
|
+
if (options.aliases?.length) {
|
|
164
|
+
frontmatter.aliases = options.aliases;
|
|
165
|
+
}
|
|
166
|
+
if (options.description) {
|
|
167
|
+
frontmatter.description = options.description;
|
|
168
|
+
}
|
|
169
|
+
// Create file content with frontmatter if needed
|
|
170
|
+
let fileContent;
|
|
171
|
+
if (Object.keys(frontmatter).length > 0) {
|
|
172
|
+
fileContent = matter.stringify(content, frontmatter);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
fileContent = content;
|
|
176
|
+
}
|
|
177
|
+
await writeFile(filePath, fileContent, "utf-8");
|
|
178
|
+
logger.info("Created snippet", { name, path: filePath });
|
|
179
|
+
return filePath;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Deletes a snippet file
|
|
183
|
+
*
|
|
184
|
+
* @param name - The snippet name (without extension)
|
|
185
|
+
* @param projectDir - If provided, looks in project directory first; otherwise global
|
|
186
|
+
* @returns The path of the deleted file, or null if not found
|
|
187
|
+
*/
|
|
188
|
+
export async function deleteSnippet(name, projectDir) {
|
|
189
|
+
// Try project directory first if provided
|
|
190
|
+
if (projectDir) {
|
|
191
|
+
const projectPath = join(projectDir, ".opencode", "snippet", `${name}${CONFIG.SNIPPET_EXTENSION}`);
|
|
192
|
+
try {
|
|
193
|
+
await unlink(projectPath);
|
|
194
|
+
logger.info("Deleted project snippet", { name, path: projectPath });
|
|
195
|
+
return projectPath;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Not found in project, try global
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Try global directory
|
|
202
|
+
const globalPath = join(PATHS.SNIPPETS_DIR, `${name}${CONFIG.SNIPPET_EXTENSION}`);
|
|
203
|
+
try {
|
|
204
|
+
await unlink(globalPath);
|
|
205
|
+
logger.info("Deleted global snippet", { name, path: globalPath });
|
|
206
|
+
return globalPath;
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
logger.warn("Snippet not found for deletion", { name });
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Reloads snippets into the registry from disk
|
|
215
|
+
*/
|
|
216
|
+
export async function reloadSnippets(registry, projectDir) {
|
|
217
|
+
registry.clear();
|
|
218
|
+
const fresh = await loadSnippets(projectDir);
|
|
219
|
+
for (const [key, value] of fresh) {
|
|
220
|
+
registry.set(key, value);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class Logger {
|
|
2
|
+
private logDir;
|
|
3
|
+
constructor(logDirOverride?: string);
|
|
4
|
+
get enabled(): boolean;
|
|
5
|
+
private ensureLogDir;
|
|
6
|
+
private formatData;
|
|
7
|
+
private getCallerFile;
|
|
8
|
+
private write;
|
|
9
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
10
|
+
debug(message: string, data?: Record<string, unknown>): void;
|
|
11
|
+
warn(message: string, data?: Record<string, unknown>): void;
|
|
12
|
+
error(message: string, data?: Record<string, unknown>): void;
|
|
13
|
+
}
|
|
14
|
+
export declare const logger: Logger;
|
|
15
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAYA,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAS;gBAEX,cAAc,CAAC,EAAE,MAAM;IAInC,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,UAAU;IAyBlB,OAAO,CAAC,aAAa;IAqBrB,OAAO,CAAC,KAAK;IAuBb,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAKpD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAKrD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAKpD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAItD;AAGD,eAAO,MAAM,MAAM,QAAe,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { PATHS } from "./constants.js";
|
|
4
|
+
/**
|
|
5
|
+
* Check if debug logging is enabled via environment variable
|
|
6
|
+
*/
|
|
7
|
+
function isDebugEnabled() {
|
|
8
|
+
const value = process.env.DEBUG_SNIPPETS;
|
|
9
|
+
return value === "1" || value === "true";
|
|
10
|
+
}
|
|
11
|
+
export class Logger {
|
|
12
|
+
logDir;
|
|
13
|
+
constructor(logDirOverride) {
|
|
14
|
+
this.logDir = logDirOverride ?? join(PATHS.CONFIG_DIR, "logs", "snippets");
|
|
15
|
+
}
|
|
16
|
+
get enabled() {
|
|
17
|
+
return isDebugEnabled();
|
|
18
|
+
}
|
|
19
|
+
ensureLogDir() {
|
|
20
|
+
if (!existsSync(this.logDir)) {
|
|
21
|
+
mkdirSync(this.logDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
formatData(data) {
|
|
25
|
+
if (!data)
|
|
26
|
+
return "";
|
|
27
|
+
const parts = [];
|
|
28
|
+
for (const [key, value] of Object.entries(data)) {
|
|
29
|
+
if (value === undefined || value === null)
|
|
30
|
+
continue;
|
|
31
|
+
// Format arrays compactly
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
if (value.length === 0)
|
|
34
|
+
continue;
|
|
35
|
+
parts.push(`${key}=[${value.slice(0, 3).join(",")}${value.length > 3 ? `...+${value.length - 3}` : ""}]`);
|
|
36
|
+
}
|
|
37
|
+
else if (typeof value === "object") {
|
|
38
|
+
const str = JSON.stringify(value);
|
|
39
|
+
if (str.length < 50) {
|
|
40
|
+
parts.push(`${key}=${str}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
parts.push(`${key}=${value}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return parts.join(" ");
|
|
48
|
+
}
|
|
49
|
+
getCallerFile() {
|
|
50
|
+
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
51
|
+
try {
|
|
52
|
+
const err = new Error();
|
|
53
|
+
Error.prepareStackTrace = (_, stack) => stack;
|
|
54
|
+
const stack = err.stack;
|
|
55
|
+
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
56
|
+
for (let i = 3; i < stack.length; i++) {
|
|
57
|
+
const filename = stack[i]?.getFileName();
|
|
58
|
+
if (filename && !filename.includes("logger.")) {
|
|
59
|
+
const match = filename.match(/([^/\\]+)\.[tj]s$/);
|
|
60
|
+
return match ? match[1] : "unknown";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return "unknown";
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return "unknown";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
write(level, component, message, data) {
|
|
70
|
+
if (!this.enabled)
|
|
71
|
+
return;
|
|
72
|
+
try {
|
|
73
|
+
this.ensureLogDir();
|
|
74
|
+
const timestamp = new Date().toISOString();
|
|
75
|
+
const dataStr = this.formatData(data);
|
|
76
|
+
const dailyLogDir = join(this.logDir, "daily");
|
|
77
|
+
if (!existsSync(dailyLogDir)) {
|
|
78
|
+
mkdirSync(dailyLogDir, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
const logLine = `${timestamp} ${level.padEnd(5)} ${component}: ${message}${dataStr ? ` | ${dataStr}` : ""}\n`;
|
|
81
|
+
const logFile = join(dailyLogDir, `${new Date().toISOString().split("T")[0]}.log`);
|
|
82
|
+
writeFileSync(logFile, logLine, { flag: "a" });
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Silent fail
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
info(message, data) {
|
|
89
|
+
const component = this.getCallerFile();
|
|
90
|
+
this.write("INFO", component, message, data);
|
|
91
|
+
}
|
|
92
|
+
debug(message, data) {
|
|
93
|
+
const component = this.getCallerFile();
|
|
94
|
+
this.write("DEBUG", component, message, data);
|
|
95
|
+
}
|
|
96
|
+
warn(message, data) {
|
|
97
|
+
const component = this.getCallerFile();
|
|
98
|
+
this.write("WARN", component, message, data);
|
|
99
|
+
}
|
|
100
|
+
error(message, data) {
|
|
101
|
+
const component = this.getCallerFile();
|
|
102
|
+
this.write("ERROR", component, message, data);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Export singleton logger instance
|
|
106
|
+
export const logger = new Logger();
|
|
107
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"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"}
|