@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,590 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
4
|
+
import { getAccountToken, ensureDevEmail } from "./login.js";
|
|
5
|
+
import { init } from "./init.js";
|
|
6
|
+
import { repairScaffolding, ensureSettings } from "./sync.js";
|
|
7
|
+
const API_URL = "https://api.mug.work";
|
|
8
|
+
function prompt(question) {
|
|
9
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
rl.question(question, (answer) => {
|
|
12
|
+
rl.close();
|
|
13
|
+
resolve(answer.trim());
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function getWorkspaceId() {
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
const mugJsonPath = join(cwd, "mug.json");
|
|
20
|
+
if (!existsSync(mugJsonPath)) {
|
|
21
|
+
console.error("No mug.json found. Run `mug create workspace <name>` or `mug init` first.");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const config = JSON.parse(readFileSync(mugJsonPath, "utf-8"));
|
|
25
|
+
if (!config.id) {
|
|
26
|
+
console.error("Workspace not registered on platform. Run `mug create workspace <name>` to register.");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
return config.id;
|
|
30
|
+
}
|
|
31
|
+
function authHeaders(token) {
|
|
32
|
+
return { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
|
|
33
|
+
}
|
|
34
|
+
function slugify(name) {
|
|
35
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 63);
|
|
36
|
+
}
|
|
37
|
+
function validateSubdomain(subdomain) {
|
|
38
|
+
if (subdomain.length < 3)
|
|
39
|
+
return "Subdomain must be at least 3 characters.";
|
|
40
|
+
if (subdomain.length > 63)
|
|
41
|
+
return "Subdomain must be 63 characters or fewer.";
|
|
42
|
+
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(subdomain))
|
|
43
|
+
return "Subdomain must be lowercase alphanumeric with hyphens, starting and ending with a letter or number.";
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function openUrl(url) {
|
|
47
|
+
const { execSync } = require("node:child_process");
|
|
48
|
+
try {
|
|
49
|
+
execSync(`open "${url}"`, { stdio: "ignore" });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
try {
|
|
53
|
+
execSync(`xdg-open "${url}"`, { stdio: "ignore" });
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// fallback: URL is already printed
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function createWorkspace(name, opts) {
|
|
61
|
+
const token = getAccountToken();
|
|
62
|
+
const validTiers = ["free", "starter", "pro", "business"];
|
|
63
|
+
const tier = opts.tier ?? "free";
|
|
64
|
+
if (!validTiers.includes(tier)) {
|
|
65
|
+
console.error(`Invalid tier: ${tier}. Must be one of: ${validTiers.join(", ")}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
const subdomain = opts.subdomain ?? slugify(name);
|
|
69
|
+
const subdomainError = validateSubdomain(subdomain);
|
|
70
|
+
if (subdomainError) {
|
|
71
|
+
console.error(subdomainError);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
console.log(`Checking subdomain "${subdomain}"...`);
|
|
75
|
+
const checkRes = await fetch(`${API_URL}/workspaces/check-subdomain/${subdomain}`, {
|
|
76
|
+
headers: authHeaders(token),
|
|
77
|
+
});
|
|
78
|
+
if (checkRes.ok) {
|
|
79
|
+
const check = (await checkRes.json());
|
|
80
|
+
if (!check.available) {
|
|
81
|
+
console.error(`Subdomain "${subdomain}" is not available${check.reason ? `: ${check.reason}` : ""}.`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
console.log(`Creating workspace "${name}" (${subdomain}.mug.work, ${tier})...`);
|
|
86
|
+
const res = await fetch(`${API_URL}/workspaces`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: authHeaders(token),
|
|
89
|
+
body: JSON.stringify({ name, subdomain, tier }),
|
|
90
|
+
});
|
|
91
|
+
const data = (await res.json());
|
|
92
|
+
if (!res.ok || data.error) {
|
|
93
|
+
console.error(`Failed: ${data.error ?? res.statusText}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
console.log(`\nRegistered: ${data.name} (${data.id})`);
|
|
97
|
+
console.log(`Plan: ${data.planTier}`);
|
|
98
|
+
console.log(`URL: https://${data.subdomain ?? data.name}.mug.work`);
|
|
99
|
+
if (data.checkoutUrl) {
|
|
100
|
+
console.log(`\nOpening Stripe Checkout to activate ${tier} plan...`);
|
|
101
|
+
console.log(` ${data.checkoutUrl}`);
|
|
102
|
+
openUrl(data.checkoutUrl);
|
|
103
|
+
}
|
|
104
|
+
console.log("");
|
|
105
|
+
const cwd = process.cwd();
|
|
106
|
+
const mugJsonPath = join(cwd, "mug.json");
|
|
107
|
+
if (existsSync(mugJsonPath)) {
|
|
108
|
+
repairScaffolding(cwd);
|
|
109
|
+
ensureSettings(mugJsonPath);
|
|
110
|
+
const config = JSON.parse(readFileSync(mugJsonPath, "utf-8"));
|
|
111
|
+
config.id = data.id;
|
|
112
|
+
if (!config.name)
|
|
113
|
+
config.name = data.name;
|
|
114
|
+
config.subdomain = data.subdomain ?? subdomain;
|
|
115
|
+
writeFileSync(mugJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
116
|
+
console.log(`Updated mug.json with workspace ID.`);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
await init(name);
|
|
120
|
+
const config = JSON.parse(readFileSync(mugJsonPath, "utf-8"));
|
|
121
|
+
config.id = data.id;
|
|
122
|
+
config.subdomain = data.subdomain ?? subdomain;
|
|
123
|
+
writeFileSync(mugJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
124
|
+
console.log(`Scaffolded workspace and registered on platform.`);
|
|
125
|
+
}
|
|
126
|
+
ensureDevEmail(mugJsonPath);
|
|
127
|
+
}
|
|
128
|
+
export async function workspaceStatus() {
|
|
129
|
+
const token = getAccountToken();
|
|
130
|
+
const workspaceId = getWorkspaceId();
|
|
131
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}`, {
|
|
132
|
+
headers: authHeaders(token),
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
if (res.status === 401) {
|
|
136
|
+
console.error("Unauthorized. Check your account permissions for this workspace.");
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
const err = (await res.json());
|
|
140
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
const ws = (await res.json());
|
|
144
|
+
console.log(`Workspace: ${ws.name} (${ws.id})`);
|
|
145
|
+
console.log(`URL: https://${ws.name}.mug.work`);
|
|
146
|
+
if (ws.domain)
|
|
147
|
+
console.log(`Domain: ${ws.domain}`);
|
|
148
|
+
console.log(`Plan: ${ws.plan_tier}`);
|
|
149
|
+
console.log(`Role: ${ws.role}`);
|
|
150
|
+
console.log(`Version: ${ws.version}`);
|
|
151
|
+
if (ws.last_deployed_at) {
|
|
152
|
+
console.log(`Last deployed: ${ws.last_deployed_at}`);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log(`Last deployed: never`);
|
|
156
|
+
}
|
|
157
|
+
console.log(`\nUse "mug workspace plan" to change plan tier.`);
|
|
158
|
+
}
|
|
159
|
+
export async function workspacePlan() {
|
|
160
|
+
const token = getAccountToken();
|
|
161
|
+
const workspaceId = getWorkspaceId();
|
|
162
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}`, {
|
|
163
|
+
headers: authHeaders(token),
|
|
164
|
+
});
|
|
165
|
+
if (!res.ok) {
|
|
166
|
+
console.error("Failed to fetch workspace info.");
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
const ws = (await res.json());
|
|
170
|
+
const tiers = ["free", "starter", "pro", "business"];
|
|
171
|
+
const prices = { free: "$0/mo", starter: "$99/mo", pro: "$299/mo", business: "$599/mo" };
|
|
172
|
+
const descriptions = {
|
|
173
|
+
free: "20K ops, 10K records, 100 MB storage",
|
|
174
|
+
starter: "1M ops, 250K records, 5 GB storage",
|
|
175
|
+
pro: "10M ops, 2.5M records, 50 GB storage, white-label",
|
|
176
|
+
business: "50M ops, 25M records, 500 GB storage, custom domain",
|
|
177
|
+
};
|
|
178
|
+
console.log(`Workspace: ${ws.name}`);
|
|
179
|
+
console.log(`Current plan: ${ws.plan_tier}\n`);
|
|
180
|
+
console.log("Available plans:");
|
|
181
|
+
for (const t of tiers) {
|
|
182
|
+
const marker = t === ws.plan_tier ? " ← current" : "";
|
|
183
|
+
console.log(` ${tiers.indexOf(t) + 1}. ${t} (${prices[t]})${marker}`);
|
|
184
|
+
console.log(` ${descriptions[t]}`);
|
|
185
|
+
}
|
|
186
|
+
const available = tiers.filter((t) => t !== ws.plan_tier);
|
|
187
|
+
const choice = await prompt(`\nSelect plan (1-${tiers.length}), or enter to cancel: `);
|
|
188
|
+
if (!choice) {
|
|
189
|
+
console.log("Cancelled.");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const idx = parseInt(choice) - 1;
|
|
193
|
+
if (isNaN(idx) || idx < 0 || idx >= tiers.length) {
|
|
194
|
+
console.error("Invalid selection.");
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
const newTier = tiers[idx];
|
|
198
|
+
if (newTier === ws.plan_tier) {
|
|
199
|
+
console.log("Already on that plan.");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const direction = tiers.indexOf(newTier) > tiers.indexOf(ws.plan_tier) ? "Upgrade" : "Downgrade";
|
|
203
|
+
const confirm = await prompt(`${direction} to ${newTier} (${prices[newTier]})? (y/n): `);
|
|
204
|
+
if (confirm.toLowerCase() !== "y") {
|
|
205
|
+
console.log("Cancelled.");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const changeRes = await fetch(`${API_URL}/billing/change-plan`, {
|
|
209
|
+
method: "POST",
|
|
210
|
+
headers: authHeaders(token),
|
|
211
|
+
body: JSON.stringify({ workspaceId, tier: newTier }),
|
|
212
|
+
});
|
|
213
|
+
if (!changeRes.ok) {
|
|
214
|
+
const err = (await changeRes.json());
|
|
215
|
+
console.error(`Failed: ${err.error ?? changeRes.statusText}`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
const data = (await changeRes.json());
|
|
219
|
+
if (data.status === "checkout" && data.url) {
|
|
220
|
+
console.log(`\nOpening Stripe Checkout...`);
|
|
221
|
+
console.log(` ${data.url}`);
|
|
222
|
+
openUrl(data.url);
|
|
223
|
+
}
|
|
224
|
+
else if (data.status === "changed") {
|
|
225
|
+
console.log(`\nPlan changed to ${data.tier}.`);
|
|
226
|
+
}
|
|
227
|
+
else if (data.status === "downgraded") {
|
|
228
|
+
console.log(`\nDowngraded to ${data.tier}. Changes take effect at end of billing period.`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
export async function workspaceInvite(email) {
|
|
232
|
+
const token = getAccountToken();
|
|
233
|
+
const workspaceId = getWorkspaceId();
|
|
234
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}/invite`, {
|
|
235
|
+
method: "POST",
|
|
236
|
+
headers: authHeaders(token),
|
|
237
|
+
body: JSON.stringify({ email, role: "admin" }),
|
|
238
|
+
});
|
|
239
|
+
if (!res.ok) {
|
|
240
|
+
const err = (await res.json());
|
|
241
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
const data = (await res.json());
|
|
245
|
+
console.log(`Invite sent to ${data.email} (${data.role}). They'll receive an email to accept.`);
|
|
246
|
+
}
|
|
247
|
+
export async function workspaceTransfer(email) {
|
|
248
|
+
const token = getAccountToken();
|
|
249
|
+
const workspaceId = getWorkspaceId();
|
|
250
|
+
const confirm = await prompt(`Transfer ownership to ${email}? They'll receive an invite to accept. (y/n): `);
|
|
251
|
+
if (confirm.toLowerCase() !== "y") {
|
|
252
|
+
console.log("Cancelled.");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}/invite`, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers: authHeaders(token),
|
|
258
|
+
body: JSON.stringify({ email, role: "owner" }),
|
|
259
|
+
});
|
|
260
|
+
if (!res.ok) {
|
|
261
|
+
const err = (await res.json());
|
|
262
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
const data = (await res.json());
|
|
266
|
+
console.log(`Ownership transfer invite sent to ${data.email}. Ownership transfers when they accept.`);
|
|
267
|
+
}
|
|
268
|
+
export async function workspaceRemove(email) {
|
|
269
|
+
const token = getAccountToken();
|
|
270
|
+
const workspaceId = getWorkspaceId();
|
|
271
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}/remove`, {
|
|
272
|
+
method: "POST",
|
|
273
|
+
headers: authHeaders(token),
|
|
274
|
+
body: JSON.stringify({ email }),
|
|
275
|
+
});
|
|
276
|
+
if (!res.ok) {
|
|
277
|
+
const err = (await res.json());
|
|
278
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
console.log(`Removed ${email} from workspace.`);
|
|
282
|
+
}
|
|
283
|
+
export async function workspaceArchive() {
|
|
284
|
+
const token = getAccountToken();
|
|
285
|
+
const workspaceId = getWorkspaceId();
|
|
286
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}`, {
|
|
287
|
+
headers: authHeaders(token),
|
|
288
|
+
});
|
|
289
|
+
if (!res.ok) {
|
|
290
|
+
console.error("Failed to fetch workspace info.");
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
const ws = (await res.json());
|
|
294
|
+
const confirm = await prompt(`Type "${ws.name}" to confirm archive: `);
|
|
295
|
+
if (confirm !== ws.name) {
|
|
296
|
+
console.log("Cancelled.");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const archiveRes = await fetch(`${API_URL}/workspaces/${workspaceId}/archive`, {
|
|
300
|
+
method: "POST",
|
|
301
|
+
headers: authHeaders(token),
|
|
302
|
+
});
|
|
303
|
+
if (!archiveRes.ok) {
|
|
304
|
+
const err = (await archiveRes.json());
|
|
305
|
+
console.error(`Failed: ${err.error ?? archiveRes.statusText}`);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
const data = (await archiveRes.json());
|
|
309
|
+
console.log(`\nWorkspace archived.`);
|
|
310
|
+
console.log(`Archived at: ${data.archivedAt}`);
|
|
311
|
+
console.log(`Data retained for ${data.retentionDays} days. Restore anytime from your account dashboard or CLI.`);
|
|
312
|
+
}
|
|
313
|
+
export async function workspaceRestore() {
|
|
314
|
+
const token = getAccountToken();
|
|
315
|
+
const workspaceId = getWorkspaceId();
|
|
316
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}/restore`, {
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: authHeaders(token),
|
|
319
|
+
});
|
|
320
|
+
if (!res.ok) {
|
|
321
|
+
const err = (await res.json());
|
|
322
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
const data = (await res.json());
|
|
326
|
+
console.log("Workspace restored.");
|
|
327
|
+
if (data.checkoutUrl) {
|
|
328
|
+
console.log(`\nOpening Stripe Checkout to reactivate paid plan...`);
|
|
329
|
+
console.log(` ${data.checkoutUrl}`);
|
|
330
|
+
openUrl(data.checkoutUrl);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
export async function workspaceDelete() {
|
|
334
|
+
const token = getAccountToken();
|
|
335
|
+
const workspaceId = getWorkspaceId();
|
|
336
|
+
const wsRes = await fetch(`${API_URL}/workspaces/${workspaceId}`, {
|
|
337
|
+
headers: authHeaders(token),
|
|
338
|
+
});
|
|
339
|
+
if (!wsRes.ok) {
|
|
340
|
+
console.error("Failed to fetch workspace info.");
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
const ws = (await wsRes.json());
|
|
344
|
+
if (ws.status !== "archived") {
|
|
345
|
+
console.error("Only archived workspaces can be permanently deleted. Run `mug workspace archive` first.");
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
console.log("WARNING: This permanently deletes all workspace data. This cannot be undone.");
|
|
349
|
+
const confirm = await prompt(`Type "${ws.name}" to confirm permanent deletion: `);
|
|
350
|
+
if (confirm !== ws.name) {
|
|
351
|
+
console.log("Cancelled.");
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}/delete`, {
|
|
355
|
+
method: "POST",
|
|
356
|
+
headers: authHeaders(token),
|
|
357
|
+
});
|
|
358
|
+
if (!res.ok) {
|
|
359
|
+
const err = (await res.json());
|
|
360
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
console.log("Workspace permanently deleted.");
|
|
364
|
+
}
|
|
365
|
+
function formatBytes(bytes) {
|
|
366
|
+
if (bytes < 1000)
|
|
367
|
+
return `${bytes} B`;
|
|
368
|
+
if (bytes < 1_000_000)
|
|
369
|
+
return `${(bytes / 1000).toFixed(1)} KB`;
|
|
370
|
+
return `${(bytes / 1_000_000).toFixed(1)} MB`;
|
|
371
|
+
}
|
|
372
|
+
export async function workspaceExport(opts) {
|
|
373
|
+
const token = getAccountToken();
|
|
374
|
+
const workspaceId = getWorkspaceId();
|
|
375
|
+
console.log("Fetching export manifest...");
|
|
376
|
+
const manifestRes = await fetch(`${API_URL}/workspaces/${workspaceId}/export-manifest`, {
|
|
377
|
+
headers: authHeaders(token),
|
|
378
|
+
});
|
|
379
|
+
if (!manifestRes.ok) {
|
|
380
|
+
const err = (await manifestRes.json());
|
|
381
|
+
console.error(`Failed: ${err.error ?? manifestRes.statusText}`);
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
const manifest = (await manifestRes.json());
|
|
385
|
+
const allCategories = Object.keys(manifest.categories);
|
|
386
|
+
if (!opts.all && !opts.categories) {
|
|
387
|
+
console.log(`\nWorkspace: ${manifest.workspace}\n`);
|
|
388
|
+
console.log("Categories:");
|
|
389
|
+
for (const [key, cat] of Object.entries(manifest.categories)) {
|
|
390
|
+
console.log(` ${key.padEnd(12)} ${String(cat.files).padStart(4)} files ${formatBytes(cat.bytes).padStart(10)} ${cat.label}`);
|
|
391
|
+
}
|
|
392
|
+
console.log(`\nUse --all to download everything, or --categories config,code,... to select.`);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const selectedCategories = opts.all ? allCategories : opts.categories.split(",");
|
|
396
|
+
console.log(`Requesting export for ${opts.all ? "all categories" : selectedCategories.join(", ")}...`);
|
|
397
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}/export`, {
|
|
398
|
+
method: "POST",
|
|
399
|
+
headers: { ...authHeaders(token), "Content-Type": "application/json" },
|
|
400
|
+
body: JSON.stringify({ categories: selectedCategories }),
|
|
401
|
+
});
|
|
402
|
+
if (!res.ok) {
|
|
403
|
+
const err = (await res.json());
|
|
404
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
405
|
+
process.exit(1);
|
|
406
|
+
}
|
|
407
|
+
console.log(`Export is being prepared. Check your inbox for a download link.`);
|
|
408
|
+
}
|
|
409
|
+
export async function accountInvites() {
|
|
410
|
+
const token = getAccountToken();
|
|
411
|
+
const res = await fetch(`${API_URL}/auth/me`, {
|
|
412
|
+
headers: authHeaders(token),
|
|
413
|
+
});
|
|
414
|
+
if (!res.ok) {
|
|
415
|
+
console.error("Failed to fetch account info.");
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
const data = (await res.json());
|
|
419
|
+
const incoming = data.pendingInvites ?? [];
|
|
420
|
+
const sent = (data.sentInvites ?? []).filter((i) => i.status === "pending");
|
|
421
|
+
if (incoming.length === 0 && sent.length === 0) {
|
|
422
|
+
console.log("No pending invites.");
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (incoming.length > 0) {
|
|
426
|
+
console.log("Incoming invites:");
|
|
427
|
+
for (const inv of incoming) {
|
|
428
|
+
console.log(` #${inv.id} — ${inv.workspace_name} (${inv.role}), from ${inv.invited_by_email}`);
|
|
429
|
+
}
|
|
430
|
+
console.log(`\n Accept: mug account accept <id>`);
|
|
431
|
+
console.log(` Decline: mug account decline <id>`);
|
|
432
|
+
}
|
|
433
|
+
if (sent.length > 0) {
|
|
434
|
+
if (incoming.length > 0)
|
|
435
|
+
console.log("");
|
|
436
|
+
console.log("Sent invites (pending):");
|
|
437
|
+
for (const inv of sent) {
|
|
438
|
+
console.log(` #${inv.id} — ${inv.email} → ${inv.workspace_name} (${inv.role})`);
|
|
439
|
+
}
|
|
440
|
+
console.log(`\n Cancel: mug workspace cancel-invite <id>`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
export async function accountAccept(inviteId) {
|
|
444
|
+
const token = getAccountToken();
|
|
445
|
+
const res = await fetch(`${API_URL}/invites/${inviteId}/accept`, {
|
|
446
|
+
method: "POST",
|
|
447
|
+
headers: authHeaders(token),
|
|
448
|
+
});
|
|
449
|
+
if (!res.ok) {
|
|
450
|
+
const err = (await res.json());
|
|
451
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
const data = (await res.json());
|
|
455
|
+
console.log(`Invite accepted. You are now ${data.role}.`);
|
|
456
|
+
}
|
|
457
|
+
export async function accountDecline(inviteId) {
|
|
458
|
+
const token = getAccountToken();
|
|
459
|
+
const res = await fetch(`${API_URL}/invites/${inviteId}/decline`, {
|
|
460
|
+
method: "POST",
|
|
461
|
+
headers: authHeaders(token),
|
|
462
|
+
});
|
|
463
|
+
if (!res.ok) {
|
|
464
|
+
const err = (await res.json());
|
|
465
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
console.log("Invite declined.");
|
|
469
|
+
}
|
|
470
|
+
export async function workspaceCancelInvite(inviteId) {
|
|
471
|
+
const token = getAccountToken();
|
|
472
|
+
const res = await fetch(`${API_URL}/invites/${inviteId}`, {
|
|
473
|
+
method: "DELETE",
|
|
474
|
+
headers: authHeaders(token),
|
|
475
|
+
});
|
|
476
|
+
if (!res.ok) {
|
|
477
|
+
const err = (await res.json());
|
|
478
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
console.log("Invite cancelled.");
|
|
482
|
+
}
|
|
483
|
+
export async function accountChangeEmail(newEmail) {
|
|
484
|
+
const token = getAccountToken();
|
|
485
|
+
const headers = authHeaders(token);
|
|
486
|
+
const meRes = await fetch(`${API_URL}/auth/me`, { headers });
|
|
487
|
+
if (!meRes.ok) {
|
|
488
|
+
console.error("Failed to fetch account info.");
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
const me = (await meRes.json());
|
|
492
|
+
const oldEmail = me.email;
|
|
493
|
+
console.log(`Current email: ${oldEmail}`);
|
|
494
|
+
console.log(`New email: ${newEmail}\n`);
|
|
495
|
+
console.log(`Sending verification code to ${oldEmail}...`);
|
|
496
|
+
const sendOld = await fetch(`${API_URL}/auth/send-code`, {
|
|
497
|
+
method: "POST",
|
|
498
|
+
headers,
|
|
499
|
+
body: JSON.stringify({ email: oldEmail }),
|
|
500
|
+
});
|
|
501
|
+
if (!sendOld.ok) {
|
|
502
|
+
console.error("Failed to send verification code to current email.");
|
|
503
|
+
process.exit(1);
|
|
504
|
+
}
|
|
505
|
+
const oldCode = await prompt(`Enter the code sent to ${oldEmail}: `);
|
|
506
|
+
if (!oldCode) {
|
|
507
|
+
console.log("Cancelled.");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
console.log(`Sending verification code to ${newEmail}...`);
|
|
511
|
+
const sendNew = await fetch(`${API_URL}/auth/send-code`, {
|
|
512
|
+
method: "POST",
|
|
513
|
+
headers,
|
|
514
|
+
body: JSON.stringify({ email: newEmail }),
|
|
515
|
+
});
|
|
516
|
+
if (!sendNew.ok) {
|
|
517
|
+
console.error("Failed to send verification code to new email.");
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
const newCode = await prompt(`Enter the code sent to ${newEmail}: `);
|
|
521
|
+
if (!newCode) {
|
|
522
|
+
console.log("Cancelled.");
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const res = await fetch(`${API_URL}/auth/email`, {
|
|
526
|
+
method: "PUT",
|
|
527
|
+
headers,
|
|
528
|
+
body: JSON.stringify({ email: newEmail, oldCode, newCode }),
|
|
529
|
+
});
|
|
530
|
+
if (!res.ok) {
|
|
531
|
+
const err = (await res.json());
|
|
532
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
const data = (await res.json());
|
|
536
|
+
console.log(`\nEmail updated to ${data.email}.`);
|
|
537
|
+
}
|
|
538
|
+
export async function workspaceMembers() {
|
|
539
|
+
const token = getAccountToken();
|
|
540
|
+
const workspaceId = getWorkspaceId();
|
|
541
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}/members`, {
|
|
542
|
+
headers: authHeaders(token),
|
|
543
|
+
});
|
|
544
|
+
if (!res.ok) {
|
|
545
|
+
const err = (await res.json());
|
|
546
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
const data = (await res.json());
|
|
550
|
+
console.log("Members:");
|
|
551
|
+
for (const m of data.members) {
|
|
552
|
+
console.log(` ${m.email} — ${m.role} (since ${m.created_at.split("T")[0]})`);
|
|
553
|
+
}
|
|
554
|
+
const meRes = await fetch(`${API_URL}/auth/me`, {
|
|
555
|
+
headers: authHeaders(token),
|
|
556
|
+
});
|
|
557
|
+
if (meRes.ok) {
|
|
558
|
+
const me = (await meRes.json());
|
|
559
|
+
const pending = me.sentInvites?.filter((i) => i.status === "pending" && i.workspace_id === workspaceId) ?? [];
|
|
560
|
+
if (pending.length > 0) {
|
|
561
|
+
console.log("\nPending invites:");
|
|
562
|
+
for (const inv of pending) {
|
|
563
|
+
console.log(` ${inv.email} — ${inv.role} (invite #${inv.id})`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
export async function workspaceCheckSubdomain(subdomain) {
|
|
569
|
+
const token = getAccountToken();
|
|
570
|
+
const error = validateSubdomain(subdomain);
|
|
571
|
+
if (error) {
|
|
572
|
+
console.error(error);
|
|
573
|
+
process.exit(1);
|
|
574
|
+
}
|
|
575
|
+
const res = await fetch(`${API_URL}/workspaces/check-subdomain/${subdomain}`, {
|
|
576
|
+
headers: authHeaders(token),
|
|
577
|
+
});
|
|
578
|
+
if (!res.ok) {
|
|
579
|
+
const err = (await res.json());
|
|
580
|
+
console.error(`Failed: ${err.error ?? res.statusText}`);
|
|
581
|
+
process.exit(1);
|
|
582
|
+
}
|
|
583
|
+
const data = (await res.json());
|
|
584
|
+
if (data.available) {
|
|
585
|
+
console.log(`"${subdomain}" is available — ${subdomain}.mug.work`);
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
console.log(`"${subdomain}" is not available${data.reason ? `: ${data.reason}` : ""}.`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SourcesConfig, SourceAuth } from "../worker/src/source-types.js";
|
|
2
|
+
interface OldConnection {
|
|
3
|
+
auth?: SourceAuth;
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
}
|
|
6
|
+
interface OldConnector {
|
|
7
|
+
connection?: string;
|
|
8
|
+
database?: string;
|
|
9
|
+
schedule?: string;
|
|
10
|
+
syncSchedule?: string;
|
|
11
|
+
}
|
|
12
|
+
interface MugConfig {
|
|
13
|
+
sources?: SourcesConfig;
|
|
14
|
+
connections?: Record<string, OldConnection>;
|
|
15
|
+
connectors?: Record<string, OldConnector>;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
export declare function migrateConfig(config: MugConfig): SourcesConfig;
|
|
19
|
+
export declare function configNeedsMigration(config: MugConfig): boolean;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function migrateConfig(config) {
|
|
2
|
+
if (config.sources && !config.connections && !config.connectors) {
|
|
3
|
+
return config.sources;
|
|
4
|
+
}
|
|
5
|
+
if (!config.connections && !config.connectors) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
const connections = config.connections ?? {};
|
|
9
|
+
const connectors = config.connectors ?? {};
|
|
10
|
+
const sources = {};
|
|
11
|
+
for (const [name, conn] of Object.entries(connections)) {
|
|
12
|
+
sources[name] = {
|
|
13
|
+
...(conn.auth ? { auth: conn.auth } : {}),
|
|
14
|
+
...(conn.baseUrl ? { baseUrl: conn.baseUrl } : {}),
|
|
15
|
+
syncs: {},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
for (const [connectorName, connector] of Object.entries(connectors)) {
|
|
19
|
+
const schedule = connector.schedule ?? connector.syncSchedule;
|
|
20
|
+
const sync = {
|
|
21
|
+
database: connector.database ?? connectorName,
|
|
22
|
+
...(schedule ? { schedule } : {}),
|
|
23
|
+
};
|
|
24
|
+
let sourceName;
|
|
25
|
+
if (connector.connection && sources[connector.connection]) {
|
|
26
|
+
sourceName = connector.connection;
|
|
27
|
+
}
|
|
28
|
+
else if (sources[connectorName]) {
|
|
29
|
+
sourceName = connectorName;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
sourceName = connector.connection ?? connectorName;
|
|
33
|
+
if (!sources[sourceName]) {
|
|
34
|
+
sources[sourceName] = { syncs: {} };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
sources[sourceName].syncs[connectorName] = sync;
|
|
38
|
+
}
|
|
39
|
+
return sources;
|
|
40
|
+
}
|
|
41
|
+
export function configNeedsMigration(config) {
|
|
42
|
+
return !!(config.connections || config.connectors) && !config.sources;
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ParsedTable {
|
|
2
|
+
name: string;
|
|
3
|
+
primaryKey: string;
|
|
4
|
+
endpoint?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ParsedSource {
|
|
8
|
+
name: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
database: string;
|
|
11
|
+
tables: ParsedTable[];
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function parseSourceFile(source: string, fileName: string): ParsedSource | null;
|