kernelbot 1.0.22 → 1.0.24
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 +3 -0
- package/README.md +195 -24
- package/config.example.yaml +5 -0
- package/package.json +2 -2
- package/src/tools/index.js +3 -0
- package/src/tools/jira.js +232 -0
- package/src/utils/config.js +32 -0
- package/src/utils/display.js +8 -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
|
@@ -177,6 +177,12 @@ export function loadConfig() {
|
|
|
177
177
|
if (!config.github) config.github = {};
|
|
178
178
|
config.github.token = process.env.GITHUB_TOKEN;
|
|
179
179
|
}
|
|
180
|
+
if (process.env.JIRA_BASE_URL || process.env.JIRA_EMAIL || process.env.JIRA_API_TOKEN) {
|
|
181
|
+
if (!config.jira) config.jira = {};
|
|
182
|
+
if (process.env.JIRA_BASE_URL) config.jira.base_url = process.env.JIRA_BASE_URL;
|
|
183
|
+
if (process.env.JIRA_EMAIL) config.jira.email = process.env.JIRA_EMAIL;
|
|
184
|
+
if (process.env.JIRA_API_TOKEN) config.jira.api_token = process.env.JIRA_API_TOKEN;
|
|
185
|
+
}
|
|
180
186
|
|
|
181
187
|
return config;
|
|
182
188
|
}
|
|
@@ -220,6 +226,18 @@ export function saveCredential(config, envKey, value) {
|
|
|
220
226
|
case 'TELEGRAM_BOT_TOKEN':
|
|
221
227
|
config.telegram.bot_token = value;
|
|
222
228
|
break;
|
|
229
|
+
case 'JIRA_BASE_URL':
|
|
230
|
+
if (!config.jira) config.jira = {};
|
|
231
|
+
config.jira.base_url = value;
|
|
232
|
+
break;
|
|
233
|
+
case 'JIRA_EMAIL':
|
|
234
|
+
if (!config.jira) config.jira = {};
|
|
235
|
+
config.jira.email = value;
|
|
236
|
+
break;
|
|
237
|
+
case 'JIRA_API_TOKEN':
|
|
238
|
+
if (!config.jira) config.jira = {};
|
|
239
|
+
config.jira.api_token = value;
|
|
240
|
+
break;
|
|
223
241
|
}
|
|
224
242
|
|
|
225
243
|
// Also set in process.env so tools pick it up
|
|
@@ -239,5 +257,19 @@ export function getMissingCredential(toolName, config) {
|
|
|
239
257
|
}
|
|
240
258
|
}
|
|
241
259
|
|
|
260
|
+
const jiraTools = ['jira_get_ticket', 'jira_search_tickets', 'jira_list_my_tickets', 'jira_get_project_tickets'];
|
|
261
|
+
|
|
262
|
+
if (jiraTools.includes(toolName)) {
|
|
263
|
+
if (!config.jira?.base_url && !process.env.JIRA_BASE_URL) {
|
|
264
|
+
return { envKey: 'JIRA_BASE_URL', label: 'JIRA Base URL (e.g. https://yourcompany.atlassian.net)' };
|
|
265
|
+
}
|
|
266
|
+
if (!config.jira?.email && !process.env.JIRA_EMAIL) {
|
|
267
|
+
return { envKey: 'JIRA_EMAIL', label: 'JIRA Email / Username' };
|
|
268
|
+
}
|
|
269
|
+
if (!config.jira?.api_token && !process.env.JIRA_API_TOKEN) {
|
|
270
|
+
return { envKey: 'JIRA_API_TOKEN', label: 'JIRA API Token' };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
242
274
|
return null;
|
|
243
275
|
}
|
package/src/utils/display.js
CHANGED
|
@@ -26,19 +26,17 @@ const LOGO = `
|
|
|
26
26
|
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝╚═════╝ ╚═════╝ ╚═╝
|
|
27
27
|
`;
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
const
|
|
31
|
-
'#
|
|
32
|
-
'#
|
|
33
|
-
'#
|
|
34
|
-
'#
|
|
35
|
-
'#
|
|
36
|
-
'#1E90FF', // Dodger Blue
|
|
37
|
-
'#9370DB' // Medium Purple
|
|
29
|
+
// White to ~70% black gradient
|
|
30
|
+
const monoGradient = gradient([
|
|
31
|
+
'#FFFFFF',
|
|
32
|
+
'#D0D0D0',
|
|
33
|
+
'#A0A0A0',
|
|
34
|
+
'#707070',
|
|
35
|
+
'#4D4D4D',
|
|
38
36
|
]);
|
|
39
37
|
|
|
40
38
|
export function showLogo() {
|
|
41
|
-
console.log(
|
|
39
|
+
console.log(monoGradient.multiline(LOGO));
|
|
42
40
|
console.log(chalk.dim(` AI Engineering Agent — v${getVersion()}\n`));
|
|
43
41
|
console.log(
|
|
44
42
|
boxen(
|
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!* 🤖
|