@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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/dist/explorer.js +3 -0
  4. package/dist/packages/email-template/src/email-template.d.ts +18 -0
  5. package/dist/packages/email-template/src/email-template.js +74 -0
  6. package/dist/packages/email-template/src/index.d.ts +1 -0
  7. package/dist/packages/email-template/src/index.js +1 -0
  8. package/dist/packages/surface-renderer/src/form-renderer.d.ts +117 -0
  9. package/dist/packages/surface-renderer/src/form-renderer.js +719 -0
  10. package/dist/packages/surface-renderer/src/index.d.ts +4 -0
  11. package/dist/packages/surface-renderer/src/index.js +2 -0
  12. package/dist/packages/surface-renderer/src/portal-renderer.d.ts +177 -0
  13. package/dist/packages/surface-renderer/src/portal-renderer.js +1089 -0
  14. package/dist/packages/surface-renderer/src/workspace-home.d.ts +46 -0
  15. package/dist/packages/surface-renderer/src/workspace-home.js +345 -0
  16. package/dist/runtime/agent-types.d.ts +48 -0
  17. package/dist/runtime/agent-types.js +3 -0
  18. package/dist/runtime/ai-router.d.ts +32 -0
  19. package/dist/runtime/ai-router.js +112 -0
  20. package/dist/runtime/app.d.ts +6 -0
  21. package/dist/runtime/app.js +399 -0
  22. package/dist/runtime/chunker.d.ts +6 -0
  23. package/dist/runtime/chunker.js +30 -0
  24. package/dist/runtime/context.d.ts +115 -0
  25. package/dist/runtime/context.js +440 -0
  26. package/dist/runtime/do/workspace-database.d.ts +10 -0
  27. package/dist/runtime/do/workspace-database.js +199 -0
  28. package/dist/runtime/form-types.d.ts +143 -0
  29. package/dist/runtime/form-types.js +1 -0
  30. package/dist/runtime/runtime.d.ts +9 -0
  31. package/dist/runtime/runtime.js +7 -0
  32. package/dist/runtime/source-types.d.ts +15 -0
  33. package/dist/runtime/source-types.js +1 -0
  34. package/dist/runtime/source.d.ts +70 -0
  35. package/dist/runtime/source.js +21 -0
  36. package/dist/runtime/sync-runtime.d.ts +10 -0
  37. package/dist/runtime/sync-runtime.js +185 -0
  38. package/dist/runtime/types.d.ts +21 -0
  39. package/dist/runtime/types.js +1 -0
  40. package/dist/runtime/workflow-entrypoint.d.ts +31 -0
  41. package/dist/runtime/workflow-entrypoint.js +1297 -0
  42. package/dist/runtime/workflow.d.ts +285 -0
  43. package/dist/runtime/workflow.js +1008 -0
  44. package/dist/src/cli.d.ts +2 -0
  45. package/dist/src/cli.js +44116 -0
  46. package/dist/src/commands/ai-gateway-route.d.ts +24 -0
  47. package/dist/src/commands/ai-gateway-route.js +192 -0
  48. package/dist/src/commands/auth.d.ts +1 -0
  49. package/dist/src/commands/auth.js +42 -0
  50. package/dist/src/commands/billing.d.ts +6 -0
  51. package/dist/src/commands/billing.js +76 -0
  52. package/dist/src/commands/brain.d.ts +1 -0
  53. package/dist/src/commands/brain.js +194 -0
  54. package/dist/src/commands/demo.d.ts +12 -0
  55. package/dist/src/commands/demo.js +147 -0
  56. package/dist/src/commands/deploy.d.ts +1 -0
  57. package/dist/src/commands/deploy.js +1052 -0
  58. package/dist/src/commands/dev.d.ts +14 -0
  59. package/dist/src/commands/dev.js +2818 -0
  60. package/dist/src/commands/form.d.ts +8 -0
  61. package/dist/src/commands/form.js +396 -0
  62. package/dist/src/commands/init.d.ts +1 -0
  63. package/dist/src/commands/init.js +139 -0
  64. package/dist/src/commands/issue.d.ts +7 -0
  65. package/dist/src/commands/issue.js +191 -0
  66. package/dist/src/commands/login.d.ts +9 -0
  67. package/dist/src/commands/login.js +163 -0
  68. package/dist/src/commands/logs.d.ts +8 -0
  69. package/dist/src/commands/logs.js +113 -0
  70. package/dist/src/commands/portal.d.ts +2 -0
  71. package/dist/src/commands/portal.js +111 -0
  72. package/dist/src/commands/pull.d.ts +3 -0
  73. package/dist/src/commands/pull.js +184 -0
  74. package/dist/src/commands/push.d.ts +4 -0
  75. package/dist/src/commands/push.js +183 -0
  76. package/dist/src/commands/run.d.ts +6 -0
  77. package/dist/src/commands/run.js +91 -0
  78. package/dist/src/commands/secret.d.ts +7 -0
  79. package/dist/src/commands/secret.js +105 -0
  80. package/dist/src/commands/shutdown.d.ts +1 -0
  81. package/dist/src/commands/shutdown.js +46 -0
  82. package/dist/src/commands/sql.d.ts +8 -0
  83. package/dist/src/commands/sql.js +142 -0
  84. package/dist/src/commands/status.d.ts +5 -0
  85. package/dist/src/commands/status.js +39 -0
  86. package/dist/src/commands/sync.d.ts +7 -0
  87. package/dist/src/commands/sync.js +991 -0
  88. package/dist/src/commands/usage.d.ts +6 -0
  89. package/dist/src/commands/usage.js +78 -0
  90. package/dist/src/commands/webhooks.d.ts +1 -0
  91. package/dist/src/commands/webhooks.js +102 -0
  92. package/dist/src/commands/workspace.d.ts +23 -0
  93. package/dist/src/commands/workspace.js +590 -0
  94. package/dist/src/connector-migration.d.ts +20 -0
  95. package/dist/src/connector-migration.js +43 -0
  96. package/dist/src/connector-parser.d.ts +14 -0
  97. package/dist/src/connector-parser.js +94 -0
  98. package/dist/src/connector-service/discover.d.ts +37 -0
  99. package/dist/src/connector-service/discover.js +79 -0
  100. package/dist/src/connector-service/gather.d.ts +22 -0
  101. package/dist/src/connector-service/gather.js +89 -0
  102. package/dist/src/connector-service/init.d.ts +14 -0
  103. package/dist/src/connector-service/init.js +109 -0
  104. package/dist/src/connector-service/scaffold.d.ts +17 -0
  105. package/dist/src/connector-service/scaffold.js +194 -0
  106. package/dist/src/connector-service/spec-storage.d.ts +8 -0
  107. package/dist/src/connector-service/spec-storage.js +48 -0
  108. package/dist/src/connector-service/types.d.ts +57 -0
  109. package/dist/src/connector-service/types.js +2 -0
  110. package/dist/src/connector-service/verify.d.ts +24 -0
  111. package/dist/src/connector-service/verify.js +575 -0
  112. package/dist/src/email-template.d.ts +2 -0
  113. package/dist/src/email-template.js +1 -0
  114. package/dist/src/manifest.d.ts +31 -0
  115. package/dist/src/manifest.js +25 -0
  116. package/dist/src/mug-icon.d.ts +1 -0
  117. package/dist/src/mug-icon.js +12 -0
  118. package/dist/src/slack-manifest.d.ts +119 -0
  119. package/dist/src/slack-manifest.js +163 -0
  120. package/dist/src/source-migration.d.ts +20 -0
  121. package/dist/src/source-migration.js +43 -0
  122. package/dist/src/surface-renderer.d.ts +5 -0
  123. package/dist/src/surface-renderer.js +3 -0
  124. package/dist/src/templates.d.ts +3 -0
  125. package/dist/src/templates.js +48 -0
  126. package/dist/src/version-check.d.ts +1 -0
  127. package/dist/src/version-check.js +28 -0
  128. package/dist/src/workflow-parser.d.ts +95 -0
  129. package/dist/src/workflow-parser.js +526 -0
  130. package/dist/worker/src/agent-types.d.ts +27 -0
  131. package/dist/worker/src/agent-types.js +3 -0
  132. package/dist/worker/src/source-types.d.ts +14 -0
  133. package/dist/worker/src/source-types.js +1 -0
  134. package/package.json +90 -0
  135. package/src/data/model-capabilities.json +171 -0
