@sweny-ai/core 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/dist/__tests__/claude.test.d.ts +1 -0
- package/dist/__tests__/claude.test.js +328 -0
- package/dist/__tests__/executor.test.d.ts +1 -0
- package/dist/__tests__/executor.test.js +296 -0
- package/dist/__tests__/integration/datadog.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/datadog.integration.test.js +23 -0
- package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/e2e-workflow.integration.test.js +75 -0
- package/dist/__tests__/integration/github.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/github.integration.test.js +37 -0
- package/dist/__tests__/integration/harness.d.ts +24 -0
- package/dist/__tests__/integration/harness.js +34 -0
- package/dist/__tests__/integration/linear.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/linear.integration.test.js +15 -0
- package/dist/__tests__/integration/sentry.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/sentry.integration.test.js +20 -0
- package/dist/__tests__/integration/slack.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/slack.integration.test.js +22 -0
- package/dist/__tests__/schema.test.d.ts +1 -0
- package/dist/__tests__/schema.test.js +239 -0
- package/dist/__tests__/skills-index.test.d.ts +1 -0
- package/dist/__tests__/skills-index.test.js +122 -0
- package/dist/__tests__/skills.test.d.ts +1 -0
- package/dist/__tests__/skills.test.js +296 -0
- package/dist/__tests__/studio.test.d.ts +1 -0
- package/dist/__tests__/studio.test.js +172 -0
- package/dist/__tests__/testing.test.d.ts +1 -0
- package/dist/__tests__/testing.test.js +224 -0
- package/dist/browser.d.ts +17 -0
- package/dist/browser.js +22 -0
- package/dist/claude.d.ts +48 -0
- package/dist/claude.js +293 -0
- package/dist/cli/check.d.ts +11 -0
- package/dist/cli/check.js +237 -0
- package/dist/cli/config-file.d.ts +12 -0
- package/dist/cli/config-file.js +208 -0
- package/dist/cli/config.d.ts +77 -0
- package/dist/cli/config.js +565 -0
- package/dist/cli/main.d.ts +10 -0
- package/dist/cli/main.js +744 -0
- package/dist/cli/output.d.ts +26 -0
- package/dist/cli/output.js +357 -0
- package/dist/cli/renderer.d.ts +33 -0
- package/dist/cli/renderer.js +423 -0
- package/dist/cli/renderer.test.d.ts +1 -0
- package/dist/cli/renderer.test.js +302 -0
- package/dist/cli/setup.d.ts +11 -0
- package/dist/cli/setup.js +310 -0
- package/dist/executor.d.ts +29 -0
- package/dist/executor.js +173 -0
- package/dist/executor.test.d.ts +1 -0
- package/dist/executor.test.js +314 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +36 -0
- package/dist/mcp.d.ts +11 -0
- package/dist/mcp.js +183 -0
- package/dist/mcp.test.d.ts +1 -0
- package/dist/mcp.test.js +334 -0
- package/dist/schema.d.ts +318 -0
- package/dist/schema.js +207 -0
- package/dist/skills/betterstack.d.ts +7 -0
- package/dist/skills/betterstack.js +114 -0
- package/dist/skills/datadog.d.ts +7 -0
- package/dist/skills/datadog.js +107 -0
- package/dist/skills/github.d.ts +8 -0
- package/dist/skills/github.js +155 -0
- package/dist/skills/index.d.ts +68 -0
- package/dist/skills/index.js +134 -0
- package/dist/skills/linear.d.ts +7 -0
- package/dist/skills/linear.js +89 -0
- package/dist/skills/notification.d.ts +11 -0
- package/dist/skills/notification.js +142 -0
- package/dist/skills/sentry.d.ts +7 -0
- package/dist/skills/sentry.js +105 -0
- package/dist/skills/slack.d.ts +8 -0
- package/dist/skills/slack.js +115 -0
- package/dist/studio.d.ts +124 -0
- package/dist/studio.js +174 -0
- package/dist/testing.d.ts +88 -0
- package/dist/testing.js +253 -0
- package/dist/types.d.ts +144 -0
- package/dist/types.js +11 -0
- package/dist/workflow-builder.d.ts +45 -0
- package/dist/workflow-builder.js +120 -0
- package/dist/workflow-builder.test.d.ts +1 -0
- package/dist/workflow-builder.test.js +117 -0
- package/dist/workflows/implement.d.ts +11 -0
- package/dist/workflows/implement.js +83 -0
- package/dist/workflows/index.d.ts +2 -0
- package/dist/workflows/index.js +2 -0
- package/dist/workflows/triage.d.ts +18 -0
- package/dist/workflows/triage.js +108 -0
- package/package.json +83 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datadog Skill
|
|
3
|
+
*
|
|
4
|
+
* Replaces: observability/datadog.ts
|
|
5
|
+
*/
|
|
6
|
+
async function ddApi(path, ctx, init) {
|
|
7
|
+
const base = ctx.config.DD_SITE ? `https://api.${ctx.config.DD_SITE}` : "https://api.datadoghq.com";
|
|
8
|
+
const version = init?.v2 ? "v2" : "v1";
|
|
9
|
+
const res = await fetch(`${base}/api/${version}${path}`, {
|
|
10
|
+
...init,
|
|
11
|
+
headers: {
|
|
12
|
+
"DD-API-KEY": ctx.config.DD_API_KEY,
|
|
13
|
+
"DD-APPLICATION-KEY": ctx.config.DD_APP_KEY,
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
...init?.headers,
|
|
16
|
+
},
|
|
17
|
+
signal: AbortSignal.timeout(30_000),
|
|
18
|
+
});
|
|
19
|
+
if (!res.ok)
|
|
20
|
+
throw new Error(`[Datadog] API request failed (HTTP ${res.status}): ${await res.text()}`);
|
|
21
|
+
return res.json();
|
|
22
|
+
}
|
|
23
|
+
export const datadog = {
|
|
24
|
+
id: "datadog",
|
|
25
|
+
name: "Datadog",
|
|
26
|
+
description: "Query logs, metrics, and monitors from Datadog",
|
|
27
|
+
category: "observability",
|
|
28
|
+
config: {
|
|
29
|
+
DD_API_KEY: {
|
|
30
|
+
description: "Datadog API key",
|
|
31
|
+
required: true,
|
|
32
|
+
env: "DD_API_KEY",
|
|
33
|
+
},
|
|
34
|
+
DD_APP_KEY: {
|
|
35
|
+
description: "Datadog application key",
|
|
36
|
+
required: true,
|
|
37
|
+
env: "DD_APP_KEY",
|
|
38
|
+
},
|
|
39
|
+
DD_SITE: {
|
|
40
|
+
description: "Datadog site (e.g., datadoghq.eu). Default: datadoghq.com",
|
|
41
|
+
required: false,
|
|
42
|
+
env: "DD_SITE",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
tools: [
|
|
46
|
+
{
|
|
47
|
+
name: "datadog_search_logs",
|
|
48
|
+
description: "Search logs in Datadog",
|
|
49
|
+
input_schema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
query: { type: "string", description: "Log search query (Datadog syntax)" },
|
|
53
|
+
from: { type: "string", description: "Start time (ISO 8601 or relative like 'now-1h')" },
|
|
54
|
+
to: { type: "string", description: "End time (default: now)" },
|
|
55
|
+
limit: { type: "number", description: "Max results (default: 50)" },
|
|
56
|
+
},
|
|
57
|
+
required: ["query"],
|
|
58
|
+
},
|
|
59
|
+
handler: async (input, ctx) => ddApi("/logs/events/search", ctx, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
v2: true,
|
|
62
|
+
body: JSON.stringify({
|
|
63
|
+
filter: {
|
|
64
|
+
query: input.query,
|
|
65
|
+
from: input.from ?? "now-1h",
|
|
66
|
+
to: input.to ?? "now",
|
|
67
|
+
},
|
|
68
|
+
page: { limit: input.limit ?? 50 },
|
|
69
|
+
sort: "-timestamp",
|
|
70
|
+
}),
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "datadog_query_metrics",
|
|
75
|
+
description: "Query time-series metrics from Datadog",
|
|
76
|
+
input_schema: {
|
|
77
|
+
type: "object",
|
|
78
|
+
properties: {
|
|
79
|
+
query: { type: "string", description: "Metrics query (e.g., 'avg:system.cpu.user{*}')" },
|
|
80
|
+
from: { type: "number", description: "Start time (UNIX epoch seconds)" },
|
|
81
|
+
to: { type: "number", description: "End time (UNIX epoch seconds)" },
|
|
82
|
+
},
|
|
83
|
+
required: ["query", "from", "to"],
|
|
84
|
+
},
|
|
85
|
+
handler: async (input, ctx) => ddApi(`/query?query=${encodeURIComponent(input.query)}&from=${input.from}&to=${input.to}`, ctx),
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "datadog_list_monitors",
|
|
89
|
+
description: "List Datadog monitors, optionally filtered by tag or name",
|
|
90
|
+
input_schema: {
|
|
91
|
+
type: "object",
|
|
92
|
+
properties: {
|
|
93
|
+
name: { type: "string", description: "Filter by monitor name (substring match)" },
|
|
94
|
+
tags: { type: "string", description: "Comma-separated tags to filter by" },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
handler: async (input, ctx) => {
|
|
98
|
+
const params = new URLSearchParams();
|
|
99
|
+
if (input.name)
|
|
100
|
+
params.set("name", input.name);
|
|
101
|
+
if (input.tags)
|
|
102
|
+
params.set("monitor_tags", input.tags);
|
|
103
|
+
return ddApi(`/monitor?${params}`, ctx);
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Skill
|
|
3
|
+
*
|
|
4
|
+
* Replaces: source-control/github.ts + issue-tracking/github-issues.ts
|
|
5
|
+
* ~800 lines → ~120 lines
|
|
6
|
+
*/
|
|
7
|
+
async function gh(path, ctx, init) {
|
|
8
|
+
const res = await fetch(`https://api.github.com${path}`, {
|
|
9
|
+
...init,
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `token ${ctx.config.GITHUB_TOKEN}`,
|
|
12
|
+
Accept: "application/vnd.github.v3+json",
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
...init?.headers,
|
|
15
|
+
},
|
|
16
|
+
signal: AbortSignal.timeout(30_000),
|
|
17
|
+
});
|
|
18
|
+
if (!res.ok)
|
|
19
|
+
throw new Error(`[GitHub] API request failed (HTTP ${res.status}): ${await res.text()}`);
|
|
20
|
+
return res.json();
|
|
21
|
+
}
|
|
22
|
+
export const github = {
|
|
23
|
+
id: "github",
|
|
24
|
+
name: "GitHub",
|
|
25
|
+
description: "Search code, manage issues and pull requests on GitHub",
|
|
26
|
+
category: "git",
|
|
27
|
+
config: {
|
|
28
|
+
GITHUB_TOKEN: {
|
|
29
|
+
description: "GitHub personal access token or app installation token",
|
|
30
|
+
required: true,
|
|
31
|
+
env: "GITHUB_TOKEN",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
tools: [
|
|
35
|
+
{
|
|
36
|
+
name: "github_search_code",
|
|
37
|
+
description: "Search for code in a GitHub repository",
|
|
38
|
+
input_schema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
query: { type: "string", description: "Search query (GitHub code search syntax)" },
|
|
42
|
+
repo: { type: "string", description: "Repository in owner/repo format" },
|
|
43
|
+
},
|
|
44
|
+
required: ["query", "repo"],
|
|
45
|
+
},
|
|
46
|
+
handler: async (input, ctx) => gh(`/search/code?q=${encodeURIComponent(`${input.query} repo:${input.repo}`)}&per_page=20`, ctx),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "github_get_issue",
|
|
50
|
+
description: "Get details of a GitHub issue",
|
|
51
|
+
input_schema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
repo: { type: "string", description: "owner/repo" },
|
|
55
|
+
number: { type: "number", description: "Issue number" },
|
|
56
|
+
},
|
|
57
|
+
required: ["repo", "number"],
|
|
58
|
+
},
|
|
59
|
+
handler: async (input, ctx) => gh(`/repos/${input.repo}/issues/${input.number}`, ctx),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "github_search_issues",
|
|
63
|
+
description: "Search issues and pull requests",
|
|
64
|
+
input_schema: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
query: { type: "string", description: "Search query (GitHub issues search syntax)" },
|
|
68
|
+
repo: { type: "string", description: "owner/repo — optional, scope to a repo" },
|
|
69
|
+
},
|
|
70
|
+
required: ["query"],
|
|
71
|
+
},
|
|
72
|
+
handler: async (input, ctx) => {
|
|
73
|
+
const q = input.repo ? `${input.query} repo:${input.repo}` : input.query;
|
|
74
|
+
return gh(`/search/issues?q=${encodeURIComponent(q)}&per_page=20`, ctx);
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "github_create_issue",
|
|
79
|
+
description: "Create a new GitHub issue",
|
|
80
|
+
input_schema: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
repo: { type: "string", description: "owner/repo" },
|
|
84
|
+
title: { type: "string" },
|
|
85
|
+
body: { type: "string" },
|
|
86
|
+
labels: { type: "array", items: { type: "string" } },
|
|
87
|
+
},
|
|
88
|
+
required: ["repo", "title"],
|
|
89
|
+
},
|
|
90
|
+
handler: async (input, ctx) => gh(`/repos/${input.repo}/issues`, ctx, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
body: JSON.stringify({ title: input.title, body: input.body, labels: input.labels }),
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "github_create_pr",
|
|
97
|
+
description: "Create a pull request",
|
|
98
|
+
input_schema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
repo: { type: "string", description: "owner/repo" },
|
|
102
|
+
title: { type: "string" },
|
|
103
|
+
body: { type: "string" },
|
|
104
|
+
head: { type: "string", description: "Branch with changes" },
|
|
105
|
+
base: { type: "string", description: "Target branch (default: main)" },
|
|
106
|
+
},
|
|
107
|
+
required: ["repo", "title", "head"],
|
|
108
|
+
},
|
|
109
|
+
handler: async (input, ctx) => gh(`/repos/${input.repo}/pulls`, ctx, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
title: input.title,
|
|
113
|
+
body: input.body,
|
|
114
|
+
head: input.head,
|
|
115
|
+
base: input.base ?? "main",
|
|
116
|
+
}),
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "github_list_recent_commits",
|
|
121
|
+
description: "List recent commits on a branch",
|
|
122
|
+
input_schema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
repo: { type: "string", description: "owner/repo" },
|
|
126
|
+
branch: { type: "string", description: "Branch name (default: main)" },
|
|
127
|
+
per_page: { type: "number", description: "Number of commits (default: 10)" },
|
|
128
|
+
},
|
|
129
|
+
required: ["repo"],
|
|
130
|
+
},
|
|
131
|
+
handler: async (input, ctx) => gh(`/repos/${input.repo}/commits?sha=${input.branch ?? "main"}&per_page=${input.per_page ?? 10}`, ctx),
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "github_get_file",
|
|
135
|
+
description: "Get a file's contents from a repository",
|
|
136
|
+
input_schema: {
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: {
|
|
139
|
+
repo: { type: "string", description: "owner/repo" },
|
|
140
|
+
path: { type: "string", description: "File path in the repo" },
|
|
141
|
+
ref: { type: "string", description: "Branch or commit SHA (default: main)" },
|
|
142
|
+
},
|
|
143
|
+
required: ["repo", "path"],
|
|
144
|
+
},
|
|
145
|
+
handler: async (input, ctx) => {
|
|
146
|
+
const ref = input.ref ? `?ref=${input.ref}` : "";
|
|
147
|
+
const data = await gh(`/repos/${input.repo}/contents/${input.path}${ref}`, ctx);
|
|
148
|
+
if (data.content && data.encoding === "base64") {
|
|
149
|
+
return { ...data, decoded_content: Buffer.from(data.content, "base64").toString("utf-8") };
|
|
150
|
+
}
|
|
151
|
+
return data;
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Registry
|
|
3
|
+
*
|
|
4
|
+
* All built-in skills + helpers to build a skill map from config.
|
|
5
|
+
*/
|
|
6
|
+
import type { Skill, SkillCategory } from "../types.js";
|
|
7
|
+
import { github } from "./github.js";
|
|
8
|
+
import { linear } from "./linear.js";
|
|
9
|
+
import { slack } from "./slack.js";
|
|
10
|
+
import { sentry } from "./sentry.js";
|
|
11
|
+
import { datadog } from "./datadog.js";
|
|
12
|
+
import { betterstack } from "./betterstack.js";
|
|
13
|
+
import { notification } from "./notification.js";
|
|
14
|
+
export declare const builtinSkills: Skill[];
|
|
15
|
+
export { github, linear, slack, sentry, datadog, betterstack, notification };
|
|
16
|
+
/**
|
|
17
|
+
* Build a skill map from an array of skills.
|
|
18
|
+
* Pass to `execute()` as the `skills` option.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const skills = createSkillMap([github, sentry, slack])
|
|
23
|
+
* await execute(workflow, input, { skills, claude })
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function createSkillMap(skills: Skill[]): Map<string, Skill>;
|
|
27
|
+
/**
|
|
28
|
+
* Create a skill map from all built-in skills.
|
|
29
|
+
*/
|
|
30
|
+
export declare function allSkills(): Map<string, Skill>;
|
|
31
|
+
/**
|
|
32
|
+
* Check if a skill is usable given the available environment.
|
|
33
|
+
*
|
|
34
|
+
* - All required config fields must have values.
|
|
35
|
+
* - If a skill has env-backed config fields (even optional ones),
|
|
36
|
+
* at least one must be set — otherwise the skill has zero credentials.
|
|
37
|
+
* - Skills with no env-backed config are always considered configured.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isSkillConfigured(skill: Skill, env?: Record<string, string | undefined>): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* From all builtins, return only the skills that have their required env vars set.
|
|
42
|
+
*/
|
|
43
|
+
export declare function configuredSkills(env?: Record<string, string | undefined>): Skill[];
|
|
44
|
+
/**
|
|
45
|
+
* Validate that a workflow's skill requirements are satisfiable.
|
|
46
|
+
*
|
|
47
|
+
* Groups each node's skills by category and checks that at least one
|
|
48
|
+
* skill per category is available. Returns a report of what's configured,
|
|
49
|
+
* what's missing, and any hard errors (required category with zero providers).
|
|
50
|
+
*/
|
|
51
|
+
export interface SkillValidationResult {
|
|
52
|
+
/** All skills referenced by the workflow, grouped by availability */
|
|
53
|
+
configured: Skill[];
|
|
54
|
+
missing: {
|
|
55
|
+
id: string;
|
|
56
|
+
category: SkillCategory | "unknown";
|
|
57
|
+
missingEnv: string[];
|
|
58
|
+
}[];
|
|
59
|
+
/** Nodes where an entire non-notification category has zero configured skills */
|
|
60
|
+
errors: string[];
|
|
61
|
+
/** Nodes where notification category has zero configured skills (non-fatal) */
|
|
62
|
+
warnings: string[];
|
|
63
|
+
}
|
|
64
|
+
export declare function validateWorkflowSkills(workflow: {
|
|
65
|
+
nodes: Record<string, {
|
|
66
|
+
skills: string[];
|
|
67
|
+
}>;
|
|
68
|
+
}, available: Map<string, Skill>): SkillValidationResult;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Registry
|
|
3
|
+
*
|
|
4
|
+
* All built-in skills + helpers to build a skill map from config.
|
|
5
|
+
*/
|
|
6
|
+
import { github } from "./github.js";
|
|
7
|
+
import { linear } from "./linear.js";
|
|
8
|
+
import { slack } from "./slack.js";
|
|
9
|
+
import { sentry } from "./sentry.js";
|
|
10
|
+
import { datadog } from "./datadog.js";
|
|
11
|
+
import { betterstack } from "./betterstack.js";
|
|
12
|
+
import { notification } from "./notification.js";
|
|
13
|
+
// ─── Built-in skill catalog ─────────────────────────────────────
|
|
14
|
+
export const builtinSkills = [github, linear, slack, sentry, datadog, betterstack, notification];
|
|
15
|
+
export { github, linear, slack, sentry, datadog, betterstack, notification };
|
|
16
|
+
// ─── Registry helpers ───────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Build a skill map from an array of skills.
|
|
19
|
+
* Pass to `execute()` as the `skills` option.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const skills = createSkillMap([github, sentry, slack])
|
|
24
|
+
* await execute(workflow, input, { skills, claude })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function createSkillMap(skills) {
|
|
28
|
+
const map = new Map();
|
|
29
|
+
for (const skill of skills) {
|
|
30
|
+
if (map.has(skill.id)) {
|
|
31
|
+
throw new Error(`Duplicate skill ID: "${skill.id}"`);
|
|
32
|
+
}
|
|
33
|
+
map.set(skill.id, skill);
|
|
34
|
+
}
|
|
35
|
+
return map;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a skill map from all built-in skills.
|
|
39
|
+
*/
|
|
40
|
+
export function allSkills() {
|
|
41
|
+
return createSkillMap(builtinSkills);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Check if a skill is usable given the available environment.
|
|
45
|
+
*
|
|
46
|
+
* - All required config fields must have values.
|
|
47
|
+
* - If a skill has env-backed config fields (even optional ones),
|
|
48
|
+
* at least one must be set — otherwise the skill has zero credentials.
|
|
49
|
+
* - Skills with no env-backed config are always considered configured.
|
|
50
|
+
*/
|
|
51
|
+
export function isSkillConfigured(skill, env = process.env) {
|
|
52
|
+
const envFields = Object.values(skill.config).filter((f) => f.env);
|
|
53
|
+
if (envFields.length === 0)
|
|
54
|
+
return true; // no env needed
|
|
55
|
+
// All required fields must be set
|
|
56
|
+
for (const field of envFields) {
|
|
57
|
+
if (field.required && !env[field.env])
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
// At least one env-backed field must be set (handles all-optional skills like slack)
|
|
61
|
+
return envFields.some((f) => env[f.env]);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* From all builtins, return only the skills that have their required env vars set.
|
|
65
|
+
*/
|
|
66
|
+
export function configuredSkills(env = process.env) {
|
|
67
|
+
return builtinSkills.filter((s) => isSkillConfigured(s, env));
|
|
68
|
+
}
|
|
69
|
+
export function validateWorkflowSkills(workflow, available) {
|
|
70
|
+
const configured = [];
|
|
71
|
+
const configuredIds = new Set();
|
|
72
|
+
const missing = [];
|
|
73
|
+
const missingIds = new Set();
|
|
74
|
+
const errors = [];
|
|
75
|
+
const warnings = [];
|
|
76
|
+
// Collect all referenced skill IDs
|
|
77
|
+
const allReferencedIds = new Set();
|
|
78
|
+
for (const node of Object.values(workflow.nodes)) {
|
|
79
|
+
for (const id of node.skills)
|
|
80
|
+
allReferencedIds.add(id);
|
|
81
|
+
}
|
|
82
|
+
// Classify each referenced skill
|
|
83
|
+
for (const id of allReferencedIds) {
|
|
84
|
+
const skill = available.get(id);
|
|
85
|
+
if (skill) {
|
|
86
|
+
if (!configuredIds.has(id)) {
|
|
87
|
+
configured.push(skill);
|
|
88
|
+
configuredIds.add(id);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
if (!missingIds.has(id)) {
|
|
93
|
+
// Try to find it in builtins to get its category and required env vars
|
|
94
|
+
const builtin = builtinSkills.find((s) => s.id === id);
|
|
95
|
+
const category = builtin?.category ?? "unknown";
|
|
96
|
+
// Show required env vars first, then optional ones (for all-optional skills like slack)
|
|
97
|
+
const envFields = builtin ? Object.entries(builtin.config).filter(([, f]) => f.env) : [];
|
|
98
|
+
const requiredEnv = envFields.filter(([, f]) => f.required).map(([, f]) => f.env);
|
|
99
|
+
const missingEnv = requiredEnv.length > 0 ? requiredEnv : envFields.map(([, f]) => f.env); // show all options for all-optional skills
|
|
100
|
+
missing.push({ id, category, missingEnv });
|
|
101
|
+
missingIds.add(id);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Per-node validation: check that each node has at least one skill per category
|
|
106
|
+
for (const [nodeId, node] of Object.entries(workflow.nodes)) {
|
|
107
|
+
if (node.skills.length === 0)
|
|
108
|
+
continue;
|
|
109
|
+
// Group this node's skills by category
|
|
110
|
+
const categoriesNeeded = new Map();
|
|
111
|
+
for (const id of node.skills) {
|
|
112
|
+
const skill = available.get(id);
|
|
113
|
+
const builtin = builtinSkills.find((s) => s.id === id);
|
|
114
|
+
const cat = skill?.category ?? builtin?.category ?? "unknown";
|
|
115
|
+
if (!categoriesNeeded.has(cat))
|
|
116
|
+
categoriesNeeded.set(cat, []);
|
|
117
|
+
categoriesNeeded.get(cat).push(id);
|
|
118
|
+
}
|
|
119
|
+
// Check each category has at least one configured skill
|
|
120
|
+
for (const [cat, skillIds] of categoriesNeeded) {
|
|
121
|
+
const hasAny = skillIds.some((id) => available.has(id));
|
|
122
|
+
if (!hasAny) {
|
|
123
|
+
const msg = `Node "${nodeId}" has no configured ${cat} providers (needs one of: ${skillIds.join(", ")})`;
|
|
124
|
+
if (cat === "notification") {
|
|
125
|
+
warnings.push(msg);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
errors.push(msg);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { configured, missing, errors, warnings };
|
|
134
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear Skill
|
|
3
|
+
*
|
|
4
|
+
* Replaces: issue-tracking/linear.ts (~400 lines → ~80 lines)
|
|
5
|
+
*/
|
|
6
|
+
async function linearGql(query, variables, ctx) {
|
|
7
|
+
const res = await fetch("https://api.linear.app/graphql", {
|
|
8
|
+
method: "POST",
|
|
9
|
+
headers: {
|
|
10
|
+
Authorization: ctx.config.LINEAR_API_KEY,
|
|
11
|
+
"Content-Type": "application/json",
|
|
12
|
+
},
|
|
13
|
+
body: JSON.stringify({ query, variables }),
|
|
14
|
+
signal: AbortSignal.timeout(30_000),
|
|
15
|
+
});
|
|
16
|
+
if (!res.ok)
|
|
17
|
+
throw new Error(`[Linear] API request failed (HTTP ${res.status}): ${await res.text()}`);
|
|
18
|
+
const json = await res.json();
|
|
19
|
+
if (json.errors?.length)
|
|
20
|
+
throw new Error(`[Linear] GraphQL error: ${json.errors[0].message}`);
|
|
21
|
+
return json.data;
|
|
22
|
+
}
|
|
23
|
+
export const linear = {
|
|
24
|
+
id: "linear",
|
|
25
|
+
name: "Linear",
|
|
26
|
+
description: "Create, search, and update issues in Linear",
|
|
27
|
+
category: "tasks",
|
|
28
|
+
config: {
|
|
29
|
+
LINEAR_API_KEY: {
|
|
30
|
+
description: "Linear API key",
|
|
31
|
+
required: true,
|
|
32
|
+
env: "LINEAR_API_KEY",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
tools: [
|
|
36
|
+
{
|
|
37
|
+
name: "linear_create_issue",
|
|
38
|
+
description: "Create a new Linear issue",
|
|
39
|
+
input_schema: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
teamId: { type: "string", description: "Linear team ID" },
|
|
43
|
+
title: { type: "string" },
|
|
44
|
+
description: { type: "string", description: "Markdown description" },
|
|
45
|
+
priority: { type: "number", description: "0=none, 1=urgent, 2=high, 3=medium, 4=low" },
|
|
46
|
+
labelIds: { type: "array", items: { type: "string" } },
|
|
47
|
+
},
|
|
48
|
+
required: ["teamId", "title"],
|
|
49
|
+
},
|
|
50
|
+
handler: async (input, ctx) => linearGql(`mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier url title } } }`, { input }, ctx),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "linear_search_issues",
|
|
54
|
+
description: "Search Linear issues by text query",
|
|
55
|
+
input_schema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
query: { type: "string", description: "Search text" },
|
|
59
|
+
limit: { type: "number", description: "Max results (default: 10)" },
|
|
60
|
+
},
|
|
61
|
+
required: ["query"],
|
|
62
|
+
},
|
|
63
|
+
handler: async (input, ctx) => linearGql(`query($query: String!, $first: Int) {
|
|
64
|
+
searchIssues(term: $query, first: $first) {
|
|
65
|
+
nodes { id identifier title state { name } priority url }
|
|
66
|
+
}
|
|
67
|
+
}`, { query: input.query, first: input.limit ?? 10 }, ctx),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "linear_update_issue",
|
|
71
|
+
description: "Update an existing Linear issue",
|
|
72
|
+
input_schema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
issueId: { type: "string", description: "Linear issue ID" },
|
|
76
|
+
title: { type: "string" },
|
|
77
|
+
description: { type: "string" },
|
|
78
|
+
stateId: { type: "string", description: "State/status ID" },
|
|
79
|
+
priority: { type: "number" },
|
|
80
|
+
},
|
|
81
|
+
required: ["issueId"],
|
|
82
|
+
},
|
|
83
|
+
handler: async (input, ctx) => {
|
|
84
|
+
const { issueId, ...updates } = input;
|
|
85
|
+
return linearGql(`mutation($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success issue { id identifier url title state { name } } } }`, { id: issueId, input: updates }, ctx);
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notification Skill — generic webhook / multi-channel
|
|
3
|
+
*
|
|
4
|
+
* Replaces: notification/webhook.ts + notification/discord-webhook.ts +
|
|
5
|
+
* notification/email.ts + notification/teams-webhook.ts
|
|
6
|
+
*
|
|
7
|
+
* For Slack-specific notifications, use the Slack skill instead.
|
|
8
|
+
* This skill handles generic webhooks, Discord, Teams, and email.
|
|
9
|
+
*/
|
|
10
|
+
import type { Skill } from "../types.js";
|
|
11
|
+
export declare const notification: Skill;
|