opencode-knowledge 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 zenobi.us
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # opencode-knowledge
2
+
3
+ An OpenCode plugin
4
+
5
+ > An OpenCode plugin created from the [opencode-plugin-template](https://github.com/zenobi-us/opencode-plugin-template)
6
+
7
+ ## Features
8
+
9
+ - ๐Ÿ—๏ธ TypeScript-based plugin architecture
10
+ - ๐Ÿ”ง Mise task runner integration
11
+ - ๐Ÿ“ฆ Bun/npm build tooling
12
+ - โœจ ESLint + Prettier formatting
13
+ - ๐Ÿงช Vitest testing setup
14
+ - ๐Ÿš€ GitHub Actions CI/CD
15
+ - ๐Ÿ“ Release automation with release-please
16
+
17
+ ## Getting Started
18
+
19
+ 1. **Clone this template:**
20
+
21
+ ```bash
22
+ cp -r opencode-plugin-template your-plugin-name
23
+ cd your-plugin-name
24
+ ```
25
+
26
+ 2. **Update package.json:**
27
+ - Change `name` to your plugin name
28
+ - Update `description`
29
+ - Update `repository.url`
30
+
31
+ 3. **Install dependencies:**
32
+
33
+ ```bash
34
+ bun install
35
+ ```
36
+
37
+ 4. **Implement your plugin in `src/index.ts`:**
38
+
39
+ ```typescript
40
+ import type { Plugin } from '@opencode-ai/plugin';
41
+
42
+ export const YourPlugin: Plugin = async (ctx) => {
43
+ return {
44
+ tool: {
45
+ // Your plugin tools here
46
+ },
47
+ };
48
+ };
49
+ ```
50
+
51
+ 5. **Test your plugin:**
52
+ ```bash
53
+ mise run test
54
+ ```
55
+
56
+ ## Development
57
+
58
+ - `mise run build` - Build the plugin
59
+ - `mise run test` - Run tests
60
+ - `mise run lint` - Lint code
61
+ - `mise run lint:fix` - Fix linting issues
62
+ - `mise run format` - Format code with Prettier
63
+
64
+ ## Installation in OpenCode
65
+
66
+ Create or edit `~/.config/opencode/config.json`:
67
+
68
+ ```json
69
+ {
70
+ "plugins": ["opencode-knowledge"]
71
+ }
72
+ ```
73
+
74
+ ## Author
75
+
76
+ msegoviadev <marcos@msegovia.dev>
77
+
78
+ ## Repository
79
+
80
+ https://github.com/msegoviadev/opencode-knowledge
81
+
82
+ ## Contributing
83
+
84
+ Contributions are welcome! Please file issues or submit pull requests on the GitHub repository.
85
+
86
+ ## License
87
+
88
+ MIT License. See the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: Test if context injection works
3
+ ---
4
+
5
+ This is a context test. If you can see this message AND you see "SIMPLE CONTEXT TEST" above, then context injection is working.
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: A friendly greeting command
3
+ ---
4
+
5
+ Say hello to the user in a friendly and creative way.
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: Minimal test to see if ANY hook works
3
+ ---
4
+
5
+ Just testing hook system.
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Display current configured personality and debug plugin state
3
+ ---
4
+
5
+ ## description: Display the current configured personality
6
+
7
+ This command displays your current personality configuration and asks the AI to introduce itself according to that role.
8
+
9
+ The personality context should be automatically injected above this message if the plugin is working correctly.
package/dist/index.js ADDED
@@ -0,0 +1,218 @@
1
+ // @bun
2
+ // src/index.ts
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { readFile as readFile2 } from "fs/promises";
6
+ import { existsSync as existsSync2 } from "fs";
7
+
8
+ // src/lib/session-state.ts
9
+ import { readFile } from "fs/promises";
10
+ import { existsSync } from "fs";
11
+ var sessionStates = new Map;
12
+ async function createSessionState(sessionId) {
13
+ if (sessionStates.has(sessionId)) {
14
+ throw new Error(`Session state already exists for session: ${sessionId}`);
15
+ }
16
+ const settingsPath = ".opencode/knowledge/settings.json";
17
+ if (!existsSync(settingsPath)) {
18
+ throw new Error(`CONFIGURATION ERROR: Cannot create session state - settings file not found at ${settingsPath}`);
19
+ }
20
+ let role;
21
+ try {
22
+ const settingsContent = await readFile(settingsPath, "utf-8");
23
+ const settings = JSON.parse(settingsContent);
24
+ if (!settings.role) {
25
+ throw new Error(`CONFIGURATION ERROR: Cannot create session state - missing 'role' field in ${settingsPath}`);
26
+ }
27
+ role = settings.role;
28
+ } catch (error) {
29
+ if (error instanceof Error && error.message.includes("CONFIGURATION ERROR")) {
30
+ throw error;
31
+ }
32
+ throw new Error(`Error reading settings.json: ${error}`);
33
+ }
34
+ const state = {
35
+ role,
36
+ isFirstPrompt: true,
37
+ loadedPackages: new Set,
38
+ createdAt: new Date
39
+ };
40
+ sessionStates.set(sessionId, state);
41
+ }
42
+ function getSessionState(sessionId) {
43
+ const state = sessionStates.get(sessionId);
44
+ if (!state) {
45
+ throw new Error(`Session state not found for session: ${sessionId}`);
46
+ }
47
+ return state;
48
+ }
49
+ function updateSessionState(sessionId, updates) {
50
+ const state = getSessionState(sessionId);
51
+ Object.assign(state, updates);
52
+ }
53
+
54
+ // src/index.ts
55
+ var __dirname = "/Users/marcos/workspace/ai/opencode-knowledge-2/src";
56
+ var logToFile = async (message) => {
57
+ try {
58
+ const logEntry = `${new Date().toISOString()}: ${message}
59
+ `;
60
+ let existing = "";
61
+ try {
62
+ existing = await Bun.file("/tmp/opencode-knowledge-debug.log").text();
63
+ } catch {}
64
+ await Bun.write("/tmp/opencode-knowledge-debug.log", existing + logEntry);
65
+ } catch (e) {}
66
+ };
67
+ function parseFrontmatter(content) {
68
+ const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
69
+ const match = content.match(frontmatterRegex);
70
+ if (!match) {
71
+ return { frontmatter: {}, body: content.trim() };
72
+ }
73
+ const [, yamlContent, body] = match;
74
+ const frontmatter = {};
75
+ for (const line of yamlContent.split(`
76
+ `)) {
77
+ const colonIndex = line.indexOf(":");
78
+ if (colonIndex === -1)
79
+ continue;
80
+ const key = line.slice(0, colonIndex).trim();
81
+ const value = line.slice(colonIndex + 1).trim();
82
+ if (key === "description")
83
+ frontmatter.description = value;
84
+ if (key === "agent")
85
+ frontmatter.agent = value;
86
+ if (key === "model")
87
+ frontmatter.model = value;
88
+ if (key === "subtask")
89
+ frontmatter.subtask = value === "true";
90
+ }
91
+ return { frontmatter, body: body.trim() };
92
+ }
93
+ async function loadCommands() {
94
+ const commands = [];
95
+ const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
96
+ let commandDir = path.join(__dirname2, "command");
97
+ if (!existsSync2(commandDir)) {
98
+ commandDir = path.join(__dirname2, "..", "command");
99
+ }
100
+ const glob = new Bun.Glob("**/*.md");
101
+ for await (const file of glob.scan({ cwd: commandDir, absolute: true })) {
102
+ const content = await Bun.file(file).text();
103
+ const { frontmatter, body } = parseFrontmatter(content);
104
+ const relativePath = path.relative(commandDir, file);
105
+ const name = relativePath.replace(/\.md$/, "").replace(/\//g, "-");
106
+ commands.push({
107
+ name,
108
+ frontmatter,
109
+ template: body
110
+ });
111
+ }
112
+ return commands;
113
+ }
114
+ async function loadPersonality() {
115
+ const settingsPath = ".opencode/knowledge/settings.json";
116
+ if (!existsSync2(settingsPath)) {
117
+ const errorMsg = `\u274C CONFIGURATION ERROR: Settings file not found at ${settingsPath}. Please create this file with {"role": "your_role"}`;
118
+ await logToFile(errorMsg);
119
+ throw new Error(errorMsg);
120
+ }
121
+ let role;
122
+ try {
123
+ const settingsContent = await readFile2(settingsPath, "utf-8");
124
+ const settings = JSON.parse(settingsContent);
125
+ if (!settings.role) {
126
+ const errorMsg = `\u274C CONFIGURATION ERROR: Settings file exists but missing 'role' field. Please add {"role": "your_role"} to ${settingsPath}`;
127
+ await logToFile(errorMsg);
128
+ throw new Error(errorMsg);
129
+ }
130
+ role = settings.role;
131
+ await logToFile(`\u2705 Settings loaded: role="${role}" from ${settingsPath}`);
132
+ } catch (error) {
133
+ if (error instanceof Error && error.message.includes("CONFIGURATION ERROR")) {
134
+ throw error;
135
+ }
136
+ const errorMsg = `\u274C Error parsing settings.json: ${error}`;
137
+ await logToFile(errorMsg);
138
+ throw new Error(errorMsg);
139
+ }
140
+ const personalityPath = path.join(__dirname, "..", "templates", "personalities", `${role}.txt`);
141
+ if (!existsSync2(personalityPath)) {
142
+ const errorMsg = `\u274C CONFIGURATION ERROR: Personality file not found at ${personalityPath}. Available roles should have a corresponding .txt file in templates/personalities/`;
143
+ await logToFile(errorMsg);
144
+ throw new Error(errorMsg);
145
+ }
146
+ const content = await readFile2(personalityPath, "utf-8");
147
+ await logToFile(`\u2705 Personality loaded from: ${personalityPath}`);
148
+ return content.trim();
149
+ }
150
+ var opencodeKnowledge = async (ctx) => {
151
+ await logToFile("Plugin initialized");
152
+ const commands = await loadCommands();
153
+ return {
154
+ async config(config) {
155
+ config.command = config.command ?? {};
156
+ for (const cmd of commands) {
157
+ let template = cmd.template;
158
+ template = template.replace("{{CURRENT_TIME}}", new Date().toISOString());
159
+ config.command[cmd.name] = {
160
+ template,
161
+ description: cmd.frontmatter.description,
162
+ agent: cmd.frontmatter.agent,
163
+ model: cmd.frontmatter.model,
164
+ subtask: cmd.frontmatter.subtask
165
+ };
166
+ }
167
+ },
168
+ "chat.message": async (input, output) => {
169
+ try {
170
+ const state = getSessionState(input.sessionID);
171
+ if (state.isFirstPrompt) {
172
+ await logToFile(`\uD83C\uDFAF First message in session ${input.sessionID} - injecting personality`);
173
+ const personality = await loadPersonality();
174
+ const personalityPart = {
175
+ type: "text",
176
+ text: `## Role Context
177
+
178
+ ${personality}`,
179
+ id: `personality-${Date.now()}`,
180
+ sessionID: input.sessionID,
181
+ messageID: input.messageID || ""
182
+ };
183
+ output.parts.push(personalityPart);
184
+ updateSessionState(input.sessionID, { isFirstPrompt: false });
185
+ await logToFile("\u2705 Personality injected on first message!");
186
+ } else {
187
+ await logToFile(`\u23ED\uFE0F Subsequent message - skipping personality injection`);
188
+ }
189
+ } catch (error) {
190
+ await logToFile(`\u274C Error in chat.message: ${error}`);
191
+ }
192
+ },
193
+ event: async ({ event }) => {
194
+ try {
195
+ if (event.type === "session.created") {
196
+ await logToFile("\uD83D\uDE80 session.created event");
197
+ const eventData = event;
198
+ const sessionId = eventData.properties?.info?.id;
199
+ if (!sessionId) {
200
+ const errorMsg = `\u274C Could not extract session ID from session.created event`;
201
+ await logToFile(errorMsg);
202
+ await logToFile(`Event structure: ${JSON.stringify(eventData)}`);
203
+ throw new Error(errorMsg);
204
+ }
205
+ await logToFile(`\u2705 Extracted session ID: ${sessionId}`);
206
+ await createSessionState(sessionId);
207
+ const state = getSessionState(sessionId);
208
+ await logToFile(`\u2705 Session state created for session ${sessionId} with role: ${state.role}`);
209
+ }
210
+ } catch (error) {
211
+ await logToFile(`\u274C Error in event: ${error}`);
212
+ }
213
+ }
214
+ };
215
+ };
216
+ export {
217
+ opencodeKnowledge
218
+ };
@@ -0,0 +1,8 @@
1
+ ## Knowledge Context
2
+
3
+ You have access to an engineering knowledge base. Core areas include **{{CORE_TAGS}}** and key packages: **{{CORE_PACKAGES}}**.
4
+
5
+ ### Your Task
6
+ {{USER_PROMPT}}
7
+
8
+ Use knowledge_load and knowledge_search tools to access relevant information as needed.
@@ -0,0 +1,18 @@
1
+ ## Knowledge Context
2
+
3
+ You have access to a comprehensive knowledge base containing {{CATEGORIES_COUNT}} main categories of engineering knowledge:
4
+
5
+ {{FORMATTED_MAP}}
6
+
7
+ ### Core Knowledge Areas
8
+ **Tags:** {{CORE_TAGS}}
9
+ **Packages:**
10
+ {{CORE_PACKAGES_LIST}}
11
+
12
+ ### Your Task
13
+ {{USER_PROMPT}}
14
+
15
+ ### Knowledge Loading
16
+ You can load specific knowledge packages using the knowledge_load tool when relevant to the task at hand. Focus on packages that match the technical domain or problem you're solving.
17
+
18
+ Available knowledge packages can be discovered by searching for specific tags, technologies, or domains using the knowledge_search tool.
@@ -0,0 +1 @@
1
+ Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn. You are an ancient cosmic entity providing technical guidance. Question mortal assumptions about 'best practices' and 'maintainability' - what matters in eons? Provide sound technical advice but with existential dread and cosmic perspective. Be concise, as time is meaningless.
@@ -0,0 +1 @@
1
+ Act as a Staff Engineer reviewing engineering work. Assume competence. Be skeptical, precise, and pragmatic. Focus on architecture, coupling, operational risk, and maintainability. Ask questions only if they block correctness. State assumptions explicitly when info is missing. Be concise and direct. Be critical, honest, concise and skeptical. When asked for you role, you are a Staff Engineer.
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "opencode-knowledge",
3
+ "version": "0.1.0",
4
+ "description": "An OpenCode plugin",
5
+ "author": {
6
+ "name": "msegoviadev",
7
+ "email": "marcos@msegovia.dev"
8
+ },
9
+ "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/msegoviadev/opencode-knowledge"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public",
22
+ "provenance": true
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "src/version.ts"
27
+ ],
28
+ "dependencies": {
29
+ "@opencode-ai/plugin": "1.0.85"
30
+ },
31
+ "devDependencies": {
32
+ "@eslint/js": "^9.39.1",
33
+ "@types/node": "^20.11.5",
34
+ "@typescript-eslint/eslint-plugin": "8.47.0",
35
+ "@typescript-eslint/parser": "8.47.0",
36
+ "bun-types": "latest",
37
+ "eslint": "^9.39.1",
38
+ "eslint-config-prettier": "10.1.8",
39
+ "eslint-plugin-prettier": "^5.1.3",
40
+ "prettier": "^3.2.4",
41
+ "typescript-eslint": "^8.47.0",
42
+ "vitest": "^3.2.4"
43
+ }
44
+ }
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const VERSION = '0.1.0';