@@ -0,0 +1,8 @@
1
+ export declare function formInit(name: string): void;
2
+ export interface ValidationError {
3
+ file: string;
4
+ message: string;
5
+ }
6
+ export declare function formValidate(name?: string): void;
7
+ export declare function validateFormConfigErrors(file: string, cfg: Record<string, unknown>): ValidationError[];
8
+ export declare function formList(): void;
@@ -0,0 +1,396 @@
1
+ import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ function loadMugJson(dir) {
4
+ const path = join(dir, "mug.json");
5
+ if (!existsSync(path)) {
6
+ console.error("No mug.json found. Run `mug init` first.");
7
+ process.exit(1);
8
+ }
9
+ return JSON.parse(readFileSync(path, "utf-8"));
10
+ }
11
+ export function formInit(name) {
12
+ const dir = process.cwd();
13
+ loadMugJson(dir);
14
+ const surfacesDir = join(dir, "src", "surfaces");
15
+ if (!existsSync(surfacesDir)) {
16
+ mkdirSync(surfacesDir, { recursive: true });
17
+ }
18
+ const configPath = join(surfacesDir, `${name}.json`);
19
+ if (existsSync(configPath)) {
20
+ console.error(`Surface config already exists: src/surfaces/${name}.json`);
21
+ process.exit(1);
22
+ }
23
+ const handlerPath = join(dir, "src", "workflows", `handle-${name}.ts`);
24
+ if (existsSync(handlerPath)) {
25
+ console.error(`File already exists: src/workflows/handle-${name}.ts`);
26
+ process.exit(1);
27
+ }
28
+ const formConfig = {
29
+ type: "form",
30
+ title: titleCase(name),
31
+ description: "",
32
+ submitText: "Submit",
33
+ workflow: `handle-${name}`,
34
+ access: { mode: "public" },
35
+ fields: [
36
+ { name: "name", type: "text", label: "Name", required: true },
37
+ { name: "email", type: "email", label: "Email", required: true },
38
+ { name: "message", type: "textarea", label: "Message", rows: 4 },
39
+ ],
40
+ };
41
+ writeFileSync(configPath, JSON.stringify(formConfig, null, 2) + "\n");
42
+ console.log(` [+] src/surfaces/${name}.json`);
43
+ const handlerWorkflow = `import { workflow } from "../workflow.js";
44
+
45
+ workflow("handle-${name}", async (ctx) => {
46
+ const params = ctx.params as Record<string, string>;
47
+ const name = params.name;
48
+ const email = params._verified_email ?? params.email;
49
+
50
+ // TODO: process the submission
51
+ // await ctx.exec("main", "INSERT INTO ...", [...]);
52
+ // await ctx.notify.email({ to: "owner@example.com", subject: "New submission", message: "..." });
53
+
54
+ return { name, email, received: true };
55
+ });
56
+ `;
57
+ writeFileSync(handlerPath, handlerWorkflow);
58
+ console.log(` [+] src/workflows/handle-${name}.ts`);
59
+ const indexPath = join(dir, "src", "index.ts");
60
+ if (existsSync(indexPath)) {
61
+ let index = readFileSync(indexPath, "utf-8");
62
+ const handlerImport = `import "./workflows/handle-${name}.js";`;
63
+ if (!index.includes(handlerImport)) {
64
+ index = addImport(index, handlerImport);
65
+ }
66
+ writeFileSync(indexPath, index);
67
+ console.log(` [+] import wired in src/index.ts`);
68
+ }
69
+ console.log(`\nForm "${name}" ready. Edit the config to set your fields.`);
70
+ console.log(` mug dev`);
71
+ console.log(` open http://localhost:8787/${name}`);
72
+ }
73
+ function addImport(source, importLine) {
74
+ const marker = "// Import workflows here:";
75
+ const idx = source.indexOf(marker);
76
+ if (idx !== -1) {
77
+ const insertAt = idx + marker.length;
78
+ return source.slice(0, insertAt) + "\n" + importLine + source.slice(insertAt);
79
+ }
80
+ const lastImportMatch = source.match(/^import\s.+$/gm);
81
+ if (lastImportMatch) {
82
+ const lastImport = lastImportMatch[lastImportMatch.length - 1];
83
+ const lastIdx = source.lastIndexOf(lastImport);
84
+ const insertAt = lastIdx + lastImport.length;
85
+ return source.slice(0, insertAt) + "\n" + importLine + source.slice(insertAt);
86
+ }
87
+ return importLine + "\n" + source;
88
+ }
89
+ function titleCase(kebab) {
90
+ return kebab
91
+ .split("-")
92
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
93
+ .join(" ");
94
+ }
95
+ const VALID_FIELD_TYPES = new Set([
96
+ "text", "email", "phone", "number", "select", "multiselect",
97
+ "date", "textarea", "file", "calculated",
98
+ ]);
99
+ const VALID_OPERATORS = new Set([
100
+ "eq", "neq", "in", "gt", "lt", "filled", "empty",
101
+ ]);
102
+ const VALID_FORMATS = new Set(["number", "currency", "percent"]);
103
+ const VALID_VALIDATION_RULES = new Set(["min", "max", "minLength", "maxLength", "pattern"]);
104
+ export function formValidate(name) {
105
+ const dir = process.cwd();
106
+ loadMugJson(dir);
107
+ const errors = [];
108
+ let formsFound = 0;
109
+ const surfacesDir = join(dir, "src", "surfaces");
110
+ if (existsSync(surfacesDir)) {
111
+ const jsonFiles = name
112
+ ? [`${name}.json`]
113
+ : readdirSync(surfacesDir).filter((f) => f.endsWith(".json"));
114
+ for (const file of jsonFiles) {
115
+ const filePath = join(surfacesDir, file);
116
+ if (!existsSync(filePath))
117
+ continue;
118
+ try {
119
+ const cfg = JSON.parse(readFileSync(filePath, "utf-8"));
120
+ if (cfg.type === "portal")
121
+ continue;
122
+ formsFound++;
123
+ validateCollectOptions(`src/surfaces/${file}`, cfg, errors);
124
+ }
125
+ catch {
126
+ errors.push({ file: `src/surfaces/${file}`, message: "Could not parse JSON" });
127
+ }
128
+ }
129
+ }
130
+ const workflowsDir = join(dir, "src", "workflows");
131
+ if (existsSync(workflowsDir)) {
132
+ const tsFiles = name
133
+ ? [`${name}.ts`]
134
+ : readdirSync(workflowsDir).filter((f) => f.endsWith(".ts"));
135
+ for (const file of tsFiles) {
136
+ const filePath = join(workflowsDir, file);
137
+ if (!existsSync(filePath))
138
+ continue;
139
+ const content = readFileSync(filePath, "utf-8");
140
+ if (!content.includes("ctx.collect(") && !content.includes("ctx.collect({"))
141
+ continue;
142
+ formsFound++;
143
+ const collectMatch = content.match(/ctx\.collect\(\s*\{/);
144
+ if (!collectMatch)
145
+ continue;
146
+ const startIdx = collectMatch.index + collectMatch[0].length - 1;
147
+ const block = extractObject(content, startIdx);
148
+ if (!block) {
149
+ errors.push({ file, message: "Could not parse ctx.collect() options" });
150
+ continue;
151
+ }
152
+ try {
153
+ const fn = new Function(`return (${block})`);
154
+ const opts = fn();
155
+ validateCollectOptions(file, opts, errors);
156
+ }
157
+ catch {
158
+ errors.push({ file, message: "Could not evaluate ctx.collect() options (dynamic expressions are skipped)" });
159
+ }
160
+ }
161
+ }
162
+ if (formsFound === 0) {
163
+ console.log("No forms found. Create one with: mug form init <name>");
164
+ return;
165
+ }
166
+ if (errors.length === 0) {
167
+ console.log(` [+] ${formsFound} form${formsFound !== 1 ? "s" : ""} validated — no errors`);
168
+ }
169
+ else {
170
+ console.log(` [x] ${errors.length} error${errors.length !== 1 ? "s" : ""} in ${formsFound} form${formsFound !== 1 ? "s" : ""}:\n`);
171
+ for (const err of errors) {
172
+ console.log(` ${err.file}: ${err.message}`);
173
+ }
174
+ process.exit(1);
175
+ }
176
+ }
177
+ function validateCollectOptions(file, opts, errors) {
178
+ if (!opts.title) {
179
+ errors.push({ file, message: "Missing required field: title" });
180
+ }
181
+ if (!opts.workflow) {
182
+ errors.push({ file, message: "Missing required field: workflow" });
183
+ }
184
+ const pages = opts.pages ?? [];
185
+ const fields = opts.fields ?? [];
186
+ if (pages.length === 0 && fields.length === 0) {
187
+ errors.push({ file, message: "Must specify either pages or fields" });
188
+ return;
189
+ }
190
+ const pageIds = new Set();
191
+ const allFieldNames = new Set();
192
+ if (fields.length > 0) {
193
+ for (const field of fields) {
194
+ validateField(file, field, errors);
195
+ if (field.name) {
196
+ const n = field.name;
197
+ if (allFieldNames.has(n)) {
198
+ errors.push({ file, message: `Duplicate field name: "${n}"` });
199
+ }
200
+ allFieldNames.add(n);
201
+ }
202
+ }
203
+ }
204
+ for (const page of pages) {
205
+ if (!page.id) {
206
+ errors.push({ file, message: "Page missing required field: id" });
207
+ }
208
+ else {
209
+ pageIds.add(page.id);
210
+ }
211
+ const pageFields = page.fields ?? [];
212
+ for (const field of pageFields) {
213
+ validateField(file, field, errors);
214
+ if (field.name) {
215
+ const n = field.name;
216
+ if (allFieldNames.has(n)) {
217
+ errors.push({ file, message: `Duplicate field name: "${n}" (in page "${page.id ?? "?"}")` });
218
+ }
219
+ allFieldNames.add(n);
220
+ }
221
+ }
222
+ }
223
+ for (const page of pages) {
224
+ const nextPage = page.nextPage;
225
+ if (typeof nextPage === "string") {
226
+ if (!pageIds.has(nextPage)) {
227
+ errors.push({ file, message: `Page "${page.id}": nextPage references unknown page "${nextPage}"` });
228
+ }
229
+ }
230
+ else if (nextPage && typeof nextPage === "object") {
231
+ const np = nextPage;
232
+ const conditions = np.conditions ?? [];
233
+ for (const cond of conditions) {
234
+ if (cond.goto && !pageIds.has(cond.goto)) {
235
+ errors.push({ file, message: `Page "${page.id}": nextPage.goto references unknown page "${cond.goto}"` });
236
+ }
237
+ validateConditions(file, `page "${page.id}" nextPage`, cond.when ?? [], errors);
238
+ }
239
+ if (np.default && !pageIds.has(np.default)) {
240
+ errors.push({ file, message: `Page "${page.id}": nextPage.default references unknown page "${np.default}"` });
241
+ }
242
+ }
243
+ }
244
+ if (opts.access && typeof opts.access === "object") {
245
+ const access = opts.access;
246
+ const mode = access.mode;
247
+ if (mode && !["public", "identify", "auth"].includes(mode)) {
248
+ errors.push({ file, message: `Invalid access mode: "${mode}" (must be public, identify, or auth)` });
249
+ }
250
+ if (mode === "identify" || mode === "auth") {
251
+ if (!access.method || !["email", "phone"].includes(access.method)) {
252
+ errors.push({ file, message: `Access mode "${mode}" requires method: "email" or "phone"` });
253
+ }
254
+ }
255
+ if (mode === "auth") {
256
+ if (!access.table)
257
+ errors.push({ file, message: `Access mode "auth" requires table` });
258
+ if (!access.matchColumn)
259
+ errors.push({ file, message: `Access mode "auth" requires matchColumn` });
260
+ }
261
+ }
262
+ }
263
+ function validateField(file, field, errors) {
264
+ const name = field.name;
265
+ const type = field.type;
266
+ if (!name) {
267
+ errors.push({ file, message: "Field missing required property: name" });
268
+ return;
269
+ }
270
+ if (!type) {
271
+ errors.push({ file, message: `Field "${name}": missing required property: type` });
272
+ return;
273
+ }
274
+ if (!VALID_FIELD_TYPES.has(type)) {
275
+ errors.push({ file, message: `Field "${name}": invalid type "${type}" (valid: ${[...VALID_FIELD_TYPES].join(", ")})` });
276
+ }
277
+ if ((type === "select" || type === "multiselect") && !field.options) {
278
+ errors.push({ file, message: `Field "${name}": ${type} requires options array` });
279
+ }
280
+ if (type === "calculated") {
281
+ if (!field.expression) {
282
+ errors.push({ file, message: `Field "${name}": calculated field requires expression` });
283
+ }
284
+ if (field.format && !VALID_FORMATS.has(field.format)) {
285
+ errors.push({ file, message: `Field "${name}": invalid format "${field.format}" (valid: number, currency, percent)` });
286
+ }
287
+ }
288
+ if (field.showWhen) {
289
+ validateConditions(file, `field "${name}"`, field.showWhen, errors);
290
+ }
291
+ if (field.validate) {
292
+ if (!Array.isArray(field.validate)) {
293
+ errors.push({ file, message: `Field "${name}": validate must be an array` });
294
+ }
295
+ else {
296
+ for (const rule of field.validate) {
297
+ if (!rule.rule || !VALID_VALIDATION_RULES.has(rule.rule)) {
298
+ errors.push({ file, message: `Field "${name}": invalid validation rule "${rule.rule}" (valid: ${[...VALID_VALIDATION_RULES].join(", ")})` });
299
+ }
300
+ if (rule.value == null) {
301
+ errors.push({ file, message: `Field "${name}": validation rule "${rule.rule}" requires a value` });
302
+ }
303
+ if (!rule.message || typeof rule.message !== "string") {
304
+ errors.push({ file, message: `Field "${name}": validation rule "${rule.rule}" requires a message string` });
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+ function validateConditions(file, context, conditions, errors) {
311
+ if (!Array.isArray(conditions))
312
+ return;
313
+ for (const cond of conditions) {
314
+ if (!cond.field) {
315
+ errors.push({ file, message: `${context}: condition missing "field"` });
316
+ }
317
+ if (!cond.op) {
318
+ errors.push({ file, message: `${context}: condition missing "op"` });
319
+ }
320
+ else if (!VALID_OPERATORS.has(cond.op)) {
321
+ errors.push({ file, message: `${context}: invalid operator "${cond.op}" (valid: ${[...VALID_OPERATORS].join(", ")})` });
322
+ }
323
+ }
324
+ }
325
+ export function validateFormConfigErrors(file, cfg) {
326
+ const errors = [];
327
+ validateCollectOptions(file, cfg, errors);
328
+ return errors;
329
+ }
330
+ function extractObject(source, startIdx) {
331
+ let depth = 0;
332
+ for (let i = startIdx; i < source.length; i++) {
333
+ if (source[i] === "{")
334
+ depth++;
335
+ else if (source[i] === "}") {
336
+ depth--;
337
+ if (depth === 0)
338
+ return source.slice(startIdx, i + 1);
339
+ }
340
+ }
341
+ return null;
342
+ }
343
+ export function formList() {
344
+ const dir = process.cwd();
345
+ const config = loadMugJson(dir);
346
+ const workspace = config.name;
347
+ const forms = [];
348
+ const surfacesDir = join(dir, "src", "surfaces");
349
+ if (existsSync(surfacesDir)) {
350
+ for (const file of readdirSync(surfacesDir).filter((f) => f.endsWith(".json"))) {
351
+ try {
352
+ const cfg = JSON.parse(readFileSync(join(surfacesDir, file), "utf-8"));
353
+ if (cfg.type === "portal")
354
+ continue;
355
+ forms.push({
356
+ file: `src/surfaces/${file}`,
357
+ id: file.replace(/\.json$/, ""),
358
+ title: cfg.title ?? "(untitled)",
359
+ handler: cfg.workflow ?? "(none)",
360
+ });
361
+ }
362
+ catch { }
363
+ }
364
+ }
365
+ const workflowsDir = join(dir, "src", "workflows");
366
+ if (existsSync(workflowsDir)) {
367
+ for (const file of readdirSync(workflowsDir).filter((f) => f.endsWith(".ts"))) {
368
+ const content = readFileSync(join(workflowsDir, file), "utf-8");
369
+ if (!content.includes("ctx.collect("))
370
+ continue;
371
+ const idMatch = content.match(/id:\s*["']([^"']+)["']/);
372
+ const titleMatch = content.match(/title:\s*["']([^"']+)["']/);
373
+ const workflowMatch = content.match(/workflow:\s*["']([^"']+)["']/);
374
+ forms.push({
375
+ file: `src/workflows/${file}`,
376
+ id: idMatch?.[1] ?? "(auto)",
377
+ title: titleMatch?.[1] ?? "(untitled)",
378
+ handler: workflowMatch?.[1] ?? "(none)",
379
+ });
380
+ }
381
+ }
382
+ if (forms.length === 0) {
383
+ console.log("No forms found. Create one with: mug form init <name>");
384
+ return;
385
+ }
386
+ console.log(`Forms in workspace "${workspace}":\n`);
387
+ for (const form of forms) {
388
+ console.log(` ${form.title}`);
389
+ console.log(` ID: ${form.id}`);
390
+ console.log(` File: ${form.file}`);
391
+ console.log(` Handler: ${form.handler}`);
392
+ console.log(` Local: http://localhost:8787/${form.id}`);
393
+ console.log(` Prod: https://${workspace}.mug.work/${form.id}`);
394
+ console.log();
395
+ }
396
+ }
@@ -0,0 +1 @@
1
+ export declare function init(name?: string): Promise<void>;
@@ -0,0 +1,139 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+ import { execSync } from "node:child_process";
4
+ import { sync } from "./sync.js";
5
+ import { ensureDevEmail } from "./login.js";
6
+ import { skillTemplates } from "../templates.js";
7
+ export async function init(name) {
8
+ const dir = process.cwd();
9
+ const workspaceName = name || dirname(dir).split("/").pop() || "workspace";
10
+ if (existsSync(join(dir, "mug.json"))) {
11
+ console.error("mug.json already exists in this directory.");
12
+ process.exit(1);
13
+ }
14
+ // User code directories (root-level)
15
+ mkdirSync(join(dir, "agents"), { recursive: true });
16
+ mkdirSync(join(dir, "agents", "shared-skills"), { recursive: true });
17
+ mkdirSync(join(dir, "connectors"), { recursive: true });
18
+ mkdirSync(join(dir, "workflows"), { recursive: true });
19
+ mkdirSync(join(dir, "surfaces"), { recursive: true });
20
+ // Data sync directories
21
+ mkdirSync(join(dir, "files"), { recursive: true });
22
+ mkdirSync(join(dir, "databases"), { recursive: true });
23
+ // Internal directories
24
+ mkdirSync(join(dir, ".mug"), { recursive: true });
25
+ writeFileSync(join(dir, "files", ".remote"), JSON.stringify({ synced_at: null, files: {} }, null, 2) + "\n");
26
+ writeFileSync(join(dir, "databases", ".remote"), JSON.stringify({ synced_at: null, databases: {} }, null, 2) + "\n");
27
+ writeFileSync(join(dir, "mug.json"), JSON.stringify({
28
+ name: workspaceName,
29
+ id: null,
30
+ settings: {
31
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
32
+ },
33
+ ai: {
34
+ routing: {
35
+ fast: "openai/gpt-5.4-nano",
36
+ balanced: "@cf/moonshotai/kimi-k2.6",
37
+ powerful: "anthropic/claude-sonnet-4-6",
38
+ },
39
+ billing: {
40
+ default: "mug-metered",
41
+ fast: "mug-metered",
42
+ balanced: "mug-metered",
43
+ powerful: "mug-metered",
44
+ },
45
+ },
46
+ sources: {},
47
+ }, null, 2) + "\n");
48
+ writeFileSync(join(dir, "slack.json"), JSON.stringify({
49
+ enabled: false,
50
+ }, null, 2) + "\n");
51
+ ensureDevEmail(join(dir, "mug.json"));
52
+ writeFileSync(join(dir, "package.json"), JSON.stringify({
53
+ name: workspaceName,
54
+ version: "0.1.0",
55
+ type: "module",
56
+ scripts: {
57
+ dev: "mug dev",
58
+ deploy: "mug deploy",
59
+ },
60
+ dependencies: {
61
+ "@mugwork/mug": "latest",
62
+ hono: "^4.12.18",
63
+ },
64
+ devDependencies: {
65
+ "@cloudflare/workers-types": "^4.20260402.1",
66
+ typescript: "^6.0.2",
67
+ },
68
+ }, null, 2) + "\n");
69
+ writeFileSync(join(dir, "tsconfig.json"), JSON.stringify({
70
+ compilerOptions: {
71
+ target: "ES2022",
72
+ module: "ES2022",
73
+ lib: ["ES2022"],
74
+ types: ["@cloudflare/workers-types"],
75
+ moduleResolution: "bundler",
76
+ strict: true,
77
+ skipLibCheck: true,
78
+ isolatedModules: true,
79
+ esModuleInterop: true,
80
+ },
81
+ include: ["**/*.ts"],
82
+ }, null, 2) + "\n");
83
+ writeFileSync(join(dir, ".gitignore"), [
84
+ "node_modules/",
85
+ "databases/*.db",
86
+ ".mug/",
87
+ ".wrangler/",
88
+ ".dev.vars",
89
+ "wrangler.toml",
90
+ ].join("\n") + "\n");
91
+ writeFileSync(join(dir, ".mug", "secrets"), "# Workspace secrets — DO NOT commit this file\n");
92
+ writeFileSync(join(dir, "CLAUDE.md"), `# ${workspaceName}
93
+
94
+ <!-- mug:start -->
95
+ <!-- mug:end -->
96
+ `);
97
+ writeFileSync(join(dir, "AGENTS.md"), `# ${workspaceName}
98
+
99
+ <!-- mug:start -->
100
+ <!-- mug:end -->
101
+ `);
102
+ mkdirSync(join(dir, ".cursor", "rules"), { recursive: true });
103
+ writeFileSync(join(dir, ".cursor", "rules", "mug.mdc"), `---
104
+ description: Mug workspace instructions
105
+ globs: ["**/*.ts", "**/*.json"]
106
+ ---
107
+
108
+ <!-- mug:start -->
109
+ <!-- mug:end -->
110
+ `);
111
+ // Scaffold skills for all AI agents
112
+ for (const [skillPath, content] of Object.entries(skillTemplates)) {
113
+ const parts = skillPath.split("/");
114
+ const skillName = parts[0];
115
+ const fileName = parts[parts.length - 1];
116
+ if (fileName.endsWith(".md")) {
117
+ mkdirSync(join(dir, ".claude", "skills", skillName), { recursive: true });
118
+ writeFileSync(join(dir, ".claude", "skills", skillName, fileName), content);
119
+ mkdirSync(join(dir, ".agents", "skills", skillName), { recursive: true });
120
+ writeFileSync(join(dir, ".agents", "skills", skillName, fileName), content);
121
+ }
122
+ if (fileName.endsWith(".mdc")) {
123
+ writeFileSync(join(dir, ".cursor", "rules", fileName), content);
124
+ }
125
+ }
126
+ console.log(`Creating workspace "${workspaceName}"...`);
127
+ execSync("npm install", { cwd: dir, stdio: "inherit" });
128
+ execSync("git init", { cwd: dir, stdio: "pipe" });
129
+ await sync(dir);
130
+ execSync("git add -A", { cwd: dir, stdio: "pipe" });
131
+ execSync('git commit -m "mug init"', { cwd: dir, stdio: "pipe" });
132
+ console.log(`\nWorkspace "${workspaceName}" ready.`);
133
+ console.log(` mug dev`);
134
+ console.log(`\nAI is included with credits on your plan.`);
135
+ console.log(`For unlimited AI, add your own API key (recommended):`);
136
+ console.log(` mug secret set ai.anthropic=<your-key>`);
137
+ console.log(` mug secret set ai.openai=<your-key>`);
138
+ console.log(`\nRestart your AI coding agent to load Mug's instruction and skill files.`);
139
+ }
@@ -0,0 +1,7 @@
1
+ interface IssueOptions {
2
+ dryRun?: boolean;
3
+ attachDiagnostics?: boolean;
4
+ token?: string;
5
+ }
6
+ export declare function issue(opts: IssueOptions): Promise<void>;
7
+ export {};