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.
Files changed (56) hide show
  1. package/dist/index.d.ts +11 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +72 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/arg-parser.d.ts +16 -0
  6. package/dist/src/arg-parser.d.ts.map +1 -0
  7. package/dist/src/arg-parser.js +94 -0
  8. package/dist/src/arg-parser.js.map +1 -0
  9. package/dist/src/commands.d.ts +30 -0
  10. package/dist/src/commands.d.ts.map +1 -0
  11. package/dist/src/commands.js +315 -0
  12. package/dist/src/commands.js.map +1 -0
  13. package/dist/src/constants.d.ts +26 -0
  14. package/dist/src/constants.d.ts.map +1 -0
  15. package/dist/src/constants.js +28 -0
  16. package/dist/src/constants.js.map +1 -0
  17. package/dist/src/expander.d.ts +36 -0
  18. package/dist/src/expander.d.ts.map +1 -0
  19. package/dist/src/expander.js +187 -0
  20. package/dist/src/expander.js.map +1 -0
  21. package/dist/src/loader.d.ts +46 -0
  22. package/dist/src/loader.d.ts.map +1 -0
  23. package/dist/src/loader.js +223 -0
  24. package/dist/src/loader.js.map +1 -0
  25. package/dist/src/logger.d.ts +15 -0
  26. package/dist/src/logger.d.ts.map +1 -0
  27. package/dist/src/logger.js +107 -0
  28. package/dist/src/logger.js.map +1 -0
  29. package/dist/src/notification.d.ts +11 -0
  30. package/dist/src/notification.d.ts.map +1 -0
  31. package/dist/src/notification.js +26 -0
  32. package/dist/src/notification.js.map +1 -0
  33. package/dist/src/shell.d.ts +18 -0
  34. package/dist/src/shell.d.ts.map +1 -0
  35. package/dist/src/shell.js +30 -0
  36. package/dist/src/shell.js.map +1 -0
  37. package/dist/src/types.d.ts +65 -0
  38. package/dist/src/types.d.ts.map +1 -0
  39. package/dist/src/types.js +2 -0
  40. package/dist/src/types.js.map +1 -0
  41. package/package.json +8 -5
  42. package/index.ts +0 -81
  43. package/src/arg-parser.test.ts +0 -177
  44. package/src/arg-parser.ts +0 -87
  45. package/src/commands.test.ts +0 -188
  46. package/src/commands.ts +0 -414
  47. package/src/constants.ts +0 -32
  48. package/src/expander.test.ts +0 -846
  49. package/src/expander.ts +0 -225
  50. package/src/loader.test.ts +0 -352
  51. package/src/loader.ts +0 -268
  52. package/src/logger.test.ts +0 -136
  53. package/src/logger.ts +0 -121
  54. package/src/notification.ts +0 -30
  55. package/src/shell.ts +0 -50
  56. 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":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAEvC;;GAEG;AACH,SAAS,cAAc;IACrB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IACzC,OAAO,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,MAAM,CAAC;AAC3C,CAAC;AAED,MAAM,OAAO,MAAM;IACT,MAAM,CAAS;IAEvB,YAAY,cAAuB;QACjC,IAAI,CAAC,MAAM,GAAG,cAAc,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAC7E,CAAC;IAED,IAAI,OAAO;QACT,OAAO,cAAc,EAAE,CAAC;IAC1B,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAEO,UAAU,CAAC,IAA8B;QAC/C,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,CAAC;QAErB,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;gBAAE,SAAS;YAEpD,0BAA0B;YAC1B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;oBAAE,SAAS;gBACjC,KAAK,CAAC,IAAI,CACR,GAAG,GAAG,KAAK,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAC9F,CAAC;YACJ,CAAC;iBAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACrC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;gBAClC,IAAI,GAAG,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;oBACpB,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC;gBAC9B,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAEO,aAAa;QACnB,MAAM,yBAAyB,GAAG,KAAK,CAAC,iBAAiB,CAAC;QAC1D,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;YACxB,KAAK,CAAC,iBAAiB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC;YAC9C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAqC,CAAC;YACxD,KAAK,CAAC,iBAAiB,GAAG,yBAAyB,CAAC;YAEpD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;gBACzC,IAAI,QAAQ,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC9C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;oBAClD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;gBACtC,CAAC;YACH,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,KAAa,EAAE,SAAiB,EAAE,OAAe,EAAE,IAA8B;QAC7F,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAE1B,IAAI,CAAC;YACH,IAAI,CAAC,YAAY,EAAE,CAAC;YAEpB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAEtC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC/C,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC7B,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9C,CAAC;YAED,MAAM,OAAO,GAAG,GAAG,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,SAAS,KAAK,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;YAE9G,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACnF,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,cAAc;QAChB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC;CACF;AAED,mCAAmC;AACnC,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC"}