@overaihq/cli 0.1.0 → 0.2.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/README.md +25 -3
- package/dist/{types-5J3QQFGH.js → chunk-UMCJCOZX.js} +1 -691
- package/dist/index.js +550 -159
- package/dist/types-7F2H2ASS.js +713 -0
- package/dist/webPage-FN3A45OT.js +394 -0
- package/package.json +4 -9
package/dist/index.js
CHANGED
|
@@ -48,15 +48,7 @@ var appBaseUrl = trimSlash(
|
|
|
48
48
|
|
|
49
49
|
// src/api.ts
|
|
50
50
|
var apiBaseUrl = () => apiBaseUrlOverride ?? readConfig()?.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
51
|
-
var
|
|
52
|
-
const res = await fetch(
|
|
53
|
-
`${apiBaseUrl()}/v1/utils/trpc/unauthenticated/${procedure}`,
|
|
54
|
-
{
|
|
55
|
-
method: "POST",
|
|
56
|
-
headers: { "Content-Type": "application/json" },
|
|
57
|
-
body: JSON.stringify(input)
|
|
58
|
-
}
|
|
59
|
-
);
|
|
51
|
+
var trpcResult = async (res, procedure) => {
|
|
60
52
|
const body = await res.json().catch(() => null);
|
|
61
53
|
if (body != null && "error" in body) {
|
|
62
54
|
throw new Error(
|
|
@@ -68,6 +60,17 @@ var trpcMutation = async (procedure, input) => {
|
|
|
68
60
|
}
|
|
69
61
|
return body.result.data;
|
|
70
62
|
};
|
|
63
|
+
var trpcMutation = async (procedure, input) => {
|
|
64
|
+
const res = await fetch(
|
|
65
|
+
`${apiBaseUrl()}/v1/utils/trpc/unauthenticated/${procedure}`,
|
|
66
|
+
{
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: { "Content-Type": "application/json" },
|
|
69
|
+
body: JSON.stringify(input)
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
return trpcResult(res, procedure);
|
|
73
|
+
};
|
|
71
74
|
var exchangeCliAuthCode = (code, codeVerifier) => trpcMutation("exchangeCliAuthCode", { code, codeVerifier });
|
|
72
75
|
var exchangeSessionToken = (refreshToken, destinationOrgId) => trpcMutation("exchangeSessionToken", {
|
|
73
76
|
refreshToken,
|
|
@@ -136,6 +139,14 @@ var v2Fetch = async (path, init) => {
|
|
|
136
139
|
headers.set("Authorization", `Bearer ${auth.token}`);
|
|
137
140
|
return fetch(`${apiBaseUrl()}${path}`, { ...init, headers });
|
|
138
141
|
};
|
|
142
|
+
var userTrpcMutation = async (procedure, input) => {
|
|
143
|
+
const res = await v2Fetch(`/v1/utils/trpc/${procedure}`, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: { "Content-Type": "application/json" },
|
|
146
|
+
body: JSON.stringify(input)
|
|
147
|
+
});
|
|
148
|
+
return trpcResult(res, procedure);
|
|
149
|
+
};
|
|
139
150
|
|
|
140
151
|
// src/browser.ts
|
|
141
152
|
import { spawn } from "child_process";
|
|
@@ -284,6 +295,7 @@ var whoami = () => {
|
|
|
284
295
|
};
|
|
285
296
|
|
|
286
297
|
// src/args.ts
|
|
298
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
287
299
|
var flagValue = (args, ...names) => {
|
|
288
300
|
for (const name of names) {
|
|
289
301
|
const i = args.indexOf(name);
|
|
@@ -291,21 +303,44 @@ var flagValue = (args, ...names) => {
|
|
|
291
303
|
}
|
|
292
304
|
return void 0;
|
|
293
305
|
};
|
|
294
|
-
var
|
|
306
|
+
var stdinClaimedBy = null;
|
|
307
|
+
var readStdin = async (claimant = "stdin") => {
|
|
308
|
+
if (stdinClaimedBy != null) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`Only one value can be read from stdin; ${stdinClaimedBy} already consumed it.`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
stdinClaimedBy = claimant;
|
|
295
314
|
const chunks = [];
|
|
296
315
|
for await (const chunk of process.stdin) {
|
|
297
316
|
chunks.push(chunk);
|
|
298
317
|
}
|
|
299
318
|
return Buffer.concat(chunks).toString("utf8");
|
|
300
319
|
};
|
|
320
|
+
var readValue = async (value, label) => {
|
|
321
|
+
if (value?.startsWith("@") !== true) return value;
|
|
322
|
+
if (value.startsWith("@@")) return value.slice(1);
|
|
323
|
+
const path = value.slice(1);
|
|
324
|
+
if (path === "-") return (await readStdin(`${label} @-`)).trim();
|
|
325
|
+
try {
|
|
326
|
+
return readFileSync2(path, "utf8").trim();
|
|
327
|
+
} catch {
|
|
328
|
+
throw new Error(
|
|
329
|
+
`${label}: could not read file "${path}". Write "@@" to send a literal value starting with "@".`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
301
333
|
|
|
302
334
|
// src/gql.ts
|
|
303
|
-
var gqlRequest = async (query, variables) => {
|
|
304
|
-
const res = await v2Fetch(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
335
|
+
var gqlRequest = async (query, variables, options) => {
|
|
336
|
+
const res = await v2Fetch(
|
|
337
|
+
options?.v1 === true ? "/v1/graphql" : "/v2/graphql",
|
|
338
|
+
{
|
|
339
|
+
method: "POST",
|
|
340
|
+
headers: { "Content-Type": "application/json" },
|
|
341
|
+
body: JSON.stringify({ query, variables })
|
|
342
|
+
}
|
|
343
|
+
);
|
|
309
344
|
const body = await res.json().catch(() => null);
|
|
310
345
|
if (body?.errors != null && body.errors.length > 0) {
|
|
311
346
|
throw new Error(body.errors.map((e) => e.message).join("; "));
|
|
@@ -467,7 +502,7 @@ import { createServer as createServer2 } from "http";
|
|
|
467
502
|
import { dirname as dirname2, join as join4 } from "path";
|
|
468
503
|
|
|
469
504
|
// src/workspace/manifest.ts
|
|
470
|
-
import { existsSync, mkdirSync as mkdirSync2, readFileSync as
|
|
505
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
471
506
|
import { dirname, join as join2, resolve } from "path";
|
|
472
507
|
var MANIFEST_DIR = ".overai";
|
|
473
508
|
var MANIFEST_FILE = "manifest.json";
|
|
@@ -486,7 +521,7 @@ var findWorkspaceRoot = (from = process.cwd()) => {
|
|
|
486
521
|
}
|
|
487
522
|
};
|
|
488
523
|
var readManifest = (root) => JSON.parse(
|
|
489
|
-
|
|
524
|
+
readFileSync3(join2(root, MANIFEST_DIR, MANIFEST_FILE), "utf8")
|
|
490
525
|
);
|
|
491
526
|
var openClone = () => {
|
|
492
527
|
const root = findWorkspaceRoot();
|
|
@@ -511,14 +546,14 @@ var writeSynced = (root, relPath, content) => {
|
|
|
511
546
|
};
|
|
512
547
|
var readWorkingFile = (root, relPath) => {
|
|
513
548
|
try {
|
|
514
|
-
return
|
|
549
|
+
return readFileSync3(join2(root, relPath), "utf8");
|
|
515
550
|
} catch {
|
|
516
551
|
return null;
|
|
517
552
|
}
|
|
518
553
|
};
|
|
519
554
|
var readBaseFile = (root, relPath) => {
|
|
520
555
|
try {
|
|
521
|
-
return
|
|
556
|
+
return readFileSync3(basePath(root, relPath), "utf8");
|
|
522
557
|
} catch {
|
|
523
558
|
return null;
|
|
524
559
|
}
|
|
@@ -542,10 +577,14 @@ var localState = (root, resource) => {
|
|
|
542
577
|
};
|
|
543
578
|
|
|
544
579
|
// src/workspace/validateElements.ts
|
|
580
|
+
var findings = (parsed) => parsed.success ? [] : parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`);
|
|
545
581
|
var validateAutomationElements = async (elements) => {
|
|
546
|
-
const { automationNodesSchema } = await import("./types-
|
|
547
|
-
|
|
548
|
-
|
|
582
|
+
const { automationNodesSchema } = await import("./types-7F2H2ASS.js");
|
|
583
|
+
return findings(automationNodesSchema.safeParse(elements));
|
|
584
|
+
};
|
|
585
|
+
var validateWebPageConfig = async (config) => {
|
|
586
|
+
const { webPageConfigWriteSchema } = await import("./webPage-FN3A45OT.js");
|
|
587
|
+
return findings(webPageConfigWriteSchema.safeParse(config));
|
|
549
588
|
};
|
|
550
589
|
|
|
551
590
|
// src/workspace/adapters.ts
|
|
@@ -633,7 +672,7 @@ var functionAdapter = {
|
|
|
633
672
|
push: async (id, files, message) => {
|
|
634
673
|
const meta = JSON.parse(fileBySuffix(files, ".meta.json"));
|
|
635
674
|
const data = await gqlRequest(
|
|
636
|
-
`mutation CliCreateFunctionVersion($functionId: String!, $code: String!, $message: String, $inputSchema:
|
|
675
|
+
`mutation CliCreateFunctionVersion($functionId: String!, $code: String!, $message: String, $inputSchema: String, $executionTimeout: Int, $executionMemory: Int) {
|
|
637
676
|
createFunctionVersion(input: { functionVersion: {
|
|
638
677
|
functionId: $functionId, code: $code, message: $message,
|
|
639
678
|
inputSchema: $inputSchema, executionTimeout: $executionTimeout, executionMemory: $executionMemory
|
|
@@ -643,8 +682,9 @@ var functionAdapter = {
|
|
|
643
682
|
functionId: id,
|
|
644
683
|
code: fileBySuffix(files, ".ts"),
|
|
645
684
|
message,
|
|
646
|
-
|
|
647
|
-
//
|
|
685
|
+
// Match the webapp's save defaults so a missing meta doesn't zero out
|
|
686
|
+
// limits, and so an explicit null doesn't hit the NOT NULL column.
|
|
687
|
+
inputSchema: meta.inputSchema ?? "",
|
|
648
688
|
executionTimeout: meta.executionTimeout ?? 1e4,
|
|
649
689
|
executionMemory: meta.executionMemory ?? 512
|
|
650
690
|
}
|
|
@@ -757,6 +797,13 @@ var pageAdapter = (opts) => {
|
|
|
757
797
|
},
|
|
758
798
|
push: async (id, files, message) => {
|
|
759
799
|
const config = pageConfigFromFiles(files);
|
|
800
|
+
const findings2 = await opts.validateConfig?.(config) ?? [];
|
|
801
|
+
if (findings2.length > 0) {
|
|
802
|
+
throw new Error(
|
|
803
|
+
`Invalid page config:
|
|
804
|
+
${findings2.map((f) => ` ${f}`).join("\n")}`
|
|
805
|
+
);
|
|
806
|
+
}
|
|
760
807
|
const data = await gqlRequest(
|
|
761
808
|
`mutation CliCreatePageVersion($id: String!, $message: String, $config: JSON!) {
|
|
762
809
|
create${capitalized}Version(input: { ${gqlName}Version: { ${idArg}: $id, message: $message, config: $config } }) {
|
|
@@ -860,11 +907,11 @@ var automationAdapter = {
|
|
|
860
907
|
},
|
|
861
908
|
push: async (id, files, message) => {
|
|
862
909
|
const doc = JSON.parse(fileBySuffix(files, ".json"));
|
|
863
|
-
const
|
|
864
|
-
if (
|
|
910
|
+
const findings2 = await validateAutomationElements(doc.elements);
|
|
911
|
+
if (findings2.length > 0) {
|
|
865
912
|
throw new Error(
|
|
866
913
|
`Invalid automation elements:
|
|
867
|
-
${
|
|
914
|
+
${findings2.map((f) => ` ${f}`).join("\n")}`
|
|
868
915
|
);
|
|
869
916
|
}
|
|
870
917
|
const data = await gqlRequest(
|
|
@@ -908,7 +955,8 @@ ${findings.map((f) => ` ${f}`).join("\n")}`
|
|
|
908
955
|
var webPageAdapter = pageAdapter({
|
|
909
956
|
type: "webPage",
|
|
910
957
|
dir: "web-pages",
|
|
911
|
-
gqlName: "webPage"
|
|
958
|
+
gqlName: "webPage",
|
|
959
|
+
validateConfig: validateWebPageConfig
|
|
912
960
|
});
|
|
913
961
|
var printablePageAdapter = pageAdapter({
|
|
914
962
|
type: "printablePage",
|
|
@@ -1443,16 +1491,29 @@ var runFn = async (args) => {
|
|
|
1443
1491
|
const functionId = args[0];
|
|
1444
1492
|
if (functionId == null || functionId.startsWith("-")) {
|
|
1445
1493
|
throw new Error(
|
|
1446
|
-
"Usage: overai fn <functionId> [--input key=value ... | -]\n overai fn logs <functionId> [--limit N] [--execution <id>]"
|
|
1494
|
+
"Usage: overai fn <functionId> [--method GET] [--path /features/123] [--input key=value ... | -]\n overai fn logs <functionId> [--limit N] [--execution <id>]"
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
const method = (flagValue(args, "--method") ?? "POST").toUpperCase();
|
|
1498
|
+
const rawPath = flagValue(args, "--path") ?? "";
|
|
1499
|
+
const subPath = rawPath && !rawPath.startsWith("/") ? `/${rawPath}` : rawPath;
|
|
1500
|
+
const sendsBody = !["GET", "HEAD", "DELETE"].includes(method);
|
|
1501
|
+
if (!sendsBody && (args.includes("--input") || args.includes("-"))) {
|
|
1502
|
+
process.stderr.write(
|
|
1503
|
+
`warning: ${method} sends no request body, so --input is ignored. Pass values as a query string on --path instead, e.g. --path '/search?a=1'.
|
|
1504
|
+
`
|
|
1447
1505
|
);
|
|
1448
1506
|
}
|
|
1449
|
-
const input = await collectInput(args);
|
|
1507
|
+
const input = sendsBody ? await collectInput(args) : void 0;
|
|
1450
1508
|
const { organizationId } = await getAccessToken();
|
|
1451
|
-
const res = await v2Fetch(
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1509
|
+
const res = await v2Fetch(
|
|
1510
|
+
`/v2/functions/${organizationId}/${functionId}${subPath}`,
|
|
1511
|
+
{
|
|
1512
|
+
method,
|
|
1513
|
+
headers: sendsBody ? { "Content-Type": "application/json" } : {},
|
|
1514
|
+
body: sendsBody ? JSON.stringify(input) : void 0
|
|
1515
|
+
}
|
|
1516
|
+
);
|
|
1456
1517
|
const executionId = res.headers.get("x-execution-id");
|
|
1457
1518
|
if (executionId != null) {
|
|
1458
1519
|
process.stderr.write(`execution: ${executionId}
|
|
@@ -1476,21 +1537,32 @@ var runFn = async (args) => {
|
|
|
1476
1537
|
};
|
|
1477
1538
|
|
|
1478
1539
|
// src/commands/graphql.ts
|
|
1479
|
-
import { readFileSync as
|
|
1540
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1480
1541
|
var runGraphql = async (args) => {
|
|
1481
|
-
const inline = flagValue(args, "-q", "--query");
|
|
1542
|
+
const inline = await readValue(flagValue(args, "-q", "--query"), "-q");
|
|
1482
1543
|
const file = flagValue(args, "-f", "--file");
|
|
1483
|
-
const query = inline ?? (file != null ?
|
|
1544
|
+
const query = inline ?? (file != null ? readFileSync4(file, "utf8") : process.stdin.isTTY ? "" : (await readStdin("the query")).trim());
|
|
1484
1545
|
if (query.length === 0) {
|
|
1485
|
-
throw new Error(
|
|
1546
|
+
throw new Error(
|
|
1547
|
+
"Provide a query with -q '<query>', -q @<file>, -f <file>, or stdin."
|
|
1548
|
+
);
|
|
1486
1549
|
}
|
|
1487
|
-
const
|
|
1550
|
+
const variablesArg = flagValue(args, "--variables");
|
|
1551
|
+
const variablesJson = await readValue(variablesArg, "--variables");
|
|
1488
1552
|
const variables = (() => {
|
|
1489
1553
|
if (variablesJson == null) return void 0;
|
|
1490
1554
|
try {
|
|
1491
1555
|
return JSON.parse(variablesJson);
|
|
1492
|
-
} catch {
|
|
1493
|
-
throw new Error(
|
|
1556
|
+
} catch (error) {
|
|
1557
|
+
throw new Error(
|
|
1558
|
+
[
|
|
1559
|
+
`--variables is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
1560
|
+
`Got ${String(variablesJson.length)} chars starting: ${variablesJson.slice(0, 200)}`,
|
|
1561
|
+
...variablesArg?.startsWith("@") === true ? [] : [
|
|
1562
|
+
`Pass --variables @<file.json> instead \u2014 an inline value is re-read by the shell, which expands $names and drops quotes.`
|
|
1563
|
+
]
|
|
1564
|
+
].join("\n")
|
|
1565
|
+
);
|
|
1494
1566
|
}
|
|
1495
1567
|
})();
|
|
1496
1568
|
const res = await v2Fetch(
|
|
@@ -1636,6 +1708,154 @@ ${result.redirectUrl}
|
|
|
1636
1708
|
`);
|
|
1637
1709
|
};
|
|
1638
1710
|
|
|
1711
|
+
// src/commands/update.ts
|
|
1712
|
+
import { spawnSync } from "child_process";
|
|
1713
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
1714
|
+
import { fileURLToPath } from "url";
|
|
1715
|
+
var PACKAGE_NAME = "@overaihq/cli";
|
|
1716
|
+
var packageRootUrl = () => {
|
|
1717
|
+
for (const up of ["..", "../..", "../../.."]) {
|
|
1718
|
+
if (existsSync3(new URL(`${up}/package.json`, import.meta.url))) {
|
|
1719
|
+
return new URL(`${up}/`, import.meta.url);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
throw new Error("package.json not found relative to the CLI bundle");
|
|
1723
|
+
};
|
|
1724
|
+
var cliVersion = () => {
|
|
1725
|
+
const pkg = JSON.parse(
|
|
1726
|
+
readFileSync5(new URL("package.json", packageRootUrl()), "utf8")
|
|
1727
|
+
);
|
|
1728
|
+
return pkg.version;
|
|
1729
|
+
};
|
|
1730
|
+
var detectInstaller = () => {
|
|
1731
|
+
const path = fileURLToPath(import.meta.url);
|
|
1732
|
+
if (path.includes("_npx") || path.includes("dlx-")) return "ephemeral";
|
|
1733
|
+
if (path.includes(".pnpm") || /[\\/]pnpm[\\/]/.test(path)) return "pnpm";
|
|
1734
|
+
if (path.includes(".bun")) return "bun";
|
|
1735
|
+
return "npm";
|
|
1736
|
+
};
|
|
1737
|
+
var runUpdate = async () => {
|
|
1738
|
+
const current = cliVersion();
|
|
1739
|
+
if (existsSync3(new URL("src", packageRootUrl()))) {
|
|
1740
|
+
process.stdout.write(
|
|
1741
|
+
"Development build \u2014 rebuild from the repo instead of updating from npm.\n"
|
|
1742
|
+
);
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
const res = await fetch(
|
|
1746
|
+
`https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`,
|
|
1747
|
+
// Bound the lookup so a slow/unreachable registry can't hang the command.
|
|
1748
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
1749
|
+
);
|
|
1750
|
+
if (!res.ok) {
|
|
1751
|
+
throw new Error(`npm registry lookup failed (HTTP ${String(res.status)})`);
|
|
1752
|
+
}
|
|
1753
|
+
const body = await res.json();
|
|
1754
|
+
const latest = typeof body.version === "string" ? body.version : null;
|
|
1755
|
+
if (latest == null) {
|
|
1756
|
+
throw new Error("npm registry returned an unexpected response");
|
|
1757
|
+
}
|
|
1758
|
+
if (latest === current) {
|
|
1759
|
+
process.stdout.write(`overai ${current} is up to date.
|
|
1760
|
+
`);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
const cur = current.split(".").map(Number);
|
|
1764
|
+
const lat = latest.split(".").map(Number);
|
|
1765
|
+
const cmp = cur.map((n, i) => n - (lat[i] ?? 0)).find((d) => d !== 0) ?? 0;
|
|
1766
|
+
if (cmp > 0) {
|
|
1767
|
+
process.stdout.write(
|
|
1768
|
+
`overai ${current} is ahead of latest (${latest}) \u2014 likely a @next install; nothing to do.
|
|
1769
|
+
`
|
|
1770
|
+
);
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
const installer = detectInstaller();
|
|
1774
|
+
if (installer === "ephemeral") {
|
|
1775
|
+
process.stdout.write(
|
|
1776
|
+
`Running from an npx/dlx cache (current ${current}, latest ${latest}) \u2014 invoke as \`npx ${PACKAGE_NAME}@latest\` to always get the newest release.
|
|
1777
|
+
`
|
|
1778
|
+
);
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
const spec = `${PACKAGE_NAME}@${latest}`;
|
|
1782
|
+
const argsByInstaller = {
|
|
1783
|
+
npm: ["npm", "install", "-g", spec],
|
|
1784
|
+
pnpm: ["pnpm", "add", "-g", spec],
|
|
1785
|
+
bun: ["bun", "add", "-g", spec]
|
|
1786
|
+
};
|
|
1787
|
+
const [cmd, ...args] = argsByInstaller[installer];
|
|
1788
|
+
process.stdout.write(
|
|
1789
|
+
`Updating overai ${current} \u2192 ${latest} via ${installer}\u2026
|
|
1790
|
+
`
|
|
1791
|
+
);
|
|
1792
|
+
const result = spawnSync(cmd, args, {
|
|
1793
|
+
stdio: "inherit",
|
|
1794
|
+
// npm/pnpm are .cmd shims on Windows; a shell is required to resolve them.
|
|
1795
|
+
shell: process.platform === "win32"
|
|
1796
|
+
});
|
|
1797
|
+
if (result.status !== 0) {
|
|
1798
|
+
throw new Error(
|
|
1799
|
+
`${[cmd, ...args].join(" ")} failed (exit ${String(result.status ?? "?")})`
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
process.stdout.write(`Updated to ${latest}.
|
|
1803
|
+
`);
|
|
1804
|
+
};
|
|
1805
|
+
|
|
1806
|
+
// src/commands/report.ts
|
|
1807
|
+
var reportInput = async (args, stdin = readStdin) => {
|
|
1808
|
+
const [type, ...bodyArgs] = args;
|
|
1809
|
+
if (type !== "bug" && type !== "feature") {
|
|
1810
|
+
throw new Error(
|
|
1811
|
+
'Usage: overai report bug|feature "<details>" (or pass - to read stdin)'
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
const detailedBugReport = (bodyArgs.length === 1 && bodyArgs[0] === "-" ? await stdin() : bodyArgs.join(" ")).trim();
|
|
1815
|
+
if (detailedBugReport.length === 0) {
|
|
1816
|
+
throw new Error(
|
|
1817
|
+
`${type === "bug" ? "Bug report" : "Feature request"} details cannot be empty.`
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
if (detailedBugReport.length > 8e3) {
|
|
1821
|
+
throw new Error("Report details cannot exceed 8000 characters.");
|
|
1822
|
+
}
|
|
1823
|
+
return { feedbackType: type, detailedBugReport };
|
|
1824
|
+
};
|
|
1825
|
+
var runReport = async (args) => {
|
|
1826
|
+
const input = await reportInput(args);
|
|
1827
|
+
const manifest = (() => {
|
|
1828
|
+
try {
|
|
1829
|
+
return openClone().manifest;
|
|
1830
|
+
} catch {
|
|
1831
|
+
return null;
|
|
1832
|
+
}
|
|
1833
|
+
})();
|
|
1834
|
+
const resources = manifest ? Object.entries(manifest.resources) : [];
|
|
1835
|
+
await userTrpcMutation("architectIssues.report", {
|
|
1836
|
+
...input,
|
|
1837
|
+
source: "cli",
|
|
1838
|
+
cliContext: {
|
|
1839
|
+
cliVersion: cliVersion(),
|
|
1840
|
+
nodeVersion: process.version,
|
|
1841
|
+
platform: process.platform,
|
|
1842
|
+
architecture: process.arch,
|
|
1843
|
+
workspaceId: manifest?.workspaceId.slice(0, 200),
|
|
1844
|
+
workspaceName: manifest?.workspaceName.slice(0, 500),
|
|
1845
|
+
resources: manifest ? resources.slice(0, 500).map(([id, resource]) => ({
|
|
1846
|
+
id: id.slice(0, 200),
|
|
1847
|
+
name: resource.name.slice(0, 500),
|
|
1848
|
+
type: resource.type.slice(0, 100)
|
|
1849
|
+
})) : void 0,
|
|
1850
|
+
omittedResourceCount: Math.max(0, resources.length - 500)
|
|
1851
|
+
}
|
|
1852
|
+
});
|
|
1853
|
+
process.stdout.write(
|
|
1854
|
+
`${input.feedbackType === "bug" ? "Bug report" : "Feature request"} sent to OverAI.
|
|
1855
|
+
`
|
|
1856
|
+
);
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1639
1859
|
// src/docs/bestPractices.ts
|
|
1640
1860
|
var platform2 = `# OverAI platform primer
|
|
1641
1861
|
|
|
@@ -1651,11 +1871,23 @@ deploy\` makes it live. Pick primitives by trigger:
|
|
|
1651
1871
|
**Data Files** (env.DFxxx) for versioned single-file datasets. Prefer
|
|
1652
1872
|
first-class platform features over third-party integrations.
|
|
1653
1873
|
|
|
1874
|
+
When broken or unexplained platform behavior blocks progress, send a detailed
|
|
1875
|
+
report with \`overai report bug -\`. When a missing platform capability forces
|
|
1876
|
+
a significant workaround, use \`overai report feature -\`. Include the goal,
|
|
1877
|
+
actual limitation, desired behavior, and relevant errors or resource IDs. Do
|
|
1878
|
+
not report speculative niceties or every failed attempt; no separate user
|
|
1879
|
+
confirmation is needed because invoking the command explicitly submits it.
|
|
1880
|
+
|
|
1654
1881
|
The env SDK is the same in pages, functions, and automation code nodes:
|
|
1655
1882
|
env.TBSxxx.sql\`...\` (postgres.js tagged template \u2014 interpolations are
|
|
1656
|
-
auto-parameterized, never string-concatenate user input), env.FNxxx.call()
|
|
1883
|
+
auto-parameterized, never string-concatenate user input), env.FNxxx.call()
|
|
1884
|
+
(pass \`path\` to hit a sub-path of a function that routes internally),
|
|
1657
1885
|
env.ATxxx.start(inputs?) (fire-and-forget), env.DFxxx.read.json()/.saveVersion(),
|
|
1658
|
-
env.STORAGE
|
|
1886
|
+
env.STORAGE (incl. zipTree/zipExtract to list a zip without decompressing it and
|
|
1887
|
+
pull out only the entries you need; upload and zipExtract shorten stored names over
|
|
1888
|
+
128 characters in the middle while preserving their ends),
|
|
1889
|
+
env.AI.generateText/generateObject,
|
|
1890
|
+
env.GRAPHQL.request().`;
|
|
1659
1891
|
var automations = `# Automation best practices
|
|
1660
1892
|
|
|
1661
1893
|
An automation's elements document is the node graph: an array of nodes, each
|
|
@@ -1673,6 +1905,10 @@ Rules that keep graphs healthy:
|
|
|
1673
1905
|
{{S1.field}}). After renaming/removing a node, grep the document for its
|
|
1674
1906
|
variableId \u2014 dangling references fail at runtime, not at push.
|
|
1675
1907
|
- Bring IF_ELSE branches back together with an ALWAYS node.
|
|
1908
|
+
- A loop (LOOP_ARRAY/LOOP_RANGE) always resolves to its success handle once all
|
|
1909
|
+
iterations finish, even if some or all iterations error \u2014 it never takes a
|
|
1910
|
+
failure path. Handle errors on the child nodes inside the loop, or inspect
|
|
1911
|
+
\`iterations\`, to catch per-iteration failures.
|
|
1676
1912
|
- Activation requires a deployed version with an EVENT trigger node \u2014
|
|
1677
1913
|
activating without one silently no-ops.
|
|
1678
1914
|
|
|
@@ -1709,6 +1945,31 @@ language, customTitle, hideTopBar, ...). Push reassembles them.
|
|
|
1709
1945
|
- For guest-facing workspaces, add page-to-page navigation (guests land on the
|
|
1710
1946
|
workspace homepage and can only follow links). "Hide Top Bar" gives an
|
|
1711
1947
|
app-like experience.
|
|
1948
|
+
- Public pages (/public/:id) have no viewer identity \u2014 useCurrentUser() is
|
|
1949
|
+
null. Identity-scoped features degrade: useAgentChat streams but persists
|
|
1950
|
+
nothing (conversationId stays null, useAgentConversations is empty, the
|
|
1951
|
+
conversation helpers throw), and author-requiring writes like
|
|
1952
|
+
env.DFxxx.saveVersion 401. Reads use the workspace's own access, so treat
|
|
1953
|
+
anything the page can query as public.
|
|
1954
|
+
- Write meta.json flags (noSandbox, hideTopBar) as JSON booleans. A page cloned
|
|
1955
|
+
before the shape settled may hold the strings "true"/"false" \u2014 those still
|
|
1956
|
+
push, anything else is rejected. noSandbox drops the iframe sandbox, which
|
|
1957
|
+
localStorage/sessionStorage, cookies, form submission and
|
|
1958
|
+
alert/confirm/prompt all require.
|
|
1959
|
+
- dataSourceUrl and filter in meta.json are legacy: they populate the \`data\` and
|
|
1960
|
+
\`items\` globals, frozen at render start, and filter reads the old
|
|
1961
|
+
item/attribute model. For new pages load data in the page instead \u2014 web pages
|
|
1962
|
+
via env.TBSxxx.sql / env.DFxxx, printable pages via fetch with the \`token\`
|
|
1963
|
+
global (no env proxy there). Don't rewrite a working page just to migrate it.
|
|
1964
|
+
- dataSourceUrl in meta.json is fetched verbatim and its response becomes the
|
|
1965
|
+
\`data\` global. It is a static URL, not a template \u2014 search params are NOT
|
|
1966
|
+
substituted into it (neither {x} nor Liquid {{ x }}). If the data depends on
|
|
1967
|
+
a search param, fetch it in the page instead. To clear it use JSON null; the
|
|
1968
|
+
string "null" is a four-character URL that fails at fetch time.
|
|
1969
|
+
- Printable page PDFs render in a headless browser that waits for network idle,
|
|
1970
|
+
so useEffect fetches DO resolve before capture. Load data in the page \u2014 you
|
|
1971
|
+
don't need to pre-fetch it into searchParams or stage it in storage. A blank
|
|
1972
|
+
PDF is far more often an undeployed version than a timing problem.
|
|
1712
1973
|
- Console logs are visible in the webapp editor; use them to verify behavior
|
|
1713
1974
|
after deploying.`;
|
|
1714
1975
|
var functions = `# Function best practices
|
|
@@ -1718,10 +1979,14 @@ esm.sh imports allowed. The CLI materializes <slug>.<id>.ts (code) and
|
|
|
1718
1979
|
<slug>.<id>.meta.json (inputSchema, executionTimeout ms, executionMemory MB).
|
|
1719
1980
|
|
|
1720
1981
|
- Functions are full HTTP apps: method + sub-path are forwarded to your code,
|
|
1721
|
-
and each execution records requestMethod/requestPath.
|
|
1982
|
+
and each execution records requestMethod/requestPath. Callers reach a route
|
|
1983
|
+
with env.FNxxx.call({ method, path: "/features/123" }) \u2014 path defaults to the
|
|
1984
|
+
base path and method to POST.
|
|
1722
1985
|
- Keep the inputSchema in sync with what the code actually reads \u2014 callers
|
|
1723
|
-
(agents, pages, automations) rely on it.
|
|
1724
|
-
|
|
1986
|
+
(agents, pages, automations) rely on it. It is a JSON-encoded string, so it
|
|
1987
|
+
appears escaped inside meta.json.
|
|
1988
|
+
- Verify with \`overai fn <id> --input k=v\` (invokes the DEPLOYED version;
|
|
1989
|
+
add \`--method\`/\`--path\` to exercise one route of a router) and
|
|
1725
1990
|
\`overai fn logs <id>\` / \`--execution <FEX id>\` for status, timing, logs,
|
|
1726
1991
|
and input/output bodies.
|
|
1727
1992
|
- Version discipline: function_execution.versionNumber tells you exactly which
|
|
@@ -1729,7 +1994,10 @@ esm.sh imports allowed. The CLI materializes <slug>.<id>.ts (code) and
|
|
|
1729
1994
|
failure.`;
|
|
1730
1995
|
var workflow = `# CLI editing workflow
|
|
1731
1996
|
|
|
1732
|
-
1. \`overai
|
|
1997
|
+
1. \`overai workspace list\` to find the id, then \`overai clone <workspaceId>\`
|
|
1998
|
+
(or \`overai pull\` in an existing clone). Workspaces nest, and clone pulls
|
|
1999
|
+
ONE workspace \u2014 a parent's clone does not include its children, so clone
|
|
2000
|
+
each nested workspace you need separately.
|
|
1733
2001
|
2. Edit files. The id in each filename is authoritative; slugs are cosmetic.
|
|
1734
2002
|
3. \`overai status\` / \`overai diff\` \u2014 review what changed.
|
|
1735
2003
|
4. \`overai review\` \u2014 validation + these guides; fix findings first.
|
|
@@ -1772,7 +2040,7 @@ var runBestPractices = (args) => {
|
|
|
1772
2040
|
var runReview = async () => {
|
|
1773
2041
|
const root = findWorkspaceRoot();
|
|
1774
2042
|
const manifest = readManifest(root);
|
|
1775
|
-
const
|
|
2043
|
+
const findings2 = [];
|
|
1776
2044
|
const touchedTypes = /* @__PURE__ */ new Set();
|
|
1777
2045
|
const changed = [];
|
|
1778
2046
|
for (const [id, resource] of Object.entries(manifest.resources)) {
|
|
@@ -1789,14 +2057,14 @@ var runReview = async () => {
|
|
|
1789
2057
|
try {
|
|
1790
2058
|
JSON.parse(content);
|
|
1791
2059
|
} catch (error) {
|
|
1792
|
-
|
|
2060
|
+
findings2.push(
|
|
1793
2061
|
`${relPath}: invalid JSON \u2014 ${error instanceof Error ? error.message : String(error)}`
|
|
1794
2062
|
);
|
|
1795
2063
|
continue;
|
|
1796
2064
|
}
|
|
1797
2065
|
}
|
|
1798
2066
|
if (!relPath.endsWith(".meta.json") && content.trim().length === 0) {
|
|
1799
|
-
|
|
2067
|
+
findings2.push(
|
|
1800
2068
|
`${relPath}: file is empty \u2014 push would blank the resource.`
|
|
1801
2069
|
);
|
|
1802
2070
|
}
|
|
@@ -1805,17 +2073,17 @@ var runReview = async () => {
|
|
|
1805
2073
|
for (const match of content.matchAll(importRe)) {
|
|
1806
2074
|
const specifier = match[1];
|
|
1807
2075
|
if (specifier == null || specifier.startsWith("https://")) continue;
|
|
1808
|
-
|
|
2076
|
+
findings2.push(
|
|
1809
2077
|
/^react(-dom)?(\/|$)/.test(specifier) ? `${relPath}: imports "${specifier}" \u2014 React and its hooks are already in scope; remove the import or the page renders blank.` : `${relPath}: imports "${specifier}" \u2014 the page sandbox can only import https URLs (use https://esm.sh/...).`
|
|
1810
2078
|
);
|
|
1811
2079
|
}
|
|
1812
2080
|
if (/^\s*export\s+default\b/m.test(content)) {
|
|
1813
|
-
|
|
2081
|
+
findings2.push(
|
|
1814
2082
|
`${relPath}: uses \`export default\` \u2014 declare a plain \`function App() {}\`; the runtime appends the render call.`
|
|
1815
2083
|
);
|
|
1816
2084
|
}
|
|
1817
2085
|
if (!/\b(function|const|let)\s+App\b/.test(content)) {
|
|
1818
|
-
|
|
2086
|
+
findings2.push(
|
|
1819
2087
|
`${relPath}: no App component found \u2014 page code must declare \`function App() {}\`.`
|
|
1820
2088
|
);
|
|
1821
2089
|
}
|
|
@@ -1827,7 +2095,7 @@ var runReview = async () => {
|
|
|
1827
2095
|
if (docFile != null && raw != null) {
|
|
1828
2096
|
try {
|
|
1829
2097
|
const doc = JSON.parse(raw);
|
|
1830
|
-
|
|
2098
|
+
findings2.push(
|
|
1831
2099
|
...(await validateAutomationElements(doc.elements)).map(
|
|
1832
2100
|
(f) => `${docFile} elements.${f}`
|
|
1833
2101
|
)
|
|
@@ -1849,7 +2117,7 @@ var runReview = async () => {
|
|
|
1849
2117
|
...changed.map((c) => `- ${c}`),
|
|
1850
2118
|
"",
|
|
1851
2119
|
"## Validation findings",
|
|
1852
|
-
|
|
2120
|
+
findings2.length > 0 ? findings2.map((f) => `- ${f}`).join("\n") : "- none (mechanical checks passed \u2014 still review the diff against the guides below)",
|
|
1853
2121
|
"",
|
|
1854
2122
|
"## Review guides",
|
|
1855
2123
|
"Review `overai diff` output against these before pushing:",
|
|
@@ -1860,6 +2128,155 @@ var runReview = async () => {
|
|
|
1860
2128
|
);
|
|
1861
2129
|
};
|
|
1862
2130
|
|
|
2131
|
+
// src/commands/runner.ts
|
|
2132
|
+
var runCreate = async (workspaceId, args) => {
|
|
2133
|
+
const name = flagValue(args, "--name") ?? "Action Runner";
|
|
2134
|
+
const identityResult = await gqlRequest(
|
|
2135
|
+
`mutation CliCreateMachineIdentity($name: String!) {
|
|
2136
|
+
identityInsert(object: { name: $name, type: "machine" }) { id }
|
|
2137
|
+
}`,
|
|
2138
|
+
{ name },
|
|
2139
|
+
{ v1: true }
|
|
2140
|
+
);
|
|
2141
|
+
const identityId = identityResult.identityInsert?.id;
|
|
2142
|
+
if (identityId == null) throw new Error("Failed to create machine identity");
|
|
2143
|
+
await gqlRequest(
|
|
2144
|
+
`mutation CliCreateIdentityOrganization($identityId: String!) {
|
|
2145
|
+
createIdentityOrganization(
|
|
2146
|
+
input: { identityOrganization: { identityId: $identityId } }
|
|
2147
|
+
) { identityOrganization { identityId } }
|
|
2148
|
+
}`,
|
|
2149
|
+
{ identityId }
|
|
2150
|
+
);
|
|
2151
|
+
const runnerResult = await gqlRequest(
|
|
2152
|
+
`mutation CliCreateActionRunner($identityId: String!, $workspaceId: String!) {
|
|
2153
|
+
createActionRunner(
|
|
2154
|
+
input: { actionRunner: { identityId: $identityId, workspaceId: $workspaceId } }
|
|
2155
|
+
) { actionRunner { id } }
|
|
2156
|
+
}`,
|
|
2157
|
+
{ identityId, workspaceId }
|
|
2158
|
+
);
|
|
2159
|
+
const actionRunnerId = runnerResult.createActionRunner?.actionRunner?.id;
|
|
2160
|
+
if (actionRunnerId == null) throw new Error("Failed to create action runner");
|
|
2161
|
+
printResult(
|
|
2162
|
+
{ actionRunnerId, identityId, name, workspaceId },
|
|
2163
|
+
resolveFormat(flagValue(args, "-o", "--output"))
|
|
2164
|
+
);
|
|
2165
|
+
};
|
|
2166
|
+
var runToken = async (runnerId, args) => {
|
|
2167
|
+
const note = flagValue(args, "--note") ?? "cli";
|
|
2168
|
+
const result = await gqlRequest(
|
|
2169
|
+
`mutation CliCreateActionRunnerToken($actionRunnerId: String!, $note: String) {
|
|
2170
|
+
createActionRunnerToken(
|
|
2171
|
+
input: { actionRunnerId: $actionRunnerId, note: $note }
|
|
2172
|
+
) { result }
|
|
2173
|
+
}`,
|
|
2174
|
+
{ actionRunnerId: runnerId, note }
|
|
2175
|
+
);
|
|
2176
|
+
const secret = result.createActionRunnerToken?.result;
|
|
2177
|
+
if (secret == null) throw new Error("Failed to mint runner token");
|
|
2178
|
+
printResult(
|
|
2179
|
+
{ runnerId, secret },
|
|
2180
|
+
resolveFormat(flagValue(args, "-o", "--output"))
|
|
2181
|
+
);
|
|
2182
|
+
};
|
|
2183
|
+
var resolveWorkspaceId = async (runnerId) => {
|
|
2184
|
+
const data = await gqlRequest(
|
|
2185
|
+
`query CliResolveRunner($id: String!) {
|
|
2186
|
+
actionRunners(condition: { id: $id }, first: 1) { nodes { workspaceId } }
|
|
2187
|
+
}`,
|
|
2188
|
+
{ id: runnerId }
|
|
2189
|
+
);
|
|
2190
|
+
const workspaceId = data.actionRunners.nodes[0]?.workspaceId;
|
|
2191
|
+
if (workspaceId == null) {
|
|
2192
|
+
throw new Error(
|
|
2193
|
+
`Unknown runner ${runnerId} (or it has no workspace). Try: overai runner list`
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
return workspaceId;
|
|
2197
|
+
};
|
|
2198
|
+
var enqueueAction = async (runnerId, actionId, inputs, args) => {
|
|
2199
|
+
const { organizationId } = await getAccessToken();
|
|
2200
|
+
const workspaceId = await resolveWorkspaceId(runnerId);
|
|
2201
|
+
const res = await v2Fetch(
|
|
2202
|
+
`/v2/actions/${organizationId}/${workspaceId}/run`,
|
|
2203
|
+
{
|
|
2204
|
+
method: "POST",
|
|
2205
|
+
headers: { "Content-Type": "application/json" },
|
|
2206
|
+
body: JSON.stringify({ actionId, inputs, actionRunnerId: runnerId })
|
|
2207
|
+
}
|
|
2208
|
+
);
|
|
2209
|
+
const body = await res.json().catch(() => null);
|
|
2210
|
+
if (!res.ok) {
|
|
2211
|
+
throw new Error(body?.error ?? `run failed (${String(res.status)})`);
|
|
2212
|
+
}
|
|
2213
|
+
printResult(body, resolveFormat(flagValue(args, "-o", "--output")));
|
|
2214
|
+
};
|
|
2215
|
+
var runRun = async (runnerId, args) => {
|
|
2216
|
+
const code = await readValue(flagValue(args, "--code"), "--code") ?? (process.stdin.isTTY ? "" : (await readStdin("the code")).trim());
|
|
2217
|
+
if (code.length === 0) {
|
|
2218
|
+
throw new Error(
|
|
2219
|
+
"Provide a command with --code '<code>', --code @<file>, or piped stdin."
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
const interpreter = flagValue(args, "--interpreter") ?? "shell";
|
|
2223
|
+
if (interpreter !== "shell" && interpreter !== "powershell") {
|
|
2224
|
+
throw new Error("--interpreter must be 'shell' or 'powershell'.");
|
|
2225
|
+
}
|
|
2226
|
+
await enqueueAction(
|
|
2227
|
+
runnerId,
|
|
2228
|
+
"RUNNER_RUN_COMMAND",
|
|
2229
|
+
{ actionRunnerId: runnerId, interpreter, code },
|
|
2230
|
+
args
|
|
2231
|
+
);
|
|
2232
|
+
};
|
|
2233
|
+
var runUpload = async (runnerId, args) => {
|
|
2234
|
+
const path = flagValue(args, "--path");
|
|
2235
|
+
if (path == null || path.length === 0) {
|
|
2236
|
+
throw new Error("Provide a file with --path '<path>'.");
|
|
2237
|
+
}
|
|
2238
|
+
await enqueueAction(
|
|
2239
|
+
runnerId,
|
|
2240
|
+
"RUNNER_UPLOAD_FILE",
|
|
2241
|
+
{ actionRunnerId: runnerId, filePath: path },
|
|
2242
|
+
args
|
|
2243
|
+
);
|
|
2244
|
+
};
|
|
2245
|
+
var runList = async (args) => {
|
|
2246
|
+
const workspaceId = args.find((a) => !a.startsWith("-"));
|
|
2247
|
+
const limit = Number(flagValue(args, "--limit") ?? 50);
|
|
2248
|
+
const data = await gqlRequest(
|
|
2249
|
+
`query CliRunnerList($workspaceId: String, $first: Int!) {
|
|
2250
|
+
actionRunners(condition: { workspaceId: $workspaceId }, first: $first) {
|
|
2251
|
+
nodes { id identity { name } workspaceId isCloud isArchived createdAt }
|
|
2252
|
+
}
|
|
2253
|
+
}`,
|
|
2254
|
+
{ workspaceId: workspaceId ?? null, first: limit }
|
|
2255
|
+
);
|
|
2256
|
+
printResult(
|
|
2257
|
+
data.actionRunners.nodes.map((r) => ({
|
|
2258
|
+
id: r.id,
|
|
2259
|
+
name: r.identity?.name ?? null,
|
|
2260
|
+
workspaceId: r.workspaceId,
|
|
2261
|
+
kind: r.isCloud ? "cloud" : "self-hosted",
|
|
2262
|
+
archived: r.isArchived,
|
|
2263
|
+
createdAt: r.createdAt
|
|
2264
|
+
})),
|
|
2265
|
+
resolveFormat(flagValue(args, "-o", "--output"))
|
|
2266
|
+
);
|
|
2267
|
+
};
|
|
2268
|
+
var USAGE = "Usage: overai runner create <workspaceId> [--name <name>]\n overai runner token <runnerId> [--note <note>]\n overai runner run <runnerId> [--interpreter shell|powershell] --code '<code>'\n overai runner upload <runnerId> --path '<path>'\n overai runner list [workspaceId] [--limit <n>]\n";
|
|
2269
|
+
var runRunner = async (args) => {
|
|
2270
|
+
const [sub, id, ...rest] = args;
|
|
2271
|
+
if (sub === "create" && id != null) return runCreate(id, rest);
|
|
2272
|
+
if (sub === "token" && id != null) return runToken(id, rest);
|
|
2273
|
+
if (sub === "run" && id != null) return runRun(id, rest);
|
|
2274
|
+
if (sub === "upload" && id != null) return runUpload(id, rest);
|
|
2275
|
+
if (sub === "list") return runList([id, ...rest].filter((a) => a != null));
|
|
2276
|
+
process.stderr.write(USAGE);
|
|
2277
|
+
process.exitCode = 1;
|
|
2278
|
+
};
|
|
2279
|
+
|
|
1863
2280
|
// src/commands/sql.ts
|
|
1864
2281
|
import { createInterface } from "readline";
|
|
1865
2282
|
var runQuery = async (organizationId, tableSpaceId, query, params) => {
|
|
@@ -2032,7 +2449,11 @@ var runSql = async (args) => {
|
|
|
2032
2449
|
return void 0;
|
|
2033
2450
|
})();
|
|
2034
2451
|
if (query == null) return runRepl(organizationId, tableSpaceId, format);
|
|
2035
|
-
const result = await runQuery(
|
|
2452
|
+
const result = await runQuery(
|
|
2453
|
+
organizationId,
|
|
2454
|
+
tableSpaceId,
|
|
2455
|
+
await readValue(query, "the query") ?? query
|
|
2456
|
+
);
|
|
2036
2457
|
printResult(result.rows, format);
|
|
2037
2458
|
if (process.stderr.isTTY) {
|
|
2038
2459
|
process.stderr.write(`${summarize(result)}
|
|
@@ -2040,101 +2461,6 @@ var runSql = async (args) => {
|
|
|
2040
2461
|
}
|
|
2041
2462
|
};
|
|
2042
2463
|
|
|
2043
|
-
// src/commands/update.ts
|
|
2044
|
-
import { spawnSync } from "child_process";
|
|
2045
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
2046
|
-
import { fileURLToPath } from "url";
|
|
2047
|
-
var PACKAGE_NAME = "@overaihq/cli";
|
|
2048
|
-
var packageRootUrl = () => {
|
|
2049
|
-
for (const up of ["..", "../..", "../../.."]) {
|
|
2050
|
-
if (existsSync3(new URL(`${up}/package.json`, import.meta.url))) {
|
|
2051
|
-
return new URL(`${up}/`, import.meta.url);
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
throw new Error("package.json not found relative to the CLI bundle");
|
|
2055
|
-
};
|
|
2056
|
-
var cliVersion = () => {
|
|
2057
|
-
const pkg = JSON.parse(
|
|
2058
|
-
readFileSync4(new URL("package.json", packageRootUrl()), "utf8")
|
|
2059
|
-
);
|
|
2060
|
-
return pkg.version;
|
|
2061
|
-
};
|
|
2062
|
-
var detectInstaller = () => {
|
|
2063
|
-
const path = fileURLToPath(import.meta.url);
|
|
2064
|
-
if (path.includes("_npx") || path.includes("dlx-")) return "ephemeral";
|
|
2065
|
-
if (path.includes(".pnpm") || /[\\/]pnpm[\\/]/.test(path)) return "pnpm";
|
|
2066
|
-
if (path.includes(".bun")) return "bun";
|
|
2067
|
-
return "npm";
|
|
2068
|
-
};
|
|
2069
|
-
var runUpdate = async () => {
|
|
2070
|
-
const current = cliVersion();
|
|
2071
|
-
if (existsSync3(new URL("src", packageRootUrl()))) {
|
|
2072
|
-
process.stdout.write(
|
|
2073
|
-
"Development build \u2014 rebuild from the repo instead of updating from npm.\n"
|
|
2074
|
-
);
|
|
2075
|
-
return;
|
|
2076
|
-
}
|
|
2077
|
-
const res = await fetch(
|
|
2078
|
-
`https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`,
|
|
2079
|
-
// Bound the lookup so a slow/unreachable registry can't hang the command.
|
|
2080
|
-
{ signal: AbortSignal.timeout(1e4) }
|
|
2081
|
-
);
|
|
2082
|
-
if (!res.ok) {
|
|
2083
|
-
throw new Error(`npm registry lookup failed (HTTP ${String(res.status)})`);
|
|
2084
|
-
}
|
|
2085
|
-
const body = await res.json();
|
|
2086
|
-
const latest = typeof body.version === "string" ? body.version : null;
|
|
2087
|
-
if (latest == null) {
|
|
2088
|
-
throw new Error("npm registry returned an unexpected response");
|
|
2089
|
-
}
|
|
2090
|
-
if (latest === current) {
|
|
2091
|
-
process.stdout.write(`overai ${current} is up to date.
|
|
2092
|
-
`);
|
|
2093
|
-
return;
|
|
2094
|
-
}
|
|
2095
|
-
const cur = current.split(".").map(Number);
|
|
2096
|
-
const lat = latest.split(".").map(Number);
|
|
2097
|
-
const cmp = cur.map((n, i) => n - (lat[i] ?? 0)).find((d) => d !== 0) ?? 0;
|
|
2098
|
-
if (cmp > 0) {
|
|
2099
|
-
process.stdout.write(
|
|
2100
|
-
`overai ${current} is ahead of latest (${latest}) \u2014 likely a @next install; nothing to do.
|
|
2101
|
-
`
|
|
2102
|
-
);
|
|
2103
|
-
return;
|
|
2104
|
-
}
|
|
2105
|
-
const installer = detectInstaller();
|
|
2106
|
-
if (installer === "ephemeral") {
|
|
2107
|
-
process.stdout.write(
|
|
2108
|
-
`Running from an npx/dlx cache (current ${current}, latest ${latest}) \u2014 invoke as \`npx ${PACKAGE_NAME}@latest\` to always get the newest release.
|
|
2109
|
-
`
|
|
2110
|
-
);
|
|
2111
|
-
return;
|
|
2112
|
-
}
|
|
2113
|
-
const spec = `${PACKAGE_NAME}@${latest}`;
|
|
2114
|
-
const argsByInstaller = {
|
|
2115
|
-
npm: ["npm", "install", "-g", spec],
|
|
2116
|
-
pnpm: ["pnpm", "add", "-g", spec],
|
|
2117
|
-
bun: ["bun", "add", "-g", spec]
|
|
2118
|
-
};
|
|
2119
|
-
const [cmd, ...args] = argsByInstaller[installer];
|
|
2120
|
-
process.stdout.write(
|
|
2121
|
-
`Updating overai ${current} \u2192 ${latest} via ${installer}\u2026
|
|
2122
|
-
`
|
|
2123
|
-
);
|
|
2124
|
-
const result = spawnSync(cmd, args, {
|
|
2125
|
-
stdio: "inherit",
|
|
2126
|
-
// npm/pnpm are .cmd shims on Windows; a shell is required to resolve them.
|
|
2127
|
-
shell: process.platform === "win32"
|
|
2128
|
-
});
|
|
2129
|
-
if (result.status !== 0) {
|
|
2130
|
-
throw new Error(
|
|
2131
|
-
`${[cmd, ...args].join(" ")} failed (exit ${String(result.status ?? "?")})`
|
|
2132
|
-
);
|
|
2133
|
-
}
|
|
2134
|
-
process.stdout.write(`Updated to ${latest}.
|
|
2135
|
-
`);
|
|
2136
|
-
};
|
|
2137
|
-
|
|
2138
2464
|
// src/commands/versions.ts
|
|
2139
2465
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2140
2466
|
import { mkdirSync as mkdirSync4, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -2270,19 +2596,59 @@ var runDiff = async (args) => {
|
|
|
2270
2596
|
}
|
|
2271
2597
|
};
|
|
2272
2598
|
|
|
2599
|
+
// src/commands/workspace.ts
|
|
2600
|
+
var listWorkspaces = async (args) => {
|
|
2601
|
+
const archived = args.includes("--archived");
|
|
2602
|
+
const limit = Number(flagValue(args, "--limit") ?? 200);
|
|
2603
|
+
const data = await gqlRequest(
|
|
2604
|
+
`query CliWorkspaceList($first: Int!, $includeArchived: IncludeArchivedOption!) {
|
|
2605
|
+
workspaces(first: $first, includeArchived: $includeArchived) {
|
|
2606
|
+
nodes { id name isArchived workspaceWorkspace { parentWorkspaceId } }
|
|
2607
|
+
}
|
|
2608
|
+
myWorkspaceRoles { nodes { workspaceId role } }
|
|
2609
|
+
}`,
|
|
2610
|
+
{ first: limit, includeArchived: archived ? "YES" : "NO" }
|
|
2611
|
+
);
|
|
2612
|
+
const roles = new Map(
|
|
2613
|
+
data.myWorkspaceRoles.nodes.map((r) => [r.workspaceId, r.role])
|
|
2614
|
+
);
|
|
2615
|
+
printResult(
|
|
2616
|
+
data.workspaces.nodes.map((w) => ({
|
|
2617
|
+
id: w.id,
|
|
2618
|
+
name: w.name,
|
|
2619
|
+
role: roles.get(w.id) ?? "",
|
|
2620
|
+
...archived ? { archived: w.isArchived } : {},
|
|
2621
|
+
parentId: w.workspaceWorkspace?.parentWorkspaceId ?? ""
|
|
2622
|
+
})).sort((a, b) => a.name.localeCompare(b.name)),
|
|
2623
|
+
resolveFormat(flagValue(args, "-o", "--output"))
|
|
2624
|
+
);
|
|
2625
|
+
};
|
|
2626
|
+
var USAGE2 = "Usage: overai workspace list [--archived] [--limit <n>]\n";
|
|
2627
|
+
var runWorkspace = async (args) => {
|
|
2628
|
+
if (args[0] !== "list") {
|
|
2629
|
+
process.stderr.write(USAGE2);
|
|
2630
|
+
process.exitCode = 1;
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
await listWorkspaces(args.slice(1));
|
|
2634
|
+
};
|
|
2635
|
+
|
|
2273
2636
|
// src/index.ts
|
|
2274
2637
|
var HELP = `overai \u2014 OverAI command-line interface
|
|
2275
2638
|
|
|
2276
2639
|
Usage:
|
|
2277
|
-
overai signin Sign in via your browser
|
|
2640
|
+
overai signin Sign in via your browser; choose your
|
|
2641
|
+
organization there to complete SSO
|
|
2278
2642
|
overai signout Sign out and revoke this session
|
|
2279
2643
|
overai whoami Show the current identity and org
|
|
2280
2644
|
overai org list List your organizations
|
|
2281
2645
|
overai org switch <orgId> Switch the active org
|
|
2282
|
-
overai
|
|
2646
|
+
overai workspace list [--archived] List workspaces you can see in this org
|
|
2647
|
+
overai graphql -q '<query>' | -f <file> [--variables <json>] [--v1]
|
|
2283
2648
|
Run a GraphQL operation (/v2; --v1 = legacy Hasura)
|
|
2284
|
-
overai fn <functionId> [--input k=v ... | -]
|
|
2285
|
-
Invoke a function
|
|
2649
|
+
overai fn <functionId> [--method GET] [--path /features/123] [--input k=v ... | -]
|
|
2650
|
+
Invoke a function; --path reaches a
|
|
2651
|
+
sub-path of one that routes internally
|
|
2286
2652
|
overai fn logs <functionId> [--limit N] [--execution <id>]
|
|
2287
2653
|
Execution history / logs
|
|
2288
2654
|
overai sql <tableSpaceId> ["<SQL>"] Run SQL against a tablespace; no query
|
|
@@ -2306,6 +2672,8 @@ Workspace sync (edit resources as local files):
|
|
|
2306
2672
|
Live-preview a web page from local files
|
|
2307
2673
|
overai review Validation + best-practice review packet
|
|
2308
2674
|
overai best-practices [topic] Print bundled guides
|
|
2675
|
+
overai report bug|feature "<details>"
|
|
2676
|
+
Send product feedback (use - for stdin)
|
|
2309
2677
|
overai update Update the global install to the latest release
|
|
2310
2678
|
overai version Print the CLI version
|
|
2311
2679
|
|
|
@@ -2314,8 +2682,21 @@ Automations:
|
|
|
2314
2682
|
overai automation state <runId> [--node <variableId>]
|
|
2315
2683
|
overai automation run <automationId> [--input k=v ... | -]
|
|
2316
2684
|
|
|
2685
|
+
Self-hosted runners (enabling remote access stays on-device \u2014 not a CLI action):
|
|
2686
|
+
overai runner create <workspaceId> [--name <name>]
|
|
2687
|
+
overai runner token <runnerId> [--note <note>] Mint a pairing secret (shown once)
|
|
2688
|
+
overai runner run <runnerId> [--interpreter shell|powershell] --code '<code>'
|
|
2689
|
+
overai runner upload <runnerId> --path '<path>'
|
|
2690
|
+
overai runner list [workspaceId]
|
|
2691
|
+
|
|
2692
|
+
Arguments that carry a document \u2014 the graphql query and --variables, the sql
|
|
2693
|
+
statement, runner --code \u2014 also take @<file> to read it from disk, or @- to read
|
|
2694
|
+
it from stdin. Prefer that for anything long or quote-heavy: an inline value is
|
|
2695
|
+
parsed by your shell first, and one apostrophe inside it silently truncates the
|
|
2696
|
+
rest. Write @@ to send a literal value that starts with "@".
|
|
2697
|
+
|
|
2317
2698
|
Output: table on a TTY, JSON when piped; -o json|table|csv to force.
|
|
2318
|
-
Aliases: login = signin, logout = signout.
|
|
2699
|
+
Aliases: login = signin, logout = signout, ws = workspace.
|
|
2319
2700
|
Env: OVERAI_API_URL, OVERAI_APP_URL override the target deployment.
|
|
2320
2701
|
`;
|
|
2321
2702
|
process.stdout.on("error", (error) => {
|
|
@@ -2348,6 +2729,10 @@ var run = async () => {
|
|
|
2348
2729
|
process.stderr.write("Usage: overai org list | org switch <orgId>\n");
|
|
2349
2730
|
process.exitCode = 1;
|
|
2350
2731
|
return;
|
|
2732
|
+
case "workspace":
|
|
2733
|
+
case "ws":
|
|
2734
|
+
await runWorkspace(rest);
|
|
2735
|
+
return;
|
|
2351
2736
|
case "graphql":
|
|
2352
2737
|
await runGraphql(rest);
|
|
2353
2738
|
return;
|
|
@@ -2393,9 +2778,15 @@ var run = async () => {
|
|
|
2393
2778
|
case "best-practices":
|
|
2394
2779
|
runBestPractices(rest);
|
|
2395
2780
|
return;
|
|
2781
|
+
case "report":
|
|
2782
|
+
await runReport(rest);
|
|
2783
|
+
return;
|
|
2396
2784
|
case "automation":
|
|
2397
2785
|
await runAutomation(rest);
|
|
2398
2786
|
return;
|
|
2787
|
+
case "runner":
|
|
2788
|
+
await runRunner(rest);
|
|
2789
|
+
return;
|
|
2399
2790
|
case "update":
|
|
2400
2791
|
case "upgrade":
|
|
2401
2792
|
await runUpdate();
|