nia-opencode 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/README.md +130 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +272 -0
- package/dist/config.d.ts +11 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +171 -0
- package/dist/keywords.d.ts +5 -0
- package/dist/services/logger.d.ts +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# nia-opencode
|
|
2
|
+
|
|
3
|
+
OpenCode plugin that integrates [Nia Knowledge Agent](https://trynia.ai) for research, documentation, and codebase exploration.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Keyword Detection**: Automatically detects research-related queries and prompts the agent to use Nia tools
|
|
8
|
+
- **MCP Integration**: Connects to Nia's MCP server for semantic search, documentation indexing, and AI research
|
|
9
|
+
- **Zero Config**: Works out of the box with automatic MCP server setup
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bunx nia-opencode@latest install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The installer will:
|
|
18
|
+
1. Prompt for your Nia API key
|
|
19
|
+
2. Create `~/.config/opencode/nia.json` with your config
|
|
20
|
+
3. Add the plugin and MCP server to your OpenCode config
|
|
21
|
+
|
|
22
|
+
### Non-Interactive Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
bunx nia-opencode@latest install --no-tui --api-key nk_your_api_key
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Manual Setup
|
|
29
|
+
|
|
30
|
+
1. Add to your `~/.config/opencode/opencode.json`:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"plugin": ["nia-opencode@latest"],
|
|
35
|
+
"mcp": {
|
|
36
|
+
"nia": {
|
|
37
|
+
"type": "remote",
|
|
38
|
+
"url": "https://apigcp.trynia.ai/mcp",
|
|
39
|
+
"headers": {
|
|
40
|
+
"Authorization": "Bearer nk_your_api_key"
|
|
41
|
+
},
|
|
42
|
+
"oauth": false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
2. Set your API key (choose one):
|
|
49
|
+
|
|
50
|
+
**Environment variable:**
|
|
51
|
+
```bash
|
|
52
|
+
export NIA_API_KEY="nk_your_api_key"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Config file** (`~/.config/opencode/nia.json`):
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"apiKey": "nk_your_api_key"
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
Once installed, the plugin automatically detects research-related queries and prompts the agent to use Nia tools.
|
|
65
|
+
|
|
66
|
+
### Trigger Keywords
|
|
67
|
+
|
|
68
|
+
The plugin activates when you say things like:
|
|
69
|
+
- "Research how React hooks work"
|
|
70
|
+
- "Look up the Next.js documentation"
|
|
71
|
+
- "Search the codebase for authentication"
|
|
72
|
+
- "Find docs for Prisma migrations"
|
|
73
|
+
- "Grep for error handling patterns"
|
|
74
|
+
- "Index this repo"
|
|
75
|
+
|
|
76
|
+
### Available Nia Tools
|
|
77
|
+
|
|
78
|
+
When triggered, the agent has access to:
|
|
79
|
+
|
|
80
|
+
| Tool | Description |
|
|
81
|
+
|------|-------------|
|
|
82
|
+
| `nia.search` | Semantic search across indexed repos, docs, papers |
|
|
83
|
+
| `nia.nia_research` | Web search (quick) or deep AI research (deep/oracle) |
|
|
84
|
+
| `nia.index` | Index GitHub repos, docs sites, or arXiv papers |
|
|
85
|
+
| `nia.nia_read` | Read files from indexed sources |
|
|
86
|
+
| `nia.nia_grep` | Regex search across codebases |
|
|
87
|
+
| `nia.nia_explore` | Browse file trees |
|
|
88
|
+
| `nia.manage_resource` | List/manage indexed sources |
|
|
89
|
+
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
### `~/.config/opencode/nia.json`
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"apiKey": "nk_...",
|
|
97
|
+
"mcpUrl": "https://apigcp.trynia.ai/mcp",
|
|
98
|
+
"keywords": {
|
|
99
|
+
"enabled": true,
|
|
100
|
+
"patterns": [
|
|
101
|
+
"my custom pattern"
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Options
|
|
108
|
+
|
|
109
|
+
| Option | Default | Description |
|
|
110
|
+
|--------|---------|-------------|
|
|
111
|
+
| `apiKey` | - | Your Nia API key |
|
|
112
|
+
| `mcpUrl` | `https://apigcp.trynia.ai/mcp` | MCP server URL |
|
|
113
|
+
| `keywords.enabled` | `true` | Enable/disable keyword detection |
|
|
114
|
+
| `keywords.patterns` | `[]` | Additional regex patterns to trigger Nia |
|
|
115
|
+
|
|
116
|
+
## Debugging
|
|
117
|
+
|
|
118
|
+
Enable debug logging:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
NIA_DEBUG=true opencode
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Get Your API Key
|
|
125
|
+
|
|
126
|
+
Get your Nia API key at [trynia.ai/api-keys](https://trynia.ai/api-keys)
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import * as readline from "node:readline";
|
|
8
|
+
var OPENCODE_CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
9
|
+
var NIA_CONFIG_PATH = join(OPENCODE_CONFIG_DIR, "nia.json");
|
|
10
|
+
var PLUGIN_NAME = "nia-opencode@latest";
|
|
11
|
+
function stripJsoncComments(content) {
|
|
12
|
+
return content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
13
|
+
}
|
|
14
|
+
function createReadline() {
|
|
15
|
+
return readline.createInterface({
|
|
16
|
+
input: process.stdin,
|
|
17
|
+
output: process.stdout
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async function confirm(rl, question) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
rl.question(`${question} (y/n) `, (answer) => {
|
|
23
|
+
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async function prompt(rl, question) {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
rl.question(question, (answer) => {
|
|
30
|
+
resolve(answer.trim());
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function findOpencodeConfig() {
|
|
35
|
+
const candidates = [
|
|
36
|
+
join(OPENCODE_CONFIG_DIR, "opencode.jsonc"),
|
|
37
|
+
join(OPENCODE_CONFIG_DIR, "opencode.json")
|
|
38
|
+
];
|
|
39
|
+
for (const path of candidates) {
|
|
40
|
+
if (existsSync(path)) {
|
|
41
|
+
return path;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function addPluginToConfig(configPath) {
|
|
47
|
+
try {
|
|
48
|
+
const content = readFileSync(configPath, "utf-8");
|
|
49
|
+
if (content.includes("nia-opencode")) {
|
|
50
|
+
console.log(" Plugin already registered in config");
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
const jsonContent = stripJsoncComments(content);
|
|
54
|
+
let config;
|
|
55
|
+
try {
|
|
56
|
+
config = JSON.parse(jsonContent);
|
|
57
|
+
} catch {
|
|
58
|
+
console.error(" Failed to parse config file");
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const plugins = config.plugin || [];
|
|
62
|
+
plugins.push(PLUGIN_NAME);
|
|
63
|
+
config.plugin = plugins;
|
|
64
|
+
if (configPath.endsWith(".jsonc")) {
|
|
65
|
+
if (content.includes('"plugin"')) {
|
|
66
|
+
const newContent = content.replace(/("plugin"\s*:\s*\[)([^\]]*?)(\])/, (_match, start, middle, end) => {
|
|
67
|
+
const trimmed = middle.trim();
|
|
68
|
+
if (trimmed === "") {
|
|
69
|
+
return `${start}
|
|
70
|
+
"${PLUGIN_NAME}"
|
|
71
|
+
${end}`;
|
|
72
|
+
}
|
|
73
|
+
return `${start}${middle.trimEnd()},
|
|
74
|
+
"${PLUGIN_NAME}"
|
|
75
|
+
${end}`;
|
|
76
|
+
});
|
|
77
|
+
writeFileSync(configPath, newContent);
|
|
78
|
+
} else {
|
|
79
|
+
const newContent = content.replace(/^(\s*\{)/, `$1
|
|
80
|
+
"plugin": ["${PLUGIN_NAME}"],`);
|
|
81
|
+
writeFileSync(configPath, newContent);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
85
|
+
}
|
|
86
|
+
console.log(` Added plugin to ${configPath}`);
|
|
87
|
+
return true;
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error(" Failed to update config:", err);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function addMcpServerToConfig(configPath, apiKey) {
|
|
94
|
+
try {
|
|
95
|
+
const content = readFileSync(configPath, "utf-8");
|
|
96
|
+
const jsonContent = stripJsoncComments(content);
|
|
97
|
+
let config;
|
|
98
|
+
try {
|
|
99
|
+
config = JSON.parse(jsonContent);
|
|
100
|
+
} catch {
|
|
101
|
+
console.error(" Failed to parse config file");
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const mcp = config.mcp || {};
|
|
105
|
+
if (mcp.nia) {
|
|
106
|
+
console.log(" MCP server 'nia' already configured");
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
mcp.nia = {
|
|
110
|
+
type: "remote",
|
|
111
|
+
url: "https://apigcp.trynia.ai/mcp",
|
|
112
|
+
headers: {
|
|
113
|
+
Authorization: `Bearer ${apiKey}`
|
|
114
|
+
},
|
|
115
|
+
oauth: false
|
|
116
|
+
};
|
|
117
|
+
config.mcp = mcp;
|
|
118
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
119
|
+
console.log(" Added MCP server 'nia' to config");
|
|
120
|
+
return true;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error(" Failed to add MCP server:", err);
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function createNewConfig(apiKey) {
|
|
127
|
+
mkdirSync(OPENCODE_CONFIG_DIR, { recursive: true });
|
|
128
|
+
const configPath = join(OPENCODE_CONFIG_DIR, "opencode.json");
|
|
129
|
+
const config = {
|
|
130
|
+
plugin: [PLUGIN_NAME],
|
|
131
|
+
mcp: {
|
|
132
|
+
nia: {
|
|
133
|
+
type: "remote",
|
|
134
|
+
url: "https://apigcp.trynia.ai/mcp",
|
|
135
|
+
headers: {
|
|
136
|
+
Authorization: `Bearer ${apiKey}`
|
|
137
|
+
},
|
|
138
|
+
oauth: false
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
143
|
+
console.log(` Created ${configPath}`);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
function createNiaConfig(apiKey) {
|
|
147
|
+
mkdirSync(OPENCODE_CONFIG_DIR, { recursive: true });
|
|
148
|
+
const config = {
|
|
149
|
+
apiKey,
|
|
150
|
+
keywords: {
|
|
151
|
+
enabled: true
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
writeFileSync(NIA_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
155
|
+
console.log(` Created ${NIA_CONFIG_PATH}`);
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
async function install(options) {
|
|
159
|
+
console.log(`
|
|
160
|
+
Nia OpenCode Plugin Installer
|
|
161
|
+
`);
|
|
162
|
+
const rl = options.tui ? createReadline() : null;
|
|
163
|
+
console.log("Step 1: Configure API Key");
|
|
164
|
+
let apiKey = options.apiKey || process.env.NIA_API_KEY || "";
|
|
165
|
+
if (!apiKey && options.tui && rl) {
|
|
166
|
+
console.log(`Get your API key from: https://trynia.ai/api-keys
|
|
167
|
+
`);
|
|
168
|
+
apiKey = await prompt(rl, "Enter your Nia API key (nk_...): ");
|
|
169
|
+
}
|
|
170
|
+
if (!apiKey) {
|
|
171
|
+
console.log(" No API key provided. You can set NIA_API_KEY environment variable later.");
|
|
172
|
+
console.log(` Get your API key at: https://trynia.ai/api-keys
|
|
173
|
+
`);
|
|
174
|
+
} else if (!apiKey.startsWith("nk_")) {
|
|
175
|
+
console.log(" Warning: API key should start with 'nk_'");
|
|
176
|
+
} else {
|
|
177
|
+
console.log(" API key configured");
|
|
178
|
+
}
|
|
179
|
+
console.log(`
|
|
180
|
+
Step 2: Create Nia Config`);
|
|
181
|
+
if (apiKey) {
|
|
182
|
+
createNiaConfig(apiKey);
|
|
183
|
+
} else {
|
|
184
|
+
console.log(" Skipped (no API key)");
|
|
185
|
+
}
|
|
186
|
+
console.log(`
|
|
187
|
+
Step 3: Configure OpenCode`);
|
|
188
|
+
const configPath = findOpencodeConfig();
|
|
189
|
+
if (configPath) {
|
|
190
|
+
if (options.tui && rl) {
|
|
191
|
+
const shouldModify = await confirm(rl, `Modify ${configPath}?`);
|
|
192
|
+
if (shouldModify) {
|
|
193
|
+
addPluginToConfig(configPath);
|
|
194
|
+
if (apiKey) {
|
|
195
|
+
addMcpServerToConfig(configPath, apiKey);
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
console.log(" Skipped.");
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
addPluginToConfig(configPath);
|
|
202
|
+
if (apiKey) {
|
|
203
|
+
addMcpServerToConfig(configPath, apiKey);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
if (options.tui && rl) {
|
|
208
|
+
const shouldCreate = await confirm(rl, "No OpenCode config found. Create one?");
|
|
209
|
+
if (shouldCreate && apiKey) {
|
|
210
|
+
createNewConfig(apiKey);
|
|
211
|
+
} else {
|
|
212
|
+
console.log(" Skipped.");
|
|
213
|
+
}
|
|
214
|
+
} else if (apiKey) {
|
|
215
|
+
createNewConfig(apiKey);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
console.log(`
|
|
219
|
+
` + "-".repeat(50));
|
|
220
|
+
console.log(`
|
|
221
|
+
Setup Complete!
|
|
222
|
+
`);
|
|
223
|
+
if (!apiKey) {
|
|
224
|
+
console.log("Next steps:");
|
|
225
|
+
console.log("1. Get your API key from: https://trynia.ai/api-keys");
|
|
226
|
+
console.log("2. Set the environment variable:");
|
|
227
|
+
console.log(' export NIA_API_KEY="nk_..."');
|
|
228
|
+
console.log(" Or edit ~/.config/opencode/nia.json");
|
|
229
|
+
} else {
|
|
230
|
+
console.log("Nia is configured and ready to use!");
|
|
231
|
+
}
|
|
232
|
+
console.log(`
|
|
233
|
+
Keyword triggers enabled:`);
|
|
234
|
+
console.log(' - "research...", "look up...", "find docs..."');
|
|
235
|
+
console.log(' - "search codebase...", "grep for..."');
|
|
236
|
+
console.log(' - "index this repo", "add to nia"');
|
|
237
|
+
console.log(`
|
|
238
|
+
Restart OpenCode to activate the plugin.
|
|
239
|
+
`);
|
|
240
|
+
if (rl)
|
|
241
|
+
rl.close();
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
function printHelp() {
|
|
245
|
+
console.log(`
|
|
246
|
+
nia-opencode - Nia Knowledge Agent plugin for OpenCode
|
|
247
|
+
|
|
248
|
+
Commands:
|
|
249
|
+
install Install and configure the plugin
|
|
250
|
+
--no-tui Non-interactive mode
|
|
251
|
+
--api-key <key> Provide API key directly
|
|
252
|
+
|
|
253
|
+
Examples:
|
|
254
|
+
bunx nia-opencode@latest install
|
|
255
|
+
bunx nia-opencode@latest install --no-tui --api-key nk_xxx
|
|
256
|
+
`);
|
|
257
|
+
}
|
|
258
|
+
var args = process.argv.slice(2);
|
|
259
|
+
if (args.length === 0 || args[0] === "help" || args[0] === "--help" || args[0] === "-h") {
|
|
260
|
+
printHelp();
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}
|
|
263
|
+
if (args[0] === "install") {
|
|
264
|
+
const noTui = args.includes("--no-tui");
|
|
265
|
+
const apiKeyIndex = args.indexOf("--api-key");
|
|
266
|
+
const apiKey = apiKeyIndex !== -1 ? args[apiKeyIndex + 1] : undefined;
|
|
267
|
+
install({ tui: !noTui, apiKey }).then((code) => process.exit(code));
|
|
268
|
+
} else {
|
|
269
|
+
console.error(`Unknown command: ${args[0]}`);
|
|
270
|
+
printHelp();
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const NIA_API_KEY: string | undefined;
|
|
2
|
+
export declare const NIA_MCP_URL: string;
|
|
3
|
+
export declare const CONFIG: {
|
|
4
|
+
mcpUrl: string;
|
|
5
|
+
keywords: {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
customPatterns: string[];
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export declare function isConfigured(): boolean;
|
|
11
|
+
export declare function getConfigDir(): string;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
var CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
6
|
+
var CONFIG_FILES = [
|
|
7
|
+
join(CONFIG_DIR, "nia.jsonc"),
|
|
8
|
+
join(CONFIG_DIR, "nia.json")
|
|
9
|
+
];
|
|
10
|
+
var DEFAULTS = {
|
|
11
|
+
mcpUrl: "https://apigcp.trynia.ai/mcp",
|
|
12
|
+
keywords: {
|
|
13
|
+
enabled: true,
|
|
14
|
+
patterns: []
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function stripJsoncComments(content) {
|
|
18
|
+
return content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
19
|
+
}
|
|
20
|
+
function loadConfig() {
|
|
21
|
+
for (const path of CONFIG_FILES) {
|
|
22
|
+
if (existsSync(path)) {
|
|
23
|
+
try {
|
|
24
|
+
const content = readFileSync(path, "utf-8");
|
|
25
|
+
const json = stripJsoncComments(content);
|
|
26
|
+
return JSON.parse(json);
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
var fileConfig = loadConfig();
|
|
33
|
+
var NIA_API_KEY = fileConfig.apiKey ?? process.env.NIA_API_KEY;
|
|
34
|
+
var NIA_MCP_URL = fileConfig.mcpUrl ?? process.env.NIA_MCP_URL ?? DEFAULTS.mcpUrl;
|
|
35
|
+
var CONFIG = {
|
|
36
|
+
mcpUrl: NIA_MCP_URL,
|
|
37
|
+
keywords: {
|
|
38
|
+
enabled: fileConfig.keywords?.enabled ?? DEFAULTS.keywords.enabled,
|
|
39
|
+
customPatterns: fileConfig.keywords?.patterns ?? []
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
function isConfigured() {
|
|
43
|
+
return !!NIA_API_KEY;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/keywords.ts
|
|
47
|
+
var CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
|
|
48
|
+
var INLINE_CODE_PATTERN = /`[^`]+`/g;
|
|
49
|
+
var DEFAULT_PATTERNS = [
|
|
50
|
+
/\b(research|look\s*up|find\s*docs?)\b/i,
|
|
51
|
+
/\b(search\s+for|search\s+codebase|search\s+repo|search\s+docs?)\b/i,
|
|
52
|
+
/\b(grep\s+for|grep\s+in)\b/i,
|
|
53
|
+
/\b(index\s+(this\s+)?repo|add\s+to\s+nia)\b/i,
|
|
54
|
+
/\b(what\s+is|how\s+does|explain)\s+\w+\s+(library|package|framework|module)/i,
|
|
55
|
+
/\bcheck\s+(the\s+)?(docs?|documentation)\s+(for|about|on)\b/i,
|
|
56
|
+
/\bfind\s+(examples?|usage)\s+(of|for)\b/i
|
|
57
|
+
];
|
|
58
|
+
function removeCodeBlocks(text) {
|
|
59
|
+
return text.replace(CODE_BLOCK_PATTERN, "").replace(INLINE_CODE_PATTERN, "");
|
|
60
|
+
}
|
|
61
|
+
function compileCustomPatterns() {
|
|
62
|
+
return CONFIG.keywords.customPatterns.map((pattern) => {
|
|
63
|
+
try {
|
|
64
|
+
return new RegExp(pattern, "i");
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}).filter((p) => p !== null);
|
|
69
|
+
}
|
|
70
|
+
function detectResearchKeyword(text) {
|
|
71
|
+
if (!CONFIG.keywords.enabled) {
|
|
72
|
+
return { detected: false };
|
|
73
|
+
}
|
|
74
|
+
const textWithoutCode = removeCodeBlocks(text);
|
|
75
|
+
const allPatterns = [...DEFAULT_PATTERNS, ...compileCustomPatterns()];
|
|
76
|
+
for (const pattern of allPatterns) {
|
|
77
|
+
const match = textWithoutCode.match(pattern);
|
|
78
|
+
if (match) {
|
|
79
|
+
return { detected: true, match: match[0] };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { detected: false };
|
|
83
|
+
}
|
|
84
|
+
var NIA_NUDGE_MESSAGE = `[NIA KNOWLEDGE TRIGGER]
|
|
85
|
+
The user is asking for research, documentation, or codebase exploration. You have access to **Nia** tools via MCP.
|
|
86
|
+
|
|
87
|
+
**Available Nia tools:**
|
|
88
|
+
- \`nia.search\` - Semantic search across indexed repos, docs, and papers
|
|
89
|
+
- \`nia.nia_research\` - Web search (quick) or deep AI research (deep/oracle modes)
|
|
90
|
+
- \`nia.index\` - Index new GitHub repos, documentation sites, or arXiv papers
|
|
91
|
+
- \`nia.nia_read\` - Read specific files from indexed sources
|
|
92
|
+
- \`nia.nia_grep\` - Regex search across indexed codebases
|
|
93
|
+
- \`nia.nia_explore\` - Browse file trees of indexed repos/docs
|
|
94
|
+
- \`nia.manage_resource\` - List/check status of indexed sources
|
|
95
|
+
|
|
96
|
+
**Workflow:**
|
|
97
|
+
1. Check what's indexed: \`nia.manage_resource(action: "list")\`
|
|
98
|
+
2. Search or research based on the user's question
|
|
99
|
+
3. Read specific files if needed for deeper context
|
|
100
|
+
|
|
101
|
+
Use these tools to provide accurate, up-to-date information instead of relying solely on training data.`;
|
|
102
|
+
|
|
103
|
+
// src/services/logger.ts
|
|
104
|
+
var DEBUG = process.env.NIA_DEBUG === "true" || process.env.NIA_DEBUG === "1";
|
|
105
|
+
function log(message, data) {
|
|
106
|
+
if (!DEBUG)
|
|
107
|
+
return;
|
|
108
|
+
const timestamp = new Date().toISOString();
|
|
109
|
+
const prefix = `[nia-opencode ${timestamp}]`;
|
|
110
|
+
if (data) {
|
|
111
|
+
console.log(prefix, message, JSON.stringify(data, null, 2));
|
|
112
|
+
} else {
|
|
113
|
+
console.log(prefix, message);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/index.ts
|
|
118
|
+
var NiaPlugin = async (ctx) => {
|
|
119
|
+
const { directory } = ctx;
|
|
120
|
+
const injectedSessions = new Set;
|
|
121
|
+
log("Plugin initialized", { directory, configured: isConfigured() });
|
|
122
|
+
if (!isConfigured()) {
|
|
123
|
+
log("Plugin disabled - NIA_API_KEY not set");
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
"chat.message": async (input, output) => {
|
|
127
|
+
if (!isConfigured())
|
|
128
|
+
return;
|
|
129
|
+
const start = Date.now();
|
|
130
|
+
try {
|
|
131
|
+
const textParts = output.parts.filter((p) => p.type === "text");
|
|
132
|
+
if (textParts.length === 0) {
|
|
133
|
+
log("chat.message: no text parts found");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const userMessage = textParts.map((p) => p.text).join(`
|
|
137
|
+
`);
|
|
138
|
+
if (!userMessage.trim()) {
|
|
139
|
+
log("chat.message: empty message, skipping");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
log("chat.message: processing", {
|
|
143
|
+
messagePreview: userMessage.slice(0, 100),
|
|
144
|
+
partsCount: output.parts.length
|
|
145
|
+
});
|
|
146
|
+
const { detected, match } = detectResearchKeyword(userMessage);
|
|
147
|
+
if (detected) {
|
|
148
|
+
log("chat.message: research keyword detected", { match });
|
|
149
|
+
const nudgePart = {
|
|
150
|
+
id: `nia-nudge-${Date.now()}`,
|
|
151
|
+
sessionID: input.sessionID,
|
|
152
|
+
messageID: output.message.id,
|
|
153
|
+
type: "text",
|
|
154
|
+
text: NIA_NUDGE_MESSAGE,
|
|
155
|
+
synthetic: true
|
|
156
|
+
};
|
|
157
|
+
output.parts.push(nudgePart);
|
|
158
|
+
const duration = Date.now() - start;
|
|
159
|
+
log("chat.message: nudge injected", { duration, match });
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
log("chat.message: ERROR", { error: String(error) });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
var src_default = NiaPlugin;
|
|
168
|
+
export {
|
|
169
|
+
src_default as default,
|
|
170
|
+
NiaPlugin
|
|
171
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function detectResearchKeyword(text: string): {
|
|
2
|
+
detected: boolean;
|
|
3
|
+
match?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const NIA_NUDGE_MESSAGE = "[NIA KNOWLEDGE TRIGGER]\nThe user is asking for research, documentation, or codebase exploration. You have access to **Nia** tools via MCP.\n\n**Available Nia tools:**\n- `nia.search` - Semantic search across indexed repos, docs, and papers\n- `nia.nia_research` - Web search (quick) or deep AI research (deep/oracle modes)\n- `nia.index` - Index new GitHub repos, documentation sites, or arXiv papers\n- `nia.nia_read` - Read specific files from indexed sources\n- `nia.nia_grep` - Regex search across indexed codebases\n- `nia.nia_explore` - Browse file trees of indexed repos/docs\n- `nia.manage_resource` - List/check status of indexed sources\n\n**Workflow:**\n1. Check what's indexed: `nia.manage_resource(action: \"list\")`\n2. Search or research based on the user's question\n3. Read specific files if needed for deeper context\n\nUse these tools to provide accurate, up-to-date information instead of relying solely on training data.";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function log(message: string, data?: Record<string, unknown>): void;
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nia-opencode",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenCode plugin that integrates Nia Knowledge Agent for research and documentation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"nia-opencode": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target node && bun build ./src/cli.ts --outfile ./dist/cli.js --target node && tsc --emitDeclarationOnly",
|
|
13
|
+
"dev": "tsc --watch",
|
|
14
|
+
"typecheck": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"opencode",
|
|
18
|
+
"plugin",
|
|
19
|
+
"nia",
|
|
20
|
+
"knowledge",
|
|
21
|
+
"research",
|
|
22
|
+
"documentation",
|
|
23
|
+
"ai",
|
|
24
|
+
"coding-agent"
|
|
25
|
+
],
|
|
26
|
+
"author": "Nia",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/nozomio-labs/nia-opencode"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@opencode-ai/plugin": "^1.0.162"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/bun": "latest",
|
|
37
|
+
"typescript": "^5.7.3"
|
|
38
|
+
},
|
|
39
|
+
"opencode": {
|
|
40
|
+
"type": "plugin",
|
|
41
|
+
"hooks": [
|
|
42
|
+
"chat.message"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist"
|
|
47
|
+
]
|
|
48
|
+
}
|