@sweny-ai/core 0.1.7 → 0.1.9
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/dist/cli/config.js +2 -2
- package/dist/cli/main.js +2 -0
- package/dist/mcp.js +5 -2
- package/dist/skills/betterstack.d.ts +8 -0
- package/dist/skills/betterstack.js +151 -0
- package/dist/skills/index.d.ts +2 -1
- package/dist/skills/index.js +3 -2
- package/dist/skills/linear.js +35 -0
- package/dist/workflows/triage.js +1 -1
- package/package.json +1 -1
package/dist/cli/config.js
CHANGED
|
@@ -274,7 +274,7 @@ export function validateInputs(config) {
|
|
|
274
274
|
break;
|
|
275
275
|
case "betterstack":
|
|
276
276
|
if (!config.observabilityCredentials.apiToken)
|
|
277
|
-
errors.push("Missing: BETTERSTACK_API_TOKEN is required for betterstack provider");
|
|
277
|
+
errors.push("Missing: BETTERSTACK_API_TOKEN, BETTERSTACK_TELEMETRY_TOKEN, or BETTERSTACK_UPTIME_TOKEN is required for betterstack provider");
|
|
278
278
|
if (!config.observabilityCredentials.sourceId && !config.observabilityCredentials.tableName)
|
|
279
279
|
errors.push("Missing: either BETTERSTACK_SOURCE_ID (--betterstack-source-id) or BETTERSTACK_TABLE_NAME (--betterstack-table-name) is required for betterstack provider");
|
|
280
280
|
break;
|
|
@@ -501,7 +501,7 @@ function parseObservabilityCredentials(provider, options, fileConfig = {}) {
|
|
|
501
501
|
};
|
|
502
502
|
case "betterstack":
|
|
503
503
|
return {
|
|
504
|
-
apiToken: env.BETTERSTACK_API_TOKEN || "",
|
|
504
|
+
apiToken: env.BETTERSTACK_API_TOKEN || env.BETTERSTACK_TELEMETRY_TOKEN || env.BETTERSTACK_UPTIME_TOKEN || "",
|
|
505
505
|
sourceId: options.betterstackSourceId || env.BETTERSTACK_SOURCE_ID || f("betterstack-source-id") || "",
|
|
506
506
|
tableName: options.betterstackTableName || env.BETTERSTACK_TABLE_NAME || f("betterstack-table-name") || "",
|
|
507
507
|
};
|
package/dist/cli/main.js
CHANGED
package/dist/mcp.js
CHANGED
|
@@ -106,9 +106,12 @@ export function buildAutoMcpServers(config) {
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
// Better Stack MCP — HTTP remote MCP; Bearer token auth.
|
|
109
|
-
//
|
|
109
|
+
// BetterStack uses separate tokens for Uptime vs Telemetry APIs.
|
|
110
|
+
// The MCP server accepts the telemetry token for log/metric queries.
|
|
111
|
+
// Accept: BETTERSTACK_API_TOKEN (legacy), BETTERSTACK_TELEMETRY_TOKEN, or BETTERSTACK_UPTIME_TOKEN.
|
|
112
|
+
// Injected whenever any token is present (not just when it's the primary provider)
|
|
110
113
|
// because BetterStack logs complement any primary observability provider.
|
|
111
|
-
const bsApiToken = creds.BETTERSTACK_API_TOKEN;
|
|
114
|
+
const bsApiToken = creds.BETTERSTACK_API_TOKEN || creds.BETTERSTACK_TELEMETRY_TOKEN || creds.BETTERSTACK_UPTIME_TOKEN;
|
|
112
115
|
if (bsApiToken) {
|
|
113
116
|
auto["betterstack"] = {
|
|
114
117
|
type: "http",
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BetterStack Skill
|
|
3
|
+
*
|
|
4
|
+
* Telemetry REST API for source management + ClickHouse HTTP for log queries.
|
|
5
|
+
* CI-native alternative to the BetterStack MCP server.
|
|
6
|
+
*/
|
|
7
|
+
// ─── REST API (source management) ──────────────────────────────
|
|
8
|
+
async function bsApi(path, ctx) {
|
|
9
|
+
const res = await fetch(`https://telemetry.betterstack.com/api/v1${path}`, {
|
|
10
|
+
headers: { Authorization: `Bearer ${ctx.config.BETTERSTACK_API_TOKEN}` },
|
|
11
|
+
signal: AbortSignal.timeout(30_000),
|
|
12
|
+
});
|
|
13
|
+
if (!res.ok)
|
|
14
|
+
throw new Error(`[BetterStack] API request failed (HTTP ${res.status}): ${await res.text()}`);
|
|
15
|
+
return res.json();
|
|
16
|
+
}
|
|
17
|
+
// ─── ClickHouse HTTP (log queries) ─────────────────────────────
|
|
18
|
+
async function bsQuery(sql, ctx) {
|
|
19
|
+
const endpoint = ctx.config.BETTERSTACK_QUERY_ENDPOINT.replace(/\/+$/, "");
|
|
20
|
+
const res = await fetch(`${endpoint}?output_format_pretty_row_numbers=0`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: {
|
|
23
|
+
"Content-Type": "text/plain",
|
|
24
|
+
Authorization: `Basic ${btoa(`${ctx.config.BETTERSTACK_QUERY_USERNAME}:${ctx.config.BETTERSTACK_QUERY_PASSWORD}`)}`,
|
|
25
|
+
},
|
|
26
|
+
body: `${sql} FORMAT JSONEachRow`,
|
|
27
|
+
signal: AbortSignal.timeout(30_000),
|
|
28
|
+
});
|
|
29
|
+
if (!res.ok)
|
|
30
|
+
throw new Error(`[BetterStack] ClickHouse query failed (HTTP ${res.status}): ${await res.text()}`);
|
|
31
|
+
// JSONEachRow returns one JSON object per line (NDJSON)
|
|
32
|
+
const text = await res.text();
|
|
33
|
+
if (!text.trim())
|
|
34
|
+
return [];
|
|
35
|
+
return text
|
|
36
|
+
.trim()
|
|
37
|
+
.split("\n")
|
|
38
|
+
.map((line) => JSON.parse(line));
|
|
39
|
+
}
|
|
40
|
+
// ─── Skill definition ──────────────────────────────────────────
|
|
41
|
+
export const betterstack = {
|
|
42
|
+
id: "betterstack",
|
|
43
|
+
name: "BetterStack",
|
|
44
|
+
description: "Query logs and manage telemetry sources in BetterStack",
|
|
45
|
+
category: "observability",
|
|
46
|
+
config: {
|
|
47
|
+
BETTERSTACK_API_TOKEN: {
|
|
48
|
+
description: "BetterStack Telemetry API token (team-scoped)",
|
|
49
|
+
required: true,
|
|
50
|
+
env: "BETTERSTACK_API_TOKEN",
|
|
51
|
+
},
|
|
52
|
+
BETTERSTACK_QUERY_ENDPOINT: {
|
|
53
|
+
description: "ClickHouse HTTP endpoint (e.g. https://eu-fsn-3-connect.betterstackdata.com)",
|
|
54
|
+
required: true,
|
|
55
|
+
env: "BETTERSTACK_QUERY_ENDPOINT",
|
|
56
|
+
},
|
|
57
|
+
BETTERSTACK_QUERY_USERNAME: {
|
|
58
|
+
description: "ClickHouse connection username",
|
|
59
|
+
required: true,
|
|
60
|
+
env: "BETTERSTACK_QUERY_USERNAME",
|
|
61
|
+
},
|
|
62
|
+
BETTERSTACK_QUERY_PASSWORD: {
|
|
63
|
+
description: "ClickHouse connection password",
|
|
64
|
+
required: true,
|
|
65
|
+
env: "BETTERSTACK_QUERY_PASSWORD",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
tools: [
|
|
69
|
+
{
|
|
70
|
+
name: "betterstack_list_sources",
|
|
71
|
+
description: "List available telemetry sources (id, name, table_name, platform)",
|
|
72
|
+
input_schema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
name: { type: "string", description: "Filter by name (partial match)" },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
handler: async (input, ctx) => {
|
|
79
|
+
const params = new URLSearchParams({ per_page: "50" });
|
|
80
|
+
if (input.name)
|
|
81
|
+
params.set("name", input.name);
|
|
82
|
+
const data = await bsApi(`/sources?${params}`, ctx);
|
|
83
|
+
return data.data.map((s) => ({
|
|
84
|
+
id: s.id,
|
|
85
|
+
name: s.attributes.name,
|
|
86
|
+
table_name: s.attributes.table_name,
|
|
87
|
+
platform: s.attributes.platform,
|
|
88
|
+
}));
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "betterstack_get_source",
|
|
93
|
+
description: "Get full details for a telemetry source (table name, retention, config)",
|
|
94
|
+
input_schema: {
|
|
95
|
+
type: "object",
|
|
96
|
+
properties: {
|
|
97
|
+
id: { type: "number", description: "Source ID" },
|
|
98
|
+
},
|
|
99
|
+
required: ["id"],
|
|
100
|
+
},
|
|
101
|
+
handler: async (input, ctx) => {
|
|
102
|
+
const data = await bsApi(`/sources/${input.id}`, ctx);
|
|
103
|
+
return { id: data.data.id, ...data.data.attributes };
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "betterstack_get_source_fields",
|
|
108
|
+
description: "Get queryable fields for a source table (column names and types)",
|
|
109
|
+
input_schema: {
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: {
|
|
112
|
+
table: { type: "string", description: "Table name (e.g. t273774_offload_ecs_production)" },
|
|
113
|
+
},
|
|
114
|
+
required: ["table"],
|
|
115
|
+
},
|
|
116
|
+
handler: async (input, ctx) => {
|
|
117
|
+
return bsQuery(`DESCRIBE TABLE remote(${input.table}_logs)`, ctx);
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "betterstack_query",
|
|
122
|
+
description: `Execute a read-only ClickHouse SQL query against a telemetry source.
|
|
123
|
+
Tables: remote(TABLE_logs) for recent logs, s3Cluster(primary, TABLE_s3) for historical (add WHERE _row_type = 1).
|
|
124
|
+
Key fields: dt (timestamp), raw (JSON blob with all log fields).
|
|
125
|
+
Extract nested fields: JSONExtract(raw, 'field_name', 'Nullable(String)').
|
|
126
|
+
Use betterstack_get_source_fields to discover available columns.`,
|
|
127
|
+
input_schema: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
query: { type: "string", description: "ClickHouse SQL query (SELECT only)" },
|
|
131
|
+
source_id: { type: "number", description: "Source ID (for context)" },
|
|
132
|
+
table: { type: "string", description: "Table name (e.g. t273774_offload_ecs_production)" },
|
|
133
|
+
},
|
|
134
|
+
required: ["query", "source_id", "table"],
|
|
135
|
+
},
|
|
136
|
+
handler: async (input, ctx) => {
|
|
137
|
+
const trimmed = input.query.trim();
|
|
138
|
+
const upper = trimmed.toUpperCase();
|
|
139
|
+
if (!upper.startsWith("SELECT") && !upper.startsWith("DESCRIBE")) {
|
|
140
|
+
throw new Error("[BetterStack] Only SELECT and DESCRIBE queries are allowed");
|
|
141
|
+
}
|
|
142
|
+
// Append LIMIT if none present to prevent unbounded result sets
|
|
143
|
+
let sql = trimmed;
|
|
144
|
+
if (!upper.includes("LIMIT")) {
|
|
145
|
+
sql = `${sql} LIMIT 500`;
|
|
146
|
+
}
|
|
147
|
+
return bsQuery(sql, ctx);
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
};
|
package/dist/skills/index.d.ts
CHANGED
|
@@ -9,9 +9,10 @@ import { linear } from "./linear.js";
|
|
|
9
9
|
import { slack } from "./slack.js";
|
|
10
10
|
import { sentry } from "./sentry.js";
|
|
11
11
|
import { datadog } from "./datadog.js";
|
|
12
|
+
import { betterstack } from "./betterstack.js";
|
|
12
13
|
import { notification } from "./notification.js";
|
|
13
14
|
export declare const builtinSkills: Skill[];
|
|
14
|
-
export { github, linear, slack, sentry, datadog, notification };
|
|
15
|
+
export { github, linear, slack, sentry, datadog, betterstack, notification };
|
|
15
16
|
/**
|
|
16
17
|
* Build a skill map from an array of skills.
|
|
17
18
|
* Pass to `execute()` as the `skills` option.
|
package/dist/skills/index.js
CHANGED
|
@@ -8,10 +8,11 @@ import { linear } from "./linear.js";
|
|
|
8
8
|
import { slack } from "./slack.js";
|
|
9
9
|
import { sentry } from "./sentry.js";
|
|
10
10
|
import { datadog } from "./datadog.js";
|
|
11
|
+
import { betterstack } from "./betterstack.js";
|
|
11
12
|
import { notification } from "./notification.js";
|
|
12
13
|
// ─── Built-in skill catalog ─────────────────────────────────────
|
|
13
|
-
export const builtinSkills = [github, linear, slack, sentry, datadog, notification];
|
|
14
|
-
export { github, linear, slack, sentry, datadog, notification };
|
|
14
|
+
export const builtinSkills = [github, linear, slack, sentry, datadog, betterstack, notification];
|
|
15
|
+
export { github, linear, slack, sentry, datadog, betterstack, notification };
|
|
15
16
|
// ─── Registry helpers ───────────────────────────────────────────
|
|
16
17
|
/**
|
|
17
18
|
* Build a skill map from an array of skills.
|
package/dist/skills/linear.js
CHANGED
|
@@ -79,6 +79,41 @@ export const linear = {
|
|
|
79
79
|
},
|
|
80
80
|
handler: async (input, ctx) => linearGql(`mutation($input: CommentCreateInput!) { commentCreate(input: $input) { success comment { id body } } }`, { input: { issueId: input.issueId, body: input.body } }, ctx),
|
|
81
81
|
},
|
|
82
|
+
{
|
|
83
|
+
name: "linear_get_issue",
|
|
84
|
+
description: "Get a Linear issue by ID or identifier (e.g. 'OFF-1020')",
|
|
85
|
+
input_schema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
id: { type: "string", description: "Linear issue ID (UUID) or identifier (e.g. 'OFF-1020')" },
|
|
89
|
+
},
|
|
90
|
+
required: ["id"],
|
|
91
|
+
},
|
|
92
|
+
handler: async (input, ctx) => linearGql(`query($id: String!) {
|
|
93
|
+
issue(id: $id) {
|
|
94
|
+
id identifier title url description
|
|
95
|
+
state { name type }
|
|
96
|
+
priority priorityLabel
|
|
97
|
+
assignee { name email }
|
|
98
|
+
labels { nodes { name } }
|
|
99
|
+
team { key name }
|
|
100
|
+
createdAt updatedAt
|
|
101
|
+
}
|
|
102
|
+
}`, { id: input.id }, ctx),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "linear_list_teams",
|
|
106
|
+
description: "List Linear teams (needed for teamId when creating issues)",
|
|
107
|
+
input_schema: {
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {},
|
|
110
|
+
},
|
|
111
|
+
handler: async (_input, ctx) => linearGql(`query {
|
|
112
|
+
teams {
|
|
113
|
+
nodes { id key name description }
|
|
114
|
+
}
|
|
115
|
+
}`, {}, ctx),
|
|
116
|
+
},
|
|
82
117
|
{
|
|
83
118
|
name: "linear_update_issue",
|
|
84
119
|
description: "Update an existing Linear issue",
|
package/dist/workflows/triage.js
CHANGED
|
@@ -43,7 +43,7 @@ If there are no URLs to fetch, just pass through — this step is a no-op.`,
|
|
|
43
43
|
3. **Issue tracker**: Search for similar past issues or known problems.
|
|
44
44
|
|
|
45
45
|
Be thorough — the investigation step depends on complete context. Use every tool available to you.`,
|
|
46
|
-
skills: ["github", "sentry", "datadog", "linear"],
|
|
46
|
+
skills: ["github", "sentry", "datadog", "betterstack", "linear"],
|
|
47
47
|
},
|
|
48
48
|
investigate: {
|
|
49
49
|
name: "Root Cause Analysis",
|