@usefillo/cli 0.23.0 → 0.24.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/dist/index.js
CHANGED
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
5
|
+
|
|
6
|
+
// src/lib/client-telemetry.ts
|
|
7
|
+
import { readFileSync } from "fs";
|
|
8
|
+
var command;
|
|
9
|
+
var version = "0.0.0";
|
|
10
|
+
try {
|
|
11
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
12
|
+
if (typeof pkg.version === "string") version = pkg.version;
|
|
13
|
+
} catch {
|
|
14
|
+
}
|
|
15
|
+
function setClientCommand(name) {
|
|
16
|
+
command = name;
|
|
17
|
+
}
|
|
18
|
+
function clientTelemetryHeaders() {
|
|
19
|
+
return {
|
|
20
|
+
"X-Fillo-Client": `@usefillo/cli@${version}`,
|
|
21
|
+
...command ? { "X-Fillo-Command": command } : {}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
5
24
|
|
|
6
25
|
// src/lib/config.ts
|
|
7
|
-
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
26
|
+
import { chmodSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
8
27
|
import { homedir } from "os";
|
|
9
28
|
import { join } from "path";
|
|
10
29
|
var CONFIG_DIR = join(homedir(), ".fillo");
|
|
11
30
|
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
12
31
|
function readConfig() {
|
|
13
32
|
try {
|
|
14
|
-
const parsed = JSON.parse(
|
|
33
|
+
const parsed = JSON.parse(readFileSync2(CONFIG_PATH, "utf8"));
|
|
15
34
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
16
35
|
const record2 = parsed;
|
|
17
36
|
return {
|
|
@@ -85,7 +104,7 @@ function printTable(header, rows) {
|
|
|
85
104
|
for (const cells of rows) console.log(line(cells));
|
|
86
105
|
}
|
|
87
106
|
var dateOnly = (iso) => iso.slice(0, 10);
|
|
88
|
-
var plural = (
|
|
107
|
+
var plural = (count2, noun) => `${count2} ${noun}${count2 === 1 ? "" : "s"}`;
|
|
89
108
|
function die(msg) {
|
|
90
109
|
if (jsonOutput) emitResult({ error: terminalText(msg) });
|
|
91
110
|
console.error(`${failMark()} ${terminalText(msg)}`);
|
|
@@ -148,6 +167,7 @@ async function api(path, init2 = {}, apiBase = API) {
|
|
|
148
167
|
signal: rest.signal ?? AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
149
168
|
headers: {
|
|
150
169
|
"Content-Type": "application/json",
|
|
170
|
+
...clientTelemetryHeaders(),
|
|
151
171
|
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
152
172
|
...rest.headers
|
|
153
173
|
}
|
|
@@ -232,10 +252,10 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
232
252
|
var BOOLISH_FLAGS = /* @__PURE__ */ new Set(["include-abandoned"]);
|
|
233
253
|
var CONFIRM_FLAGS = /* @__PURE__ */ new Set(["confirm"]);
|
|
234
254
|
var GLOBAL_FLAGS = /* @__PURE__ */ new Set(["help", "h", "json", "version", "v"]);
|
|
235
|
-
function validateFlags(
|
|
255
|
+
function validateFlags(command2, flags, commandFlags) {
|
|
236
256
|
const allowed = /* @__PURE__ */ new Set([...GLOBAL_FLAGS, ...commandFlags ?? []]);
|
|
237
257
|
for (const [key, value] of Object.entries(flags)) {
|
|
238
|
-
if (!allowed.has(key)) die(`Unknown flag for ${
|
|
258
|
+
if (!allowed.has(key)) die(`Unknown flag for ${command2 ?? "fillo"}: --${key}`);
|
|
239
259
|
if (BOOLEAN_FLAGS.has(key)) {
|
|
240
260
|
if (value !== true) die(`--${key} does not take a value.`);
|
|
241
261
|
} else if (BOOLISH_FLAGS.has(key)) {
|
|
@@ -1098,7 +1118,7 @@ import {
|
|
|
1098
1118
|
lstatSync,
|
|
1099
1119
|
mkdirSync as mkdirSync2,
|
|
1100
1120
|
readdirSync,
|
|
1101
|
-
readFileSync as
|
|
1121
|
+
readFileSync as readFileSync3,
|
|
1102
1122
|
realpathSync,
|
|
1103
1123
|
renameSync,
|
|
1104
1124
|
rmSync
|
|
@@ -1172,7 +1192,7 @@ function sameSkillBundle(source, target) {
|
|
|
1172
1192
|
const targetFiles = skillFiles(target);
|
|
1173
1193
|
if (sourceFiles.length !== targetFiles.length) return false;
|
|
1174
1194
|
return sourceFiles.every(
|
|
1175
|
-
(file, index) => file === targetFiles[index] &&
|
|
1195
|
+
(file, index) => file === targetFiles[index] && readFileSync3(join3(source, file)).equals(readFileSync3(join3(target, file)))
|
|
1176
1196
|
);
|
|
1177
1197
|
} catch {
|
|
1178
1198
|
return false;
|
|
@@ -1190,7 +1210,7 @@ function pathEntryExists(path) {
|
|
|
1190
1210
|
var MANAGED_SKILL_MARKER = ".fillo-managed.json";
|
|
1191
1211
|
function isCliManagedFilloSkill(target) {
|
|
1192
1212
|
try {
|
|
1193
|
-
const marker = JSON.parse(
|
|
1213
|
+
const marker = JSON.parse(readFileSync3(join3(target, MANAGED_SKILL_MARKER), "utf8"));
|
|
1194
1214
|
return marker.managedBy === "@usefillo/cli" && marker.skill === "build-with-fillo" && marker.format === 1 && marker.allowAutomaticUpdates === true;
|
|
1195
1215
|
} catch {
|
|
1196
1216
|
return false;
|
|
@@ -1487,6 +1507,7 @@ async function postAgentEvent(apiBase, run, token, body) {
|
|
|
1487
1507
|
res = await fetch(`${apiBase}/api/v1/agent-runs/${encodeURIComponent(run)}/events`, {
|
|
1488
1508
|
method: "POST",
|
|
1489
1509
|
headers: {
|
|
1510
|
+
...clientTelemetryHeaders(),
|
|
1490
1511
|
Authorization: `Bearer ${token}`,
|
|
1491
1512
|
"Content-Type": "application/json"
|
|
1492
1513
|
},
|
|
@@ -1507,6 +1528,7 @@ async function attachAgentAccount(apiBase, run, progressToken) {
|
|
|
1507
1528
|
method: "POST",
|
|
1508
1529
|
redirect: "error",
|
|
1509
1530
|
headers: {
|
|
1531
|
+
...clientTelemetryHeaders(),
|
|
1510
1532
|
Authorization: `Bearer ${accountToken}`,
|
|
1511
1533
|
"X-Fillo-Agent-Run-Token": progressToken
|
|
1512
1534
|
},
|
|
@@ -1578,13 +1600,192 @@ var agentCommand = {
|
|
|
1578
1600
|
help: agentHelp
|
|
1579
1601
|
};
|
|
1580
1602
|
|
|
1603
|
+
// src/commands/billing.ts
|
|
1604
|
+
var VOLUMES = [
|
|
1605
|
+
{ tier: "5k", responses: 5e3 },
|
|
1606
|
+
{ tier: "25k", responses: 25e3 },
|
|
1607
|
+
{ tier: "100k", responses: 1e5 }
|
|
1608
|
+
];
|
|
1609
|
+
var TRIAL_DAYS = 30;
|
|
1610
|
+
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
1611
|
+
var count = (value) => new Intl.NumberFormat("en-US").format(value);
|
|
1612
|
+
function parseDate(iso) {
|
|
1613
|
+
if (typeof iso !== "string" || !iso) return null;
|
|
1614
|
+
const date2 = new Date(iso);
|
|
1615
|
+
return Number.isNaN(date2.getTime()) ? null : date2;
|
|
1616
|
+
}
|
|
1617
|
+
var longDate = (date2) => `${date2.getUTCDate()} ${MONTHS[date2.getUTCMonth()]} ${date2.getUTCFullYear()}`;
|
|
1618
|
+
var shortDate = (date2) => `${date2.getUTCDate()} ${MONTHS[date2.getUTCMonth()]}`;
|
|
1619
|
+
function periodLabel(period, currentYear) {
|
|
1620
|
+
const match = /^(\d{4})-(\d{2})$/.exec(period);
|
|
1621
|
+
const month = match ? MONTHS[Number(match[2]) - 1] : void 0;
|
|
1622
|
+
if (!match || !month) return terminalText(period);
|
|
1623
|
+
return match[1] === currentYear ? month : `${month} ${match[1]}`;
|
|
1624
|
+
}
|
|
1625
|
+
function daysLeft(end, now = Date.now()) {
|
|
1626
|
+
const days = Math.max(0, Math.ceil((end.getTime() - now) / 864e5));
|
|
1627
|
+
return days === 0 ? "ends today" : days === 1 ? "1 day left" : `${days} days left`;
|
|
1628
|
+
}
|
|
1629
|
+
var tierName = (tier) => tier ? `Everything ${terminalText(tier)}` : "Everything";
|
|
1630
|
+
function planLine(body) {
|
|
1631
|
+
if (body.billingEnabled === false) {
|
|
1632
|
+
return body.plan === "free" ? "Free" : "Everything";
|
|
1633
|
+
}
|
|
1634
|
+
switch (body.plan) {
|
|
1635
|
+
case "free":
|
|
1636
|
+
return `Free \xB7 ${count(body.allowance)} responses a month`;
|
|
1637
|
+
case "trial": {
|
|
1638
|
+
const end = parseDate(body.trialEndsAt);
|
|
1639
|
+
const ends = end ? ` \xB7 ends ${longDate(end)} (${daysLeft(end)})` : "";
|
|
1640
|
+
return `Everything trial \xB7 ${count(body.allowance)} responses${ends} \xB7 no card`;
|
|
1641
|
+
}
|
|
1642
|
+
case "complimentary":
|
|
1643
|
+
return `${tierName(body.tier)} \xB7 complimentary (no charge, no renewal date)`;
|
|
1644
|
+
case "subscribed": {
|
|
1645
|
+
const billed = body.interval === "year" ? "billed yearly" : "billed monthly";
|
|
1646
|
+
const end = parseDate(body.currentPeriodEnd);
|
|
1647
|
+
let next = "";
|
|
1648
|
+
if (body.status === "past_due") next = " \xB7 payment failed";
|
|
1649
|
+
else if (body.cancelAtPeriodEnd) next = end ? ` \xB7 cancels ${longDate(end)}` : " \xB7 canceling";
|
|
1650
|
+
else if (end)
|
|
1651
|
+
next = `${body.status === "trialing" ? " \xB7 first payment" : " \xB7 renews"} ${longDate(end)}`;
|
|
1652
|
+
return `${tierName(body.tier)} \xB7 ${billed}${next}`;
|
|
1653
|
+
}
|
|
1654
|
+
default:
|
|
1655
|
+
return terminalText(body.plan);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
function usageLine(body) {
|
|
1659
|
+
const { responses: responses2 } = body.usage;
|
|
1660
|
+
const reset = parseDate(body.usage.resetsAt);
|
|
1661
|
+
const resets = reset ? ` \xB7 resets ${shortDate(reset)}` : "";
|
|
1662
|
+
if (!(body.allowance > 0)) return `${count(responses2)} responses this month${resets}`;
|
|
1663
|
+
const percent = Math.floor(responses2 / body.allowance * 100);
|
|
1664
|
+
return `${count(responses2)} of ${count(body.allowance)} responses this month (${percent}%)${resets}`;
|
|
1665
|
+
}
|
|
1666
|
+
function historyLine(body) {
|
|
1667
|
+
const currentYear = body.usage.period?.slice(0, 4);
|
|
1668
|
+
const earlier = (body.history ?? []).filter(
|
|
1669
|
+
(row) => typeof row.period === "string" && row.period !== body.usage.period && typeof row.responses === "number"
|
|
1670
|
+
);
|
|
1671
|
+
if (!earlier.some((row) => row.responses > 0)) return null;
|
|
1672
|
+
return earlier.map((row) => `${periodLabel(row.period, currentYear)} ${count(row.responses)}`).join(" \xB7 ");
|
|
1673
|
+
}
|
|
1674
|
+
function manageUrl(body) {
|
|
1675
|
+
try {
|
|
1676
|
+
const url = new URL(body.manageUrl ?? "");
|
|
1677
|
+
if (url.protocol === "https:" || url.protocol === "http:") return terminalText(url.href);
|
|
1678
|
+
} catch {
|
|
1679
|
+
}
|
|
1680
|
+
return `${terminalText(API)}/settings/plan`;
|
|
1681
|
+
}
|
|
1682
|
+
function fittingVolume(body) {
|
|
1683
|
+
return VOLUMES.find(
|
|
1684
|
+
(volume) => volume.responses > body.allowance && volume.responses >= body.usage.responses
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
function notes(body) {
|
|
1688
|
+
const lines = [];
|
|
1689
|
+
const billing2 = body.billingEnabled !== false;
|
|
1690
|
+
if (billing2 && body.plan === "subscribed" && body.status === "past_due") {
|
|
1691
|
+
lines.push(
|
|
1692
|
+
"The last payment failed. Stripe is retrying it, and Everything stays on meanwhile.",
|
|
1693
|
+
"An owner or admin updates the card from Manage billing at the link above."
|
|
1694
|
+
);
|
|
1695
|
+
} else if (billing2 && body.plan === "subscribed" && body.cancelAtPeriodEnd) {
|
|
1696
|
+
lines.push(
|
|
1697
|
+
"Everything stays active until the period ends; the workspace returns to Free after that."
|
|
1698
|
+
);
|
|
1699
|
+
} else if (billing2 && body.plan === "trial") {
|
|
1700
|
+
lines.push("When the trial ends without a subscription, the workspace returns to Free.");
|
|
1701
|
+
} else if (billing2 && body.plan === "free" && body.trialAvailable) {
|
|
1702
|
+
lines.push(
|
|
1703
|
+
`The one-time ${TRIAL_DAYS}-day Everything trial (no card) is unused.`,
|
|
1704
|
+
"An owner or admin can start it at the link above."
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1707
|
+
if (body.allowance > 0) {
|
|
1708
|
+
const ratio = body.usage.responses / body.allowance;
|
|
1709
|
+
if (ratio >= 0.8) {
|
|
1710
|
+
lines.push(
|
|
1711
|
+
ratio >= 1 ? `${body.usage.responses > body.allowance ? "Past this month's allowance." : "This month's allowance is used up."} Forms keep collecting and nothing is dropped.` : `${Math.floor(ratio * 100)}% of this month's allowance is used. Forms keep collecting if it runs over.`
|
|
1712
|
+
);
|
|
1713
|
+
if (billing2) {
|
|
1714
|
+
const next = fittingVolume(body);
|
|
1715
|
+
if (next) {
|
|
1716
|
+
lines.push(
|
|
1717
|
+
`${ratio >= 1 ? "The volume that fits" : "The next volume"} is Everything ${next.tier} (${count(next.responses)} a month).`
|
|
1718
|
+
);
|
|
1719
|
+
} else {
|
|
1720
|
+
lines.push(
|
|
1721
|
+
`For steady volume above ${count(body.allowance)} a month, write to hello@fillo.so.`
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
if (!billing2) lines.push("Billing isn't enabled on this Fillo server.");
|
|
1728
|
+
return lines;
|
|
1729
|
+
}
|
|
1730
|
+
async function billing(subcommand, flags) {
|
|
1731
|
+
if (subcommand === "help") return billingHelp();
|
|
1732
|
+
if (subcommand !== void 0) {
|
|
1733
|
+
die(`Unknown billing command: ${subcommand} (fillo billing takes no arguments).`);
|
|
1734
|
+
}
|
|
1735
|
+
const body = await callApi(
|
|
1736
|
+
"/cli/workspace/billing",
|
|
1737
|
+
{},
|
|
1738
|
+
{
|
|
1739
|
+
fallback: failed("billing"),
|
|
1740
|
+
expect: (b) => typeof b.plan === "string" && typeof b.allowance === "number" && typeof b.usage?.responses === "number"
|
|
1741
|
+
}
|
|
1742
|
+
);
|
|
1743
|
+
if (jsonMode(flags)) return emitResult(body);
|
|
1744
|
+
const rows = [
|
|
1745
|
+
["Plan:", planLine(body)],
|
|
1746
|
+
["Usage:", usageLine(body)]
|
|
1747
|
+
];
|
|
1748
|
+
const earlier = historyLine(body);
|
|
1749
|
+
if (earlier) rows.push(["Earlier:", earlier]);
|
|
1750
|
+
if (body.billingEnabled !== false) rows.push(["Manage:", `${manageUrl(body)} (owner or admin)`]);
|
|
1751
|
+
console.log("");
|
|
1752
|
+
for (const [label, value] of rows) console.log(` ${label.padEnd(9)}${value}`);
|
|
1753
|
+
const extra = notes(body);
|
|
1754
|
+
if (extra.length > 0) {
|
|
1755
|
+
console.log("");
|
|
1756
|
+
for (const line of extra) console.log(` ${dim(line)}`);
|
|
1757
|
+
}
|
|
1758
|
+
console.log("");
|
|
1759
|
+
}
|
|
1760
|
+
function billingHelp() {
|
|
1761
|
+
console.log(`
|
|
1762
|
+
${boldRaw("fillo billing")} \u2014 the workspace's plan and this month's response usage
|
|
1763
|
+
|
|
1764
|
+
${boldRaw("Usage")}
|
|
1765
|
+
billing Print the plan, usage against the monthly allowance,
|
|
1766
|
+
recent months, and the Manage link
|
|
1767
|
+
|
|
1768
|
+
${dimRaw("Read-only. Needs an owner or admin login. Subscribing, the one-time")}
|
|
1769
|
+
${dimRaw("30-day trial, volume changes, cards, and invoices are a human click in")}
|
|
1770
|
+
${dimRaw("Settings \u2192 Billing & plan (the printed Manage link). Allowances are soft:")}
|
|
1771
|
+
${dimRaw("forms keep collecting past them.")}
|
|
1772
|
+
${dimRaw("--json prints the raw server response on stdout.")}
|
|
1773
|
+
`);
|
|
1774
|
+
}
|
|
1775
|
+
var billingCommand = {
|
|
1776
|
+
name: "billing",
|
|
1777
|
+
flags: [],
|
|
1778
|
+
run: (args, flags) => billing(args[0], flags),
|
|
1779
|
+
help: billingHelp
|
|
1780
|
+
};
|
|
1781
|
+
|
|
1581
1782
|
// src/commands/branding.ts
|
|
1582
1783
|
function printState(body) {
|
|
1583
1784
|
const plan = body.plan === "pro" ? "Everything" : "Free";
|
|
1584
1785
|
console.log(` Plan: ${plan}`);
|
|
1585
1786
|
console.log(` Badge: ${body.poweredBy ? "shown" : "hidden"}`);
|
|
1586
1787
|
if (body.canHide === false) {
|
|
1587
|
-
console.log(dim(" Turning it off needs the Everything plan (Settings \u2192
|
|
1788
|
+
console.log(dim(" Turning it off needs the Everything plan (Settings \u2192 Billing & plan)."));
|
|
1588
1789
|
}
|
|
1589
1790
|
}
|
|
1590
1791
|
async function branding(subcommand, flags) {
|
|
@@ -1617,8 +1818,9 @@ function brandingHelp() {
|
|
|
1617
1818
|
branding off Hide the badge on Fillo-rendered forms (Everything plan)
|
|
1618
1819
|
branding on Show the badge again
|
|
1619
1820
|
|
|
1620
|
-
${dimRaw("Hiding needs the Everything plan
|
|
1621
|
-
${dimRaw("
|
|
1821
|
+
${dimRaw("Hiding needs the Everything plan: a workspace owner or admin subscribes")}
|
|
1822
|
+
${dimRaw("(or starts the one-time 30-day trial) in Settings \u2192 Billing & plan.")}
|
|
1823
|
+
${dimRaw("The CLI never buys the plan. Headless embeds never show a badge.")}
|
|
1622
1824
|
${dimRaw("--json prints the raw server response on stdout.")}
|
|
1623
1825
|
`);
|
|
1624
1826
|
}
|
|
@@ -3183,7 +3385,7 @@ var insightsCommand = {
|
|
|
3183
3385
|
};
|
|
3184
3386
|
|
|
3185
3387
|
// src/commands/forms.ts
|
|
3186
|
-
import { readFileSync as
|
|
3388
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
3187
3389
|
import { isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
|
|
3188
3390
|
import { pathToFileURL } from "url";
|
|
3189
3391
|
|
|
@@ -3640,7 +3842,7 @@ var $ZodCheckStringFormat = /* @__PURE__ */ $constructor("$ZodCheckStringFormat"
|
|
|
3640
3842
|
});
|
|
3641
3843
|
|
|
3642
3844
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/versions.js
|
|
3643
|
-
var
|
|
3845
|
+
var version2 = {
|
|
3644
3846
|
major: 4,
|
|
3645
3847
|
minor: 4,
|
|
3646
3848
|
patch: 3
|
|
@@ -3652,7 +3854,7 @@ var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
|
|
|
3652
3854
|
inst ?? (inst = {});
|
|
3653
3855
|
inst._zod.def = def;
|
|
3654
3856
|
inst._zod.bag = inst._zod.bag || {};
|
|
3655
|
-
inst._zod.version =
|
|
3857
|
+
inst._zod.version = version2;
|
|
3656
3858
|
const checks = [...inst._zod.def.checks ?? []];
|
|
3657
3859
|
if (inst._zod.traits.has("$ZodCheck")) {
|
|
3658
3860
|
checks.unshift(inst);
|
|
@@ -4689,7 +4891,7 @@ var schemaShape = object({
|
|
|
4689
4891
|
});
|
|
4690
4892
|
var MAX_SCHEMA_VERSION = 1;
|
|
4691
4893
|
var FILLO_SCHEMA_VERSION = 1;
|
|
4692
|
-
var FILLO_SDK_VERSION = true ? "0.
|
|
4894
|
+
var FILLO_SDK_VERSION = true ? "0.24.0" : "0.0.0-dev";
|
|
4693
4895
|
function str(value, max, fallback = "") {
|
|
4694
4896
|
return typeof value === "string" ? value.trim().slice(0, max) : fallback;
|
|
4695
4897
|
}
|
|
@@ -5664,7 +5866,7 @@ async function loadResponseData(file) {
|
|
|
5664
5866
|
die("Test response data must be a .json file (or - for JSON on stdin).");
|
|
5665
5867
|
}
|
|
5666
5868
|
try {
|
|
5667
|
-
value = JSON.parse(
|
|
5869
|
+
value = JSON.parse(readFileSync4(abs, "utf8"));
|
|
5668
5870
|
} catch (error) {
|
|
5669
5871
|
die(`Couldn't read ${file}: ${error.message}`);
|
|
5670
5872
|
}
|
|
@@ -5746,7 +5948,7 @@ async function loadSchema(file, allowCode) {
|
|
|
5746
5948
|
}
|
|
5747
5949
|
try {
|
|
5748
5950
|
if (abs.endsWith(".json")) {
|
|
5749
|
-
const parsed = JSON.parse(
|
|
5951
|
+
const parsed = JSON.parse(readFileSync4(abs, "utf8"));
|
|
5750
5952
|
if (parsed == null) die(`${file} is empty \u2014 it must contain a form schema.`);
|
|
5751
5953
|
return parsed;
|
|
5752
5954
|
}
|
|
@@ -7439,7 +7641,7 @@ async function summary(handle, flags) {
|
|
|
7439
7641
|
);
|
|
7440
7642
|
const distribution = Object.entries(field2.distribution ?? {});
|
|
7441
7643
|
if (distribution.length > 0) {
|
|
7442
|
-
const shown = distribution.slice(0, 5).map(([label,
|
|
7644
|
+
const shown = distribution.slice(0, 5).map(([label, count2]) => `${terminalText(label)} \u2014 ${count2}`).join(" \xB7 ");
|
|
7443
7645
|
const rest = distribution.length - Math.min(distribution.length, 5);
|
|
7444
7646
|
console.log(` ${shown}${rest > 0 ? dim(` +${rest} more`) : ""}`);
|
|
7445
7647
|
}
|
|
@@ -8768,10 +8970,10 @@ async function origins(subcommand, flags) {
|
|
|
8768
8970
|
body.origins.length === 0 ? ` ${okMark()} Cleared the allow-list \u2014 any origin may stage with the publishable key.` : ` ${okMark()} Allowed origins: ${terminalText(body.origins.join(", "))}.`
|
|
8769
8971
|
);
|
|
8770
8972
|
}
|
|
8771
|
-
function printProtectedForms(
|
|
8772
|
-
if (
|
|
8973
|
+
function printProtectedForms(count2) {
|
|
8974
|
+
if (count2 === 0) return;
|
|
8773
8975
|
console.log(
|
|
8774
|
-
` ${dim(`${
|
|
8976
|
+
` ${dim(`${count2} ${count2 === 1 ? "form requires" : "forms require"} verified respondents.`)}`
|
|
8775
8977
|
);
|
|
8776
8978
|
}
|
|
8777
8979
|
async function identityStatus(flags) {
|
|
@@ -9149,6 +9351,7 @@ var REGISTRY = [
|
|
|
9149
9351
|
webhooksCommand,
|
|
9150
9352
|
settingsCommand,
|
|
9151
9353
|
brandingCommand,
|
|
9354
|
+
billingCommand,
|
|
9152
9355
|
membersCommand,
|
|
9153
9356
|
workspaceCommand,
|
|
9154
9357
|
syncTokensCommand,
|
|
@@ -9157,13 +9360,13 @@ var REGISTRY = [
|
|
|
9157
9360
|
deleteCommand
|
|
9158
9361
|
];
|
|
9159
9362
|
var COMMANDS = /* @__PURE__ */ new Map();
|
|
9160
|
-
for (const
|
|
9161
|
-
COMMANDS.set(
|
|
9162
|
-
for (const alias of
|
|
9363
|
+
for (const command2 of REGISTRY) {
|
|
9364
|
+
COMMANDS.set(command2.name, command2);
|
|
9365
|
+
for (const alias of command2.aliases ?? []) COMMANDS.set(alias, command2);
|
|
9163
9366
|
}
|
|
9164
9367
|
function printVersion() {
|
|
9165
9368
|
try {
|
|
9166
|
-
const pkg = JSON.parse(
|
|
9369
|
+
const pkg = JSON.parse(readFileSync5(new URL("../package.json", import.meta.url), "utf8"));
|
|
9167
9370
|
console.log(pkg.version);
|
|
9168
9371
|
} catch {
|
|
9169
9372
|
console.log("unknown");
|
|
@@ -9240,6 +9443,7 @@ function help() {
|
|
|
9240
9443
|
${dimRaw("--auth none|bearer|x-api-key secret via prompt or env, never argv")}
|
|
9241
9444
|
settings <cmd> A form's operational settings: get, set key=value
|
|
9242
9445
|
branding [on|off] The workspace's "Powered by Fillo" badge (off needs Everything)
|
|
9446
|
+
billing Plan and this month's response usage (read-only)
|
|
9243
9447
|
members <cmd> Workspace members: list, invite, cancel-invite, role, remove
|
|
9244
9448
|
workspace rename Rename the workspace (ids, keys, and URLs stay put)
|
|
9245
9449
|
sync-tokens <cmd> Stage-only deploy credentials: list, create, revoke
|
|
@@ -9266,18 +9470,21 @@ async function main() {
|
|
|
9266
9470
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
9267
9471
|
const { positional, flags } = parseFlags(rest);
|
|
9268
9472
|
if (flags.json === true) enableJsonOutput();
|
|
9269
|
-
const
|
|
9270
|
-
validateFlags(cmd, flags,
|
|
9473
|
+
const command2 = cmd ? COMMANDS.get(cmd) : void 0;
|
|
9474
|
+
validateFlags(cmd, flags, command2?.flags);
|
|
9271
9475
|
if (cmd === "version" || cmd === "--version" || cmd === "-v" || flags.version === true || flags.v === true) {
|
|
9272
9476
|
return printVersion();
|
|
9273
9477
|
}
|
|
9274
9478
|
if (flags.help === true || flags.h === true) {
|
|
9275
|
-
return (
|
|
9479
|
+
return (command2?.help ?? help)();
|
|
9276
9480
|
}
|
|
9277
9481
|
if (cmd === void 0 || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
9278
9482
|
return help();
|
|
9279
9483
|
}
|
|
9280
|
-
if (
|
|
9484
|
+
if (command2) {
|
|
9485
|
+
setClientCommand(command2.name);
|
|
9486
|
+
return command2.run(positional, flags);
|
|
9487
|
+
}
|
|
9281
9488
|
console.error(`Unknown command: ${terminalText(cmd)}`);
|
|
9282
9489
|
help();
|
|
9283
9490
|
return process.exit(2);
|
|
@@ -160,17 +160,27 @@ could not be verified.
|
|
|
160
160
|
`"light"` or `"dark"`. Use `"auto"` only for a deliberately OS-driven page.
|
|
161
161
|
Verify every theme the host exposes, at desktop and narrow widths.
|
|
162
162
|
- The "Powered by Fillo" badge is a server-driven workspace checkbox: always
|
|
163
|
-
visible on Free, hideable on the paid plan
|
|
164
|
-
someone turns it off), and never present in a fully
|
|
165
|
-
(headless is free on every plan). Never hide or obscure it
|
|
166
|
-
edits, or style overrides — on Free that violates Fillo's
|
|
167
|
-
user asks to remove it: with a CLI login on
|
|
163
|
+
visible on Free, hideable on the paid Everything plan and its trial (default
|
|
164
|
+
stays visible until someone turns it off), and never present in a fully
|
|
165
|
+
headless layout (headless is free on every plan). Never hide or obscure it
|
|
166
|
+
with CSS, DOM edits, or style overrides — on Free that violates Fillo's
|
|
167
|
+
terms. When the user asks to remove it: with a CLI login on Everything, run
|
|
168
168
|
`npx @usefillo/cli@latest branding off` (`branding` alone prints the state,
|
|
169
169
|
`branding on` restores it). On Free, present the two honest paths and let
|
|
170
|
-
the user choose:
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
the user choose: a workspace owner or admin subscribes to Everything (or
|
|
171
|
+
starts the workspace's one-time 30-day no-card trial) in Fillo Settings →
|
|
172
|
+
Billing & plan, or rebuild the embed headless with the host's own
|
|
173
173
|
components. Do not bring up plans unprompted.
|
|
174
|
+
- Billing is a human's decision. Everything is real paid billing through
|
|
175
|
+
Stripe checkout, in monthly volumes (5k, 25k, or 100k responses; Free is
|
|
176
|
+
200). Never select a plan, start the trial, change a volume, or open
|
|
177
|
+
checkout on the user's behalf. When the user asks about their plan or usage,
|
|
178
|
+
run `npx @usefillo/cli@latest billing` (owner or admin login; `--json` for
|
|
179
|
+
the raw result) or the MCP `fillo_get_billing` tool, report the plan and
|
|
180
|
+
this month's usage against the allowance, and hand the human the printed
|
|
181
|
+
Manage link. Allowances are soft: forms never stop collecting over them, so
|
|
182
|
+
never describe going over as losing responses. For prices, read
|
|
183
|
+
`https://fillo.so/pricing.md` rather than quoting from memory.
|
|
174
184
|
- Use `onSubmitted` only for host-side follow-up after Fillo stores the
|
|
175
185
|
response. Use a webhook when another backend needs durable delivery.
|
|
176
186
|
- When the user asks to build, deploy, or make the form usable, use a plain
|
|
@@ -301,8 +301,12 @@ reference: `https://fillo.so/docs/cli/reference.md`.
|
|
|
301
301
|
- **Workspace:** `workspace rename`, `project rename`, `members`
|
|
302
302
|
(`invite|cancel-invite|role|remove`), `keys create|list|revoke`,
|
|
303
303
|
`tokens create-connector|list|revoke`, `sync-tokens create|list|revoke`,
|
|
304
|
-
`developers policy|origins|identity`, `branding`, `
|
|
305
|
-
`delete form|workspace`.
|
|
304
|
+
`developers policy|origins|identity`, `branding`, `billing` (read-only plan
|
|
305
|
+
and usage), `mcp list|revoke`, `delete form|workspace`.
|
|
306
|
+
|
|
307
|
+
Paying for Fillo is the exception. Subscribing, the one-time trial, changing
|
|
308
|
+
a volume, and cards or invoices stay a human click in Settings → Billing &
|
|
309
|
+
plan; `billing` only reports and prints that page's Manage link.
|
|
306
310
|
|
|
307
311
|
Add `--json` for a machine-readable result: one JSON object on stdout, progress
|
|
308
312
|
and consent notices as JSON lines on stderr, `{"error": "…"}` and a non-zero
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usefillo/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Create and publish Fillo forms, and install the Fillo Agent Skill.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
"@types/node": "^22.10.0",
|
|
35
35
|
"tsup": "^8.4.0",
|
|
36
36
|
"typescript": "^5.8.3",
|
|
37
|
-
"@usefillo/core": "0.
|
|
37
|
+
"@usefillo/core": "0.24.0"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsup && node scripts/copy-skill.mjs",
|
|
41
|
-
"test": "node scripts/test-skill-bundle.mjs && node scripts/test-skill-install.mjs && node scripts/test-agent-account.mjs && node scripts/test-stage-push.mjs && node scripts/test-local-validation.mjs && node scripts/test-loopback.mjs && node scripts/test-init.mjs && node scripts/test-api-target.mjs && node scripts/test-init-tty.mjs && node scripts/test-keys-claim.mjs && node scripts/test-responses.mjs && node scripts/test-workspace-commands.mjs && node scripts/test-form-lifecycle.mjs && node scripts/test-discord-tokens.mjs && node scripts/test-confirm.mjs && node scripts/test-integrations.mjs && node scripts/test-responses-ops.mjs && node scripts/test-workspace-admin.mjs",
|
|
41
|
+
"test": "node scripts/test-skill-bundle.mjs && node scripts/test-skill-install.mjs && node scripts/test-agent-account.mjs && node scripts/test-stage-push.mjs && node scripts/test-local-validation.mjs && node scripts/test-loopback.mjs && node scripts/test-init.mjs && node scripts/test-api-target.mjs && node scripts/test-init-tty.mjs && node scripts/test-keys-claim.mjs && node scripts/test-responses.mjs && node scripts/test-workspace-commands.mjs && node scripts/test-form-lifecycle.mjs && node scripts/test-discord-tokens.mjs && node scripts/test-confirm.mjs && node scripts/test-integrations.mjs && node scripts/test-responses-ops.mjs && node scripts/test-workspace-admin.mjs && node scripts/test-billing.mjs",
|
|
42
42
|
"typecheck": "tsc --noEmit"
|
|
43
43
|
}
|
|
44
44
|
}
|