@sweny-ai/core 0.1.11 → 0.1.13
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/main.js +38 -20
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/skills/index.d.ts +2 -1
- package/dist/skills/index.js +3 -2
- package/dist/skills/supabase.d.ts +9 -0
- package/dist/skills/supabase.js +334 -0
- package/dist/workflows/index.d.ts +1 -0
- package/dist/workflows/index.js +1 -0
- package/dist/workflows/seed-content.d.ts +30 -0
- package/dist/workflows/seed-content.js +217 -0
- package/package.json +2 -2
package/dist/cli/main.js
CHANGED
|
@@ -9,6 +9,7 @@ import chalk from "chalk";
|
|
|
9
9
|
import { execute } from "../executor.js";
|
|
10
10
|
import { triageWorkflow } from "../workflows/triage.js";
|
|
11
11
|
import { implementWorkflow } from "../workflows/implement.js";
|
|
12
|
+
import { seedContentWorkflow } from "../workflows/seed-content.js";
|
|
12
13
|
import { consoleLogger } from "../types.js";
|
|
13
14
|
import { ClaudeClient } from "../claude.js";
|
|
14
15
|
import { createSkillMap, configuredSkills } from "../skills/index.js";
|
|
@@ -579,25 +580,38 @@ export async function workflowRunAction(file, options) {
|
|
|
579
580
|
}
|
|
580
581
|
};
|
|
581
582
|
const observer = composeObservers(wfProgressObserver, options.stream ? createStreamObserver() : undefined);
|
|
582
|
-
// Build workflow input
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
583
|
+
// Build workflow input — prefer --input JSON if provided, else fall back to config-derived input
|
|
584
|
+
let workflowInput;
|
|
585
|
+
if (options.input && typeof options.input === "string") {
|
|
586
|
+
try {
|
|
587
|
+
workflowInput = JSON.parse(options.input);
|
|
588
|
+
}
|
|
589
|
+
catch {
|
|
590
|
+
console.error(chalk.red(" --input must be valid JSON"));
|
|
591
|
+
process.exit(1);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
workflowInput = {
|
|
597
|
+
timeRange: config.timeRange,
|
|
598
|
+
severityFocus: config.severityFocus,
|
|
599
|
+
serviceFilter: config.serviceFilter,
|
|
600
|
+
repository: config.repository,
|
|
601
|
+
dryRun: config.dryRun,
|
|
602
|
+
baseBranch: config.baseBranch,
|
|
603
|
+
prLabels: config.prLabels,
|
|
604
|
+
additionalInstructions: config.additionalInstructions,
|
|
605
|
+
observabilityProvider: config.observabilityProvider,
|
|
606
|
+
...(config.observabilityCredentials.sourceId && {
|
|
607
|
+
betterstackSourceId: config.observabilityCredentials.sourceId,
|
|
608
|
+
}),
|
|
609
|
+
...(config.observabilityCredentials.tableName && {
|
|
610
|
+
betterstackTableName: config.observabilityCredentials.tableName,
|
|
611
|
+
}),
|
|
612
|
+
context: buildProviderCtx(config, mcpServers),
|
|
613
|
+
};
|
|
614
|
+
}
|
|
601
615
|
try {
|
|
602
616
|
const results = await execute(workflow, workflowInput, {
|
|
603
617
|
skills,
|
|
@@ -633,8 +647,11 @@ export function workflowExportAction(name) {
|
|
|
633
647
|
else if (name === "implement") {
|
|
634
648
|
workflow = implementWorkflow;
|
|
635
649
|
}
|
|
650
|
+
else if (name === "seed-content") {
|
|
651
|
+
workflow = seedContentWorkflow;
|
|
652
|
+
}
|
|
636
653
|
else {
|
|
637
|
-
console.error(chalk.red(` Unknown workflow "${name}". Available: triage, implement`));
|
|
654
|
+
console.error(chalk.red(` Unknown workflow "${name}". Available: triage, implement, seed-content`));
|
|
638
655
|
process.exit(1);
|
|
639
656
|
return;
|
|
640
657
|
}
|
|
@@ -695,6 +712,7 @@ workflowCmd
|
|
|
695
712
|
.option("--dry-run", "Validate workflow without running")
|
|
696
713
|
.option("--json", "Output result as JSON on stdout; suppress progress output")
|
|
697
714
|
.option("--stream", "Stream NDJSON events to stdout (for Studio / automation)")
|
|
715
|
+
.option("--input <json>", "JSON string of input data to pass to the workflow")
|
|
698
716
|
.action(workflowRunAction);
|
|
699
717
|
workflowCmd
|
|
700
718
|
.command("export <name>")
|
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export { execute } from "./executor.js";
|
|
|
27
27
|
export type { ExecuteOptions } from "./executor.js";
|
|
28
28
|
export { ClaudeClient } from "./claude.js";
|
|
29
29
|
export type { ClaudeClientOptions } from "./claude.js";
|
|
30
|
-
export { github, linear, slack, sentry, datadog, notification, builtinSkills, createSkillMap, allSkills, isSkillConfigured, configuredSkills, validateWorkflowSkills, } from "./skills/index.js";
|
|
30
|
+
export { github, linear, slack, sentry, datadog, notification, supabase, builtinSkills, createSkillMap, allSkills, isSkillConfigured, configuredSkills, validateWorkflowSkills, } from "./skills/index.js";
|
|
31
31
|
export type { SkillValidationResult } from "./skills/index.js";
|
|
32
32
|
export { workflowZ, nodeZ, edgeZ, skillZ, parseWorkflow, validateWorkflow, workflowJsonSchema } from "./schema.js";
|
|
33
33
|
export type { WorkflowError } from "./schema.js";
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ export { execute } from "./executor.js";
|
|
|
27
27
|
// Claude client
|
|
28
28
|
export { ClaudeClient } from "./claude.js";
|
|
29
29
|
// Skills
|
|
30
|
-
export { github, linear, slack, sentry, datadog, notification, builtinSkills, createSkillMap, allSkills, isSkillConfigured, configuredSkills, validateWorkflowSkills, } from "./skills/index.js";
|
|
30
|
+
export { github, linear, slack, sentry, datadog, notification, supabase, builtinSkills, createSkillMap, allSkills, isSkillConfigured, configuredSkills, validateWorkflowSkills, } from "./skills/index.js";
|
|
31
31
|
// Schema & validation
|
|
32
32
|
export { workflowZ, nodeZ, edgeZ, skillZ, parseWorkflow, validateWorkflow, workflowJsonSchema } from "./schema.js";
|
|
33
33
|
// MCP auto-injection
|
package/dist/skills/index.d.ts
CHANGED
|
@@ -11,8 +11,9 @@ import { sentry } from "./sentry.js";
|
|
|
11
11
|
import { datadog } from "./datadog.js";
|
|
12
12
|
import { betterstack } from "./betterstack.js";
|
|
13
13
|
import { notification } from "./notification.js";
|
|
14
|
+
import { supabase } from "./supabase.js";
|
|
14
15
|
export declare const builtinSkills: Skill[];
|
|
15
|
-
export { github, linear, slack, sentry, datadog, betterstack, notification };
|
|
16
|
+
export { github, linear, slack, sentry, datadog, betterstack, notification, supabase };
|
|
16
17
|
/**
|
|
17
18
|
* Build a skill map from an array of skills.
|
|
18
19
|
* Pass to `execute()` as the `skills` option.
|
package/dist/skills/index.js
CHANGED
|
@@ -10,9 +10,10 @@ import { sentry } from "./sentry.js";
|
|
|
10
10
|
import { datadog } from "./datadog.js";
|
|
11
11
|
import { betterstack } from "./betterstack.js";
|
|
12
12
|
import { notification } from "./notification.js";
|
|
13
|
+
import { supabase } from "./supabase.js";
|
|
13
14
|
// ─── Built-in skill catalog ─────────────────────────────────────
|
|
14
|
-
export const builtinSkills = [github, linear, slack, sentry, datadog, betterstack, notification];
|
|
15
|
-
export { github, linear, slack, sentry, datadog, betterstack, notification };
|
|
15
|
+
export const builtinSkills = [github, linear, slack, sentry, datadog, betterstack, notification, supabase];
|
|
16
|
+
export { github, linear, slack, sentry, datadog, betterstack, notification, supabase };
|
|
16
17
|
// ─── Registry helpers ───────────────────────────────────────────
|
|
17
18
|
/**
|
|
18
19
|
* Build a skill map from an array of skills.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase Skill
|
|
3
|
+
*
|
|
4
|
+
* Query, insert, update, and manage data in a Supabase project.
|
|
5
|
+
* Uses the Supabase REST API (PostgREST) for data operations
|
|
6
|
+
* and the Management API for project-level operations.
|
|
7
|
+
*/
|
|
8
|
+
import type { Skill } from "../types.js";
|
|
9
|
+
export declare const supabase: Skill;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase Skill
|
|
3
|
+
*
|
|
4
|
+
* Query, insert, update, and manage data in a Supabase project.
|
|
5
|
+
* Uses the Supabase REST API (PostgREST) for data operations
|
|
6
|
+
* and the Management API for project-level operations.
|
|
7
|
+
*/
|
|
8
|
+
async function supabaseRest(path, ctx, init) {
|
|
9
|
+
const url = ctx.config.SUPABASE_URL;
|
|
10
|
+
const key = ctx.config.SUPABASE_SERVICE_ROLE_KEY;
|
|
11
|
+
const res = await fetch(`${url}/rest/v1${path}`, {
|
|
12
|
+
...init,
|
|
13
|
+
headers: {
|
|
14
|
+
apikey: key,
|
|
15
|
+
Authorization: `Bearer ${key}`,
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
Prefer: init?.method === "POST" ? "return=representation" : "return=representation",
|
|
18
|
+
...init?.headers,
|
|
19
|
+
},
|
|
20
|
+
signal: AbortSignal.timeout(30_000),
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
throw new Error(`[Supabase] REST API failed (HTTP ${res.status}): ${await res.text()}`);
|
|
24
|
+
}
|
|
25
|
+
const text = await res.text();
|
|
26
|
+
return text ? JSON.parse(text) : null;
|
|
27
|
+
}
|
|
28
|
+
async function supabaseEdgeFunction(functionName, body, ctx) {
|
|
29
|
+
const url = ctx.config.SUPABASE_URL;
|
|
30
|
+
const key = ctx.config.SUPABASE_SERVICE_ROLE_KEY;
|
|
31
|
+
const res = await fetch(`${url}/functions/v1/${functionName}`, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${key}`,
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify(body),
|
|
38
|
+
signal: AbortSignal.timeout(60_000),
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
throw new Error(`[Supabase] Edge function "${functionName}" failed (HTTP ${res.status}): ${await res.text()}`);
|
|
42
|
+
}
|
|
43
|
+
return res.json();
|
|
44
|
+
}
|
|
45
|
+
export const supabase = {
|
|
46
|
+
id: "supabase",
|
|
47
|
+
name: "Supabase",
|
|
48
|
+
description: "Query, insert, update data and invoke edge functions on a Supabase project",
|
|
49
|
+
category: "general",
|
|
50
|
+
config: {
|
|
51
|
+
SUPABASE_URL: {
|
|
52
|
+
description: "Supabase project URL (e.g., https://xxxx.supabase.co)",
|
|
53
|
+
required: true,
|
|
54
|
+
env: "SUPABASE_URL",
|
|
55
|
+
},
|
|
56
|
+
SUPABASE_SERVICE_ROLE_KEY: {
|
|
57
|
+
description: "Supabase service role key (full access, server-side only)",
|
|
58
|
+
required: true,
|
|
59
|
+
env: "SUPABASE_SERVICE_ROLE_KEY",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
tools: [
|
|
63
|
+
// ─── Read ────────────────────────────────────────────────
|
|
64
|
+
{
|
|
65
|
+
name: "supabase_query",
|
|
66
|
+
description: "Query rows from a Supabase table. Uses PostgREST query syntax. " + "Returns an array of matching rows.",
|
|
67
|
+
input_schema: {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: {
|
|
70
|
+
table: { type: "string", description: "Table name (e.g., 'games', 'profiles')" },
|
|
71
|
+
select: {
|
|
72
|
+
type: "string",
|
|
73
|
+
description: "Columns to select (PostgREST syntax). Default: '*'. Examples: 'id,title,subject', 'id,title,course_modules(id,title)'",
|
|
74
|
+
},
|
|
75
|
+
filters: {
|
|
76
|
+
type: "array",
|
|
77
|
+
description: "PostgREST filter strings. Examples: ['subject=eq.math', 'difficulty=eq.easy', 'created_at=gte.2026-01-01']",
|
|
78
|
+
items: { type: "string" },
|
|
79
|
+
},
|
|
80
|
+
order: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "Order by clause. Examples: 'created_at.desc', 'title.asc'",
|
|
83
|
+
},
|
|
84
|
+
limit: {
|
|
85
|
+
type: "number",
|
|
86
|
+
description: "Maximum rows to return. Default: 100",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
required: ["table"],
|
|
90
|
+
},
|
|
91
|
+
handler: async (input, ctx) => {
|
|
92
|
+
const params = new URLSearchParams();
|
|
93
|
+
params.set("select", input.select || "*");
|
|
94
|
+
if (input.order)
|
|
95
|
+
params.set("order", input.order);
|
|
96
|
+
params.set("limit", String(input.limit || 100));
|
|
97
|
+
let path = `/${input.table}?${params}`;
|
|
98
|
+
if (input.filters) {
|
|
99
|
+
for (const f of input.filters) {
|
|
100
|
+
path += `&${f}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return supabaseRest(path, ctx);
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "supabase_count",
|
|
108
|
+
description: "Count rows in a table, optionally with filters.",
|
|
109
|
+
input_schema: {
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: {
|
|
112
|
+
table: { type: "string", description: "Table name" },
|
|
113
|
+
filters: {
|
|
114
|
+
type: "array",
|
|
115
|
+
description: "PostgREST filter strings",
|
|
116
|
+
items: { type: "string" },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
required: ["table"],
|
|
120
|
+
},
|
|
121
|
+
handler: async (input, ctx) => {
|
|
122
|
+
const url = ctx.config.SUPABASE_URL;
|
|
123
|
+
const key = ctx.config.SUPABASE_SERVICE_ROLE_KEY;
|
|
124
|
+
let path = `${url}/rest/v1/${input.table}?select=count`;
|
|
125
|
+
if (input.filters) {
|
|
126
|
+
for (const f of input.filters) {
|
|
127
|
+
path += `&${f}`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const res = await fetch(path, {
|
|
131
|
+
headers: {
|
|
132
|
+
apikey: key,
|
|
133
|
+
Authorization: `Bearer ${key}`,
|
|
134
|
+
Prefer: "count=exact",
|
|
135
|
+
},
|
|
136
|
+
signal: AbortSignal.timeout(15_000),
|
|
137
|
+
});
|
|
138
|
+
if (!res.ok) {
|
|
139
|
+
throw new Error(`[Supabase] Count failed: ${await res.text()}`);
|
|
140
|
+
}
|
|
141
|
+
const range = res.headers.get("content-range");
|
|
142
|
+
const count = range ? parseInt(range.split("/")[1], 10) : 0;
|
|
143
|
+
return { table: input.table, count };
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
// ─── Write ───────────────────────────────────────────────
|
|
147
|
+
{
|
|
148
|
+
name: "supabase_insert",
|
|
149
|
+
description: "Insert one or more rows into a table. Returns the inserted rows. " +
|
|
150
|
+
"Use upsert=true to update existing rows on conflict.",
|
|
151
|
+
input_schema: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
table: { type: "string", description: "Table name" },
|
|
155
|
+
rows: {
|
|
156
|
+
type: "array",
|
|
157
|
+
description: "Array of row objects to insert",
|
|
158
|
+
items: { type: "object" },
|
|
159
|
+
},
|
|
160
|
+
upsert: {
|
|
161
|
+
type: "boolean",
|
|
162
|
+
description: "If true, update on conflict instead of failing. Default: false",
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
required: ["table", "rows"],
|
|
166
|
+
},
|
|
167
|
+
handler: async (input, ctx) => {
|
|
168
|
+
const headers = {};
|
|
169
|
+
if (input.upsert) {
|
|
170
|
+
headers["Prefer"] = "resolution=merge-duplicates,return=representation";
|
|
171
|
+
}
|
|
172
|
+
return supabaseRest(`/${input.table}`, ctx, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers,
|
|
175
|
+
body: JSON.stringify(input.rows),
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "supabase_update",
|
|
181
|
+
description: "Update rows matching filters. Returns the updated rows.",
|
|
182
|
+
input_schema: {
|
|
183
|
+
type: "object",
|
|
184
|
+
properties: {
|
|
185
|
+
table: { type: "string", description: "Table name" },
|
|
186
|
+
filters: {
|
|
187
|
+
type: "array",
|
|
188
|
+
description: "PostgREST filter strings to match rows (REQUIRED — prevents accidental full-table updates)",
|
|
189
|
+
items: { type: "string" },
|
|
190
|
+
},
|
|
191
|
+
data: {
|
|
192
|
+
type: "object",
|
|
193
|
+
description: "Fields to update",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
required: ["table", "filters", "data"],
|
|
197
|
+
},
|
|
198
|
+
handler: async (input, ctx) => {
|
|
199
|
+
if (!input.filters || input.filters.length === 0) {
|
|
200
|
+
throw new Error("Filters are required for update operations to prevent accidental full-table updates");
|
|
201
|
+
}
|
|
202
|
+
let path = `/${input.table}`;
|
|
203
|
+
const filterParts = input.filters.map((f) => f);
|
|
204
|
+
path += `?${filterParts.join("&")}`;
|
|
205
|
+
return supabaseRest(path, ctx, {
|
|
206
|
+
method: "PATCH",
|
|
207
|
+
body: JSON.stringify(input.data),
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: "supabase_delete",
|
|
213
|
+
description: "Delete rows matching filters. Returns the deleted rows.",
|
|
214
|
+
input_schema: {
|
|
215
|
+
type: "object",
|
|
216
|
+
properties: {
|
|
217
|
+
table: { type: "string", description: "Table name" },
|
|
218
|
+
filters: {
|
|
219
|
+
type: "array",
|
|
220
|
+
description: "PostgREST filter strings (REQUIRED — prevents accidental full-table deletes)",
|
|
221
|
+
items: { type: "string" },
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
required: ["table", "filters"],
|
|
225
|
+
},
|
|
226
|
+
handler: async (input, ctx) => {
|
|
227
|
+
if (!input.filters || input.filters.length === 0) {
|
|
228
|
+
throw new Error("Filters are required for delete operations");
|
|
229
|
+
}
|
|
230
|
+
const path = `/${input.table}?${input.filters.join("&")}`;
|
|
231
|
+
return supabaseRest(path, ctx, { method: "DELETE" });
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
// ─── RPC ─────────────────────────────────────────────────
|
|
235
|
+
{
|
|
236
|
+
name: "supabase_rpc",
|
|
237
|
+
description: "Call a Supabase RPC (stored procedure / database function).",
|
|
238
|
+
input_schema: {
|
|
239
|
+
type: "object",
|
|
240
|
+
properties: {
|
|
241
|
+
function_name: { type: "string", description: "Name of the database function" },
|
|
242
|
+
params: {
|
|
243
|
+
type: "object",
|
|
244
|
+
description: "Parameters to pass to the function",
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
required: ["function_name"],
|
|
248
|
+
},
|
|
249
|
+
handler: async (input, ctx) => {
|
|
250
|
+
return supabaseRest(`/rpc/${input.function_name}`, ctx, {
|
|
251
|
+
method: "POST",
|
|
252
|
+
body: JSON.stringify(input.params || {}),
|
|
253
|
+
});
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
// ─── Edge Functions ──────────────────────────────────────
|
|
257
|
+
{
|
|
258
|
+
name: "supabase_invoke_function",
|
|
259
|
+
description: "Invoke a Supabase Edge Function by name with a JSON body.",
|
|
260
|
+
input_schema: {
|
|
261
|
+
type: "object",
|
|
262
|
+
properties: {
|
|
263
|
+
function_name: {
|
|
264
|
+
type: "string",
|
|
265
|
+
description: "Edge function name (e.g., 'agent-gateway', 'seed-content')",
|
|
266
|
+
},
|
|
267
|
+
body: {
|
|
268
|
+
type: "object",
|
|
269
|
+
description: "JSON body to send to the function",
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
required: ["function_name", "body"],
|
|
273
|
+
},
|
|
274
|
+
handler: async (input, ctx) => {
|
|
275
|
+
return supabaseEdgeFunction(input.function_name, input.body, ctx);
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
// ─── Auth Admin ──────────────────────────────────────────
|
|
279
|
+
{
|
|
280
|
+
name: "supabase_list_users",
|
|
281
|
+
description: "List auth users. Returns user IDs, emails, and metadata.",
|
|
282
|
+
input_schema: {
|
|
283
|
+
type: "object",
|
|
284
|
+
properties: {
|
|
285
|
+
page: { type: "number", description: "Page number (1-based). Default: 1" },
|
|
286
|
+
per_page: { type: "number", description: "Users per page. Default: 50" },
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
handler: async (input, ctx) => {
|
|
290
|
+
const url = ctx.config.SUPABASE_URL;
|
|
291
|
+
const key = ctx.config.SUPABASE_SERVICE_ROLE_KEY;
|
|
292
|
+
const page = input.page || 1;
|
|
293
|
+
const perPage = input.per_page || 50;
|
|
294
|
+
const res = await fetch(`${url}/auth/v1/admin/users?page=${page}&per_page=${perPage}`, {
|
|
295
|
+
headers: {
|
|
296
|
+
Authorization: `Bearer ${key}`,
|
|
297
|
+
apikey: key,
|
|
298
|
+
},
|
|
299
|
+
signal: AbortSignal.timeout(15_000),
|
|
300
|
+
});
|
|
301
|
+
if (!res.ok) {
|
|
302
|
+
throw new Error(`[Supabase] List users failed: ${await res.text()}`);
|
|
303
|
+
}
|
|
304
|
+
return res.json();
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
// ─── Schema Introspection ────────────────────────────────
|
|
308
|
+
{
|
|
309
|
+
name: "supabase_list_tables",
|
|
310
|
+
description: "List all public tables with row counts. Useful for content auditing.",
|
|
311
|
+
input_schema: {
|
|
312
|
+
type: "object",
|
|
313
|
+
properties: {},
|
|
314
|
+
},
|
|
315
|
+
handler: async (_input, ctx) => {
|
|
316
|
+
return supabaseRest("/rpc/get_table_counts", ctx, { method: "POST", body: JSON.stringify({}) }).catch(async () => {
|
|
317
|
+
// Fallback: query information_schema
|
|
318
|
+
const url = ctx.config.SUPABASE_URL;
|
|
319
|
+
const key = ctx.config.SUPABASE_SERVICE_ROLE_KEY;
|
|
320
|
+
const res = await fetch(`${url}/rest/v1/rpc/`, {
|
|
321
|
+
method: "POST",
|
|
322
|
+
headers: {
|
|
323
|
+
apikey: key,
|
|
324
|
+
Authorization: `Bearer ${key}`,
|
|
325
|
+
"Content-Type": "application/json",
|
|
326
|
+
},
|
|
327
|
+
body: JSON.stringify({}),
|
|
328
|
+
});
|
|
329
|
+
return { note: "get_table_counts RPC not available. Use supabase_query to check specific tables." };
|
|
330
|
+
});
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
};
|
package/dist/workflows/index.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Generation Workflow
|
|
3
|
+
*
|
|
4
|
+
* A DAG that audits a Supabase-backed educational platform for content gaps,
|
|
5
|
+
* generates high-quality content using Claude, validates it, and publishes it.
|
|
6
|
+
*
|
|
7
|
+
* Designed for solo operators bootstrapping educational SaaS platforms.
|
|
8
|
+
* Uses Claude (via your existing subscription) for generation instead of
|
|
9
|
+
* burning API credits on external AI gateways.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { execute, ClaudeClient, createSkillMap, supabase } from '@sweny-ai/core'
|
|
14
|
+
* import { seedContentWorkflow } from '@sweny-ai/core/workflows'
|
|
15
|
+
*
|
|
16
|
+
* const skills = createSkillMap([supabase])
|
|
17
|
+
* const claude = new ClaudeClient()
|
|
18
|
+
*
|
|
19
|
+
* const results = await execute(seedContentWorkflow, {
|
|
20
|
+
* contentType: 'worksheet', // 'worksheet' | 'game' | 'lesson'
|
|
21
|
+
* subject: 'math',
|
|
22
|
+
* topic: 'fractions',
|
|
23
|
+
* difficulty: 'easy',
|
|
24
|
+
* gradeLevel: '3rd-grade',
|
|
25
|
+
* count: 5,
|
|
26
|
+
* }, { skills, claude })
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
import type { Workflow } from "../types.js";
|
|
30
|
+
export declare const seedContentWorkflow: Workflow;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Generation Workflow
|
|
3
|
+
*
|
|
4
|
+
* A DAG that audits a Supabase-backed educational platform for content gaps,
|
|
5
|
+
* generates high-quality content using Claude, validates it, and publishes it.
|
|
6
|
+
*
|
|
7
|
+
* Designed for solo operators bootstrapping educational SaaS platforms.
|
|
8
|
+
* Uses Claude (via your existing subscription) for generation instead of
|
|
9
|
+
* burning API credits on external AI gateways.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { execute, ClaudeClient, createSkillMap, supabase } from '@sweny-ai/core'
|
|
14
|
+
* import { seedContentWorkflow } from '@sweny-ai/core/workflows'
|
|
15
|
+
*
|
|
16
|
+
* const skills = createSkillMap([supabase])
|
|
17
|
+
* const claude = new ClaudeClient()
|
|
18
|
+
*
|
|
19
|
+
* const results = await execute(seedContentWorkflow, {
|
|
20
|
+
* contentType: 'worksheet', // 'worksheet' | 'game' | 'lesson'
|
|
21
|
+
* subject: 'math',
|
|
22
|
+
* topic: 'fractions',
|
|
23
|
+
* difficulty: 'easy',
|
|
24
|
+
* gradeLevel: '3rd-grade',
|
|
25
|
+
* count: 5,
|
|
26
|
+
* }, { skills, claude })
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const seedContentWorkflow = {
|
|
30
|
+
id: "seed-content",
|
|
31
|
+
name: "Content Generation Pipeline",
|
|
32
|
+
description: "Audit content gaps, generate educational content, validate quality, and publish to Supabase",
|
|
33
|
+
entry: "audit",
|
|
34
|
+
nodes: {
|
|
35
|
+
// ─── Step 1: Audit ─────────────────────────────────────
|
|
36
|
+
audit: {
|
|
37
|
+
name: "Audit Content Gaps",
|
|
38
|
+
instruction: `You are auditing an educational platform's content library.
|
|
39
|
+
|
|
40
|
+
Use supabase_count to check how many items exist for each combination of:
|
|
41
|
+
- Content types: worksheets, games, lessons
|
|
42
|
+
- Subjects: math, science, english, history, geography, economics, music, art, life-skills, foreign-language
|
|
43
|
+
- Difficulties: beginner, easy, medium, hard
|
|
44
|
+
|
|
45
|
+
Also check the input parameters — if the user specified a subject, topic, difficulty, and count,
|
|
46
|
+
skip the full audit and just report what they want generated.
|
|
47
|
+
|
|
48
|
+
Return a structured content plan: what to generate, how many, at what difficulty and grade level.
|
|
49
|
+
Prioritize gaps where a subject has ZERO content of a given type.`,
|
|
50
|
+
skills: ["supabase"],
|
|
51
|
+
output: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
plan: {
|
|
55
|
+
type: "array",
|
|
56
|
+
items: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
contentType: { type: "string" },
|
|
60
|
+
subject: { type: "string" },
|
|
61
|
+
topic: { type: "string" },
|
|
62
|
+
difficulty: { type: "string" },
|
|
63
|
+
gradeLevel: { type: "string" },
|
|
64
|
+
count: { type: "number" },
|
|
65
|
+
reason: { type: "string" },
|
|
66
|
+
},
|
|
67
|
+
required: ["contentType", "subject", "topic", "difficulty", "count"],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
summary: { type: "string" },
|
|
71
|
+
},
|
|
72
|
+
required: ["plan", "summary"],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
// ─── Step 2: Generate ──────────────────────────────────
|
|
76
|
+
generate: {
|
|
77
|
+
name: "Generate Content",
|
|
78
|
+
instruction: `You are an expert educator creating content for children ages 5-14.
|
|
79
|
+
|
|
80
|
+
For each item in the content plan from the previous step, generate the content directly.
|
|
81
|
+
The content must be high-quality, engaging, age-appropriate, and educationally sound.
|
|
82
|
+
|
|
83
|
+
**For worksheets:**
|
|
84
|
+
Generate a JSON array of problems. Each problem needs: "problem", "answer", "format" (answer-line | multiple-choice | write-on).
|
|
85
|
+
Requirements:
|
|
86
|
+
- Every answer MUST be correct. Double-check math, facts, spelling.
|
|
87
|
+
- Problems must progress in difficulty within the set.
|
|
88
|
+
- Use at least 2 different problem structures (e.g., equations + word problems for math).
|
|
89
|
+
- Vary phrasing — no two problems should feel copy-pasted.
|
|
90
|
+
- For the target grade level, use appropriate vocabulary and complexity.
|
|
91
|
+
|
|
92
|
+
**For lessons:**
|
|
93
|
+
Generate complete HTML + CSS + JS for an interactive lesson.
|
|
94
|
+
Requirements:
|
|
95
|
+
- Must have substantial educational content (not just a shell with navigation).
|
|
96
|
+
- Include interactive elements: quizzes, click-to-reveal, drag-and-drop.
|
|
97
|
+
- Must be visually engaging with colors, icons, and clear typography.
|
|
98
|
+
- Include practice exercises with immediate feedback.
|
|
99
|
+
- Minimum 5 teaching sections plus 2 practice activities.
|
|
100
|
+
|
|
101
|
+
**For games:**
|
|
102
|
+
Generate a description and educational requirements only (actual game code is generated
|
|
103
|
+
by the platform's Phaser-based generator).
|
|
104
|
+
|
|
105
|
+
Use supabase_insert to write each piece of content to the appropriate table.
|
|
106
|
+
|
|
107
|
+
Table schemas:
|
|
108
|
+
- worksheets: id (auto), title, subject, topic, difficulty, grade_level, num_questions, problems (jsonb), status='published', source='ai-seed'
|
|
109
|
+
- lessons: id (auto), title, subject, difficulty, grade_level, lesson_type, html_code, css_code, js_code, status='published', source='ai-seed'
|
|
110
|
+
- games: id (auto), title, subject, difficulty, grade_level, game_type, html_code, css_code, js_code, status='published', source='ai-seed'
|
|
111
|
+
|
|
112
|
+
Return a summary of what was generated and inserted.`,
|
|
113
|
+
skills: ["supabase"],
|
|
114
|
+
output: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
generated: {
|
|
118
|
+
type: "array",
|
|
119
|
+
items: {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
contentType: { type: "string" },
|
|
123
|
+
title: { type: "string" },
|
|
124
|
+
subject: { type: "string" },
|
|
125
|
+
id: { type: "string" },
|
|
126
|
+
status: { type: "string" },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
totalGenerated: { type: "number" },
|
|
131
|
+
errors: { type: "array", items: { type: "string" } },
|
|
132
|
+
},
|
|
133
|
+
required: ["generated", "totalGenerated"],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
// ─── Step 3: Validate ──────────────────────────────────
|
|
137
|
+
validate: {
|
|
138
|
+
name: "Validate Content Quality",
|
|
139
|
+
instruction: `Review the content that was just generated and inserted.
|
|
140
|
+
|
|
141
|
+
For each item, use supabase_query to fetch it back and verify:
|
|
142
|
+
|
|
143
|
+
**Worksheets:**
|
|
144
|
+
- Parse the problems JSON — are all answers correct?
|
|
145
|
+
- Are problems diverse (not repetitive)?
|
|
146
|
+
- Is difficulty appropriate for the stated grade level?
|
|
147
|
+
- Are there the right number of questions?
|
|
148
|
+
|
|
149
|
+
**Lessons:**
|
|
150
|
+
- Is the HTML substantial (> 500 characters)?
|
|
151
|
+
- Does it contain actual educational content (not just navigation shells)?
|
|
152
|
+
- Are there interactive elements?
|
|
153
|
+
|
|
154
|
+
**Games:**
|
|
155
|
+
- Does the HTML contain a working game structure?
|
|
156
|
+
|
|
157
|
+
Flag any issues. If critical issues are found (wrong answers, empty content),
|
|
158
|
+
use supabase_update to set status='draft' and add a note.
|
|
159
|
+
|
|
160
|
+
Return a quality report.`,
|
|
161
|
+
skills: ["supabase"],
|
|
162
|
+
output: {
|
|
163
|
+
type: "object",
|
|
164
|
+
properties: {
|
|
165
|
+
totalReviewed: { type: "number" },
|
|
166
|
+
passed: { type: "number" },
|
|
167
|
+
flagged: { type: "number" },
|
|
168
|
+
issues: {
|
|
169
|
+
type: "array",
|
|
170
|
+
items: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
id: { type: "string" },
|
|
174
|
+
title: { type: "string" },
|
|
175
|
+
issue: { type: "string" },
|
|
176
|
+
severity: { type: "string" },
|
|
177
|
+
action: { type: "string" },
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
summary: { type: "string" },
|
|
182
|
+
},
|
|
183
|
+
required: ["totalReviewed", "passed", "flagged", "summary"],
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
// ─── Step 4: Report ────────────────────────────────────
|
|
187
|
+
report: {
|
|
188
|
+
name: "Generate Report",
|
|
189
|
+
instruction: `Compile a final report of the content generation run.
|
|
190
|
+
|
|
191
|
+
Include:
|
|
192
|
+
- Total content generated by type and subject
|
|
193
|
+
- Quality validation results
|
|
194
|
+
- Any issues found and actions taken
|
|
195
|
+
- Recommendations for next content generation run (what gaps remain)
|
|
196
|
+
|
|
197
|
+
Format as a clean markdown summary that a solo operator can scan in 30 seconds.`,
|
|
198
|
+
skills: [],
|
|
199
|
+
output: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
markdown: { type: "string" },
|
|
203
|
+
nextPriorities: {
|
|
204
|
+
type: "array",
|
|
205
|
+
items: { type: "string" },
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
required: ["markdown"],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
edges: [
|
|
213
|
+
{ from: "audit", to: "generate" },
|
|
214
|
+
{ from: "generate", to: "validate" },
|
|
215
|
+
{ from: "validate", to: "report" },
|
|
216
|
+
],
|
|
217
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sweny-ai/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sweny": "./dist/cli/main.js"
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"chalk": "^5",
|
|
53
53
|
"commander": "^13",
|
|
54
54
|
"yaml": "^2",
|
|
55
|
-
"zod": "^
|
|
55
|
+
"zod": "^3.25.76"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@anthropic-ai/claude-code": "^2.1.84",
|