@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,184 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import Database from "better-sqlite3";
|
|
4
|
+
import { getAccountToken } from "./login.js";
|
|
5
|
+
import { readFilesManifest, writeFilesManifest, readDatabasesManifest, writeDatabasesManifest } from "../manifest.js";
|
|
6
|
+
const API_URL = "https://api.mug.work";
|
|
7
|
+
function loadConfig(cwd) {
|
|
8
|
+
const mugJsonPath = join(cwd, "mug.json");
|
|
9
|
+
if (!existsSync(mugJsonPath)) {
|
|
10
|
+
console.error("No mug.json found. Run `mug init` first.");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const config = JSON.parse(readFileSync(mugJsonPath, "utf-8"));
|
|
14
|
+
return {
|
|
15
|
+
name: config.name,
|
|
16
|
+
id: config.id,
|
|
17
|
+
databases: config.databases ?? {},
|
|
18
|
+
connectors: config.connectors ?? {},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async function pullFile(cwd, filePath) {
|
|
22
|
+
const config = loadConfig(cwd);
|
|
23
|
+
if (!config.id) {
|
|
24
|
+
console.error("Workspace not registered. Run `mug create workspace` first.");
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const token = getAccountToken();
|
|
28
|
+
const res = await fetch(`${API_URL}/workspace/${config.name}/files/read/${filePath}`, {
|
|
29
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
30
|
+
signal: AbortSignal.timeout(30000),
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
console.error(`File not found: ${filePath}`);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const content = Buffer.from(await res.arrayBuffer());
|
|
37
|
+
const localPath = join(cwd, "files", filePath);
|
|
38
|
+
mkdirSync(dirname(localPath), { recursive: true });
|
|
39
|
+
writeFileSync(localPath, content);
|
|
40
|
+
const sha256 = res.headers.get("X-File-SHA256") ?? "";
|
|
41
|
+
const manifest = readFilesManifest(cwd);
|
|
42
|
+
manifest.files[filePath] = {
|
|
43
|
+
size: content.byteLength,
|
|
44
|
+
sha256,
|
|
45
|
+
updated_at: new Date().toISOString(),
|
|
46
|
+
};
|
|
47
|
+
manifest.synced_at = new Date().toISOString();
|
|
48
|
+
writeFilesManifest(cwd, manifest);
|
|
49
|
+
console.log(` ${filePath} (${(content.byteLength / 1024).toFixed(1)} KB)`);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
async function pullDatabase(cwd, dbName) {
|
|
53
|
+
const config = loadConfig(cwd);
|
|
54
|
+
if (!config.id) {
|
|
55
|
+
console.error("Workspace not registered. Run `mug create workspace` first.");
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
const token = getAccountToken();
|
|
59
|
+
const res = await fetch(`${API_URL}/workspace/${config.name}/db/${dbName}/export`, {
|
|
60
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
61
|
+
signal: AbortSignal.timeout(60000),
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
console.error(`Database not found or empty: ${dbName}`);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const data = (await res.json());
|
|
68
|
+
if (Object.keys(data.tables).length === 0) {
|
|
69
|
+
console.error(`Database is empty: ${dbName}`);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const dbPath = join(cwd, "databases", `${dbName}.db`);
|
|
73
|
+
if (existsSync(dbPath)) {
|
|
74
|
+
const backup = `${dbPath}.backup-${Date.now()}`;
|
|
75
|
+
writeFileSync(backup, readFileSync(dbPath));
|
|
76
|
+
}
|
|
77
|
+
const db = new Database(dbPath);
|
|
78
|
+
try {
|
|
79
|
+
db.exec("PRAGMA journal_mode=WAL");
|
|
80
|
+
const existing = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all();
|
|
81
|
+
for (const { name } of existing) {
|
|
82
|
+
db.exec(`DROP TABLE IF EXISTS "${name}"`);
|
|
83
|
+
}
|
|
84
|
+
for (const [, tableData] of Object.entries(data.tables)) {
|
|
85
|
+
db.exec(tableData.ddl);
|
|
86
|
+
if (tableData.rows.length === 0)
|
|
87
|
+
continue;
|
|
88
|
+
const columns = Object.keys(tableData.rows[0]);
|
|
89
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
90
|
+
const tableName = tableData.ddl.match(/CREATE TABLE\s+"?([^"\s(]+)"?/i)?.[1] ?? "";
|
|
91
|
+
const insert = db.prepare(`INSERT INTO "${tableName}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})`);
|
|
92
|
+
const insertMany = db.transaction((rows) => {
|
|
93
|
+
for (const row of rows) {
|
|
94
|
+
insert.run(...columns.map((c) => {
|
|
95
|
+
const v = row[c];
|
|
96
|
+
if (v === null || v === undefined)
|
|
97
|
+
return null;
|
|
98
|
+
if (typeof v === "object")
|
|
99
|
+
return JSON.stringify(v);
|
|
100
|
+
return v;
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
insertMany(tableData.rows);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
db.close();
|
|
109
|
+
}
|
|
110
|
+
const manifest = readDatabasesManifest(cwd);
|
|
111
|
+
const tableCount = Object.keys(data.tables).length;
|
|
112
|
+
const rowCount = Object.values(data.tables).reduce((sum, t) => sum + t.rows.length, 0);
|
|
113
|
+
const sizeMb = Math.round(statSync(dbPath).size / 1024 / 1024 * 10) / 10;
|
|
114
|
+
manifest.databases[dbName] = {
|
|
115
|
+
size_mb: sizeMb,
|
|
116
|
+
tables: Object.fromEntries(Object.entries(data.tables).map(([tName, tData]) => {
|
|
117
|
+
const db2 = new Database(dbPath, { readonly: true });
|
|
118
|
+
const columns = db2.prepare(`PRAGMA table_info("${tName}")`).all().map((c) => ({
|
|
119
|
+
name: String(c.name),
|
|
120
|
+
type: String(c.type || "TEXT"),
|
|
121
|
+
}));
|
|
122
|
+
db2.close();
|
|
123
|
+
return [tName, { columns, row_count: tData.rows.length }];
|
|
124
|
+
})),
|
|
125
|
+
updated_at: new Date().toISOString(),
|
|
126
|
+
};
|
|
127
|
+
manifest.synced_at = new Date().toISOString();
|
|
128
|
+
writeDatabasesManifest(cwd, manifest);
|
|
129
|
+
console.log(` ${dbName}: ${tableCount} table${tableCount !== 1 ? "s" : ""}, ${rowCount} row${rowCount !== 1 ? "s" : ""} (${sizeMb} MB)`);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
export async function pull(target, opts) {
|
|
133
|
+
const cwd = process.cwd();
|
|
134
|
+
if (opts.all) {
|
|
135
|
+
const config = loadConfig(cwd);
|
|
136
|
+
if (!config.id) {
|
|
137
|
+
console.error("Workspace not registered. Run `mug create workspace` first.");
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
const filesManifest = readFilesManifest(cwd);
|
|
141
|
+
const dbManifest = readDatabasesManifest(cwd);
|
|
142
|
+
const fileCount = Object.keys(filesManifest.files).length;
|
|
143
|
+
const dbCount = Object.keys(dbManifest.databases).length;
|
|
144
|
+
if (fileCount === 0 && dbCount === 0) {
|
|
145
|
+
console.log("Nothing to pull. Run `mug sync` first to fetch remote manifests.");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
let totalSize = 0;
|
|
149
|
+
for (const f of Object.values(filesManifest.files))
|
|
150
|
+
totalSize += f.size;
|
|
151
|
+
for (const d of Object.values(dbManifest.databases))
|
|
152
|
+
totalSize += d.size_mb * 1024 * 1024;
|
|
153
|
+
console.log(`Pulling ${fileCount} file${fileCount !== 1 ? "s" : ""} and ${dbCount} database${dbCount !== 1 ? "s" : ""} (~${(totalSize / 1024 / 1024).toFixed(1)} MB)...`);
|
|
154
|
+
let pulled = 0;
|
|
155
|
+
for (const filePath of Object.keys(filesManifest.files)) {
|
|
156
|
+
if (await pullFile(cwd, filePath))
|
|
157
|
+
pulled++;
|
|
158
|
+
}
|
|
159
|
+
for (const dbName of Object.keys(dbManifest.databases)) {
|
|
160
|
+
if (await pullDatabase(cwd, dbName))
|
|
161
|
+
pulled++;
|
|
162
|
+
}
|
|
163
|
+
console.log(`Pulled ${pulled} item${pulled !== 1 ? "s" : ""}.`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (!target) {
|
|
167
|
+
console.error("Usage: mug pull <files/path> or mug pull <databases/name> or mug pull --all");
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
if (target.startsWith("files/")) {
|
|
171
|
+
const filePath = target.slice("files/".length);
|
|
172
|
+
console.log("Pulling file...");
|
|
173
|
+
await pullFile(cwd, filePath);
|
|
174
|
+
}
|
|
175
|
+
else if (target.startsWith("databases/")) {
|
|
176
|
+
const dbName = target.slice("databases/".length).replace(/\.db$/, "");
|
|
177
|
+
console.log("Pulling database...");
|
|
178
|
+
await pullDatabase(cwd, dbName);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.error(`Unknown target: ${target}. Use files/<path> or databases/<name>.`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import Database from "better-sqlite3";
|
|
4
|
+
import { getAccountToken } from "./login.js";
|
|
5
|
+
import { readDatabasesManifest, writeDatabasesManifest, readFilesManifest, writeFilesManifest } from "../manifest.js";
|
|
6
|
+
const API_URL = "https://api.mug.work";
|
|
7
|
+
function loadConfig(cwd) {
|
|
8
|
+
const mugJsonPath = join(cwd, "mug.json");
|
|
9
|
+
if (!existsSync(mugJsonPath)) {
|
|
10
|
+
console.error("No mug.json found. Run `mug init` first.");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const config = JSON.parse(readFileSync(mugJsonPath, "utf-8"));
|
|
14
|
+
return { name: config.name, id: config.id };
|
|
15
|
+
}
|
|
16
|
+
async function pushDatabase(cwd, dbName) {
|
|
17
|
+
const config = loadConfig(cwd);
|
|
18
|
+
if (!config.id) {
|
|
19
|
+
console.error("Workspace not registered. Run `mug deploy` first.");
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const dbPath = join(cwd, "databases", `${dbName}.db`);
|
|
23
|
+
if (!existsSync(dbPath)) {
|
|
24
|
+
console.error(`Database not found: databases/${dbName}.db`);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const db = new Database(dbPath, { readonly: true });
|
|
28
|
+
try {
|
|
29
|
+
const tableRows = db.prepare("SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all();
|
|
30
|
+
if (tableRows.length === 0) {
|
|
31
|
+
console.error(`Database is empty: ${dbName}`);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const tables = {};
|
|
35
|
+
let totalRows = 0;
|
|
36
|
+
for (const { name, sql: ddl } of tableRows) {
|
|
37
|
+
const rows = db.prepare(`SELECT * FROM "${name}"`).all();
|
|
38
|
+
tables[name] = { ddl, rows };
|
|
39
|
+
totalRows += rows.length;
|
|
40
|
+
}
|
|
41
|
+
const token = getAccountToken();
|
|
42
|
+
const res = await fetch(`${API_URL}/workspace/${config.name}/db/${dbName}/upload`, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: {
|
|
45
|
+
Authorization: `Bearer ${token}`,
|
|
46
|
+
"Content-Type": "application/json",
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify({ tables }),
|
|
49
|
+
signal: AbortSignal.timeout(60000),
|
|
50
|
+
});
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
console.error(`Push failed: ${res.status} ${await res.text()}`);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const manifest = readDatabasesManifest(cwd);
|
|
56
|
+
const sizeMb = Math.round(statSync(dbPath).size / 1024 / 1024 * 10) / 10;
|
|
57
|
+
manifest.databases[dbName] = {
|
|
58
|
+
size_mb: sizeMb,
|
|
59
|
+
tables: Object.fromEntries(tableRows.map(({ name }) => {
|
|
60
|
+
const columns = db.prepare(`PRAGMA table_info("${name}")`).all().map((c) => ({
|
|
61
|
+
name: String(c.name),
|
|
62
|
+
type: String(c.type || "TEXT"),
|
|
63
|
+
}));
|
|
64
|
+
return [name, { columns, row_count: tables[name].rows.length }];
|
|
65
|
+
})),
|
|
66
|
+
updated_at: new Date().toISOString(),
|
|
67
|
+
};
|
|
68
|
+
manifest.synced_at = new Date().toISOString();
|
|
69
|
+
writeDatabasesManifest(cwd, manifest);
|
|
70
|
+
console.log(` ${dbName}: ${tableRows.length} table${tableRows.length !== 1 ? "s" : ""}, ${totalRows} row${totalRows !== 1 ? "s" : ""} (${sizeMb} MB)`);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
db.close();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function pushFile(cwd, filePath) {
|
|
78
|
+
const config = loadConfig(cwd);
|
|
79
|
+
if (!config.id) {
|
|
80
|
+
console.error("Workspace not registered. Run `mug deploy` first.");
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const localPath = join(cwd, "files", filePath);
|
|
84
|
+
if (!existsSync(localPath)) {
|
|
85
|
+
console.error(`File not found: files/${filePath}`);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const content = readFileSync(localPath);
|
|
89
|
+
const token = getAccountToken();
|
|
90
|
+
const res = await fetch(`${API_URL}/workspace/${config.name}/files/upload`, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: {
|
|
93
|
+
Authorization: `Bearer ${token}`,
|
|
94
|
+
"Content-Type": "application/octet-stream",
|
|
95
|
+
"X-File-Path": filePath,
|
|
96
|
+
},
|
|
97
|
+
body: content,
|
|
98
|
+
signal: AbortSignal.timeout(30000),
|
|
99
|
+
});
|
|
100
|
+
if (!res.ok) {
|
|
101
|
+
console.error(`Push failed: ${res.status} ${await res.text()}`);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const sha256 = res.headers.get("X-File-SHA256") ?? "";
|
|
105
|
+
const manifest = readFilesManifest(cwd);
|
|
106
|
+
manifest.files[filePath] = {
|
|
107
|
+
size: content.byteLength,
|
|
108
|
+
sha256,
|
|
109
|
+
updated_at: new Date().toISOString(),
|
|
110
|
+
};
|
|
111
|
+
manifest.synced_at = new Date().toISOString();
|
|
112
|
+
writeFilesManifest(cwd, manifest);
|
|
113
|
+
console.log(` ${filePath} (${(content.byteLength / 1024).toFixed(1)} KB)`);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
export async function push(target, opts) {
|
|
117
|
+
const cwd = process.cwd();
|
|
118
|
+
if (opts.all) {
|
|
119
|
+
const config = loadConfig(cwd);
|
|
120
|
+
if (!config.id) {
|
|
121
|
+
console.error("Workspace not registered. Run `mug deploy` first.");
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
const dbDir = join(cwd, "databases");
|
|
125
|
+
const filesDir = join(cwd, "files");
|
|
126
|
+
const dbFiles = existsSync(dbDir)
|
|
127
|
+
? readdirSync(dbDir).filter((f) => f.endsWith(".db"))
|
|
128
|
+
: [];
|
|
129
|
+
const fileList = existsSync(filesDir)
|
|
130
|
+
? collectFiles(filesDir, "")
|
|
131
|
+
: [];
|
|
132
|
+
if (dbFiles.length === 0 && fileList.length === 0) {
|
|
133
|
+
console.log("Nothing to push. No databases or files found locally.");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
console.log(`Pushing ${dbFiles.length} database${dbFiles.length !== 1 ? "s" : ""} and ${fileList.length} file${fileList.length !== 1 ? "s" : ""}...`);
|
|
137
|
+
let pushed = 0;
|
|
138
|
+
for (const dbFile of dbFiles) {
|
|
139
|
+
const dbName = dbFile.replace(/\.db$/, "");
|
|
140
|
+
if (await pushDatabase(cwd, dbName))
|
|
141
|
+
pushed++;
|
|
142
|
+
}
|
|
143
|
+
for (const filePath of fileList) {
|
|
144
|
+
if (await pushFile(cwd, filePath))
|
|
145
|
+
pushed++;
|
|
146
|
+
}
|
|
147
|
+
console.log(`Pushed ${pushed} item${pushed !== 1 ? "s" : ""}.`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!target) {
|
|
151
|
+
console.error("Usage: mug push <databases/name> or mug push <files/path> or mug push --all");
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
if (target.startsWith("databases/")) {
|
|
155
|
+
const dbName = target.slice("databases/".length).replace(/\.db$/, "");
|
|
156
|
+
console.log("Pushing database...");
|
|
157
|
+
await pushDatabase(cwd, dbName);
|
|
158
|
+
}
|
|
159
|
+
else if (target.startsWith("files/")) {
|
|
160
|
+
const filePath = target.slice("files/".length);
|
|
161
|
+
console.log("Pushing file...");
|
|
162
|
+
await pushFile(cwd, filePath);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.error(`Unknown target: ${target}. Use databases/<name> or files/<path>.`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function collectFiles(dir, prefix) {
|
|
170
|
+
const results = [];
|
|
171
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
172
|
+
if (entry.name.startsWith("."))
|
|
173
|
+
continue;
|
|
174
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
175
|
+
if (entry.isDirectory()) {
|
|
176
|
+
results.push(...collectFiles(join(dir, entry.name), rel));
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
results.push(rel);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return results;
|
|
183
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getAccountToken } from "./login.js";
|
|
4
|
+
const DISPATCH_URL = "https://api.mug.work";
|
|
5
|
+
const DEV_PORTS = [8787, 8788, 8789, 8790, 8791];
|
|
6
|
+
function getWorkspaceName() {
|
|
7
|
+
const cwd = process.cwd();
|
|
8
|
+
const mugJsonPath = join(cwd, "mug.json");
|
|
9
|
+
if (!existsSync(mugJsonPath)) {
|
|
10
|
+
console.error("No mug.json found. Run `mug init` first.");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
return JSON.parse(readFileSync(mugJsonPath, "utf-8")).name;
|
|
14
|
+
}
|
|
15
|
+
async function tryRun(port, workflow) {
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(`http://localhost:${port}/run/${workflow}`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "Content-Type": "application/json" },
|
|
20
|
+
signal: AbortSignal.timeout(60_000),
|
|
21
|
+
});
|
|
22
|
+
if (res.status === 404)
|
|
23
|
+
return null;
|
|
24
|
+
return await res.json();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function formatResult(result) {
|
|
31
|
+
const lines = [];
|
|
32
|
+
const icon = result.status === "complete" ? "+" : "x";
|
|
33
|
+
lines.push(`[${icon}] ${result.workflow} — ${result.status} (${result.durationMs}ms)`);
|
|
34
|
+
lines.push("");
|
|
35
|
+
for (const step of result.steps) {
|
|
36
|
+
const dur = step.durationMs != null ? ` (${step.durationMs}ms)` : "";
|
|
37
|
+
const err = step.error ? ` — ERROR: ${step.error}` : "";
|
|
38
|
+
lines.push(` ${step.name}${dur}${err}`);
|
|
39
|
+
}
|
|
40
|
+
if (result.error) {
|
|
41
|
+
lines.push("");
|
|
42
|
+
lines.push(`Error: ${result.error}`);
|
|
43
|
+
}
|
|
44
|
+
if (result.result != null) {
|
|
45
|
+
lines.push("");
|
|
46
|
+
lines.push(JSON.stringify(result.result, null, 2));
|
|
47
|
+
}
|
|
48
|
+
return lines.join("\n");
|
|
49
|
+
}
|
|
50
|
+
export async function run(workflow, opts) {
|
|
51
|
+
if (opts.production) {
|
|
52
|
+
const workspace = getWorkspaceName();
|
|
53
|
+
const token = getAccountToken();
|
|
54
|
+
console.log(`Creating workflow instance: ${workflow} (production)...`);
|
|
55
|
+
const res = await fetch(`${DISPATCH_URL}/workspace/${workspace}/workflow/${workflow}`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: {
|
|
58
|
+
Authorization: `Bearer ${token}`,
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify({}),
|
|
62
|
+
});
|
|
63
|
+
const data = await res.json();
|
|
64
|
+
if (!res.ok || data.error) {
|
|
65
|
+
console.error(`Failed: ${data.error ?? res.statusText}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
console.log(`Instance created.`);
|
|
69
|
+
console.log(` id: ${data.instanceId}`);
|
|
70
|
+
console.log(`\nCheck status: mug status ${workflow} ${data.instanceId}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (opts.port) {
|
|
74
|
+
const result = await tryRun(parseInt(opts.port), workflow);
|
|
75
|
+
if (!result) {
|
|
76
|
+
console.error(`Workflow "${workflow}" not found or dev server not reachable on port ${opts.port}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
console.log(formatResult(result));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
for (const port of DEV_PORTS) {
|
|
83
|
+
const result = await tryRun(port, workflow);
|
|
84
|
+
if (result) {
|
|
85
|
+
console.log(formatResult(result));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
console.error(`No dev server found on ports ${DEV_PORTS.join(", ")}. Is \`mug dev\` running?`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
declare function readSecrets(cwd: string): Record<string, string>;
|
|
2
|
+
export declare function secretSet(keyValue: string, opts: {
|
|
3
|
+
production?: boolean;
|
|
4
|
+
}): Promise<void>;
|
|
5
|
+
export declare function secretList(): void;
|
|
6
|
+
export declare function secretRemove(key: string): void;
|
|
7
|
+
export { readSecrets };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const DISPATCH_URL = "https://api.mug.work";
|
|
4
|
+
function getSecretsPath(cwd) {
|
|
5
|
+
return join(cwd, ".mug", "secrets");
|
|
6
|
+
}
|
|
7
|
+
function readSecrets(cwd) {
|
|
8
|
+
const path = getSecretsPath(cwd);
|
|
9
|
+
if (!existsSync(path))
|
|
10
|
+
return {};
|
|
11
|
+
const content = readFileSync(path, "utf-8");
|
|
12
|
+
const secrets = {};
|
|
13
|
+
for (const line of content.split("\n")) {
|
|
14
|
+
const trimmed = line.trim();
|
|
15
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
16
|
+
continue;
|
|
17
|
+
const eq = trimmed.indexOf("=");
|
|
18
|
+
if (eq === -1)
|
|
19
|
+
continue;
|
|
20
|
+
secrets[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
|
|
21
|
+
}
|
|
22
|
+
return secrets;
|
|
23
|
+
}
|
|
24
|
+
function writeSecrets(cwd, secrets) {
|
|
25
|
+
const dir = join(cwd, ".mug");
|
|
26
|
+
if (!existsSync(dir))
|
|
27
|
+
mkdirSync(dir, { recursive: true });
|
|
28
|
+
const lines = ["# Workspace secrets — DO NOT commit this file"];
|
|
29
|
+
for (const [key, value] of Object.entries(secrets)) {
|
|
30
|
+
lines.push(`${key}=${value}`);
|
|
31
|
+
}
|
|
32
|
+
writeFileSync(getSecretsPath(cwd), lines.join("\n") + "\n");
|
|
33
|
+
}
|
|
34
|
+
import { getAccountToken } from "./login.js";
|
|
35
|
+
export async function secretSet(keyValue, opts) {
|
|
36
|
+
const cwd = process.cwd();
|
|
37
|
+
const eq = keyValue.indexOf("=");
|
|
38
|
+
if (eq === -1) {
|
|
39
|
+
console.error("Usage: mug secret set KEY=VALUE");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const key = keyValue.slice(0, eq);
|
|
43
|
+
const value = keyValue.slice(eq + 1);
|
|
44
|
+
if (opts.production) {
|
|
45
|
+
const token = getAccountToken();
|
|
46
|
+
const mugJsonPath = join(cwd, "mug.json");
|
|
47
|
+
if (!existsSync(mugJsonPath)) {
|
|
48
|
+
console.error("No mug.json found. Run `mug init` first.");
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
const workspace = JSON.parse(readFileSync(mugJsonPath, "utf-8")).name;
|
|
52
|
+
const res = await fetch(`${DISPATCH_URL}/secret`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
Authorization: `Bearer ${token}`,
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({ key, value, workspace }),
|
|
59
|
+
});
|
|
60
|
+
const data = (await res.json());
|
|
61
|
+
if (!res.ok || data.error) {
|
|
62
|
+
console.error(`Failed: ${data.error ?? res.statusText}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
console.log(`Set ${key} on production`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const secrets = readSecrets(cwd);
|
|
69
|
+
secrets[key] = value;
|
|
70
|
+
writeSecrets(cwd, secrets);
|
|
71
|
+
if (key.startsWith("ai.")) {
|
|
72
|
+
const provider = key.slice(3);
|
|
73
|
+
console.log(`Set ${key} in .mug/secrets`);
|
|
74
|
+
console.log(` BYOK enabled for ${provider} — use "${key}" as the billing value in mug.json or ctx.ai()`);
|
|
75
|
+
console.log(` Example: "ai": { "billing": { "powerful": "${key}" } }`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.log(`Set ${key} in .mug/secrets`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export function secretList() {
|
|
82
|
+
const cwd = process.cwd();
|
|
83
|
+
const secrets = readSecrets(cwd);
|
|
84
|
+
const keys = Object.keys(secrets);
|
|
85
|
+
if (keys.length === 0) {
|
|
86
|
+
console.log("No secrets configured. Run `mug secret set KEY=VALUE` to add one.");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
console.log("Local secrets (.mug/secrets):");
|
|
90
|
+
for (const key of keys) {
|
|
91
|
+
console.log(` ${key}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function secretRemove(key) {
|
|
95
|
+
const cwd = process.cwd();
|
|
96
|
+
const secrets = readSecrets(cwd);
|
|
97
|
+
if (!(key in secrets)) {
|
|
98
|
+
console.error(`Secret "${key}" not found.`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
delete secrets[key];
|
|
102
|
+
writeSecrets(cwd, secrets);
|
|
103
|
+
console.log(`Removed ${key} from .mug/secrets`);
|
|
104
|
+
}
|
|
105
|
+
export { readSecrets };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shutdown(): Promise<void>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readPidFile } from "./dev.js";
|
|
2
|
+
import { unlinkSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export async function shutdown() {
|
|
5
|
+
const cwd = process.cwd();
|
|
6
|
+
const pidData = readPidFile(cwd);
|
|
7
|
+
if (!pidData) {
|
|
8
|
+
console.log("No dev server running (no .mug/dev.pid found).");
|
|
9
|
+
process.exit(0);
|
|
10
|
+
}
|
|
11
|
+
const { pid, workspace, userPort } = pidData;
|
|
12
|
+
try {
|
|
13
|
+
process.kill(pid, 0);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
console.log(`Dev server for "${workspace}" (PID ${pid}) is not running. Cleaning up PID file.`);
|
|
17
|
+
try {
|
|
18
|
+
unlinkSync(join(cwd, ".mug", "dev.pid"));
|
|
19
|
+
}
|
|
20
|
+
catch { }
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
console.log(`Stopping "${workspace}" dev server (PID ${pid}, port ${userPort})...`);
|
|
24
|
+
process.kill(pid, "SIGTERM");
|
|
25
|
+
const deadline = Date.now() + 5000;
|
|
26
|
+
while (Date.now() < deadline) {
|
|
27
|
+
await new Promise(r => setTimeout(r, 200));
|
|
28
|
+
try {
|
|
29
|
+
process.kill(pid, 0);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
console.log("Dev server stopped.");
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
console.warn("Dev server did not exit in 5s — sending SIGKILL.");
|
|
37
|
+
try {
|
|
38
|
+
process.kill(pid, "SIGKILL");
|
|
39
|
+
}
|
|
40
|
+
catch { }
|
|
41
|
+
try {
|
|
42
|
+
unlinkSync(join(cwd, ".mug", "dev.pid"));
|
|
43
|
+
}
|
|
44
|
+
catch { }
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|