kernelbot 1.0.23 → 1.0.25
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/.agents/skills/interface-design/SKILL.md +391 -0
- package/.agents/skills/interface-design/references/critique.md +67 -0
- package/.agents/skills/interface-design/references/example.md +86 -0
- package/.agents/skills/interface-design/references/principles.md +235 -0
- package/.agents/skills/interface-design/references/validation.md +48 -0
- package/.env.example +11 -0
- package/README.md +217 -26
- package/bin/kernel.js +30 -21
- package/config.example.yaml +7 -1
- package/package.json +6 -2
- package/src/agent.js +27 -43
- package/src/providers/anthropic.js +44 -0
- package/src/providers/base.js +30 -0
- package/src/providers/index.js +36 -0
- package/src/providers/models.js +54 -0
- package/src/providers/openai-compat.js +163 -0
- package/src/tools/index.js +3 -0
- package/src/tools/jira.js +232 -0
- package/src/utils/config.js +188 -10
- package/kernelbot/hello-world.md +0 -21
- package/kernelbot/newnew-1.md +0 -11
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create an axios instance configured for the JIRA REST API.
|
|
5
|
+
* Supports both Atlassian Cloud (*.atlassian.net) and JIRA Server instances.
|
|
6
|
+
*
|
|
7
|
+
* Authentication:
|
|
8
|
+
* - Cloud: email + API token (Basic auth)
|
|
9
|
+
* - Server: username + password/token (Basic auth)
|
|
10
|
+
*
|
|
11
|
+
* Config precedence: config.jira.* → JIRA_* env vars
|
|
12
|
+
*/
|
|
13
|
+
function getJiraClient(config) {
|
|
14
|
+
const baseUrl = config.jira?.base_url || process.env.JIRA_BASE_URL;
|
|
15
|
+
const email = config.jira?.email || process.env.JIRA_EMAIL;
|
|
16
|
+
const token = config.jira?.api_token || process.env.JIRA_API_TOKEN;
|
|
17
|
+
|
|
18
|
+
if (!baseUrl) throw new Error('JIRA base URL not configured. Set JIRA_BASE_URL or jira.base_url in config.');
|
|
19
|
+
if (!email) throw new Error('JIRA email/username not configured. Set JIRA_EMAIL or jira.email in config.');
|
|
20
|
+
if (!token) throw new Error('JIRA API token not configured. Set JIRA_API_TOKEN or jira.api_token in config.');
|
|
21
|
+
|
|
22
|
+
const cleanBase = baseUrl.replace(/\/+$/, '');
|
|
23
|
+
|
|
24
|
+
return axios.create({
|
|
25
|
+
baseURL: `${cleanBase}/rest/api/2`,
|
|
26
|
+
auth: { username: email, password: token },
|
|
27
|
+
headers: {
|
|
28
|
+
'Accept': 'application/json',
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
},
|
|
31
|
+
timeout: 30000,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Extract structured ticket data from a JIRA issue response.
|
|
37
|
+
*/
|
|
38
|
+
function formatIssue(issue) {
|
|
39
|
+
const fields = issue.fields || {};
|
|
40
|
+
return {
|
|
41
|
+
key: issue.key,
|
|
42
|
+
summary: fields.summary || '',
|
|
43
|
+
description: fields.description || '',
|
|
44
|
+
status: fields.status?.name || '',
|
|
45
|
+
assignee: fields.assignee?.displayName || 'Unassigned',
|
|
46
|
+
reporter: fields.reporter?.displayName || '',
|
|
47
|
+
priority: fields.priority?.name || '',
|
|
48
|
+
type: fields.issuetype?.name || '',
|
|
49
|
+
labels: fields.labels || [],
|
|
50
|
+
created: fields.created || '',
|
|
51
|
+
updated: fields.updated || '',
|
|
52
|
+
project: fields.project?.key || '',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const definitions = [
|
|
57
|
+
{
|
|
58
|
+
name: 'jira_get_ticket',
|
|
59
|
+
description: 'Get details of a specific JIRA ticket by its key (e.g. PROJ-123).',
|
|
60
|
+
input_schema: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
ticket_key: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'The JIRA ticket key (e.g. PROJ-123)',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
required: ['ticket_key'],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'jira_search_tickets',
|
|
73
|
+
description: 'Search for JIRA tickets using JQL (JIRA Query Language). Example: "project = PROJ AND status = Open".',
|
|
74
|
+
input_schema: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
jql_query: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
description: 'JQL query string',
|
|
80
|
+
},
|
|
81
|
+
max_results: {
|
|
82
|
+
type: 'number',
|
|
83
|
+
description: 'Maximum number of results to return (default 20)',
|
|
84
|
+
default: 20,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ['jql_query'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'jira_list_my_tickets',
|
|
92
|
+
description: 'List JIRA tickets assigned to a user. Defaults to the authenticated user.',
|
|
93
|
+
input_schema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
assignee: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
description: 'Assignee username or "currentUser()" (default)',
|
|
99
|
+
default: 'currentUser()',
|
|
100
|
+
},
|
|
101
|
+
max_results: {
|
|
102
|
+
type: 'number',
|
|
103
|
+
description: 'Maximum number of results to return (default 20)',
|
|
104
|
+
default: 20,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'jira_get_project_tickets',
|
|
111
|
+
description: 'Get tickets from a specific JIRA project.',
|
|
112
|
+
input_schema: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties: {
|
|
115
|
+
project_key: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
description: 'The JIRA project key (e.g. PROJ)',
|
|
118
|
+
},
|
|
119
|
+
max_results: {
|
|
120
|
+
type: 'number',
|
|
121
|
+
description: 'Maximum number of results to return (default 20)',
|
|
122
|
+
default: 20,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
required: ['project_key'],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
export const handlers = {
|
|
131
|
+
/**
|
|
132
|
+
* Get details of a specific JIRA ticket.
|
|
133
|
+
* @param {{ ticket_key: string }} params
|
|
134
|
+
* @param {{ config: object }} context
|
|
135
|
+
*/
|
|
136
|
+
jira_get_ticket: async (params, context) => {
|
|
137
|
+
try {
|
|
138
|
+
const client = getJiraClient(context.config);
|
|
139
|
+
const { data } = await client.get(`/issue/${params.ticket_key}`);
|
|
140
|
+
return { ticket: formatIssue(data) };
|
|
141
|
+
} catch (err) {
|
|
142
|
+
if (err.response?.status === 404) {
|
|
143
|
+
return { error: `Ticket ${params.ticket_key} not found` };
|
|
144
|
+
}
|
|
145
|
+
return { error: err.response?.data?.errorMessages?.join('; ') || err.message };
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Search for JIRA tickets using JQL.
|
|
151
|
+
* @param {{ jql_query: string, max_results?: number }} params
|
|
152
|
+
* @param {{ config: object }} context
|
|
153
|
+
*/
|
|
154
|
+
jira_search_tickets: async (params, context) => {
|
|
155
|
+
try {
|
|
156
|
+
const client = getJiraClient(context.config);
|
|
157
|
+
const maxResults = params.max_results || 20;
|
|
158
|
+
|
|
159
|
+
const { data } = await client.get('/search', {
|
|
160
|
+
params: {
|
|
161
|
+
jql: params.jql_query,
|
|
162
|
+
maxResults,
|
|
163
|
+
fields: 'summary,description,status,assignee,reporter,priority,issuetype,labels,created,updated,project',
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
total: data.total,
|
|
169
|
+
tickets: (data.issues || []).map(formatIssue),
|
|
170
|
+
};
|
|
171
|
+
} catch (err) {
|
|
172
|
+
return { error: err.response?.data?.errorMessages?.join('; ') || err.message };
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* List tickets assigned to a user.
|
|
178
|
+
* @param {{ assignee?: string, max_results?: number }} params
|
|
179
|
+
* @param {{ config: object }} context
|
|
180
|
+
*/
|
|
181
|
+
jira_list_my_tickets: async (params, context) => {
|
|
182
|
+
try {
|
|
183
|
+
const client = getJiraClient(context.config);
|
|
184
|
+
const assignee = params.assignee || 'currentUser()';
|
|
185
|
+
const maxResults = params.max_results || 20;
|
|
186
|
+
const jql = `assignee = ${assignee} ORDER BY updated DESC`;
|
|
187
|
+
|
|
188
|
+
const { data } = await client.get('/search', {
|
|
189
|
+
params: {
|
|
190
|
+
jql,
|
|
191
|
+
maxResults,
|
|
192
|
+
fields: 'summary,description,status,assignee,reporter,priority,issuetype,labels,created,updated,project',
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
total: data.total,
|
|
198
|
+
tickets: (data.issues || []).map(formatIssue),
|
|
199
|
+
};
|
|
200
|
+
} catch (err) {
|
|
201
|
+
return { error: err.response?.data?.errorMessages?.join('; ') || err.message };
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get tickets from a specific JIRA project.
|
|
207
|
+
* @param {{ project_key: string, max_results?: number }} params
|
|
208
|
+
* @param {{ config: object }} context
|
|
209
|
+
*/
|
|
210
|
+
jira_get_project_tickets: async (params, context) => {
|
|
211
|
+
try {
|
|
212
|
+
const client = getJiraClient(context.config);
|
|
213
|
+
const maxResults = params.max_results || 20;
|
|
214
|
+
const jql = `project = ${params.project_key} ORDER BY updated DESC`;
|
|
215
|
+
|
|
216
|
+
const { data } = await client.get('/search', {
|
|
217
|
+
params: {
|
|
218
|
+
jql,
|
|
219
|
+
maxResults,
|
|
220
|
+
fields: 'summary,description,status,assignee,reporter,priority,issuetype,labels,created,updated,project',
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
total: data.total,
|
|
226
|
+
tickets: (data.issues || []).map(formatIssue),
|
|
227
|
+
};
|
|
228
|
+
} catch (err) {
|
|
229
|
+
return { error: err.response?.data?.errorMessages?.join('; ') || err.message };
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
};
|
package/src/utils/config.js
CHANGED
|
@@ -5,13 +5,15 @@ import { createInterface } from 'readline';
|
|
|
5
5
|
import yaml from 'js-yaml';
|
|
6
6
|
import dotenv from 'dotenv';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
|
+
import { PROVIDERS } from '../providers/models.js';
|
|
8
9
|
|
|
9
10
|
const DEFAULTS = {
|
|
10
11
|
bot: {
|
|
11
12
|
name: 'KernelBot',
|
|
12
13
|
description: 'AI engineering agent with full OS control',
|
|
13
14
|
},
|
|
14
|
-
|
|
15
|
+
brain: {
|
|
16
|
+
provider: 'anthropic',
|
|
15
17
|
model: 'claude-sonnet-4-20250514',
|
|
16
18
|
max_tokens: 8192,
|
|
17
19
|
temperature: 0.3,
|
|
@@ -90,9 +92,126 @@ function ask(rl, question) {
|
|
|
90
92
|
return new Promise((res) => rl.question(question, res));
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Migrate legacy `anthropic` config section → `brain` section.
|
|
97
|
+
*/
|
|
98
|
+
function migrateAnthropicConfig(config) {
|
|
99
|
+
if (config.anthropic && !config.brain) {
|
|
100
|
+
config.brain = {
|
|
101
|
+
provider: 'anthropic',
|
|
102
|
+
model: config.anthropic.model || DEFAULTS.brain.model,
|
|
103
|
+
max_tokens: config.anthropic.max_tokens || DEFAULTS.brain.max_tokens,
|
|
104
|
+
temperature: config.anthropic.temperature ?? DEFAULTS.brain.temperature,
|
|
105
|
+
max_tool_depth: config.anthropic.max_tool_depth || DEFAULTS.brain.max_tool_depth,
|
|
106
|
+
};
|
|
107
|
+
if (config.anthropic.api_key) {
|
|
108
|
+
config.brain.api_key = config.anthropic.api_key;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return config;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Interactive provider → model picker.
|
|
116
|
+
*/
|
|
117
|
+
export async function promptProviderSelection(rl) {
|
|
118
|
+
const providerKeys = Object.keys(PROVIDERS);
|
|
119
|
+
|
|
120
|
+
console.log(chalk.bold('\n Select AI provider:\n'));
|
|
121
|
+
providerKeys.forEach((key, i) => {
|
|
122
|
+
console.log(` ${chalk.cyan(`${i + 1}.`)} ${PROVIDERS[key].name}`);
|
|
123
|
+
});
|
|
124
|
+
console.log('');
|
|
125
|
+
|
|
126
|
+
let providerIdx;
|
|
127
|
+
while (true) {
|
|
128
|
+
const input = await ask(rl, chalk.cyan(' Provider (number): '));
|
|
129
|
+
providerIdx = parseInt(input.trim(), 10) - 1;
|
|
130
|
+
if (providerIdx >= 0 && providerIdx < providerKeys.length) break;
|
|
131
|
+
console.log(chalk.dim(' Invalid choice, try again.'));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const providerKey = providerKeys[providerIdx];
|
|
135
|
+
const provider = PROVIDERS[providerKey];
|
|
136
|
+
|
|
137
|
+
console.log(chalk.bold(`\n Select model for ${provider.name}:\n`));
|
|
138
|
+
provider.models.forEach((m, i) => {
|
|
139
|
+
console.log(` ${chalk.cyan(`${i + 1}.`)} ${m.label} (${m.id})`);
|
|
140
|
+
});
|
|
141
|
+
console.log('');
|
|
142
|
+
|
|
143
|
+
let modelIdx;
|
|
144
|
+
while (true) {
|
|
145
|
+
const input = await ask(rl, chalk.cyan(' Model (number): '));
|
|
146
|
+
modelIdx = parseInt(input.trim(), 10) - 1;
|
|
147
|
+
if (modelIdx >= 0 && modelIdx < provider.models.length) break;
|
|
148
|
+
console.log(chalk.dim(' Invalid choice, try again.'));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const model = provider.models[modelIdx];
|
|
152
|
+
return { providerKey, modelId: model.id };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Save provider and model to config.yaml.
|
|
157
|
+
*/
|
|
158
|
+
export function saveProviderToYaml(providerKey, modelId) {
|
|
159
|
+
const configDir = getConfigDir();
|
|
160
|
+
mkdirSync(configDir, { recursive: true });
|
|
161
|
+
const configPath = join(configDir, 'config.yaml');
|
|
162
|
+
|
|
163
|
+
let existing = {};
|
|
164
|
+
if (existsSync(configPath)) {
|
|
165
|
+
existing = yaml.load(readFileSync(configPath, 'utf-8')) || {};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
existing.brain = {
|
|
169
|
+
...(existing.brain || {}),
|
|
170
|
+
provider: providerKey,
|
|
171
|
+
model: modelId,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Remove legacy anthropic section if migrating
|
|
175
|
+
delete existing.anthropic;
|
|
176
|
+
|
|
177
|
+
writeFileSync(configPath, yaml.dump(existing, { lineWidth: -1 }));
|
|
178
|
+
return configPath;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Full interactive flow: change brain model + optionally enter API key.
|
|
183
|
+
*/
|
|
184
|
+
export async function changeBrainModel(config, rl) {
|
|
185
|
+
const { providerKey, modelId } = await promptProviderSelection(rl);
|
|
186
|
+
|
|
187
|
+
const providerDef = PROVIDERS[providerKey];
|
|
188
|
+
const savedPath = saveProviderToYaml(providerKey, modelId);
|
|
189
|
+
console.log(chalk.dim(`\n Saved to ${savedPath}`));
|
|
190
|
+
|
|
191
|
+
// Update live config
|
|
192
|
+
config.brain.provider = providerKey;
|
|
193
|
+
config.brain.model = modelId;
|
|
194
|
+
|
|
195
|
+
// Check if we have the API key for this provider
|
|
196
|
+
const envKey = providerDef.envKey;
|
|
197
|
+
const currentKey = process.env[envKey];
|
|
198
|
+
if (!currentKey) {
|
|
199
|
+
const key = await ask(rl, chalk.cyan(`\n ${providerDef.name} API key (${envKey}): `));
|
|
200
|
+
if (key.trim()) {
|
|
201
|
+
saveCredential(config, envKey, key.trim());
|
|
202
|
+
config.brain.api_key = key.trim();
|
|
203
|
+
console.log(chalk.dim(' Saved.\n'));
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
config.brain.api_key = currentKey;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return config;
|
|
210
|
+
}
|
|
211
|
+
|
|
93
212
|
async function promptForMissing(config) {
|
|
94
213
|
const missing = [];
|
|
95
|
-
if (!config.
|
|
214
|
+
if (!config.brain.api_key) missing.push('brain_api_key');
|
|
96
215
|
if (!config.telegram.bot_token) missing.push('TELEGRAM_BOT_TOKEN');
|
|
97
216
|
|
|
98
217
|
if (missing.length === 0) return config;
|
|
@@ -110,10 +229,19 @@ async function promptForMissing(config) {
|
|
|
110
229
|
existingEnv = readFileSync(envPath, 'utf-8');
|
|
111
230
|
}
|
|
112
231
|
|
|
113
|
-
if (!mutableConfig.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
232
|
+
if (!mutableConfig.brain.api_key) {
|
|
233
|
+
// Run provider selection flow
|
|
234
|
+
const { providerKey, modelId } = await promptProviderSelection(rl);
|
|
235
|
+
mutableConfig.brain.provider = providerKey;
|
|
236
|
+
mutableConfig.brain.model = modelId;
|
|
237
|
+
saveProviderToYaml(providerKey, modelId);
|
|
238
|
+
|
|
239
|
+
const providerDef = PROVIDERS[providerKey];
|
|
240
|
+
const envKey = providerDef.envKey;
|
|
241
|
+
|
|
242
|
+
const key = await ask(rl, chalk.cyan(`\n ${providerDef.name} API key: `));
|
|
243
|
+
mutableConfig.brain.api_key = key.trim();
|
|
244
|
+
envLines.push(`${envKey}=${key.trim()}`);
|
|
117
245
|
}
|
|
118
246
|
|
|
119
247
|
if (!mutableConfig.telegram.bot_token) {
|
|
@@ -164,12 +292,21 @@ export function loadConfig() {
|
|
|
164
292
|
fileConfig = yaml.load(raw) || {};
|
|
165
293
|
}
|
|
166
294
|
|
|
295
|
+
// Backward compat: migrate anthropic → brain
|
|
296
|
+
migrateAnthropicConfig(fileConfig);
|
|
297
|
+
|
|
167
298
|
const config = deepMerge(DEFAULTS, fileConfig);
|
|
168
299
|
|
|
169
|
-
// Overlay env vars for
|
|
170
|
-
|
|
171
|
-
|
|
300
|
+
// Overlay env vars for brain API key based on provider
|
|
301
|
+
const providerDef = PROVIDERS[config.brain.provider];
|
|
302
|
+
if (providerDef && process.env[providerDef.envKey]) {
|
|
303
|
+
config.brain.api_key = process.env[providerDef.envKey];
|
|
304
|
+
}
|
|
305
|
+
// Legacy fallback: ANTHROPIC_API_KEY for anthropic provider
|
|
306
|
+
if (config.brain.provider === 'anthropic' && !config.brain.api_key && process.env.ANTHROPIC_API_KEY) {
|
|
307
|
+
config.brain.api_key = process.env.ANTHROPIC_API_KEY;
|
|
172
308
|
}
|
|
309
|
+
|
|
173
310
|
if (process.env.TELEGRAM_BOT_TOKEN) {
|
|
174
311
|
config.telegram.bot_token = process.env.TELEGRAM_BOT_TOKEN;
|
|
175
312
|
}
|
|
@@ -177,6 +314,12 @@ export function loadConfig() {
|
|
|
177
314
|
if (!config.github) config.github = {};
|
|
178
315
|
config.github.token = process.env.GITHUB_TOKEN;
|
|
179
316
|
}
|
|
317
|
+
if (process.env.JIRA_BASE_URL || process.env.JIRA_EMAIL || process.env.JIRA_API_TOKEN) {
|
|
318
|
+
if (!config.jira) config.jira = {};
|
|
319
|
+
if (process.env.JIRA_BASE_URL) config.jira.base_url = process.env.JIRA_BASE_URL;
|
|
320
|
+
if (process.env.JIRA_EMAIL) config.jira.email = process.env.JIRA_EMAIL;
|
|
321
|
+
if (process.env.JIRA_API_TOKEN) config.jira.api_token = process.env.JIRA_API_TOKEN;
|
|
322
|
+
}
|
|
180
323
|
|
|
181
324
|
return config;
|
|
182
325
|
}
|
|
@@ -215,11 +358,32 @@ export function saveCredential(config, envKey, value) {
|
|
|
215
358
|
config.github.token = value;
|
|
216
359
|
break;
|
|
217
360
|
case 'ANTHROPIC_API_KEY':
|
|
218
|
-
config.anthropic.api_key = value;
|
|
361
|
+
if (config.brain.provider === 'anthropic') config.brain.api_key = value;
|
|
362
|
+
break;
|
|
363
|
+
case 'OPENAI_API_KEY':
|
|
364
|
+
if (config.brain.provider === 'openai') config.brain.api_key = value;
|
|
365
|
+
break;
|
|
366
|
+
case 'GOOGLE_API_KEY':
|
|
367
|
+
if (config.brain.provider === 'google') config.brain.api_key = value;
|
|
368
|
+
break;
|
|
369
|
+
case 'GROQ_API_KEY':
|
|
370
|
+
if (config.brain.provider === 'groq') config.brain.api_key = value;
|
|
219
371
|
break;
|
|
220
372
|
case 'TELEGRAM_BOT_TOKEN':
|
|
221
373
|
config.telegram.bot_token = value;
|
|
222
374
|
break;
|
|
375
|
+
case 'JIRA_BASE_URL':
|
|
376
|
+
if (!config.jira) config.jira = {};
|
|
377
|
+
config.jira.base_url = value;
|
|
378
|
+
break;
|
|
379
|
+
case 'JIRA_EMAIL':
|
|
380
|
+
if (!config.jira) config.jira = {};
|
|
381
|
+
config.jira.email = value;
|
|
382
|
+
break;
|
|
383
|
+
case 'JIRA_API_TOKEN':
|
|
384
|
+
if (!config.jira) config.jira = {};
|
|
385
|
+
config.jira.api_token = value;
|
|
386
|
+
break;
|
|
223
387
|
}
|
|
224
388
|
|
|
225
389
|
// Also set in process.env so tools pick it up
|
|
@@ -239,5 +403,19 @@ export function getMissingCredential(toolName, config) {
|
|
|
239
403
|
}
|
|
240
404
|
}
|
|
241
405
|
|
|
406
|
+
const jiraTools = ['jira_get_ticket', 'jira_search_tickets', 'jira_list_my_tickets', 'jira_get_project_tickets'];
|
|
407
|
+
|
|
408
|
+
if (jiraTools.includes(toolName)) {
|
|
409
|
+
if (!config.jira?.base_url && !process.env.JIRA_BASE_URL) {
|
|
410
|
+
return { envKey: 'JIRA_BASE_URL', label: 'JIRA Base URL (e.g. https://yourcompany.atlassian.net)' };
|
|
411
|
+
}
|
|
412
|
+
if (!config.jira?.email && !process.env.JIRA_EMAIL) {
|
|
413
|
+
return { envKey: 'JIRA_EMAIL', label: 'JIRA Email / Username' };
|
|
414
|
+
}
|
|
415
|
+
if (!config.jira?.api_token && !process.env.JIRA_API_TOKEN) {
|
|
416
|
+
return { envKey: 'JIRA_API_TOKEN', label: 'JIRA API Token' };
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
242
420
|
return null;
|
|
243
421
|
}
|
package/kernelbot/hello-world.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Hello World! 🌍
|
|
2
|
-
|
|
3
|
-
This is a **test file** created to verify that everything is working properly.
|
|
4
|
-
|
|
5
|
-
## About This File
|
|
6
|
-
|
|
7
|
-
This file demonstrates:
|
|
8
|
-
- *Basic markdown formatting*
|
|
9
|
-
- **Bold text**
|
|
10
|
-
- Simple lists
|
|
11
|
-
|
|
12
|
-
## Features Tested
|
|
13
|
-
|
|
14
|
-
- ✅ File creation
|
|
15
|
-
- ✅ Markdown formatting
|
|
16
|
-
- ✅ Emoji support 🚀
|
|
17
|
-
- ✅ Basic structure
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
*Created as a test for the KernelBot project!* 🤖
|