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
package/src/loader.ts DELETED
@@ -1,268 +0,0 @@
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
- import type { SnippetFrontmatter, SnippetInfo, SnippetRegistry } from "./types.js";
7
-
8
- /**
9
- * Loads all snippets from global and project directories
10
- *
11
- * @param projectDir - Optional project directory path (from ctx.directory)
12
- * @param globalDir - Optional global snippets directory (for testing)
13
- * @returns A map of snippet keys (lowercase) to their SnippetInfo
14
- */
15
- export async function loadSnippets(
16
- projectDir?: string,
17
- globalDir?: string,
18
- ): Promise<SnippetRegistry> {
19
- const snippets: SnippetRegistry = new Map();
20
-
21
- // Load from global directory first (use provided or default)
22
- const globalSnippetsDir = globalDir ?? PATHS.SNIPPETS_DIR;
23
- await loadFromDirectory(globalSnippetsDir, snippets, "global");
24
-
25
- // Load from project directory if provided (overrides global)
26
- if (projectDir) {
27
- const projectSnippetsDir = join(projectDir, ".opencode", "snippet");
28
- await loadFromDirectory(projectSnippetsDir, snippets, "project");
29
- }
30
-
31
- return snippets;
32
- }
33
-
34
- /**
35
- * Loads snippets from a specific directory
36
- *
37
- * @param dir - Directory to load snippets from
38
- * @param registry - Registry to populate
39
- * @param source - Source label for logging
40
- */
41
- async function loadFromDirectory(
42
- dir: string,
43
- registry: SnippetRegistry,
44
- source: "global" | "project",
45
- ): Promise<void> {
46
- try {
47
- const files = await readdir(dir);
48
-
49
- for (const file of files) {
50
- if (!file.endsWith(CONFIG.SNIPPET_EXTENSION)) continue;
51
-
52
- const snippet = await loadSnippetFile(dir, file, source);
53
- if (snippet) {
54
- registerSnippet(registry, snippet);
55
- }
56
- }
57
-
58
- logger.debug(`Loaded snippets from ${source} directory`, {
59
- path: dir,
60
- fileCount: files.length,
61
- });
62
- } catch (error) {
63
- // Snippets directory doesn't exist or can't be read - that's fine
64
- logger.debug(`${source} snippets directory not found or unreadable`, {
65
- path: dir,
66
- error: error instanceof Error ? error.message : String(error),
67
- });
68
- }
69
- }
70
-
71
- /**
72
- * Loads and parses a single snippet file
73
- *
74
- * @param dir - Directory containing the snippet file
75
- * @param filename - The filename to load (e.g., "my-snippet.md")
76
- * @param source - Whether this is a global or project snippet
77
- * @returns The parsed snippet info, or null if parsing failed
78
- */
79
- async function loadSnippetFile(
80
- dir: string,
81
- filename: string,
82
- source: "global" | "project",
83
- ): Promise<SnippetInfo | null> {
84
- try {
85
- const name = basename(filename, CONFIG.SNIPPET_EXTENSION);
86
- const filePath = join(dir, filename);
87
- const fileContent = await readFile(filePath, "utf-8");
88
- const parsed = matter(fileContent);
89
-
90
- const content = parsed.content.trim();
91
- const frontmatter = parsed.data as SnippetFrontmatter;
92
-
93
- // Handle aliases: accept both 'aliases' (plural) and 'alias' (singular)
94
- // Prefer 'aliases' if both are present
95
- let aliases: string[] = [];
96
- const aliasSource = frontmatter.aliases ?? frontmatter.alias;
97
- if (aliasSource) {
98
- if (Array.isArray(aliasSource)) {
99
- aliases = aliasSource;
100
- } else {
101
- aliases = [aliasSource];
102
- }
103
- }
104
-
105
- return {
106
- name,
107
- content,
108
- aliases,
109
- description: frontmatter.description,
110
- filePath,
111
- source,
112
- };
113
- } catch (error) {
114
- // Failed to read or parse this snippet - skip it
115
- logger.warn("Failed to load snippet file", {
116
- filename,
117
- error: error instanceof Error ? error.message : String(error),
118
- });
119
- return null;
120
- }
121
- }
122
-
123
- /**
124
- * Registers a snippet and its aliases in the registry
125
- *
126
- * @param registry - The registry to add the snippet to
127
- * @param snippet - The snippet info to register
128
- */
129
- function registerSnippet(registry: SnippetRegistry, snippet: SnippetInfo): void {
130
- const key = snippet.name.toLowerCase();
131
-
132
- // If snippet with same name exists, remove its old aliases first
133
- const existing = registry.get(key);
134
- if (existing) {
135
- for (const alias of existing.aliases) {
136
- registry.delete(alias.toLowerCase());
137
- }
138
- }
139
-
140
- // Register the snippet under its name
141
- registry.set(key, snippet);
142
-
143
- // Register under all aliases (pointing to the same snippet info)
144
- for (const alias of snippet.aliases) {
145
- registry.set(alias.toLowerCase(), snippet);
146
- }
147
- }
148
-
149
- /**
150
- * Lists all unique snippets (by name) from the registry
151
- *
152
- * @param registry - The snippet registry
153
- * @returns Array of unique snippet info objects
154
- */
155
- export function listSnippets(registry: SnippetRegistry): SnippetInfo[] {
156
- const seen = new Set<string>();
157
- const snippets: SnippetInfo[] = [];
158
-
159
- for (const snippet of registry.values()) {
160
- if (!seen.has(snippet.name)) {
161
- seen.add(snippet.name);
162
- snippets.push(snippet);
163
- }
164
- }
165
-
166
- return snippets;
167
- }
168
-
169
- /**
170
- * Ensures the snippets directory exists
171
- */
172
- export async function ensureSnippetsDir(projectDir?: string): Promise<string> {
173
- const dir = projectDir ? join(projectDir, ".opencode", "snippet") : PATHS.SNIPPETS_DIR;
174
- await mkdir(dir, { recursive: true });
175
- return dir;
176
- }
177
-
178
- /**
179
- * Creates a new snippet file
180
- *
181
- * @param name - The snippet name (without extension)
182
- * @param content - The snippet content
183
- * @param options - Optional metadata (aliases, description)
184
- * @param projectDir - If provided, creates in project directory; otherwise global
185
- * @returns The path to the created snippet file
186
- */
187
- export async function createSnippet(
188
- name: string,
189
- content: string,
190
- options: { aliases?: string[]; description?: string } = {},
191
- projectDir?: string,
192
- ): Promise<string> {
193
- const dir = await ensureSnippetsDir(projectDir);
194
- const filePath = join(dir, `${name}${CONFIG.SNIPPET_EXTENSION}`);
195
-
196
- // Build frontmatter if we have metadata
197
- const frontmatter: SnippetFrontmatter = {};
198
- if (options.aliases?.length) {
199
- frontmatter.aliases = options.aliases;
200
- }
201
- if (options.description) {
202
- frontmatter.description = options.description;
203
- }
204
-
205
- // Create file content with frontmatter if needed
206
- let fileContent: string;
207
- if (Object.keys(frontmatter).length > 0) {
208
- fileContent = matter.stringify(content, frontmatter);
209
- } else {
210
- fileContent = content;
211
- }
212
-
213
- await writeFile(filePath, fileContent, "utf-8");
214
- logger.info("Created snippet", { name, path: filePath });
215
-
216
- return filePath;
217
- }
218
-
219
- /**
220
- * Deletes a snippet file
221
- *
222
- * @param name - The snippet name (without extension)
223
- * @param projectDir - If provided, looks in project directory first; otherwise global
224
- * @returns The path of the deleted file, or null if not found
225
- */
226
- export async function deleteSnippet(name: string, projectDir?: string): Promise<string | null> {
227
- // Try project directory first if provided
228
- if (projectDir) {
229
- const projectPath = join(
230
- projectDir,
231
- ".opencode",
232
- "snippet",
233
- `${name}${CONFIG.SNIPPET_EXTENSION}`,
234
- );
235
- try {
236
- await unlink(projectPath);
237
- logger.info("Deleted project snippet", { name, path: projectPath });
238
- return projectPath;
239
- } catch {
240
- // Not found in project, try global
241
- }
242
- }
243
-
244
- // Try global directory
245
- const globalPath = join(PATHS.SNIPPETS_DIR, `${name}${CONFIG.SNIPPET_EXTENSION}`);
246
- try {
247
- await unlink(globalPath);
248
- logger.info("Deleted global snippet", { name, path: globalPath });
249
- return globalPath;
250
- } catch {
251
- logger.warn("Snippet not found for deletion", { name });
252
- return null;
253
- }
254
- }
255
-
256
- /**
257
- * Reloads snippets into the registry from disk
258
- */
259
- export async function reloadSnippets(
260
- registry: SnippetRegistry,
261
- projectDir?: string,
262
- ): Promise<void> {
263
- registry.clear();
264
- const fresh = await loadSnippets(projectDir);
265
- for (const [key, value] of fresh) {
266
- registry.set(key, value);
267
- }
268
- }
@@ -1,136 +0,0 @@
1
- import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "bun:test";
2
- import { existsSync, mkdtempSync, readFileSync, rmSync } from "fs";
3
- import { tmpdir } from "os";
4
- import { join } from "path";
5
-
6
- describe("Logger", () => {
7
- let tempDir: string;
8
- let originalEnv: NodeJS.ProcessEnv;
9
- let Logger: typeof import("../src/logger.js").Logger;
10
-
11
- beforeAll(() => {
12
- originalEnv = { ...process.env };
13
- });
14
-
15
- beforeEach(() => {
16
- tempDir = mkdtempSync(join(tmpdir(), "snippets-logger-test-"));
17
- Logger = require("../src/logger.js").Logger;
18
- });
19
-
20
- afterEach(() => {
21
- rmSync(tempDir, { recursive: true, force: true });
22
- vi.restoreAllMocks();
23
- process.env = originalEnv;
24
- });
25
-
26
- describe("isDebugEnabled", () => {
27
- it("should return false when DEBUG_SNIPPETS is not set", () => {
28
- delete process.env.DEBUG_SNIPPETS;
29
- const testLogger = new Logger(join(tempDir, "logs"));
30
- expect(testLogger.enabled).toBe(false);
31
- });
32
-
33
- it("should return true when DEBUG_SNIPPETS=1", () => {
34
- process.env.DEBUG_SNIPPETS = "1";
35
- const testLogger = new Logger(join(tempDir, "logs"));
36
- expect(testLogger.enabled).toBe(true);
37
- });
38
-
39
- it("should return true when DEBUG_SNIPPETS=true", () => {
40
- process.env.DEBUG_SNIPPETS = "true";
41
- const testLogger = new Logger(join(tempDir, "logs"));
42
- expect(testLogger.enabled).toBe(true);
43
- });
44
-
45
- it("should return false for other values", () => {
46
- process.env.DEBUG_SNIPPETS = "yes";
47
- const testLogger = new Logger(join(tempDir, "logs"));
48
- expect(testLogger.enabled).toBe(false);
49
- });
50
- });
51
-
52
- describe("log writing", () => {
53
- it("should not write logs when debug is disabled", () => {
54
- delete process.env.DEBUG_SNIPPETS;
55
- const writeSpy = vi.spyOn(require("fs"), "writeFileSync");
56
- const logger = new Logger(join(tempDir, "logs"));
57
- logger.debug("test message", { key: "value" });
58
- expect(writeSpy).not.toHaveBeenCalled();
59
- });
60
-
61
- it("should write logs when debug is enabled", () => {
62
- process.env.DEBUG_SNIPPETS = "1";
63
- const logger = new Logger(join(tempDir, "logs"));
64
- logger.debug("test debug message", { test: true });
65
- logger.info("test info message");
66
- logger.warn("test warn message");
67
- logger.error("test error message");
68
- });
69
-
70
- it("should format data correctly", () => {
71
- process.env.DEBUG_SNIPPETS = "1";
72
- const logger = new Logger(join(tempDir, "logs"));
73
- logger.debug("with data", {
74
- string: "hello",
75
- number: 42,
76
- bool: true,
77
- nullVal: null,
78
- undefinedVal: undefined,
79
- });
80
- });
81
-
82
- it("should handle empty data", () => {
83
- process.env.DEBUG_SNIPPETS = "1";
84
- const logger = new Logger(join(tempDir, "logs"));
85
- logger.debug("no data");
86
- logger.debug("empty object", {});
87
- logger.debug("empty array", { items: [] as unknown[] });
88
- });
89
-
90
- it("should format arrays compactly", () => {
91
- process.env.DEBUG_SNIPPETS = "1";
92
- const logger = new Logger(join(tempDir, "logs"));
93
- logger.debug("small array", { items: [1, 2, 3] });
94
- logger.debug("large array", { items: [1, 2, 3, 4, 5, 6] });
95
- });
96
-
97
- it("should handle objects in data", () => {
98
- process.env.DEBUG_SNIPPETS = "1";
99
- const logger = new Logger(join(tempDir, "logs"));
100
- logger.debug("nested object", {
101
- nested: { a: 1, b: 2 },
102
- long: "this is a very long string that should be truncated if over 50 characters",
103
- });
104
- });
105
- });
106
-
107
- describe("file output", () => {
108
- it("should create log file in daily directory", () => {
109
- process.env.DEBUG_SNIPPETS = "1";
110
- const logger = new Logger(join(tempDir, "logs"));
111
- logger.info("file output test", { test: true });
112
-
113
- const today = new Date().toISOString().split("T")[0];
114
- const logFile = join(tempDir, "logs", "daily", `${today}.log`);
115
-
116
- expect(existsSync(logFile)).toBe(true);
117
- const content = readFileSync(logFile, "utf-8");
118
- expect(content).toContain("file output test");
119
- expect(content).toContain("test=true");
120
- });
121
-
122
- it("should append to existing log file", () => {
123
- process.env.DEBUG_SNIPPETS = "1";
124
- const logger = new Logger(join(tempDir, "logs"));
125
- logger.info("first message");
126
- logger.info("second message");
127
-
128
- const today = new Date().toISOString().split("T")[0];
129
- const logFile = join(tempDir, "logs", "daily", `${today}.log`);
130
- const content = readFileSync(logFile, "utf-8");
131
-
132
- expect(content).toContain("first message");
133
- expect(content).toContain("second message");
134
- });
135
- });
136
- });
package/src/logger.ts DELETED
@@ -1,121 +0,0 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "fs";
2
- import { join } from "path";
3
- import { PATHS } from "./constants.js";
4
-
5
- /**
6
- * Check if debug logging is enabled via environment variable
7
- */
8
- function isDebugEnabled(): boolean {
9
- const value = process.env.DEBUG_SNIPPETS;
10
- return value === "1" || value === "true";
11
- }
12
-
13
- export class Logger {
14
- private logDir: string;
15
-
16
- constructor(logDirOverride?: string) {
17
- this.logDir = logDirOverride ?? join(PATHS.CONFIG_DIR, "logs", "snippets");
18
- }
19
-
20
- get enabled(): boolean {
21
- return isDebugEnabled();
22
- }
23
-
24
- private ensureLogDir() {
25
- if (!existsSync(this.logDir)) {
26
- mkdirSync(this.logDir, { recursive: true });
27
- }
28
- }
29
-
30
- private formatData(data?: Record<string, unknown>): string {
31
- if (!data) return "";
32
-
33
- const parts: string[] = [];
34
- for (const [key, value] of Object.entries(data)) {
35
- if (value === undefined || value === null) continue;
36
-
37
- // Format arrays compactly
38
- if (Array.isArray(value)) {
39
- if (value.length === 0) continue;
40
- parts.push(
41
- `${key}=[${value.slice(0, 3).join(",")}${value.length > 3 ? `...+${value.length - 3}` : ""}]`,
42
- );
43
- } else if (typeof value === "object") {
44
- const str = JSON.stringify(value);
45
- if (str.length < 50) {
46
- parts.push(`${key}=${str}`);
47
- }
48
- } else {
49
- parts.push(`${key}=${value}`);
50
- }
51
- }
52
- return parts.join(" ");
53
- }
54
-
55
- private getCallerFile(): string {
56
- const originalPrepareStackTrace = Error.prepareStackTrace;
57
- try {
58
- const err = new Error();
59
- Error.prepareStackTrace = (_, stack) => stack;
60
- const stack = err.stack as unknown as NodeJS.CallSite[];
61
- Error.prepareStackTrace = originalPrepareStackTrace;
62
-
63
- for (let i = 3; i < stack.length; i++) {
64
- const filename = stack[i]?.getFileName();
65
- if (filename && !filename.includes("logger.")) {
66
- const match = filename.match(/([^/\\]+)\.[tj]s$/);
67
- return match ? match[1] : "unknown";
68
- }
69
- }
70
- return "unknown";
71
- } catch {
72
- return "unknown";
73
- }
74
- }
75
-
76
- private write(level: string, component: string, message: string, data?: Record<string, unknown>) {
77
- if (!this.enabled) return;
78
-
79
- try {
80
- this.ensureLogDir();
81
-
82
- const timestamp = new Date().toISOString();
83
- const dataStr = this.formatData(data);
84
-
85
- const dailyLogDir = join(this.logDir, "daily");
86
- if (!existsSync(dailyLogDir)) {
87
- mkdirSync(dailyLogDir, { recursive: true });
88
- }
89
-
90
- const logLine = `${timestamp} ${level.padEnd(5)} ${component}: ${message}${dataStr ? ` | ${dataStr}` : ""}\n`;
91
-
92
- const logFile = join(dailyLogDir, `${new Date().toISOString().split("T")[0]}.log`);
93
- writeFileSync(logFile, logLine, { flag: "a" });
94
- } catch {
95
- // Silent fail
96
- }
97
- }
98
-
99
- info(message: string, data?: Record<string, unknown>) {
100
- const component = this.getCallerFile();
101
- this.write("INFO", component, message, data);
102
- }
103
-
104
- debug(message: string, data?: Record<string, unknown>) {
105
- const component = this.getCallerFile();
106
- this.write("DEBUG", component, message, data);
107
- }
108
-
109
- warn(message: string, data?: Record<string, unknown>) {
110
- const component = this.getCallerFile();
111
- this.write("WARN", component, message, data);
112
- }
113
-
114
- error(message: string, data?: Record<string, unknown>) {
115
- const component = this.getCallerFile();
116
- this.write("ERROR", component, message, data);
117
- }
118
- }
119
-
120
- // Export singleton logger instance
121
- export const logger = new Logger();
@@ -1,30 +0,0 @@
1
- import { logger } from "./logger.js";
2
- import type { OpencodeClient } from "./types.js";
3
-
4
- /**
5
- * Sends a message that will be displayed but ignored by the AI
6
- * Used for command output that shouldn't trigger AI responses
7
- *
8
- * @param client - The OpenCode client instance
9
- * @param sessionId - The current session ID
10
- * @param text - The text to display
11
- */
12
- export async function sendIgnoredMessage(
13
- client: OpencodeClient,
14
- sessionId: string,
15
- text: string,
16
- ): Promise<void> {
17
- try {
18
- await client.session.prompt({
19
- path: { id: sessionId },
20
- body: {
21
- noReply: true,
22
- parts: [{ type: "text", text, ignored: true }],
23
- },
24
- });
25
- } catch (error) {
26
- logger.error("Failed to send ignored message", {
27
- error: error instanceof Error ? error.message : String(error),
28
- });
29
- }
30
- }
package/src/shell.ts DELETED
@@ -1,50 +0,0 @@
1
- import { PATTERNS } from "./constants.js";
2
- import { logger } from "./logger.js";
3
-
4
- /**
5
- * Executes shell commands in text using !`command` syntax
6
- *
7
- * @param text - The text containing shell commands to execute
8
- * @param ctx - The plugin context (with Bun shell)
9
- * @returns The text with shell commands replaced by their output
10
- */
11
- export type ShellContext = {
12
- $: (
13
- template: TemplateStringsArray,
14
- ...args: unknown[]
15
- ) => {
16
- quiet: () => { nothrow: () => { text: () => Promise<string> } };
17
- };
18
- };
19
-
20
- export async function executeShellCommands(text: string, ctx: ShellContext): Promise<string> {
21
- let result = text;
22
-
23
- // Reset regex state (global flag requires this)
24
- PATTERNS.SHELL_COMMAND.lastIndex = 0;
25
-
26
- // Find all shell command matches
27
- const matches = [...text.matchAll(PATTERNS.SHELL_COMMAND)];
28
-
29
- // Execute each command and replace in text
30
- for (const match of matches) {
31
- const cmd = match[1];
32
- const _placeholder = match[0];
33
-
34
- try {
35
- const output = await ctx.$`${{ raw: cmd }}`.quiet().nothrow().text();
36
- // Deviate from slash commands' substitution mechanism: print command first, then output
37
- const replacement = `$ ${cmd}\n--> ${output.trim()}`;
38
- result = result.replace(_placeholder, replacement);
39
- } catch (error) {
40
- // If shell command fails, leave it as-is
41
- // This preserves the original syntax for debugging
42
- logger.warn("Shell command execution failed", {
43
- command: cmd,
44
- error: error instanceof Error ? error.message : String(error),
45
- });
46
- }
47
- }
48
-
49
- return result;
50
- }
package/src/types.ts DELETED
@@ -1,71 +0,0 @@
1
- import type { createOpencodeClient } from "@opencode-ai/sdk";
2
-
3
- /**
4
- * OpenCode client type from the SDK
5
- */
6
- export type OpencodeClient = ReturnType<typeof createOpencodeClient>;
7
-
8
- /**
9
- * A snippet with its content and metadata
10
- */
11
- export interface Snippet {
12
- /** The primary name/key of the snippet */
13
- name: string;
14
- /** The content of the snippet (without frontmatter) */
15
- content: string;
16
- /** Alternative names that also trigger this snippet */
17
- aliases: string[];
18
- }
19
-
20
- /**
21
- * Extended snippet info with file metadata
22
- */
23
- export interface SnippetInfo {
24
- name: string;
25
- content: string;
26
- aliases: string[];
27
- description?: string;
28
- filePath: string;
29
- source: "global" | "project";
30
- }
31
-
32
- /**
33
- * Snippet registry that maps keys to snippet info
34
- */
35
- export type SnippetRegistry = Map<string, SnippetInfo>;
36
-
37
- /**
38
- * Frontmatter data from snippet files
39
- */
40
- export interface SnippetFrontmatter {
41
- /** Alternative hashtags for this snippet (plural form, preferred) */
42
- aliases?: string | string[];
43
- /** Alternative hashtags for this snippet (singular form, also accepted) */
44
- alias?: string | string[];
45
- /** Optional description of what this snippet does */
46
- description?: string;
47
- }
48
-
49
- /**
50
- * Parsed snippet content with inline text and prepend/append blocks
51
- */
52
- export interface ParsedSnippetContent {
53
- /** Content outside blocks (replaces hashtag inline) */
54
- inline: string;
55
- /** <prepend> block contents in document order */
56
- prepend: string[];
57
- /** <append> block contents in document order */
58
- append: string[];
59
- }
60
-
61
- /**
62
- * Result of expanding hashtags, including collected prepend/append blocks
63
- */
64
- export interface ExpansionResult {
65
- /** The inline-expanded text */
66
- text: string;
67
- /** Collected prepend blocks from all expanded snippets */
68
- prepend: string[];
69
- /** Collected append blocks from all expanded snippets */
70
- append: string[];
71
- }