@usefillo/cli 0.16.0 → 0.19.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 +35 -2
- package/dist/index.js +763 -111
- package/dist/skill/build-with-fillo/SKILL.md +142 -46
- package/dist/skill/build-with-fillo/agents/openai.yaml +2 -2
- package/dist/skill/build-with-fillo/references/auth-and-lifecycle.md +63 -21
- package/dist/skill/build-with-fillo/references/frameworks.md +22 -0
- package/dist/skill/build-with-fillo/references/operations.md +75 -6
- package/dist/skill/build-with-fillo/references/schema-and-ux.md +5 -0
- package/dist/skill/build-with-fillo/references/source-map.md +6 -2
- package/dist/skill/build-with-fillo/references/troubleshooting.md +3 -3
- package/package.json +12 -4
package/dist/index.js
CHANGED
|
@@ -182,10 +182,13 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
182
182
|
// (`--device` is the alias). Presence-only.
|
|
183
183
|
"headless",
|
|
184
184
|
"device",
|
|
185
|
-
// Workspace commands (storage/slack/delete): presence-only switches.
|
|
185
|
+
// Workspace commands (storage/slack/discord/delete): presence-only switches.
|
|
186
186
|
"force-path-style",
|
|
187
187
|
"channels",
|
|
188
188
|
"refresh",
|
|
189
|
+
// `discord enable --auto-join`: widens a role grant so a non-member can be
|
|
190
|
+
// added to the server. Presence-only — it is never "=false".
|
|
191
|
+
"auto-join",
|
|
189
192
|
"yes",
|
|
190
193
|
"also-unpublish",
|
|
191
194
|
"cancel"
|
|
@@ -292,8 +295,8 @@ async function startLoopbackServer() {
|
|
|
292
295
|
let handler = null;
|
|
293
296
|
let served = false;
|
|
294
297
|
const server = createServer((req, res) => {
|
|
295
|
-
const respond = (
|
|
296
|
-
res.statusCode =
|
|
298
|
+
const respond = (status5, message) => {
|
|
299
|
+
res.statusCode = status5;
|
|
297
300
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
298
301
|
res.setHeader("Cache-Control", "no-store");
|
|
299
302
|
res.end(resultPage(message));
|
|
@@ -592,6 +595,11 @@ async function fetchWhoami(apiBase = API) {
|
|
|
592
595
|
if (!workspace) die("Fillo returned an invalid workspace identity.");
|
|
593
596
|
return {
|
|
594
597
|
workspace,
|
|
598
|
+
...typeof me.workspaceId === "string" ? { workspaceId: terminalText(me.workspaceId) } : {},
|
|
599
|
+
...typeof me.workspaceSlug === "string" ? { workspaceSlug: terminalText(me.workspaceSlug) } : {},
|
|
600
|
+
...typeof me.project === "string" ? { project: terminalText(me.project) } : {},
|
|
601
|
+
...typeof me.projectId === "string" ? { projectId: terminalText(me.projectId) } : {},
|
|
602
|
+
...typeof me.projectSlug === "string" ? { projectSlug: terminalText(me.projectSlug) } : {},
|
|
595
603
|
canPublishFileFields: typeof me.canPublishFileFields === "boolean" ? me.canPublishFileFields : void 0
|
|
596
604
|
};
|
|
597
605
|
}
|
|
@@ -607,10 +615,15 @@ async function whoami(flags) {
|
|
|
607
615
|
return emitResult({
|
|
608
616
|
connected: true,
|
|
609
617
|
workspace: me.workspace,
|
|
618
|
+
...me.workspaceId ? { workspaceId: me.workspaceId } : {},
|
|
619
|
+
...me.workspaceSlug ? { workspaceSlug: me.workspaceSlug } : {},
|
|
620
|
+
...me.project ? { project: me.project } : {},
|
|
621
|
+
...me.projectId ? { projectId: me.projectId } : {},
|
|
622
|
+
...me.projectSlug ? { projectSlug: me.projectSlug } : {},
|
|
610
623
|
...me.canPublishFileFields === void 0 ? {} : { canPublishFileFields: me.canPublishFileFields }
|
|
611
624
|
});
|
|
612
625
|
}
|
|
613
|
-
printConnected(me.workspace);
|
|
626
|
+
printConnected(me.project ? `${me.workspace} / ${me.project}` : me.workspace);
|
|
614
627
|
if (me.canPublishFileFields === true) {
|
|
615
628
|
console.log(` ${dim("File uploads: storage ready \u2014 a file field can publish now.")}`);
|
|
616
629
|
} else if (me.canPublishFileFields === false) {
|
|
@@ -1153,7 +1166,7 @@ function installSkill(flags, opts = {}) {
|
|
|
1153
1166
|
for (const target of targets) console.log(` ${dim(target)}`);
|
|
1154
1167
|
if (targets.length > 1) {
|
|
1155
1168
|
console.log(
|
|
1156
|
-
` ${dim("Same bundle in both \u2014 cross-agent compatibility, not duplication.")}`
|
|
1169
|
+
` ${dim("Same bundle in both \u2014 cross-agent compatibility, not duplication (want one folder? --agent <name> or --dir <path>).")}`
|
|
1157
1170
|
);
|
|
1158
1171
|
}
|
|
1159
1172
|
console.log(`
|
|
@@ -1294,21 +1307,21 @@ async function agent(subcommand, flags) {
|
|
|
1294
1307
|
die("--account can only be used with `fillo agent bootstrap` or `fillo agent connect`.");
|
|
1295
1308
|
}
|
|
1296
1309
|
if (subcommand === "event") {
|
|
1297
|
-
const
|
|
1310
|
+
const status5 = enumFlag(flags, "status", AGENT_EVENT_STATUSES);
|
|
1298
1311
|
const message = flagString(flags, "message");
|
|
1299
|
-
if (!
|
|
1312
|
+
if (!status5)
|
|
1300
1313
|
die('Usage: fillo agent event --status <status> --message "<what the human does next>"');
|
|
1301
1314
|
const action = enumFlag(flags, "action", AGENT_ACTIONS);
|
|
1302
1315
|
const formStatus = enumFlag(flags, "form-status", FORM_STATUSES);
|
|
1303
1316
|
const formId = optionalStringFlag(flags, "form-id");
|
|
1304
|
-
if ((
|
|
1305
|
-
die(`--form-id is required when --status is ${
|
|
1317
|
+
if ((status5 === "done" || status5 === "needs_action") && !formId) {
|
|
1318
|
+
die(`--form-id is required when --status is ${status5}.`);
|
|
1306
1319
|
}
|
|
1307
|
-
if (
|
|
1320
|
+
if (status5 === "needs_action" && !action) {
|
|
1308
1321
|
die("--action is required when --status is needs_action.");
|
|
1309
1322
|
}
|
|
1310
1323
|
await postAgentEvent(apiBase, run, token, {
|
|
1311
|
-
status:
|
|
1324
|
+
status: status5,
|
|
1312
1325
|
message,
|
|
1313
1326
|
appUrl: flagString(flags, "app-url"),
|
|
1314
1327
|
action,
|
|
@@ -1469,13 +1482,13 @@ async function claim(flags) {
|
|
|
1469
1482
|
const grant = await readJson(codeRes);
|
|
1470
1483
|
const emailFlag = flagString(flags, "email");
|
|
1471
1484
|
const savedEmail = cfg.email;
|
|
1472
|
-
const { approval, sentTo, notice } = await sendClaimEmail(
|
|
1485
|
+
const { approval, sentTo, notice: notice2 } = await sendClaimEmail(
|
|
1473
1486
|
grant,
|
|
1474
1487
|
cfg.claimToken,
|
|
1475
1488
|
emailFlag,
|
|
1476
1489
|
savedEmail
|
|
1477
1490
|
);
|
|
1478
|
-
const alreadyClaimed = approval === "device" &&
|
|
1491
|
+
const alreadyClaimed = approval === "device" && notice2 === "already_claimed";
|
|
1479
1492
|
if (json) {
|
|
1480
1493
|
emitProgress({
|
|
1481
1494
|
status: "awaiting_claim",
|
|
@@ -1483,11 +1496,11 @@ async function claim(flags) {
|
|
|
1483
1496
|
...sentTo ? { email: sentTo } : {},
|
|
1484
1497
|
verification_uri: grant.verification_uri,
|
|
1485
1498
|
approval,
|
|
1486
|
-
...
|
|
1499
|
+
...notice2 ? { notice: notice2 } : {},
|
|
1487
1500
|
note: AGENT_WAIT_LINE
|
|
1488
1501
|
});
|
|
1489
1502
|
} else {
|
|
1490
|
-
printGuidance(grant, approval, sentTo,
|
|
1503
|
+
printGuidance(grant, approval, sentTo, notice2);
|
|
1491
1504
|
}
|
|
1492
1505
|
await pollForGrant(grant, cfg.pk, approval, alreadyClaimed, json);
|
|
1493
1506
|
}
|
|
@@ -1535,10 +1548,10 @@ async function sendClaimEmail(grant, claimToken, emailFlag, savedEmail) {
|
|
|
1535
1548
|
if (!res.ok) die(body.error ?? `Couldn't send the claim email (${res.status}).`);
|
|
1536
1549
|
return { approval: "device", sentTo: email3 };
|
|
1537
1550
|
}
|
|
1538
|
-
function printGuidance(grant, approval, sentTo,
|
|
1539
|
-
if (
|
|
1551
|
+
function printGuidance(grant, approval, sentTo, notice2) {
|
|
1552
|
+
if (notice2 === "already_claimed") {
|
|
1540
1553
|
console.log("\n This workspace is already claimed.");
|
|
1541
|
-
} else if (
|
|
1554
|
+
} else if (notice2 === "recent_email") {
|
|
1542
1555
|
console.log(
|
|
1543
1556
|
`
|
|
1544
1557
|
A claim email was already sent recently${sentTo ? ` to ${bold(terminalText(sentTo))}` : ""} \u2014 open that one.`
|
|
@@ -1552,7 +1565,7 @@ function printGuidance(grant, approval, sentTo, notice) {
|
|
|
1552
1565
|
console.log(`
|
|
1553
1566
|
Your code: ${bold(grant.user_code)}
|
|
1554
1567
|
`);
|
|
1555
|
-
if (
|
|
1568
|
+
if (notice2 === "already_claimed") {
|
|
1556
1569
|
console.log(` Sign in at ${terminalText(grant.verification_uri)} and enter the code`);
|
|
1557
1570
|
console.log(" to approve this terminal.");
|
|
1558
1571
|
} else if (approval === "page") {
|
|
@@ -1612,11 +1625,11 @@ Claim failed: ${body.error}`);
|
|
|
1612
1625
|
}
|
|
1613
1626
|
if (!claimedAnnounced && Date.now() >= nextStatusAt) {
|
|
1614
1627
|
nextStatusAt = Date.now() + statusEvery;
|
|
1615
|
-
const
|
|
1628
|
+
const status5 = await api(`/workspaces/claim-status?key=${encodeURIComponent(pk)}`).catch(
|
|
1616
1629
|
() => null
|
|
1617
1630
|
);
|
|
1618
|
-
if (
|
|
1619
|
-
const state = await
|
|
1631
|
+
if (status5?.ok) {
|
|
1632
|
+
const state = await status5.json().catch(() => ({}));
|
|
1620
1633
|
if (state.claimed === true) {
|
|
1621
1634
|
claimedAnnounced = true;
|
|
1622
1635
|
if (json) {
|
|
@@ -1858,6 +1871,403 @@ var deleteCommand = {
|
|
|
1858
1871
|
help: deleteHelp
|
|
1859
1872
|
};
|
|
1860
1873
|
|
|
1874
|
+
// src/lib/browser-connect.ts
|
|
1875
|
+
var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1876
|
+
function pollIntervalMs2(fallback) {
|
|
1877
|
+
const raw = process.env.FILLO_POLL_INTERVAL_MS;
|
|
1878
|
+
if (raw === void 0 || raw === "") return fallback;
|
|
1879
|
+
const value = Number(raw);
|
|
1880
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
1881
|
+
}
|
|
1882
|
+
async function connectViaBrowser(opts) {
|
|
1883
|
+
const interval = pollIntervalMs2(opts.intervalMs ?? 5e3);
|
|
1884
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 6e5);
|
|
1885
|
+
const dots = !opts.json && !agentMode();
|
|
1886
|
+
if (opts.json) {
|
|
1887
|
+
emitProgress({
|
|
1888
|
+
status: "awaiting_connect",
|
|
1889
|
+
provider: opts.what,
|
|
1890
|
+
url: opts.startUrl,
|
|
1891
|
+
note: "Open the URL in your signed-in browser; this command waits. Do not retry in a loop."
|
|
1892
|
+
});
|
|
1893
|
+
} else {
|
|
1894
|
+
console.log(`
|
|
1895
|
+
Connect ${terminalText(opts.what)} in your signed-in browser:
|
|
1896
|
+
`);
|
|
1897
|
+
console.log(` ${terminalText(opts.startUrl)}
|
|
1898
|
+
`);
|
|
1899
|
+
console.log(" Open in your signed-in browser and approve access \u2014 this command waits.");
|
|
1900
|
+
if (agentMode()) {
|
|
1901
|
+
console.log(
|
|
1902
|
+
" Do not retry in a loop; ask the human to complete the browser step. This command waits."
|
|
1903
|
+
);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
if (dots) process.stdout.write(" Waiting for the connection");
|
|
1907
|
+
for (; ; ) {
|
|
1908
|
+
let connected2 = false;
|
|
1909
|
+
try {
|
|
1910
|
+
connected2 = await opts.poll();
|
|
1911
|
+
} catch {
|
|
1912
|
+
}
|
|
1913
|
+
if (connected2) {
|
|
1914
|
+
const { result, lines } = opts.onConnected();
|
|
1915
|
+
if (opts.json) {
|
|
1916
|
+
emitProgress({ status: "connected" });
|
|
1917
|
+
emitResult(result);
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
if (dots) process.stdout.write("\n");
|
|
1921
|
+
for (const line of lines) console.log(line);
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
if (Date.now() >= deadline) break;
|
|
1925
|
+
if (dots) process.stdout.write(".");
|
|
1926
|
+
await sleep3(interval);
|
|
1927
|
+
}
|
|
1928
|
+
if (opts.json) {
|
|
1929
|
+
emitResult({ connected: false, error: "timeout" });
|
|
1930
|
+
process.exit(1);
|
|
1931
|
+
}
|
|
1932
|
+
if (dots) process.stdout.write("\n");
|
|
1933
|
+
console.log(
|
|
1934
|
+
` Timed out waiting for ${terminalText(opts.what)} to connect. Finish the browser step, then re-run this command.`
|
|
1935
|
+
);
|
|
1936
|
+
process.exit(1);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// src/commands/discord.ts
|
|
1940
|
+
var WEBHOOK_URL_ENV = "FILLO_DISCORD_WEBHOOK_URL";
|
|
1941
|
+
var SNOWFLAKE = /^\d{17,20}$/;
|
|
1942
|
+
var EARLY_SIGNAL_CHOICES = ["5", "10", "25", "off"];
|
|
1943
|
+
function notice(json, line) {
|
|
1944
|
+
if (json) emitProgress({ status: "notice", notice: line });
|
|
1945
|
+
else console.log(` ${line}`);
|
|
1946
|
+
}
|
|
1947
|
+
async function fetchConnection(token) {
|
|
1948
|
+
const res = await api("/cli/discord", { token });
|
|
1949
|
+
if (res.status === 401) die("Token invalid \u2014 run `fillo login` again.");
|
|
1950
|
+
const body = await readJson(res);
|
|
1951
|
+
if (!res.ok) die(body.error ?? `discord status failed (${res.status}).`);
|
|
1952
|
+
return body;
|
|
1953
|
+
}
|
|
1954
|
+
async function fetchDestination(token, form) {
|
|
1955
|
+
const res = await api(`/cli/forms/${encodeURIComponent(form)}/discord`, { token });
|
|
1956
|
+
if (res.status === 401) die("Token invalid \u2014 run `fillo login` again.");
|
|
1957
|
+
const body = await readJson(res);
|
|
1958
|
+
if (res.status === 404) die(body.error ?? "Form not found in the selected project.");
|
|
1959
|
+
if (!res.ok) die(body.error ?? `discord status failed (${res.status}).`);
|
|
1960
|
+
return body;
|
|
1961
|
+
}
|
|
1962
|
+
async function putDestination(token, form, patch) {
|
|
1963
|
+
const res = await api(`/cli/forms/${encodeURIComponent(form)}/discord`, {
|
|
1964
|
+
method: "PUT",
|
|
1965
|
+
token,
|
|
1966
|
+
body: JSON.stringify(patch)
|
|
1967
|
+
});
|
|
1968
|
+
if (res.status === 401) die("Token invalid \u2014 run `fillo login` again.");
|
|
1969
|
+
const body = await readJson(res);
|
|
1970
|
+
if (res.status === 404) die(body.error ?? "Form not found in the selected project.");
|
|
1971
|
+
if (!res.ok) die(body.error ?? `discord update failed (${res.status}).`);
|
|
1972
|
+
return body;
|
|
1973
|
+
}
|
|
1974
|
+
async function projectId(token) {
|
|
1975
|
+
const res = await api("/cli/whoami", { token });
|
|
1976
|
+
const body = await readJson(res);
|
|
1977
|
+
if (res.status === 401) die("Token invalid \u2014 run `fillo login` again.");
|
|
1978
|
+
if (!res.ok || typeof body.projectId !== "string" || !body.projectId) {
|
|
1979
|
+
die(body.error ?? "Couldn't resolve the selected Fillo project.");
|
|
1980
|
+
}
|
|
1981
|
+
return body.projectId;
|
|
1982
|
+
}
|
|
1983
|
+
async function connect(flags) {
|
|
1984
|
+
const json = jsonMode(flags);
|
|
1985
|
+
const token = requireToken();
|
|
1986
|
+
const connection = await fetchConnection(token);
|
|
1987
|
+
if (!connection.appConfigured) {
|
|
1988
|
+
die(
|
|
1989
|
+
"This Fillo deployment has no Discord app, so there's no one-click channel picker. Run `fillo discord webhook` to paste a channel webhook URL instead."
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
const startUrl = `${API}/api/integrations/discord/start?return=terminal&project=${encodeURIComponent(
|
|
1993
|
+
await projectId(token)
|
|
1994
|
+
)}`;
|
|
1995
|
+
let connected2 = null;
|
|
1996
|
+
await connectViaBrowser({
|
|
1997
|
+
json,
|
|
1998
|
+
what: "Discord",
|
|
1999
|
+
startUrl,
|
|
2000
|
+
poll: async () => {
|
|
2001
|
+
const snapshot = await fetchConnection(token);
|
|
2002
|
+
connected2 = snapshot;
|
|
2003
|
+
return snapshot.connected === true;
|
|
2004
|
+
},
|
|
2005
|
+
onConnected: () => {
|
|
2006
|
+
const label = connected2?.channelLabel ?? null;
|
|
2007
|
+
return {
|
|
2008
|
+
result: {
|
|
2009
|
+
connected: true,
|
|
2010
|
+
webhookId: connected2?.webhookId ?? null,
|
|
2011
|
+
channelLabel: label
|
|
2012
|
+
},
|
|
2013
|
+
lines: [
|
|
2014
|
+
` ${okMark()} Discord connected${label ? ` (${terminalText(label)})` : ""}.`,
|
|
2015
|
+
" Turn a form on with `fillo discord enable <form>`."
|
|
2016
|
+
]
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
async function webhook(flags) {
|
|
2022
|
+
const json = jsonMode(flags);
|
|
2023
|
+
const token = requireToken();
|
|
2024
|
+
let url = process.env[WEBHOOK_URL_ENV]?.trim() ?? "";
|
|
2025
|
+
if (!url) {
|
|
2026
|
+
if (json || !isInteractive()) {
|
|
2027
|
+
die(
|
|
2028
|
+
`Set ${WEBHOOK_URL_ENV} to the channel webhook URL and re-run. It is never accepted as an argument \u2014 arguments land in shell history and \`ps\` output.`
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
url = await readSecret(" Discord webhook URL (hidden): ").catch(() => "");
|
|
2032
|
+
if (!url) {
|
|
2033
|
+
die(`No webhook URL entered. Paste it at the prompt, or set ${WEBHOOK_URL_ENV} and re-run.`);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
const label = flagString(flags, "label");
|
|
2037
|
+
const res = await api("/cli/discord/webhook", {
|
|
2038
|
+
method: "POST",
|
|
2039
|
+
token,
|
|
2040
|
+
body: JSON.stringify({ url, ...label ? { label } : {} })
|
|
2041
|
+
});
|
|
2042
|
+
const body = await readJson(res);
|
|
2043
|
+
if (res.status === 401) die("Token invalid \u2014 run `fillo login` again.");
|
|
2044
|
+
if (!res.ok || !body.webhookId) die(body.error ?? `discord webhook failed (${res.status}).`);
|
|
2045
|
+
if (json) return emitResult(body);
|
|
2046
|
+
console.log(
|
|
2047
|
+
` ${okMark()} Connected Discord${body.label ? ` (${terminalText(body.label)})` : ""}.`
|
|
2048
|
+
);
|
|
2049
|
+
console.log(" Turn a form on with `fillo discord enable <form>`.");
|
|
2050
|
+
}
|
|
2051
|
+
function printDestination(body) {
|
|
2052
|
+
if (!body.connected) {
|
|
2053
|
+
console.log(
|
|
2054
|
+
" Discord is not connected to this project. Run `fillo discord connect` (or `fillo discord webhook` to paste a channel URL)."
|
|
2055
|
+
);
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
if (!body.enabled) {
|
|
2059
|
+
console.log(
|
|
2060
|
+
` Discord is connected${body.connectionLabel ? ` (${terminalText(body.connectionLabel)})` : ""} but this form isn't sending. Turn it on with \`fillo discord enable <form>\`.`
|
|
2061
|
+
);
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
console.log(
|
|
2065
|
+
` ${okMark()} Sending to Discord${body.channelLabel ? ` \u2014 ${terminalText(body.channelLabel)}` : ""}`
|
|
2066
|
+
);
|
|
2067
|
+
const fields = body.includedFieldIds ?? [];
|
|
2068
|
+
console.log(
|
|
2069
|
+
` ${dim("Fields:")} ${fields.length > 0 ? terminalText(fields.join(", ")) : "link only"}`
|
|
2070
|
+
);
|
|
2071
|
+
console.log(
|
|
2072
|
+
` ${dim("Early signal:")} ${body.earlySignalLimit ? `first ${body.earlySignalLimit} responses in full (${body.earlySignalDelivered} sent)` : "off"}`
|
|
2073
|
+
);
|
|
2074
|
+
const grant = body.roleGrant;
|
|
2075
|
+
console.log(
|
|
2076
|
+
` ${dim("Role grant:")} ${grant ? `${terminalText(grant.roleId)} in ${terminalText(grant.guildId)}${grant.autoJoin ? " (auto-join)" : ""}` : "off"}`
|
|
2077
|
+
);
|
|
2078
|
+
if (!body.webhookMatches) {
|
|
2079
|
+
console.log(
|
|
2080
|
+
`
|
|
2081
|
+
${bold("This form points at a different webhook than the project does.")} It keeps failing until you re-run \`fillo discord enable <form>\`.`
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
async function status(form, flags) {
|
|
2086
|
+
if (!form) die("Usage: fillo discord status <form> \u2014 a form id, slug, or push handle.");
|
|
2087
|
+
const body = await fetchDestination(requireToken(), form);
|
|
2088
|
+
if (jsonMode(flags)) return emitResult(body);
|
|
2089
|
+
printDestination(body);
|
|
2090
|
+
}
|
|
2091
|
+
function parseFields(raw) {
|
|
2092
|
+
if (raw.trim().toLowerCase() === "none") return [];
|
|
2093
|
+
return raw.split(",").map((id) => id.trim()).filter(Boolean);
|
|
2094
|
+
}
|
|
2095
|
+
function parseRole(raw) {
|
|
2096
|
+
if (raw.trim().toLowerCase() === "none") return null;
|
|
2097
|
+
const [guildId, roleId, ...extra] = raw.trim().split("/");
|
|
2098
|
+
if (extra.length > 0 || !guildId || !roleId || !SNOWFLAKE.test(guildId) || !SNOWFLAKE.test(roleId)) {
|
|
2099
|
+
die(
|
|
2100
|
+
"--role must be <guildId>/<roleId> (Discord ids), or none to clear it. List them with `fillo discord roles [guildId]`."
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
2103
|
+
return { guildId, roleId };
|
|
2104
|
+
}
|
|
2105
|
+
async function enable(form, flags) {
|
|
2106
|
+
const json = jsonMode(flags);
|
|
2107
|
+
if (!form)
|
|
2108
|
+
die("Usage: fillo discord enable <form> [--fields a,b,c] [--early-signal 5|10|25|off]");
|
|
2109
|
+
const token = requireToken();
|
|
2110
|
+
const current = await fetchDestination(token, form);
|
|
2111
|
+
if (!current.connected || !current.connectionWebhookId) {
|
|
2112
|
+
die(
|
|
2113
|
+
"Discord isn't connected to this project. Run `fillo discord connect`, or `fillo discord webhook` to paste a channel URL."
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
const patch = {};
|
|
2117
|
+
if (!current.enabled || !current.webhookMatches) {
|
|
2118
|
+
patch.enabled = true;
|
|
2119
|
+
patch.webhookId = current.connectionWebhookId;
|
|
2120
|
+
}
|
|
2121
|
+
const rawFields = flagString(flags, "fields");
|
|
2122
|
+
if (rawFields !== void 0) patch.includeFieldIds = parseFields(rawFields);
|
|
2123
|
+
const rawEarly = flagString(flags, "early-signal");
|
|
2124
|
+
if (rawEarly !== void 0) {
|
|
2125
|
+
if (!EARLY_SIGNAL_CHOICES.includes(rawEarly)) {
|
|
2126
|
+
die(`--early-signal must be one of: ${EARLY_SIGNAL_CHOICES.join(", ")}`);
|
|
2127
|
+
}
|
|
2128
|
+
patch.earlySignalLimit = rawEarly === "off" ? null : Number(rawEarly);
|
|
2129
|
+
}
|
|
2130
|
+
const rawRole = flagString(flags, "role");
|
|
2131
|
+
const autoJoin = flags["auto-join"] === true;
|
|
2132
|
+
if (rawRole !== void 0) {
|
|
2133
|
+
const grant = parseRole(rawRole);
|
|
2134
|
+
patch.roleGrant = grant ? { ...grant, ...autoJoin ? { autoJoin: true } : {} } : null;
|
|
2135
|
+
} else if (autoJoin) {
|
|
2136
|
+
die(
|
|
2137
|
+
"--auto-join needs --role <guildId>/<roleId> \u2014 it widens that grant, it isn't a setting on its own."
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
if (Object.keys(patch).length === 0) {
|
|
2141
|
+
if (json) return emitResult(current);
|
|
2142
|
+
printDestination(current);
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
if (typeof patch.earlySignalLimit === "number") {
|
|
2146
|
+
notice(
|
|
2147
|
+
json,
|
|
2148
|
+
`Early signal sends every answer to this channel for the first ${patch.earlySignalLimit} responses.`
|
|
2149
|
+
);
|
|
2150
|
+
}
|
|
2151
|
+
if (patch.roleGrant && autoJoin) {
|
|
2152
|
+
notice(
|
|
2153
|
+
json,
|
|
2154
|
+
"Auto-join adds respondents to your Discord server with that role \u2014 Discord asks each person's permission first."
|
|
2155
|
+
);
|
|
2156
|
+
} else if (patch.roleGrant) {
|
|
2157
|
+
notice(
|
|
2158
|
+
json,
|
|
2159
|
+
"Respondents who verify with Discord get that role once their response is accepted."
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
const body = await putDestination(token, form, patch);
|
|
2163
|
+
if (json) return emitResult(body);
|
|
2164
|
+
printDestination(body);
|
|
2165
|
+
}
|
|
2166
|
+
async function disable(form, flags) {
|
|
2167
|
+
if (!form) die("Usage: fillo discord disable <form> \u2014 a form id, slug, or push handle.");
|
|
2168
|
+
const token = requireToken();
|
|
2169
|
+
const body = await putDestination(token, form, { enabled: false });
|
|
2170
|
+
if (jsonMode(flags)) return emitResult(body);
|
|
2171
|
+
console.log(
|
|
2172
|
+
` ${okMark()} This form no longer posts to Discord. The workspace's webhook stays connected.`
|
|
2173
|
+
);
|
|
2174
|
+
}
|
|
2175
|
+
async function roles(guildId, flags) {
|
|
2176
|
+
const json = jsonMode(flags);
|
|
2177
|
+
const token = requireToken();
|
|
2178
|
+
const path = guildId ? `/cli/discord/roles?guildId=${encodeURIComponent(guildId)}` : "/cli/discord/roles";
|
|
2179
|
+
const res = await api(path, { token });
|
|
2180
|
+
if (res.status === 401) die("Token invalid \u2014 run `fillo login` again.");
|
|
2181
|
+
const body = await readJson(res);
|
|
2182
|
+
if (!res.ok) die(body.error ?? `discord roles failed (${res.status}).`);
|
|
2183
|
+
if (json) return emitResult(body);
|
|
2184
|
+
if (!guildId) {
|
|
2185
|
+
const guilds = body.guilds ?? [];
|
|
2186
|
+
if (guilds.length === 0) {
|
|
2187
|
+
console.log(
|
|
2188
|
+
" No Discord servers yet \u2014 connect a channel webhook first with `fillo discord connect`."
|
|
2189
|
+
);
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
console.log("");
|
|
2193
|
+
printTable(
|
|
2194
|
+
["SERVER", "ID"],
|
|
2195
|
+
guilds.map((guild) => [terminalText(guild.name ?? "(name unavailable)"), guild.id])
|
|
2196
|
+
);
|
|
2197
|
+
if (body.guildsUnavailable === true) {
|
|
2198
|
+
console.log(
|
|
2199
|
+
`
|
|
2200
|
+
${dim("Discord didn't answer, so some names are missing. Ids still work.")}`
|
|
2201
|
+
);
|
|
2202
|
+
}
|
|
2203
|
+
console.log(`
|
|
2204
|
+
${dim("List a server's roles: fillo discord roles <id>")}`);
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2207
|
+
const list5 = body.roles ?? [];
|
|
2208
|
+
if (list5.length === 0) {
|
|
2209
|
+
console.log(" That server has no roles Fillo could list.");
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
console.log("");
|
|
2213
|
+
printTable(
|
|
2214
|
+
["ROLE", "ID", "GRANTABLE"],
|
|
2215
|
+
list5.map((role) => [terminalText(role.name), role.id, role.grantable ? "yes" : "no"])
|
|
2216
|
+
);
|
|
2217
|
+
console.log(`
|
|
2218
|
+
${dim(`Use one: fillo discord enable <form> --role ${guildId}/<roleId>`)}`);
|
|
2219
|
+
console.log(
|
|
2220
|
+
` ${dim("A role marked no sits above Fillo's own role, or belongs to another integration.")}`
|
|
2221
|
+
);
|
|
2222
|
+
}
|
|
2223
|
+
async function discord(subcommand, args, flags) {
|
|
2224
|
+
if (subcommand === void 0 || subcommand === "help") return discordHelp();
|
|
2225
|
+
if (subcommand === "connect") return connect(flags);
|
|
2226
|
+
if (subcommand === "webhook") return webhook(flags);
|
|
2227
|
+
if (subcommand === "status") return status(args[0], flags);
|
|
2228
|
+
if (subcommand === "enable") return enable(args[0], flags);
|
|
2229
|
+
if (subcommand === "disable") return disable(args[0], flags);
|
|
2230
|
+
if (subcommand === "roles") return roles(args[0], flags);
|
|
2231
|
+
die(
|
|
2232
|
+
`Unknown discord command: ${terminalText(subcommand)} (expected connect, webhook, status, enable, disable, or roles).`
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
function discordHelp() {
|
|
2236
|
+
console.log(`
|
|
2237
|
+
${boldRaw("fillo discord")} \u2014 post responses to a Discord channel, from the terminal
|
|
2238
|
+
|
|
2239
|
+
${boldRaw("Commands")}
|
|
2240
|
+
discord connect Let Discord pick the channel and create the webhook
|
|
2241
|
+
${dimRaw("opens an OAuth URL to approve; needs a Discord app on this deploy")}
|
|
2242
|
+
discord webhook Connect a channel webhook you paste
|
|
2243
|
+
${dimRaw("--label <name> what Fillo shows for this channel")}
|
|
2244
|
+
${dimRaw(`reads the URL from a hidden prompt, or ${WEBHOOK_URL_ENV}`)}
|
|
2245
|
+
discord status <form> What this form sends, to where, with which extras
|
|
2246
|
+
discord enable <form> Start sending \u2014 and set any of the options below
|
|
2247
|
+
${dimRaw("--fields a,b,c up to three field ids (none = link only)")}
|
|
2248
|
+
${dimRaw("--early-signal 5|10|25 first N responses arrive in full (off to stop)")}
|
|
2249
|
+
${dimRaw("--role <guildId>/<roleId> grant that role on an accepted response")}
|
|
2250
|
+
${dimRaw("--auto-join with --role: add non-members to the server")}
|
|
2251
|
+
discord disable <form> Stop this form posting (the webhook stays connected)
|
|
2252
|
+
discord roles [guildId] Your servers, or one server's roles and what Fillo can grant
|
|
2253
|
+
|
|
2254
|
+
${dimRaw("The webhook URL is the whole credential, so it is NEVER an argument \u2014")}
|
|
2255
|
+
${dimRaw("arguments land in shell history and `ps` output. Paste it at the hidden")}
|
|
2256
|
+
${dimRaw(`prompt, or set ${WEBHOOK_URL_ENV} for an unattended run.`)}
|
|
2257
|
+
${dimRaw("--early-signal and --auto-join widen what leaves Fillo, so each prints a")}
|
|
2258
|
+
${dimRaw("one-line notice saying what was agreed to. Ask the human before setting them.")}
|
|
2259
|
+
${dimRaw("Re-enabling against a different webhook clears early signal \u2014 that consent")}
|
|
2260
|
+
${dimRaw("was given for one channel and is never carried into another.")}
|
|
2261
|
+
${dimRaw("--json prints the raw server response on stdout.")}
|
|
2262
|
+
`);
|
|
2263
|
+
}
|
|
2264
|
+
var discordCommand = {
|
|
2265
|
+
name: "discord",
|
|
2266
|
+
flags: ["label", "fields", "early-signal", "role", "auto-join"],
|
|
2267
|
+
run: (args, flags) => discord(args[0], args.slice(1), flags),
|
|
2268
|
+
help: discordHelp
|
|
2269
|
+
};
|
|
2270
|
+
|
|
1861
2271
|
// src/commands/forms.ts
|
|
1862
2272
|
import { readFileSync as readFileSync3 } from "fs";
|
|
1863
2273
|
import { isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
|
|
@@ -3258,6 +3668,48 @@ function sortForPicker(countries) {
|
|
|
3258
3668
|
return [...countries].sort((a, b) => collator.compare(a.name, b.name));
|
|
3259
3669
|
}
|
|
3260
3670
|
var byIso = new Map(PHONE_COUNTRIES.map((c) => [c.iso2, c]));
|
|
3671
|
+
var DEFAULT_STRINGS = {
|
|
3672
|
+
back: "Back",
|
|
3673
|
+
next: "Next",
|
|
3674
|
+
submit: "Submit",
|
|
3675
|
+
submitting: "Submitting\u2026",
|
|
3676
|
+
uploading: "Uploading\u2026",
|
|
3677
|
+
optional: " (optional)",
|
|
3678
|
+
other: "Other",
|
|
3679
|
+
otherPrompt: "Other \u2014 please specify",
|
|
3680
|
+
otherPlaceholder: "Your answer",
|
|
3681
|
+
choosePlaceholder: "Choose\u2026",
|
|
3682
|
+
successTitle: "Thanks!",
|
|
3683
|
+
successMessage: "Your response has been recorded.",
|
|
3684
|
+
closed: "This form is no longer accepting responses.",
|
|
3685
|
+
notLive: "This form isn't live yet.",
|
|
3686
|
+
notOpenTitle: "This form isn't open yet",
|
|
3687
|
+
notOpenBody: "The form owner is still setting things up. Please check back soon.",
|
|
3688
|
+
closedTitle: "Responses are closed",
|
|
3689
|
+
submitFailed: "This form can't submit right now. Please try again in a moment.",
|
|
3690
|
+
loadFailedNotFound: "Form not found. Check the link or ask the form owner for help.",
|
|
3691
|
+
loadFailedNetwork: "Couldn't reach the server \u2014 check your connection and try again.",
|
|
3692
|
+
loadFailed: "This form could not be loaded.",
|
|
3693
|
+
renderFailed: "This form could not be rendered.",
|
|
3694
|
+
resumeNotice: "Picked up where you left off.",
|
|
3695
|
+
editNotice: "You're updating your earlier response.",
|
|
3696
|
+
resumeStartOver: "Start over",
|
|
3697
|
+
challengeUnavailable: "The verification check couldn't load. Refresh the page and try again."
|
|
3698
|
+
};
|
|
3699
|
+
var DEFAULT_RESPONDENT_ERROR_STRINGS = {
|
|
3700
|
+
submitFailed: DEFAULT_STRINGS.submitFailed,
|
|
3701
|
+
loadFailedNotFound: DEFAULT_STRINGS.loadFailedNotFound,
|
|
3702
|
+
loadFailedNetwork: DEFAULT_STRINGS.loadFailedNetwork,
|
|
3703
|
+
formUnavailable: "This form is unavailable.",
|
|
3704
|
+
formClosed: DEFAULT_STRINGS.closed,
|
|
3705
|
+
submitRateLimited: "Too many responses are being submitted right now. Wait a moment, then try again.",
|
|
3706
|
+
respondentUnrecognized: "This form couldn't verify who is responding. Ask the form owner for help.",
|
|
3707
|
+
fileUnavailable: "One of the uploaded files is no longer available. Remove it and upload it again.",
|
|
3708
|
+
scopeMissing: "We couldn't tell which entry this response belongs to. Answer the required field and try again.",
|
|
3709
|
+
challengeIncomplete: "Please complete the verification check, then submit.",
|
|
3710
|
+
challengeRetry: "That verification didn't go through. Please complete the check again and resubmit.",
|
|
3711
|
+
reviewAnswers: "We couldn't submit \u2014 please review your answers."
|
|
3712
|
+
};
|
|
3261
3713
|
var fileValueSchema = object({
|
|
3262
3714
|
fileId: string2().check(_minLength(1)),
|
|
3263
3715
|
name: string2(),
|
|
@@ -3322,7 +3774,7 @@ var schemaShape = object({
|
|
|
3322
3774
|
});
|
|
3323
3775
|
var MAX_SCHEMA_VERSION = 1;
|
|
3324
3776
|
var FILLO_SCHEMA_VERSION = 1;
|
|
3325
|
-
var FILLO_SDK_VERSION = true ? "0.
|
|
3777
|
+
var FILLO_SDK_VERSION = true ? "0.19.0" : "0.0.0-dev";
|
|
3326
3778
|
function str(value, max, fallback = "") {
|
|
3327
3779
|
return typeof value === "string" ? value.trim().slice(0, max) : fallback;
|
|
3328
3780
|
}
|
|
@@ -4183,7 +4635,7 @@ async function list(flags) {
|
|
|
4183
4635
|
}
|
|
4184
4636
|
}
|
|
4185
4637
|
var liveDot = () => ansiEnabled() ? "\x1B[32m\u25CF\x1B[0m" : "\u25CF";
|
|
4186
|
-
async function
|
|
4638
|
+
async function status2(handle, flags) {
|
|
4187
4639
|
if (!handle) die("Usage: fillo status <formId|handle>");
|
|
4188
4640
|
const res = await api(`/cli/forms/${encodeURIComponent(handle)}`, { token: requireToken() });
|
|
4189
4641
|
if (res.status === 401) die("Token invalid \u2014 run `fillo login` again.");
|
|
@@ -4395,6 +4847,7 @@ async function loadSchema(file, allowCode) {
|
|
|
4395
4847
|
die(`Couldn't read ${file}: ${e.message}`);
|
|
4396
4848
|
}
|
|
4397
4849
|
}
|
|
4850
|
+
var PUSH_STORAGE_VALUES = /* @__PURE__ */ new Set(["gdrive", "box", "s3", "r2"]);
|
|
4398
4851
|
function isRecord(value) {
|
|
4399
4852
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4400
4853
|
}
|
|
@@ -4404,7 +4857,19 @@ function toPushItems(value, handle) {
|
|
|
4404
4857
|
return values.map((item, index) => {
|
|
4405
4858
|
if (isRecord(item) && "schema" in item) {
|
|
4406
4859
|
const ownHandle = typeof item.id === "string" ? item.id : void 0;
|
|
4407
|
-
|
|
4860
|
+
if (item.storage !== void 0 && !PUSH_STORAGE_VALUES.has(String(item.storage))) {
|
|
4861
|
+
die(`Item ${index + 1} has an unsupported storage destination.`);
|
|
4862
|
+
}
|
|
4863
|
+
if (item.purpose !== void 0 && item.purpose !== "file_request") {
|
|
4864
|
+
die(`Item ${index + 1} has an unsupported form purpose.`);
|
|
4865
|
+
}
|
|
4866
|
+
return {
|
|
4867
|
+
handle: handle ?? ownHandle,
|
|
4868
|
+
schema: item.schema,
|
|
4869
|
+
theme: item.theme,
|
|
4870
|
+
...typeof item.storage === "string" ? { storage: item.storage } : {},
|
|
4871
|
+
...item.purpose === "file_request" ? { purpose: item.purpose } : {}
|
|
4872
|
+
};
|
|
4408
4873
|
}
|
|
4409
4874
|
if (values.length > 1 && !handle) {
|
|
4410
4875
|
die(`Item ${index + 1} needs an id or --handle.`);
|
|
@@ -4414,9 +4879,9 @@ function toPushItems(value, handle) {
|
|
|
4414
4879
|
}
|
|
4415
4880
|
function printNotices(notices) {
|
|
4416
4881
|
if (!Array.isArray(notices)) return;
|
|
4417
|
-
for (const
|
|
4418
|
-
if (typeof
|
|
4419
|
-
console.log(` ${bold("Note:")} ${terminalText(
|
|
4882
|
+
for (const notice2 of notices) {
|
|
4883
|
+
if (typeof notice2 === "string" && notice2.trim()) {
|
|
4884
|
+
console.log(` ${bold("Note:")} ${terminalText(notice2)}`);
|
|
4420
4885
|
}
|
|
4421
4886
|
}
|
|
4422
4887
|
}
|
|
@@ -4501,7 +4966,9 @@ async function pushWithSync(items, credential, requestedStage, results) {
|
|
|
4501
4966
|
...credential.key ? { key: credential.key } : {},
|
|
4502
4967
|
id: handle,
|
|
4503
4968
|
schema: item.schema,
|
|
4504
|
-
theme: item.theme ?? null
|
|
4969
|
+
theme: item.theme ?? null,
|
|
4970
|
+
...item.storage ? { storage: item.storage } : {},
|
|
4971
|
+
...item.purpose ? { purpose: item.purpose } : {}
|
|
4505
4972
|
})
|
|
4506
4973
|
});
|
|
4507
4974
|
const body = await readJson(res);
|
|
@@ -4566,6 +5033,8 @@ async function push(file, flags) {
|
|
|
4566
5033
|
body: JSON.stringify({
|
|
4567
5034
|
schema: item.schema,
|
|
4568
5035
|
theme: item.theme ?? null,
|
|
5036
|
+
...item.storage ? { storage: item.storage } : {},
|
|
5037
|
+
...item.purpose ? { purpose: item.purpose } : {},
|
|
4569
5038
|
publish: false
|
|
4570
5039
|
})
|
|
4571
5040
|
});
|
|
@@ -4593,6 +5062,8 @@ async function push(file, flags) {
|
|
|
4593
5062
|
schema: item.schema,
|
|
4594
5063
|
theme: item.theme ?? null,
|
|
4595
5064
|
handle: item.handle,
|
|
5065
|
+
...item.storage ? { storage: item.storage } : {},
|
|
5066
|
+
...item.purpose ? { purpose: item.purpose } : {},
|
|
4596
5067
|
publish: true
|
|
4597
5068
|
})
|
|
4598
5069
|
});
|
|
@@ -4608,11 +5079,16 @@ async function push(file, flags) {
|
|
|
4608
5079
|
}
|
|
4609
5080
|
die(body.error ?? `push failed (${res.status}).`);
|
|
4610
5081
|
}
|
|
5082
|
+
const draft = body.status === "draft";
|
|
4611
5083
|
if (results) {
|
|
4612
5084
|
results.push(body);
|
|
4613
|
-
emitProgress({
|
|
5085
|
+
emitProgress({
|
|
5086
|
+
status: "pushed",
|
|
5087
|
+
formId: body.formId,
|
|
5088
|
+
...draft ? { draft: true } : { published: true }
|
|
5089
|
+
});
|
|
4614
5090
|
} else {
|
|
4615
|
-
printPushed(body.formId, body.url, !!body.updated,
|
|
5091
|
+
printPushed(body.formId, body.url, !!body.updated, draft, {
|
|
4616
5092
|
warning: body.warning,
|
|
4617
5093
|
warningUrl: body.warningUrl,
|
|
4618
5094
|
notices: body.notices
|
|
@@ -4643,7 +5119,7 @@ var listCommand = {
|
|
|
4643
5119
|
var statusCommand = {
|
|
4644
5120
|
name: "status",
|
|
4645
5121
|
flags: [],
|
|
4646
|
-
run: (args, flags) =>
|
|
5122
|
+
run: (args, flags) => status2(args[0], flags)
|
|
4647
5123
|
};
|
|
4648
5124
|
var publishCommand = {
|
|
4649
5125
|
name: "publish",
|
|
@@ -4656,6 +5132,61 @@ var testResponseCommand = {
|
|
|
4656
5132
|
run: (args) => testResponse(args[0], args[1])
|
|
4657
5133
|
};
|
|
4658
5134
|
|
|
5135
|
+
// src/commands/branding.ts
|
|
5136
|
+
function printState(body) {
|
|
5137
|
+
const plan = body.plan === "pro" ? "Everything" : "Free";
|
|
5138
|
+
console.log(` Plan: ${plan}`);
|
|
5139
|
+
console.log(` Badge: ${body.poweredBy ? "shown" : "hidden"}`);
|
|
5140
|
+
if (body.canHide === false) {
|
|
5141
|
+
console.log(dim(" Turning it off needs the Everything plan (Settings \u2192 Plan)."));
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
async function branding(subcommand, flags) {
|
|
5145
|
+
if (subcommand === "help") return brandingHelp();
|
|
5146
|
+
if (subcommand !== void 0 && subcommand !== "on" && subcommand !== "off") {
|
|
5147
|
+
die(`Unknown branding command: ${subcommand} (expected on, off, or nothing for status).`);
|
|
5148
|
+
}
|
|
5149
|
+
const token = requireToken();
|
|
5150
|
+
const res = subcommand === void 0 ? await api("/cli/workspace/branding", { token }) : await api("/cli/workspace/branding", {
|
|
5151
|
+
method: "PATCH",
|
|
5152
|
+
token,
|
|
5153
|
+
body: JSON.stringify({ show: subcommand === "on" })
|
|
5154
|
+
});
|
|
5155
|
+
const body = await readJson(res);
|
|
5156
|
+
if (res.status === 401) die("Token invalid \u2014 run `fillo login` again.");
|
|
5157
|
+
if (!res.ok) die(body.error ?? `branding ${subcommand ?? "status"} failed (${res.status}).`);
|
|
5158
|
+
if (jsonMode(flags)) return emitResult(body);
|
|
5159
|
+
console.log("");
|
|
5160
|
+
if (subcommand !== void 0) {
|
|
5161
|
+
console.log(
|
|
5162
|
+
` ${okMark()} Badge ${body.poweredBy ? "shown" : "hidden"} on Fillo-rendered forms.
|
|
5163
|
+
`
|
|
5164
|
+
);
|
|
5165
|
+
}
|
|
5166
|
+
printState(body);
|
|
5167
|
+
console.log("");
|
|
5168
|
+
}
|
|
5169
|
+
function brandingHelp() {
|
|
5170
|
+
console.log(`
|
|
5171
|
+
${boldRaw("fillo branding")} \u2014 the workspace's "Powered by Fillo" badge
|
|
5172
|
+
|
|
5173
|
+
${boldRaw("Commands")}
|
|
5174
|
+
branding Print the current badge state
|
|
5175
|
+
branding off Hide the badge on Fillo-rendered forms (Everything plan)
|
|
5176
|
+
branding on Show the badge again
|
|
5177
|
+
|
|
5178
|
+
${dimRaw("Hiding needs the Everything plan; a workspace owner or admin selects")}
|
|
5179
|
+
${dimRaw("that in Settings \u2192 Plan. Headless embeds never show a badge.")}
|
|
5180
|
+
${dimRaw("--json prints the raw server response on stdout.")}
|
|
5181
|
+
`);
|
|
5182
|
+
}
|
|
5183
|
+
var brandingCommand = {
|
|
5184
|
+
name: "branding",
|
|
5185
|
+
flags: [],
|
|
5186
|
+
run: (args, flags) => branding(args[0], flags),
|
|
5187
|
+
help: brandingHelp
|
|
5188
|
+
};
|
|
5189
|
+
|
|
4659
5190
|
// src/commands/keys.ts
|
|
4660
5191
|
var PRESETS = ["read", "agent", "full"];
|
|
4661
5192
|
var EXPIRY_CHOICES = ["30d", "90d", "never"];
|
|
@@ -4804,7 +5335,7 @@ async function keys(subcommand, args, flags) {
|
|
|
4804
5335
|
}
|
|
4805
5336
|
function keysHelp() {
|
|
4806
5337
|
console.log(`
|
|
4807
|
-
${boldRaw("fillo keys")} \u2014
|
|
5338
|
+
${boldRaw("fillo keys")} \u2014 project API keys (fsk_) for coding agents and integrations
|
|
4808
5339
|
|
|
4809
5340
|
${boldRaw("Commands")}
|
|
4810
5341
|
keys create --name <name> Mint a key \u2014 the plaintext is shown once, store it
|
|
@@ -5244,73 +5775,16 @@ var settingsCommand = {
|
|
|
5244
5775
|
help: settingsHelp
|
|
5245
5776
|
};
|
|
5246
5777
|
|
|
5247
|
-
// src/
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
const
|
|
5251
|
-
if (
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
}
|
|
5255
|
-
async function connectViaBrowser(opts) {
|
|
5256
|
-
const interval = pollIntervalMs2(opts.intervalMs ?? 5e3);
|
|
5257
|
-
const deadline = Date.now() + (opts.timeoutMs ?? 6e5);
|
|
5258
|
-
const dots = !opts.json && !agentMode();
|
|
5259
|
-
if (opts.json) {
|
|
5260
|
-
emitProgress({
|
|
5261
|
-
status: "awaiting_connect",
|
|
5262
|
-
provider: opts.what,
|
|
5263
|
-
url: opts.startUrl,
|
|
5264
|
-
note: "Open the URL in your signed-in browser; this command waits. Do not retry in a loop."
|
|
5265
|
-
});
|
|
5266
|
-
} else {
|
|
5267
|
-
console.log(`
|
|
5268
|
-
Connect ${terminalText(opts.what)} in your signed-in browser:
|
|
5269
|
-
`);
|
|
5270
|
-
console.log(` ${terminalText(opts.startUrl)}
|
|
5271
|
-
`);
|
|
5272
|
-
console.log(" Open in your signed-in browser and approve access \u2014 this command waits.");
|
|
5273
|
-
if (agentMode()) {
|
|
5274
|
-
console.log(
|
|
5275
|
-
" Do not retry in a loop; ask the human to complete the browser step. This command waits."
|
|
5276
|
-
);
|
|
5277
|
-
}
|
|
5278
|
-
}
|
|
5279
|
-
if (dots) process.stdout.write(" Waiting for the connection");
|
|
5280
|
-
for (; ; ) {
|
|
5281
|
-
let connected2 = false;
|
|
5282
|
-
try {
|
|
5283
|
-
connected2 = await opts.poll();
|
|
5284
|
-
} catch {
|
|
5285
|
-
}
|
|
5286
|
-
if (connected2) {
|
|
5287
|
-
const { result, lines } = opts.onConnected();
|
|
5288
|
-
if (opts.json) {
|
|
5289
|
-
emitProgress({ status: "connected" });
|
|
5290
|
-
emitResult(result);
|
|
5291
|
-
return;
|
|
5292
|
-
}
|
|
5293
|
-
if (dots) process.stdout.write("\n");
|
|
5294
|
-
for (const line of lines) console.log(line);
|
|
5295
|
-
return;
|
|
5296
|
-
}
|
|
5297
|
-
if (Date.now() >= deadline) break;
|
|
5298
|
-
if (dots) process.stdout.write(".");
|
|
5299
|
-
await sleep3(interval);
|
|
5300
|
-
}
|
|
5301
|
-
if (opts.json) {
|
|
5302
|
-
emitResult({ connected: false, error: "timeout" });
|
|
5303
|
-
process.exit(1);
|
|
5778
|
+
// src/commands/slack.ts
|
|
5779
|
+
async function slackStartUrl(token) {
|
|
5780
|
+
const res = await api("/cli/whoami", { token });
|
|
5781
|
+
const body = await readJson(res);
|
|
5782
|
+
if (res.status === 401) die("Token invalid \u2014 run `fillo login` again.");
|
|
5783
|
+
if (!res.ok || typeof body.projectId !== "string" || !body.projectId) {
|
|
5784
|
+
die(body.error ?? "Couldn't resolve the selected Fillo project.");
|
|
5304
5785
|
}
|
|
5305
|
-
|
|
5306
|
-
console.log(
|
|
5307
|
-
` Timed out waiting for ${terminalText(opts.what)} to connect. Finish the browser step, then re-run this command.`
|
|
5308
|
-
);
|
|
5309
|
-
process.exit(1);
|
|
5786
|
+
return `${API}/api/integrations/slack/start?return=terminal&project=${encodeURIComponent(body.projectId)}`;
|
|
5310
5787
|
}
|
|
5311
|
-
|
|
5312
|
-
// src/commands/slack.ts
|
|
5313
|
-
var SLACK_START_URL = `${API}/api/integrations/slack/start?return=terminal`;
|
|
5314
5788
|
async function refresh(flags) {
|
|
5315
5789
|
const json = jsonMode(flags);
|
|
5316
5790
|
const token = requireToken();
|
|
@@ -5318,8 +5792,9 @@ async function refresh(flags) {
|
|
|
5318
5792
|
const body = await readJson(res);
|
|
5319
5793
|
if (res.status === 401) die("Token invalid \u2014 run `fillo login` again.");
|
|
5320
5794
|
if (res.status === 409) {
|
|
5795
|
+
const startUrl = await slackStartUrl(token);
|
|
5321
5796
|
die(
|
|
5322
|
-
`${body.error ?? "Reconnect Slack in the browser to refresh channels."} Reconnect at ${
|
|
5797
|
+
`${body.error ?? "Reconnect Slack in the browser to refresh channels."} Reconnect at ${startUrl}`
|
|
5323
5798
|
);
|
|
5324
5799
|
}
|
|
5325
5800
|
if (res.status === 429) {
|
|
@@ -5351,7 +5826,7 @@ function printChannels(channels) {
|
|
|
5351
5826
|
console.log("");
|
|
5352
5827
|
printTable(["NAME", "ID", "VISIBILITY"], rows);
|
|
5353
5828
|
}
|
|
5354
|
-
async function
|
|
5829
|
+
async function status3(flags) {
|
|
5355
5830
|
const json = jsonMode(flags);
|
|
5356
5831
|
const showChannels = flags.channels === true;
|
|
5357
5832
|
const token = requireToken();
|
|
@@ -5376,13 +5851,14 @@ async function status2(flags) {
|
|
|
5376
5851
|
);
|
|
5377
5852
|
}
|
|
5378
5853
|
}
|
|
5379
|
-
async function
|
|
5854
|
+
async function connect2(flags) {
|
|
5380
5855
|
const json = jsonMode(flags);
|
|
5381
5856
|
const token = requireToken();
|
|
5857
|
+
const startUrl = await slackStartUrl(token);
|
|
5382
5858
|
await connectViaBrowser({
|
|
5383
5859
|
json,
|
|
5384
5860
|
what: "Slack",
|
|
5385
|
-
startUrl
|
|
5861
|
+
startUrl,
|
|
5386
5862
|
poll: async () => {
|
|
5387
5863
|
const res = await api("/cli/slack", { token });
|
|
5388
5864
|
if (res.status === 401) die("Token invalid \u2014 run `fillo login` again.");
|
|
@@ -5401,12 +5877,12 @@ async function connect(flags) {
|
|
|
5401
5877
|
}
|
|
5402
5878
|
async function slack(subcommand, flags) {
|
|
5403
5879
|
if (subcommand === "help") return slackHelp();
|
|
5404
|
-
if (subcommand === "connect") return
|
|
5880
|
+
if (subcommand === "connect") return connect2(flags);
|
|
5405
5881
|
if (subcommand !== void 0 && subcommand !== "status") {
|
|
5406
5882
|
die(`Unknown slack command: ${terminalText(subcommand)} (expected status or connect).`);
|
|
5407
5883
|
}
|
|
5408
5884
|
if (flags.refresh === true) return refresh(flags);
|
|
5409
|
-
return
|
|
5885
|
+
return status3(flags);
|
|
5410
5886
|
}
|
|
5411
5887
|
function slackHelp() {
|
|
5412
5888
|
console.log(`
|
|
@@ -5455,7 +5931,7 @@ async function fetchStatus(token) {
|
|
|
5455
5931
|
if (!res.ok || !body.providers) die(body.error ?? `storage status failed (${res.status}).`);
|
|
5456
5932
|
return body;
|
|
5457
5933
|
}
|
|
5458
|
-
async function
|
|
5934
|
+
async function status4(flags) {
|
|
5459
5935
|
const token = requireToken();
|
|
5460
5936
|
const body = await fetchStatus(token);
|
|
5461
5937
|
if (jsonMode(flags)) return emitResult(body);
|
|
@@ -5622,7 +6098,7 @@ async function connectBrowserProvider(provider, flags) {
|
|
|
5622
6098
|
}
|
|
5623
6099
|
});
|
|
5624
6100
|
}
|
|
5625
|
-
async function
|
|
6101
|
+
async function connect3(providerArg, flags) {
|
|
5626
6102
|
const provider = resolveProviderArg(providerArg);
|
|
5627
6103
|
if (!provider) {
|
|
5628
6104
|
die("Usage: fillo storage connect <s3|drive|box> (r2 and gdrive are accepted aliases).");
|
|
@@ -5645,9 +6121,9 @@ async function disconnect(providerArg, flags) {
|
|
|
5645
6121
|
console.log(` ${okMark()} Disconnected ${PROVIDER_LABEL[provider]} storage.`);
|
|
5646
6122
|
}
|
|
5647
6123
|
async function storage(subcommand, args, flags) {
|
|
5648
|
-
if (subcommand === void 0 || subcommand === "status") return
|
|
6124
|
+
if (subcommand === void 0 || subcommand === "status") return status4(flags);
|
|
5649
6125
|
if (subcommand === "help") return storageHelp();
|
|
5650
|
-
if (subcommand === "connect") return
|
|
6126
|
+
if (subcommand === "connect") return connect3(args[0], flags);
|
|
5651
6127
|
if (subcommand === "disconnect") return disconnect(args[0], flags);
|
|
5652
6128
|
die(
|
|
5653
6129
|
`Unknown storage command: ${terminalText(subcommand)} (expected status, connect, or disconnect).`
|
|
@@ -5682,6 +6158,63 @@ var storageCommand = {
|
|
|
5682
6158
|
help: storageHelp
|
|
5683
6159
|
};
|
|
5684
6160
|
|
|
6161
|
+
// src/commands/tokens.ts
|
|
6162
|
+
var TOOLS = ["zapier", "n8n"];
|
|
6163
|
+
async function createConnector(flags) {
|
|
6164
|
+
const json = jsonMode(flags);
|
|
6165
|
+
const tool = enumFlag(flags, "tool", TOOLS);
|
|
6166
|
+
if (!tool) die(`Usage: fillo tokens create-connector --tool ${TOOLS.join("|")}`);
|
|
6167
|
+
const res = await api("/cli/tokens/connector", {
|
|
6168
|
+
method: "POST",
|
|
6169
|
+
token: requireToken(),
|
|
6170
|
+
body: JSON.stringify({ tool })
|
|
6171
|
+
});
|
|
6172
|
+
const body = await readJson(res);
|
|
6173
|
+
if (res.status === 401) die(body.error ?? "Invalid or missing CLI token \u2014 run `fillo login`");
|
|
6174
|
+
if (!res.ok || !body.token) die(body.error ?? `tokens create-connector failed (${res.status}).`);
|
|
6175
|
+
if (!/^fcli_[A-Za-z0-9._~-]{8,512}$/.test(body.token)) {
|
|
6176
|
+
die("Fillo returned an unexpected token format.");
|
|
6177
|
+
}
|
|
6178
|
+
if (json) return emitResult(body);
|
|
6179
|
+
console.log(
|
|
6180
|
+
`
|
|
6181
|
+
${okMark()} Created a ${bold(terminalText(body.label ?? tool))} connector token`
|
|
6182
|
+
);
|
|
6183
|
+
console.log(`
|
|
6184
|
+
${body.token}
|
|
6185
|
+
`);
|
|
6186
|
+
console.log(` ${bold("Store it now")} \u2014 Fillo cannot show this token again.`);
|
|
6187
|
+
console.log(
|
|
6188
|
+
" Treat it like a password: it can read and change this project's forms and responses."
|
|
6189
|
+
);
|
|
6190
|
+
console.log(" Revoke it any time in Settings \u2192 Connections.\n");
|
|
6191
|
+
}
|
|
6192
|
+
async function tokens(subcommand, flags) {
|
|
6193
|
+
if (subcommand === void 0 || subcommand === "help") return tokensHelp();
|
|
6194
|
+
if (subcommand === "create-connector") return createConnector(flags);
|
|
6195
|
+
die(`Unknown tokens command: ${terminalText(subcommand)} (expected create-connector).`);
|
|
6196
|
+
}
|
|
6197
|
+
function tokensHelp() {
|
|
6198
|
+
console.log(`
|
|
6199
|
+
${boldRaw("fillo tokens")} \u2014 credentials for no-code connectors
|
|
6200
|
+
|
|
6201
|
+
${boldRaw("Commands")}
|
|
6202
|
+
tokens create-connector --tool ${TOOLS.join("|")}
|
|
6203
|
+
Mint the connector's API token \u2014 shown once, store it
|
|
6204
|
+
|
|
6205
|
+
${dimRaw("The token is non-expiring so a live automation doesn't break silently, and")}
|
|
6206
|
+
${dimRaw("stays revocable in Settings \u2192 Connections. For coding agents and your own")}
|
|
6207
|
+
${dimRaw("integrations use `fillo keys create` instead \u2014 those are scoped.")}
|
|
6208
|
+
${dimRaw("--json prints the raw server response on stdout, token included.")}
|
|
6209
|
+
`);
|
|
6210
|
+
}
|
|
6211
|
+
var tokensCommand = {
|
|
6212
|
+
name: "tokens",
|
|
6213
|
+
flags: ["tool"],
|
|
6214
|
+
run: (args, flags) => tokens(args[0], flags),
|
|
6215
|
+
help: tokensHelp
|
|
6216
|
+
};
|
|
6217
|
+
|
|
5685
6218
|
// src/commands/webhooks.ts
|
|
5686
6219
|
var dateTime2 = (iso) => iso.slice(0, 16).replace("T", " ");
|
|
5687
6220
|
async function list3(handle, flags) {
|
|
@@ -5812,6 +6345,115 @@ var webhooksCommand = {
|
|
|
5812
6345
|
help: webhooksHelp
|
|
5813
6346
|
};
|
|
5814
6347
|
|
|
6348
|
+
// src/commands/projects.ts
|
|
6349
|
+
function authFailure(status5) {
|
|
6350
|
+
if (status5 === 401) die("Token invalid \u2014 run `fillo login` again.");
|
|
6351
|
+
}
|
|
6352
|
+
function saveSelectedProject(project) {
|
|
6353
|
+
if (!project.publishableKey.startsWith("pk_")) {
|
|
6354
|
+
die("Fillo returned an invalid publishable key; the selected project was not saved locally.");
|
|
6355
|
+
}
|
|
6356
|
+
const {
|
|
6357
|
+
claimUrl: _claimUrl,
|
|
6358
|
+
email: _email2,
|
|
6359
|
+
name: _name,
|
|
6360
|
+
claimToken: _claimToken,
|
|
6361
|
+
...current
|
|
6362
|
+
} = readConfig();
|
|
6363
|
+
writeConfig({ ...current, pk: project.publishableKey });
|
|
6364
|
+
}
|
|
6365
|
+
async function list4(flags) {
|
|
6366
|
+
const res = await api("/cli/projects", { token: requireToken() });
|
|
6367
|
+
const body = await readJson(res);
|
|
6368
|
+
authFailure(res.status);
|
|
6369
|
+
if (!res.ok || !Array.isArray(body.projects)) {
|
|
6370
|
+
die(body.error ?? `project list failed (${res.status}).`);
|
|
6371
|
+
}
|
|
6372
|
+
if (jsonMode(flags)) return emitResult(body);
|
|
6373
|
+
console.log(`
|
|
6374
|
+
${bold("Projects")}`);
|
|
6375
|
+
printTable(
|
|
6376
|
+
["", "NAME", "SLUG", "ID"],
|
|
6377
|
+
body.projects.map((project) => [
|
|
6378
|
+
project.current ? "*" : "",
|
|
6379
|
+
terminalText(project.name),
|
|
6380
|
+
terminalText(project.slug),
|
|
6381
|
+
terminalText(project.id)
|
|
6382
|
+
])
|
|
6383
|
+
);
|
|
6384
|
+
console.log(`
|
|
6385
|
+
${dim("* selected for this CLI login")}`);
|
|
6386
|
+
console.log(` ${dim("Switch with `fillo project select <id-or-slug>`.")}
|
|
6387
|
+
`);
|
|
6388
|
+
}
|
|
6389
|
+
async function create(name, flags) {
|
|
6390
|
+
if (!name) die('Usage: fillo project create "Project name"');
|
|
6391
|
+
const res = await api("/cli/projects", {
|
|
6392
|
+
method: "POST",
|
|
6393
|
+
token: requireToken(),
|
|
6394
|
+
body: JSON.stringify({ name, source: "cli" })
|
|
6395
|
+
});
|
|
6396
|
+
const body = await readJson(res);
|
|
6397
|
+
authFailure(res.status);
|
|
6398
|
+
if (!res.ok || !body.project || body.selected !== true) {
|
|
6399
|
+
die(body.error ?? `project create failed (${res.status}).`);
|
|
6400
|
+
}
|
|
6401
|
+
saveSelectedProject(body.project);
|
|
6402
|
+
if (jsonMode(flags)) return emitResult(body);
|
|
6403
|
+
console.log(` ${okMark()} Created and selected ${bold(terminalText(body.project.name))}.`);
|
|
6404
|
+
console.log(` ${dim("Forms, keys, origins, identities, and agent access are isolated here.")}`);
|
|
6405
|
+
console.log(` ${dim("Members, billing, storage, and usage totals stay with the workspace.")}`);
|
|
6406
|
+
}
|
|
6407
|
+
async function select(target, flags) {
|
|
6408
|
+
if (!target) die("Usage: fillo project select <id|slug|unique-name>");
|
|
6409
|
+
const res = await api("/cli/projects/select", {
|
|
6410
|
+
method: "POST",
|
|
6411
|
+
token: requireToken(),
|
|
6412
|
+
body: JSON.stringify({ project: target, source: "cli" })
|
|
6413
|
+
});
|
|
6414
|
+
const body = await readJson(res);
|
|
6415
|
+
authFailure(res.status);
|
|
6416
|
+
if (!res.ok || !body.project || body.selected !== true) {
|
|
6417
|
+
die(body.error ?? `project select failed (${res.status}).`);
|
|
6418
|
+
}
|
|
6419
|
+
saveSelectedProject(body.project);
|
|
6420
|
+
if (jsonMode(flags)) return emitResult(body);
|
|
6421
|
+
console.log(` ${okMark()} Selected ${bold(terminalText(body.project.name))}.`);
|
|
6422
|
+
console.log(` ${dim("Future CLI commands use this project until you select another.")}`);
|
|
6423
|
+
}
|
|
6424
|
+
async function projects(subcommand, args, flags) {
|
|
6425
|
+
if (subcommand === void 0 || subcommand === "list" || subcommand === "ls") {
|
|
6426
|
+
return list4(flags);
|
|
6427
|
+
}
|
|
6428
|
+
if (subcommand === "help") return projectHelp();
|
|
6429
|
+
if (subcommand === "create") return create(args.join(" ").trim() || void 0, flags);
|
|
6430
|
+
if (subcommand === "select" || subcommand === "use") {
|
|
6431
|
+
return select(args.join(" ").trim() || void 0, flags);
|
|
6432
|
+
}
|
|
6433
|
+
die(`Unknown project command: ${terminalText(subcommand)} (expected list, create, or select).`);
|
|
6434
|
+
}
|
|
6435
|
+
function projectHelp() {
|
|
6436
|
+
console.log(`
|
|
6437
|
+
${boldRaw("fillo project")} \u2014 create and select isolated sites/apps
|
|
6438
|
+
|
|
6439
|
+
${boldRaw("Commands")}
|
|
6440
|
+
project List projects in the current workspace
|
|
6441
|
+
project create <name> Create and select a project
|
|
6442
|
+
project select <id|slug|name> Select a project for future commands
|
|
6443
|
+
|
|
6444
|
+
${dimRaw("Requires an ordinary `fillo login`. Project-pinned agent handoff")}
|
|
6445
|
+
${dimRaw("tokens cannot list, create, or select sibling projects.")}
|
|
6446
|
+
${dimRaw("--json prints the server response on stdout.")}
|
|
6447
|
+
`);
|
|
6448
|
+
}
|
|
6449
|
+
var projectCommand = {
|
|
6450
|
+
name: "project",
|
|
6451
|
+
aliases: ["projects"],
|
|
6452
|
+
flags: [],
|
|
6453
|
+
run: (args, flags) => projects(args[0], args.slice(1), flags),
|
|
6454
|
+
help: projectHelp
|
|
6455
|
+
};
|
|
6456
|
+
|
|
5815
6457
|
// src/index.ts
|
|
5816
6458
|
var REGISTRY = [
|
|
5817
6459
|
initCommand,
|
|
@@ -5819,6 +6461,7 @@ var REGISTRY = [
|
|
|
5819
6461
|
loginCommand,
|
|
5820
6462
|
logoutCommand,
|
|
5821
6463
|
whoamiCommand,
|
|
6464
|
+
projectCommand,
|
|
5822
6465
|
keysCommand,
|
|
5823
6466
|
pushCommand,
|
|
5824
6467
|
listCommand,
|
|
@@ -5830,8 +6473,11 @@ var REGISTRY = [
|
|
|
5830
6473
|
skillCommand,
|
|
5831
6474
|
storageCommand,
|
|
5832
6475
|
slackCommand,
|
|
6476
|
+
discordCommand,
|
|
6477
|
+
tokensCommand,
|
|
5833
6478
|
webhooksCommand,
|
|
5834
6479
|
settingsCommand,
|
|
6480
|
+
brandingCommand,
|
|
5835
6481
|
membersCommand,
|
|
5836
6482
|
deleteCommand
|
|
5837
6483
|
];
|
|
@@ -5862,15 +6508,17 @@ function help() {
|
|
|
5862
6508
|
${dimRaw("--headless approve a code instead of the local browser handoff")}
|
|
5863
6509
|
${dimRaw("--run <id> --token <token> bind a fresh agent handoff login")}
|
|
5864
6510
|
logout Forget the stored credentials
|
|
5865
|
-
whoami Show the signed-in workspace
|
|
5866
|
-
|
|
6511
|
+
whoami Show the signed-in workspace and selected project
|
|
6512
|
+
project <cmd> Isolated sites/apps: list, create, select
|
|
6513
|
+
${dimRaw("project create <name> \xB7 project select <id|slug|name>")}
|
|
6514
|
+
keys <cmd> Project API keys for agents/CI: create, list, revoke
|
|
5867
6515
|
${dimRaw("keys create --name <n> --preset read|agent|full")}
|
|
5868
6516
|
push <file|-> Create/update a form from JSON (file or stdin)
|
|
5869
6517
|
${dimRaw("--handle <name> idempotent id for re-pushes")}
|
|
5870
6518
|
${dimRaw("--stage stage beside the live form for dashboard review")}
|
|
5871
6519
|
${dimRaw("--draft alias with a handle; legacy one-off draft without")}
|
|
5872
6520
|
${dimRaw("--allow-code allow a .mjs/.js schema (executes the file)")}
|
|
5873
|
-
list List the
|
|
6521
|
+
list List the project's forms and their live URLs
|
|
5874
6522
|
status <form> Show one form's status, live URL, and publish blockers
|
|
5875
6523
|
${dimRaw("<form> is a form id or handle")}
|
|
5876
6524
|
publish <form> Publish staged changes (or a draft form) \u2014 prints the live URL
|
|
@@ -5886,9 +6534,13 @@ function help() {
|
|
|
5886
6534
|
storage <cmd> Inspect/connect upload destinations (status, connect, disconnect)
|
|
5887
6535
|
${dimRaw("storage connect s3 \xB7 connect drive|box \xB7 disconnect <p>")}
|
|
5888
6536
|
slack <cmd> Slack notifications: status, --channels, --refresh, connect
|
|
6537
|
+
discord <cmd> Discord destination: connect, webhook, status, enable, disable, roles
|
|
6538
|
+
${dimRaw("discord enable <form> --fields a,b --early-signal 10 --role <g>/<r>")}
|
|
6539
|
+
tokens <cmd> Connector credentials: create-connector --tool zapier|n8n
|
|
5889
6540
|
webhooks <cmd> A form's signed webhooks: list, add, set, remove
|
|
5890
6541
|
${dimRaw("webhooks add <form> --url <u> shows the signing secret once")}
|
|
5891
6542
|
settings <cmd> A form's operational settings: get, set key=value
|
|
6543
|
+
branding [on|off] The workspace's "Powered by Fillo" badge (off needs Everything)
|
|
5892
6544
|
members <cmd> Workspace members: list, invite <email>, cancel-invite <id>
|
|
5893
6545
|
delete <cmd> Irreversible deletes: delete form <form> \xB7 delete workspace
|
|
5894
6546
|
${dimRaw("typed confirmation required; --confirm for agents, --yes never skips")}
|