@mugwork/mug 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/LICENSE +21 -0
- package/README.md +251 -0
- package/dist/explorer.js +3 -0
- package/dist/packages/email-template/src/email-template.d.ts +18 -0
- package/dist/packages/email-template/src/email-template.js +74 -0
- package/dist/packages/email-template/src/index.d.ts +1 -0
- package/dist/packages/email-template/src/index.js +1 -0
- package/dist/packages/surface-renderer/src/form-renderer.d.ts +117 -0
- package/dist/packages/surface-renderer/src/form-renderer.js +719 -0
- package/dist/packages/surface-renderer/src/index.d.ts +4 -0
- package/dist/packages/surface-renderer/src/index.js +2 -0
- package/dist/packages/surface-renderer/src/portal-renderer.d.ts +177 -0
- package/dist/packages/surface-renderer/src/portal-renderer.js +1089 -0
- package/dist/packages/surface-renderer/src/workspace-home.d.ts +46 -0
- package/dist/packages/surface-renderer/src/workspace-home.js +345 -0
- package/dist/runtime/agent-types.d.ts +48 -0
- package/dist/runtime/agent-types.js +3 -0
- package/dist/runtime/ai-router.d.ts +32 -0
- package/dist/runtime/ai-router.js +112 -0
- package/dist/runtime/app.d.ts +6 -0
- package/dist/runtime/app.js +399 -0
- package/dist/runtime/chunker.d.ts +6 -0
- package/dist/runtime/chunker.js +30 -0
- package/dist/runtime/context.d.ts +115 -0
- package/dist/runtime/context.js +440 -0
- package/dist/runtime/do/workspace-database.d.ts +10 -0
- package/dist/runtime/do/workspace-database.js +199 -0
- package/dist/runtime/form-types.d.ts +143 -0
- package/dist/runtime/form-types.js +1 -0
- package/dist/runtime/runtime.d.ts +9 -0
- package/dist/runtime/runtime.js +7 -0
- package/dist/runtime/source-types.d.ts +15 -0
- package/dist/runtime/source-types.js +1 -0
- package/dist/runtime/source.d.ts +70 -0
- package/dist/runtime/source.js +21 -0
- package/dist/runtime/sync-runtime.d.ts +10 -0
- package/dist/runtime/sync-runtime.js +185 -0
- package/dist/runtime/types.d.ts +21 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/workflow-entrypoint.d.ts +31 -0
- package/dist/runtime/workflow-entrypoint.js +1297 -0
- package/dist/runtime/workflow.d.ts +285 -0
- package/dist/runtime/workflow.js +1008 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +44116 -0
- package/dist/src/commands/ai-gateway-route.d.ts +24 -0
- package/dist/src/commands/ai-gateway-route.js +192 -0
- package/dist/src/commands/auth.d.ts +1 -0
- package/dist/src/commands/auth.js +42 -0
- package/dist/src/commands/billing.d.ts +6 -0
- package/dist/src/commands/billing.js +76 -0
- package/dist/src/commands/brain.d.ts +1 -0
- package/dist/src/commands/brain.js +194 -0
- package/dist/src/commands/demo.d.ts +12 -0
- package/dist/src/commands/demo.js +147 -0
- package/dist/src/commands/deploy.d.ts +1 -0
- package/dist/src/commands/deploy.js +1052 -0
- package/dist/src/commands/dev.d.ts +14 -0
- package/dist/src/commands/dev.js +2818 -0
- package/dist/src/commands/form.d.ts +8 -0
- package/dist/src/commands/form.js +396 -0
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.js +139 -0
- package/dist/src/commands/issue.d.ts +7 -0
- package/dist/src/commands/issue.js +191 -0
- package/dist/src/commands/login.d.ts +9 -0
- package/dist/src/commands/login.js +163 -0
- package/dist/src/commands/logs.d.ts +8 -0
- package/dist/src/commands/logs.js +113 -0
- package/dist/src/commands/portal.d.ts +2 -0
- package/dist/src/commands/portal.js +111 -0
- package/dist/src/commands/pull.d.ts +3 -0
- package/dist/src/commands/pull.js +184 -0
- package/dist/src/commands/push.d.ts +4 -0
- package/dist/src/commands/push.js +183 -0
- package/dist/src/commands/run.d.ts +6 -0
- package/dist/src/commands/run.js +91 -0
- package/dist/src/commands/secret.d.ts +7 -0
- package/dist/src/commands/secret.js +105 -0
- package/dist/src/commands/shutdown.d.ts +1 -0
- package/dist/src/commands/shutdown.js +46 -0
- package/dist/src/commands/sql.d.ts +8 -0
- package/dist/src/commands/sql.js +142 -0
- package/dist/src/commands/status.d.ts +5 -0
- package/dist/src/commands/status.js +39 -0
- package/dist/src/commands/sync.d.ts +7 -0
- package/dist/src/commands/sync.js +991 -0
- package/dist/src/commands/usage.d.ts +6 -0
- package/dist/src/commands/usage.js +78 -0
- package/dist/src/commands/webhooks.d.ts +1 -0
- package/dist/src/commands/webhooks.js +102 -0
- package/dist/src/commands/workspace.d.ts +23 -0
- package/dist/src/commands/workspace.js +590 -0
- package/dist/src/connector-migration.d.ts +20 -0
- package/dist/src/connector-migration.js +43 -0
- package/dist/src/connector-parser.d.ts +14 -0
- package/dist/src/connector-parser.js +94 -0
- package/dist/src/connector-service/discover.d.ts +37 -0
- package/dist/src/connector-service/discover.js +79 -0
- package/dist/src/connector-service/gather.d.ts +22 -0
- package/dist/src/connector-service/gather.js +89 -0
- package/dist/src/connector-service/init.d.ts +14 -0
- package/dist/src/connector-service/init.js +109 -0
- package/dist/src/connector-service/scaffold.d.ts +17 -0
- package/dist/src/connector-service/scaffold.js +194 -0
- package/dist/src/connector-service/spec-storage.d.ts +8 -0
- package/dist/src/connector-service/spec-storage.js +48 -0
- package/dist/src/connector-service/types.d.ts +57 -0
- package/dist/src/connector-service/types.js +2 -0
- package/dist/src/connector-service/verify.d.ts +24 -0
- package/dist/src/connector-service/verify.js +575 -0
- package/dist/src/email-template.d.ts +2 -0
- package/dist/src/email-template.js +1 -0
- package/dist/src/manifest.d.ts +31 -0
- package/dist/src/manifest.js +25 -0
- package/dist/src/mug-icon.d.ts +1 -0
- package/dist/src/mug-icon.js +12 -0
- package/dist/src/slack-manifest.d.ts +119 -0
- package/dist/src/slack-manifest.js +163 -0
- package/dist/src/source-migration.d.ts +20 -0
- package/dist/src/source-migration.js +43 -0
- package/dist/src/surface-renderer.d.ts +5 -0
- package/dist/src/surface-renderer.js +3 -0
- package/dist/src/templates.d.ts +3 -0
- package/dist/src/templates.js +48 -0
- package/dist/src/version-check.d.ts +1 -0
- package/dist/src/version-check.js +28 -0
- package/dist/src/workflow-parser.d.ts +95 -0
- package/dist/src/workflow-parser.js +526 -0
- package/dist/worker/src/agent-types.d.ts +27 -0
- package/dist/worker/src/agent-types.js +3 -0
- package/dist/worker/src/source-types.d.ts +14 -0
- package/dist/worker/src/source-types.js +1 -0
- package/package.json +90 -0
- package/src/data/model-capabilities.json +171 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function checkCliVersion(): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
function getVersions() {
|
|
6
|
+
const cliPkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
7
|
+
if (!existsSync(cliPkgPath))
|
|
8
|
+
return { current: "unknown", latest: null };
|
|
9
|
+
const cliPkg = JSON.parse(readFileSync(cliPkgPath, "utf-8"));
|
|
10
|
+
const current = cliPkg.version;
|
|
11
|
+
try {
|
|
12
|
+
const latest = execSync("npm view @mugwork/mug version 2>/dev/null", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
13
|
+
return { current, latest: latest || null };
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return { current, latest: null };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function checkCliVersion() {
|
|
20
|
+
const { current, latest } = getVersions();
|
|
21
|
+
if (!latest || latest === current)
|
|
22
|
+
return;
|
|
23
|
+
const line = "━".repeat(56);
|
|
24
|
+
console.warn(`\n\x1b[33m${line}\x1b[0m`);
|
|
25
|
+
console.warn(`\x1b[33m ⚠ Mug CLI update available: ${current} → ${latest}\x1b[0m`);
|
|
26
|
+
console.warn(`\x1b[33m Run: npm update -g @mugwork/mug\x1b[0m`);
|
|
27
|
+
console.warn(`\x1b[33m${line}\x1b[0m\n`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export type WorkflowStep = {
|
|
2
|
+
type: "query";
|
|
3
|
+
database: string;
|
|
4
|
+
label: string;
|
|
5
|
+
sql: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
} | {
|
|
8
|
+
type: "exec";
|
|
9
|
+
database: string;
|
|
10
|
+
label: string;
|
|
11
|
+
sql: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: "ai";
|
|
15
|
+
model: string;
|
|
16
|
+
label: string;
|
|
17
|
+
prompt: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: "email";
|
|
21
|
+
label: string;
|
|
22
|
+
to?: string;
|
|
23
|
+
subject?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: "sms";
|
|
27
|
+
label: string;
|
|
28
|
+
to?: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
} | {
|
|
31
|
+
type: "slack";
|
|
32
|
+
label: string;
|
|
33
|
+
channel?: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
} | {
|
|
36
|
+
type: "collect";
|
|
37
|
+
label: string;
|
|
38
|
+
formTitle: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
} | {
|
|
41
|
+
type: "http";
|
|
42
|
+
label: string;
|
|
43
|
+
url: string;
|
|
44
|
+
method: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
} | {
|
|
47
|
+
type: "return";
|
|
48
|
+
label: string;
|
|
49
|
+
value?: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
} | {
|
|
52
|
+
type: "loop";
|
|
53
|
+
variable: string;
|
|
54
|
+
collection: string;
|
|
55
|
+
label: string;
|
|
56
|
+
steps: WorkflowStep[];
|
|
57
|
+
description?: string;
|
|
58
|
+
} | {
|
|
59
|
+
type: "branch";
|
|
60
|
+
condition: string;
|
|
61
|
+
label: string;
|
|
62
|
+
if: WorkflowStep[];
|
|
63
|
+
else?: WorkflowStep[];
|
|
64
|
+
after?: WorkflowStep[];
|
|
65
|
+
description?: string;
|
|
66
|
+
};
|
|
67
|
+
export interface WebhookConfig {
|
|
68
|
+
auth: "none" | "hmac" | "bearer";
|
|
69
|
+
secret?: string;
|
|
70
|
+
}
|
|
71
|
+
export interface TriggerConfig {
|
|
72
|
+
type?: string;
|
|
73
|
+
source?: string;
|
|
74
|
+
table?: string;
|
|
75
|
+
on?: "insert" | "update" | "delete" | "change";
|
|
76
|
+
includeInitialSync?: boolean;
|
|
77
|
+
command?: string;
|
|
78
|
+
description?: string;
|
|
79
|
+
usage_hint?: string;
|
|
80
|
+
event?: string;
|
|
81
|
+
name?: string;
|
|
82
|
+
callback_id?: string;
|
|
83
|
+
}
|
|
84
|
+
export interface WorkflowStepTree {
|
|
85
|
+
name: string;
|
|
86
|
+
description?: string;
|
|
87
|
+
schedule?: string;
|
|
88
|
+
webhook?: boolean | WebhookConfig;
|
|
89
|
+
inbound?: "sms" | "email" | "slack";
|
|
90
|
+
trigger?: TriggerConfig;
|
|
91
|
+
sourceFile: string;
|
|
92
|
+
steps: WorkflowStep[];
|
|
93
|
+
}
|
|
94
|
+
export declare function countSteps(steps: WorkflowStep[]): number;
|
|
95
|
+
export declare function parseWorkflowSteps(source: string, name: string, sourceFile?: string): WorkflowStepTree;
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import * as acorn from "acorn";
|
|
2
|
+
import * as walk from "acorn-walk";
|
|
3
|
+
import { transformSync } from "esbuild";
|
|
4
|
+
export function countSteps(steps) {
|
|
5
|
+
let count = 0;
|
|
6
|
+
for (const step of steps) {
|
|
7
|
+
count++;
|
|
8
|
+
if (step.type === "loop")
|
|
9
|
+
count += countSteps(step.steps);
|
|
10
|
+
if (step.type === "branch") {
|
|
11
|
+
count += countSteps(step.if);
|
|
12
|
+
if (step.else)
|
|
13
|
+
count += countSteps(step.else);
|
|
14
|
+
if (step.after)
|
|
15
|
+
count += countSteps(step.after);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return count;
|
|
19
|
+
}
|
|
20
|
+
function extractCommentAnnotations(originalSource) {
|
|
21
|
+
const lines = originalSource.split("\n");
|
|
22
|
+
const annotations = [];
|
|
23
|
+
for (let i = 0; i < lines.length; i++) {
|
|
24
|
+
const line = lines[i];
|
|
25
|
+
let method = null;
|
|
26
|
+
const notifyMatch = line.match(/ctx\.notify\.(email|sms|slack)\s*\(/);
|
|
27
|
+
if (notifyMatch) {
|
|
28
|
+
method = `ctx.notify.${notifyMatch[1]}`;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const ctxMatch = line.match(/ctx\.(query|exec|ai|collect|http)\s*\(/);
|
|
32
|
+
if (ctxMatch)
|
|
33
|
+
method = `ctx.${ctxMatch[1]}`;
|
|
34
|
+
}
|
|
35
|
+
if (!method && /\breturn[\s({;]/.test(line)) {
|
|
36
|
+
method = "return";
|
|
37
|
+
}
|
|
38
|
+
if (!method)
|
|
39
|
+
continue;
|
|
40
|
+
const commentLines = [];
|
|
41
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
42
|
+
const prev = lines[j].trim();
|
|
43
|
+
if (prev === "")
|
|
44
|
+
continue;
|
|
45
|
+
if (prev.startsWith("//")) {
|
|
46
|
+
commentLines.unshift(prev.replace(/^\/\/\s*/, ""));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
annotations.push({ method, comment: commentLines.length > 0 ? commentLines.join(" ") : "" });
|
|
53
|
+
}
|
|
54
|
+
return annotations;
|
|
55
|
+
}
|
|
56
|
+
export function parseWorkflowSteps(source, name, sourceFile) {
|
|
57
|
+
const commentAnnotations = extractCommentAnnotations(source);
|
|
58
|
+
const js = transformSync(source, { loader: "ts", target: "es2022" }).code;
|
|
59
|
+
const ast = acorn.parse(js, { ecmaVersion: 2022, sourceType: "module" });
|
|
60
|
+
let schedule;
|
|
61
|
+
let webhook;
|
|
62
|
+
let inbound;
|
|
63
|
+
let trigger;
|
|
64
|
+
let description;
|
|
65
|
+
let handlerBody = null;
|
|
66
|
+
walk.simple(ast, {
|
|
67
|
+
CallExpression(node) {
|
|
68
|
+
if (node.callee?.name !== "workflow")
|
|
69
|
+
return;
|
|
70
|
+
const args = node.arguments;
|
|
71
|
+
if (args.length < 2)
|
|
72
|
+
return;
|
|
73
|
+
const handler = args[1];
|
|
74
|
+
if (handler.type === "ArrowFunctionExpression" || handler.type === "FunctionExpression") {
|
|
75
|
+
handlerBody = handler.body;
|
|
76
|
+
}
|
|
77
|
+
if (args.length >= 3 && args[2].type === "ObjectExpression") {
|
|
78
|
+
for (const prop of args[2].properties) {
|
|
79
|
+
if (prop.key?.name === "schedule" && prop.value?.type === "Literal") {
|
|
80
|
+
schedule = prop.value.value;
|
|
81
|
+
}
|
|
82
|
+
if (prop.key?.name === "webhook") {
|
|
83
|
+
if (prop.value?.type === "Literal") {
|
|
84
|
+
webhook = prop.value.value;
|
|
85
|
+
}
|
|
86
|
+
else if (prop.value?.type === "ObjectExpression") {
|
|
87
|
+
const wh = { auth: "none" };
|
|
88
|
+
for (const whProp of prop.value.properties ?? []) {
|
|
89
|
+
if (whProp.key?.name === "auth" && whProp.value?.type === "Literal") {
|
|
90
|
+
wh.auth = whProp.value.value;
|
|
91
|
+
}
|
|
92
|
+
if (whProp.key?.name === "secret" && whProp.value?.type === "Literal") {
|
|
93
|
+
wh.secret = whProp.value.value;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
webhook = wh;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (prop.key?.name === "inbound" && prop.value?.type === "Literal") {
|
|
100
|
+
inbound = prop.value.value;
|
|
101
|
+
}
|
|
102
|
+
if (prop.key?.name === "trigger" && prop.value?.type === "ObjectExpression") {
|
|
103
|
+
const t = {};
|
|
104
|
+
for (const tp of prop.value.properties ?? []) {
|
|
105
|
+
if (tp.value?.type !== "Literal")
|
|
106
|
+
continue;
|
|
107
|
+
const key = tp.key?.name;
|
|
108
|
+
const val = tp.value.value;
|
|
109
|
+
if (key === "type")
|
|
110
|
+
t.type = val;
|
|
111
|
+
else if (key === "source")
|
|
112
|
+
t.source = val;
|
|
113
|
+
else if (key === "table")
|
|
114
|
+
t.table = val;
|
|
115
|
+
else if (key === "on")
|
|
116
|
+
t.on = val;
|
|
117
|
+
else if (key === "includeInitialSync")
|
|
118
|
+
t.includeInitialSync = val;
|
|
119
|
+
else if (key === "command")
|
|
120
|
+
t.command = val;
|
|
121
|
+
else if (key === "description")
|
|
122
|
+
t.description = val;
|
|
123
|
+
else if (key === "usage_hint")
|
|
124
|
+
t.usage_hint = val;
|
|
125
|
+
else if (key === "event")
|
|
126
|
+
t.event = val;
|
|
127
|
+
else if (key === "name")
|
|
128
|
+
t.name = val;
|
|
129
|
+
else if (key === "callback_id")
|
|
130
|
+
t.callback_id = val;
|
|
131
|
+
}
|
|
132
|
+
if (t.source || t.type)
|
|
133
|
+
trigger = t;
|
|
134
|
+
}
|
|
135
|
+
if (prop.key?.name === "description" && prop.value?.type === "Literal") {
|
|
136
|
+
description = prop.value.value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
if (!handlerBody) {
|
|
143
|
+
return { name, description, schedule, webhook, inbound, trigger, sourceFile: sourceFile ?? "", steps: [] };
|
|
144
|
+
}
|
|
145
|
+
const commentCursors = new Map();
|
|
146
|
+
const steps = extractSteps(handlerBody, js, commentAnnotations, commentCursors);
|
|
147
|
+
const nested = nestAfterBranches(steps);
|
|
148
|
+
return { name, description, schedule, webhook, inbound, trigger, sourceFile: sourceFile ?? "", steps: nested };
|
|
149
|
+
}
|
|
150
|
+
function branchEndsWithReturn(steps) {
|
|
151
|
+
if (steps.length === 0)
|
|
152
|
+
return false;
|
|
153
|
+
const last = steps[steps.length - 1];
|
|
154
|
+
if (last.type === "return")
|
|
155
|
+
return true;
|
|
156
|
+
if (last.type === "branch") {
|
|
157
|
+
const hasIfReturn = branchEndsWithReturn(last.if);
|
|
158
|
+
const hasElseReturn = last.else ? branchEndsWithReturn(last.else) : false;
|
|
159
|
+
return hasIfReturn && hasElseReturn;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
function nestAfterBranches(steps) {
|
|
164
|
+
const firstBranchIdx = steps.findIndex((s) => s.type === "branch");
|
|
165
|
+
if (firstBranchIdx === -1)
|
|
166
|
+
return steps;
|
|
167
|
+
const before = steps.slice(0, firstBranchIdx);
|
|
168
|
+
const branch = { ...steps[firstBranchIdx] };
|
|
169
|
+
const remaining = steps.slice(firstBranchIdx + 1);
|
|
170
|
+
branch.if = nestAfterBranches(branch.if);
|
|
171
|
+
if (branch.else)
|
|
172
|
+
branch.else = nestAfterBranches(branch.else);
|
|
173
|
+
if (remaining.length > 0) {
|
|
174
|
+
const nestedRemaining = nestAfterBranches(remaining);
|
|
175
|
+
if (!branch.else && branchEndsWithReturn(branch.if)) {
|
|
176
|
+
branch.else = nestedRemaining;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
branch.after = nestedRemaining;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return [...before, branch];
|
|
183
|
+
}
|
|
184
|
+
function extractSteps(block, source, annotations, cursors) {
|
|
185
|
+
const statements = block.type === "BlockStatement" ? block.body : [block];
|
|
186
|
+
const steps = [];
|
|
187
|
+
for (const stmt of statements) {
|
|
188
|
+
const extracted = extractFromStatement(stmt, source, annotations, cursors);
|
|
189
|
+
if (extracted)
|
|
190
|
+
steps.push(...extracted);
|
|
191
|
+
}
|
|
192
|
+
return steps;
|
|
193
|
+
}
|
|
194
|
+
function popComment(method, annotations, cursors) {
|
|
195
|
+
const cursor = cursors.get(method) ?? 0;
|
|
196
|
+
for (let i = cursor; i < annotations.length; i++) {
|
|
197
|
+
if (annotations[i].method === method) {
|
|
198
|
+
cursors.set(method, i + 1);
|
|
199
|
+
return annotations[i].comment || undefined;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
function extractFromStatement(stmt, source, annotations, cursors) {
|
|
205
|
+
if (stmt.type === "ExpressionStatement") {
|
|
206
|
+
return extractFromExpression(stmt.expression, source, annotations, cursors);
|
|
207
|
+
}
|
|
208
|
+
if (stmt.type === "VariableDeclaration") {
|
|
209
|
+
for (const decl of stmt.declarations) {
|
|
210
|
+
if (decl.init) {
|
|
211
|
+
const result = extractFromExpression(decl.init, source, annotations, cursors);
|
|
212
|
+
if (result)
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
if (stmt.type === "ReturnStatement") {
|
|
219
|
+
const value = stmt.argument ? source.slice(stmt.argument.start, stmt.argument.end) : undefined;
|
|
220
|
+
const brief = value && value.length > 80 ? value.slice(0, 77) + "..." : value;
|
|
221
|
+
const desc = popComment("return", annotations, cursors);
|
|
222
|
+
const step = { type: "return", label: generateReturnLabel(brief), value: brief };
|
|
223
|
+
if (desc)
|
|
224
|
+
step.description = desc;
|
|
225
|
+
return [step];
|
|
226
|
+
}
|
|
227
|
+
if (stmt.type === "ForStatement" || stmt.type === "ForOfStatement" || stmt.type === "ForInStatement") {
|
|
228
|
+
return extractLoop(stmt, source, annotations, cursors);
|
|
229
|
+
}
|
|
230
|
+
if (stmt.type === "IfStatement") {
|
|
231
|
+
return extractBranch(stmt, source, annotations, cursors);
|
|
232
|
+
}
|
|
233
|
+
if (stmt.type === "TryStatement") {
|
|
234
|
+
const trySteps = extractSteps(stmt.block, source, annotations, cursors);
|
|
235
|
+
if (trySteps.length > 0)
|
|
236
|
+
return trySteps;
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
function extractFromExpression(expr, source, annotations, cursors) {
|
|
242
|
+
if (expr.type === "AwaitExpression") {
|
|
243
|
+
return extractFromExpression(expr.argument, source, annotations, cursors);
|
|
244
|
+
}
|
|
245
|
+
if (expr.type === "AssignmentExpression") {
|
|
246
|
+
return extractFromExpression(expr.right, source, annotations, cursors);
|
|
247
|
+
}
|
|
248
|
+
if (expr.type !== "CallExpression")
|
|
249
|
+
return null;
|
|
250
|
+
const method = resolveCtxMethod(expr);
|
|
251
|
+
if (!method)
|
|
252
|
+
return null;
|
|
253
|
+
let step = null;
|
|
254
|
+
switch (method) {
|
|
255
|
+
case "ctx.query":
|
|
256
|
+
step = makeQueryStep(expr, source);
|
|
257
|
+
break;
|
|
258
|
+
case "ctx.exec":
|
|
259
|
+
step = makeExecStep(expr, source);
|
|
260
|
+
break;
|
|
261
|
+
case "ctx.ai":
|
|
262
|
+
step = makeAiStep(expr, source);
|
|
263
|
+
break;
|
|
264
|
+
case "ctx.notify.email":
|
|
265
|
+
step = makeEmailStep(expr, source);
|
|
266
|
+
break;
|
|
267
|
+
case "ctx.notify.sms":
|
|
268
|
+
step = makeSmsStep(expr, source);
|
|
269
|
+
break;
|
|
270
|
+
case "ctx.notify.slack":
|
|
271
|
+
step = makeSlackStep(expr, source);
|
|
272
|
+
break;
|
|
273
|
+
case "ctx.collect":
|
|
274
|
+
step = makeCollectStep(expr, source);
|
|
275
|
+
break;
|
|
276
|
+
case "ctx.http":
|
|
277
|
+
step = makeHttpStep(expr, source);
|
|
278
|
+
break;
|
|
279
|
+
default:
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
if (step) {
|
|
283
|
+
const desc = popComment(method, annotations, cursors);
|
|
284
|
+
if (desc)
|
|
285
|
+
step.description = desc;
|
|
286
|
+
return [step];
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
function resolveCtxMethod(node) {
|
|
291
|
+
const c = node.callee;
|
|
292
|
+
if (!c)
|
|
293
|
+
return null;
|
|
294
|
+
// ctx.method()
|
|
295
|
+
if (c.type === "MemberExpression" && c.object?.name === "ctx" && c.property?.name) {
|
|
296
|
+
return `ctx.${c.property.name}`;
|
|
297
|
+
}
|
|
298
|
+
// ctx.notify.email() etc
|
|
299
|
+
if (c.type === "MemberExpression" &&
|
|
300
|
+
c.object?.type === "MemberExpression" &&
|
|
301
|
+
c.object.object?.name === "ctx" &&
|
|
302
|
+
c.object.property?.name === "notify" &&
|
|
303
|
+
c.property?.name) {
|
|
304
|
+
return `ctx.notify.${c.property.name}`;
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
// --- Step constructors ---
|
|
309
|
+
function makeQueryStep(expr, source) {
|
|
310
|
+
const args = expr.arguments ?? [];
|
|
311
|
+
const database = getStringArg(args[0], source) ?? "unknown";
|
|
312
|
+
const sql = getStringArg(args[1], source) ?? "";
|
|
313
|
+
return { type: "query", database, label: generateSqlLabel("query", sql, database), sql };
|
|
314
|
+
}
|
|
315
|
+
function makeExecStep(expr, source) {
|
|
316
|
+
const args = expr.arguments ?? [];
|
|
317
|
+
const database = getStringArg(args[0], source) ?? "unknown";
|
|
318
|
+
const sql = getStringArg(args[1], source) ?? "";
|
|
319
|
+
return { type: "exec", database, label: generateSqlLabel("exec", sql, database), sql };
|
|
320
|
+
}
|
|
321
|
+
function makeAiStep(expr, source) {
|
|
322
|
+
const args = expr.arguments ?? [];
|
|
323
|
+
const model = getStringArg(args[0], source) ?? "auto";
|
|
324
|
+
let prompt = "";
|
|
325
|
+
if (args[1]?.type === "ObjectExpression") {
|
|
326
|
+
prompt = getObjectStringProp(args[1], "prompt", source) ?? "";
|
|
327
|
+
}
|
|
328
|
+
const label = prompt ? truncateClean(prompt, 80) : `AI call (${model})`;
|
|
329
|
+
return { type: "ai", model, label, prompt };
|
|
330
|
+
}
|
|
331
|
+
function makeEmailStep(expr, source) {
|
|
332
|
+
const args = expr.arguments ?? [];
|
|
333
|
+
let to;
|
|
334
|
+
let subject;
|
|
335
|
+
if (args[0]?.type === "ObjectExpression") {
|
|
336
|
+
to = getObjectStringProp(args[0], "to", source);
|
|
337
|
+
subject = getObjectStringProp(args[0], "subject", source);
|
|
338
|
+
}
|
|
339
|
+
const label = subject ? `Send email: ${truncateClean(subject, 60)}` : to ? `Send email to ${to}` : "Send email";
|
|
340
|
+
return { type: "email", label, to, subject };
|
|
341
|
+
}
|
|
342
|
+
function makeSmsStep(expr, source) {
|
|
343
|
+
const args = expr.arguments ?? [];
|
|
344
|
+
let to;
|
|
345
|
+
if (args[0]?.type === "ObjectExpression") {
|
|
346
|
+
to = getObjectStringProp(args[0], "to", source);
|
|
347
|
+
}
|
|
348
|
+
const label = to ? `Send SMS to ${to}` : "Send SMS";
|
|
349
|
+
return { type: "sms", label, to };
|
|
350
|
+
}
|
|
351
|
+
function makeSlackStep(expr, source) {
|
|
352
|
+
const args = expr.arguments ?? [];
|
|
353
|
+
let channel;
|
|
354
|
+
if (args[0]?.type === "ObjectExpression") {
|
|
355
|
+
channel = getObjectStringProp(args[0], "to", source);
|
|
356
|
+
}
|
|
357
|
+
const label = channel ? `Send Slack message to ${channel}` : "Send Slack message";
|
|
358
|
+
return { type: "slack", label, channel };
|
|
359
|
+
}
|
|
360
|
+
function makeCollectStep(expr, source) {
|
|
361
|
+
const args = expr.arguments ?? [];
|
|
362
|
+
let formTitle = "";
|
|
363
|
+
if (args[0]?.type === "ObjectExpression") {
|
|
364
|
+
formTitle = getObjectStringProp(args[0], "title", source) ?? "";
|
|
365
|
+
}
|
|
366
|
+
const label = formTitle ? `Collect: ${formTitle}` : "Collect form data";
|
|
367
|
+
return { type: "collect", label, formTitle };
|
|
368
|
+
}
|
|
369
|
+
function makeHttpStep(expr, source) {
|
|
370
|
+
const args = expr.arguments ?? [];
|
|
371
|
+
const url = getStringArg(args[0], source) ?? "unknown";
|
|
372
|
+
let method = "GET";
|
|
373
|
+
if (args[1]?.type === "ObjectExpression") {
|
|
374
|
+
method = (getObjectStringProp(args[1], "method", source) ?? "GET").toUpperCase();
|
|
375
|
+
}
|
|
376
|
+
const urlDisplay = url.length > 60 ? url.slice(0, 57) + "..." : url;
|
|
377
|
+
const label = `HTTP ${method} ${urlDisplay}`;
|
|
378
|
+
return { type: "http", label, url, method };
|
|
379
|
+
}
|
|
380
|
+
// --- Loop and branch extraction ---
|
|
381
|
+
function extractLoop(stmt, source, annotations, cursors) {
|
|
382
|
+
let variable = "item";
|
|
383
|
+
let collection = "items";
|
|
384
|
+
if (stmt.type === "ForOfStatement" || stmt.type === "ForInStatement") {
|
|
385
|
+
if (stmt.left?.type === "VariableDeclaration" && stmt.left.declarations?.[0]) {
|
|
386
|
+
variable = stmt.left.declarations[0].id?.name ?? "item";
|
|
387
|
+
}
|
|
388
|
+
collection = source.slice(stmt.right.start, stmt.right.end);
|
|
389
|
+
}
|
|
390
|
+
else if (stmt.type === "ForStatement") {
|
|
391
|
+
if (stmt.init?.declarations?.[0]?.id?.name) {
|
|
392
|
+
variable = "i";
|
|
393
|
+
}
|
|
394
|
+
collection = source.slice(stmt.init?.start ?? stmt.start, stmt.update?.end ?? stmt.test?.end ?? stmt.start);
|
|
395
|
+
}
|
|
396
|
+
const bodySteps = extractSteps(stmt.body, source, annotations, cursors);
|
|
397
|
+
if (bodySteps.length === 0)
|
|
398
|
+
return null;
|
|
399
|
+
return [{
|
|
400
|
+
type: "loop",
|
|
401
|
+
variable,
|
|
402
|
+
collection: truncateClean(collection, 60),
|
|
403
|
+
label: `For each ${variable} in ${truncateClean(collection, 40)}`,
|
|
404
|
+
steps: bodySteps,
|
|
405
|
+
}];
|
|
406
|
+
}
|
|
407
|
+
function extractBranch(stmt, source, annotations, cursors) {
|
|
408
|
+
const condition = source.slice(stmt.test.start, stmt.test.end);
|
|
409
|
+
const ifSteps = extractSteps(stmt.consequent, source, annotations, cursors);
|
|
410
|
+
let elseSteps;
|
|
411
|
+
if (stmt.alternate) {
|
|
412
|
+
if (stmt.alternate.type === "IfStatement") {
|
|
413
|
+
const nested = extractBranch(stmt.alternate, source, annotations, cursors);
|
|
414
|
+
elseSteps = nested ?? undefined;
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
const es = extractSteps(stmt.alternate, source, annotations, cursors);
|
|
418
|
+
elseSteps = es.length > 0 ? es : undefined;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (ifSteps.length === 0 && (!elseSteps || elseSteps.length === 0))
|
|
422
|
+
return null;
|
|
423
|
+
return [{
|
|
424
|
+
type: "branch",
|
|
425
|
+
condition,
|
|
426
|
+
label: `If ${simplifyCondition(condition)}`,
|
|
427
|
+
if: ifSteps,
|
|
428
|
+
else: elseSteps,
|
|
429
|
+
}];
|
|
430
|
+
}
|
|
431
|
+
// --- Label generation ---
|
|
432
|
+
function generateSqlLabel(stepType, sql, database) {
|
|
433
|
+
const normalized = sql.replace(/\s+/g, " ").trim().toUpperCase();
|
|
434
|
+
if (normalized.startsWith("CREATE TABLE IF NOT EXISTS")) {
|
|
435
|
+
const table = extractTableName(sql, /CREATE\s+TABLE\s+IF\s+NOT\s+EXISTS\s+(\w+)/i);
|
|
436
|
+
return table ? `Ensure ${table} table exists` : `Create table in ${database}`;
|
|
437
|
+
}
|
|
438
|
+
if (normalized.startsWith("CREATE TABLE")) {
|
|
439
|
+
const table = extractTableName(sql, /CREATE\s+TABLE\s+(\w+)/i);
|
|
440
|
+
return table ? `Create ${table} table` : `Create table in ${database}`;
|
|
441
|
+
}
|
|
442
|
+
if (normalized.startsWith("SELECT")) {
|
|
443
|
+
const table = extractTableName(sql, /FROM\s+(\w+)/i);
|
|
444
|
+
const where = sql.match(/WHERE\s+([\s\S]+?)(?:\s+ORDER|\s+LIMIT|\s+GROUP|$)/i);
|
|
445
|
+
if (table && where)
|
|
446
|
+
return `Get ${table} where ${truncateClean(where[1].trim(), 40)}`;
|
|
447
|
+
if (table)
|
|
448
|
+
return `Get all ${table}`;
|
|
449
|
+
return `Query ${database}`;
|
|
450
|
+
}
|
|
451
|
+
if (normalized.startsWith("INSERT")) {
|
|
452
|
+
const table = extractTableName(sql, /INSERT\s+(?:OR\s+\w+\s+)?INTO\s+(\w+)/i);
|
|
453
|
+
return table ? `Insert into ${table}` : `Insert into ${database}`;
|
|
454
|
+
}
|
|
455
|
+
if (normalized.startsWith("UPDATE")) {
|
|
456
|
+
const table = extractTableName(sql, /UPDATE\s+(\w+)/i);
|
|
457
|
+
return table ? `Update ${table}` : `Update ${database}`;
|
|
458
|
+
}
|
|
459
|
+
if (normalized.startsWith("DELETE")) {
|
|
460
|
+
const table = extractTableName(sql, /DELETE\s+FROM\s+(\w+)/i);
|
|
461
|
+
return table ? `Delete from ${table}` : `Delete from ${database}`;
|
|
462
|
+
}
|
|
463
|
+
return stepType === "query" ? `Query ${database}` : `Execute on ${database}`;
|
|
464
|
+
}
|
|
465
|
+
function extractTableName(sql, pattern) {
|
|
466
|
+
const m = sql.match(pattern);
|
|
467
|
+
return m ? m[1] : null;
|
|
468
|
+
}
|
|
469
|
+
function generateReturnLabel(value) {
|
|
470
|
+
if (!value)
|
|
471
|
+
return "Return result";
|
|
472
|
+
if (value.startsWith("{"))
|
|
473
|
+
return "Return result object";
|
|
474
|
+
return `Return ${truncateClean(value, 50)}`;
|
|
475
|
+
}
|
|
476
|
+
function simplifyCondition(condition) {
|
|
477
|
+
// urgency === "high" → urgency is high
|
|
478
|
+
const eqMatch = condition.match(/^(\w+)\s*===?\s*["']([^"']+)["']$/);
|
|
479
|
+
if (eqMatch)
|
|
480
|
+
return `${eqMatch[1]} is "${eqMatch[2]}"`;
|
|
481
|
+
// urgency !== "high" → urgency is not high
|
|
482
|
+
const neqMatch = condition.match(/^(\w+)\s*!==?\s*["']([^"']+)["']$/);
|
|
483
|
+
if (neqMatch)
|
|
484
|
+
return `${neqMatch[1]} is not "${neqMatch[2]}"`;
|
|
485
|
+
// x > 0, x >= 5, x < 10, x <= 3
|
|
486
|
+
const cmpMatch = condition.match(/^(\w+)\s*(>=?|<=?)\s*(\d+)$/);
|
|
487
|
+
if (cmpMatch) {
|
|
488
|
+
const op = cmpMatch[2] === ">" ? "greater than" : cmpMatch[2] === ">=" ? "at least" : cmpMatch[2] === "<" ? "less than" : "at most";
|
|
489
|
+
return `${cmpMatch[1]} is ${op} ${cmpMatch[3]}`;
|
|
490
|
+
}
|
|
491
|
+
// x.length > 0 → x has items
|
|
492
|
+
const lenMatch = condition.match(/^(\w+)\.length\s*>\s*0$/);
|
|
493
|
+
if (lenMatch)
|
|
494
|
+
return `${lenMatch[1]} has items`;
|
|
495
|
+
// x.length === 0 → x is empty
|
|
496
|
+
const emptyMatch = condition.match(/^(\w+)\.length\s*===?\s*0$/);
|
|
497
|
+
if (emptyMatch)
|
|
498
|
+
return `${emptyMatch[1]} is empty`;
|
|
499
|
+
return truncateClean(condition, 50);
|
|
500
|
+
}
|
|
501
|
+
// --- Helpers ---
|
|
502
|
+
function getStringArg(node, source) {
|
|
503
|
+
if (!node)
|
|
504
|
+
return undefined;
|
|
505
|
+
if (node.type === "Literal" && typeof node.value === "string")
|
|
506
|
+
return node.value;
|
|
507
|
+
if (node.type === "TemplateLiteral") {
|
|
508
|
+
return source.slice(node.start + 1, node.end - 1);
|
|
509
|
+
}
|
|
510
|
+
return undefined;
|
|
511
|
+
}
|
|
512
|
+
function getObjectStringProp(objNode, propName, source) {
|
|
513
|
+
for (const prop of objNode.properties ?? []) {
|
|
514
|
+
if (prop.type === "SpreadElement")
|
|
515
|
+
continue;
|
|
516
|
+
const key = prop.key?.name ?? prop.key?.value;
|
|
517
|
+
if (key === propName) {
|
|
518
|
+
return getStringArg(prop.value, source);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
function truncateClean(s, max) {
|
|
524
|
+
const clean = s.replace(/\s+/g, " ").trim();
|
|
525
|
+
return clean.length > max ? clean.slice(0, max - 3) + "..." : clean;
|
|
526
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type AgentModel = "claude-sonnet" | "claude-haiku" | "claude-opus" | "gpt-4o" | "gpt-4o-mini" | "gpt-4.1-nano" | "gpt-4.1-mini" | "gpt-4.1" | (string & {});
|
|
2
|
+
export interface AgentTierRouting {
|
|
3
|
+
fast?: string;
|
|
4
|
+
balanced?: string;
|
|
5
|
+
powerful?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface AgentMemory {
|
|
8
|
+
entities?: boolean;
|
|
9
|
+
outcomes?: boolean;
|
|
10
|
+
struggles?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export type AgentToolGrant = "query" | "search" | "ask" | "notify" | "http" | "workspace" | "ai" | (string & {});
|
|
13
|
+
export interface AgentCaps {
|
|
14
|
+
maxTurns?: number;
|
|
15
|
+
maxCredits?: number;
|
|
16
|
+
maxDuration?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface AgentConfig {
|
|
19
|
+
name: string;
|
|
20
|
+
model: AgentModel | AgentTierRouting;
|
|
21
|
+
instructions?: string;
|
|
22
|
+
tools?: AgentToolGrant[];
|
|
23
|
+
memory?: AgentMemory;
|
|
24
|
+
caps?: AgentCaps;
|
|
25
|
+
requireApproval?: string[];
|
|
26
|
+
}
|
|
27
|
+
export declare function agent(config: AgentConfig): AgentConfig;
|