@usefillo/cli 0.23.1 → 0.24.1
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
|
@@ -104,7 +104,7 @@ function printTable(header, rows) {
|
|
|
104
104
|
for (const cells of rows) console.log(line(cells));
|
|
105
105
|
}
|
|
106
106
|
var dateOnly = (iso) => iso.slice(0, 10);
|
|
107
|
-
var plural = (
|
|
107
|
+
var plural = (count2, noun) => `${count2} ${noun}${count2 === 1 ? "" : "s"}`;
|
|
108
108
|
function die(msg) {
|
|
109
109
|
if (jsonOutput) emitResult({ error: terminalText(msg) });
|
|
110
110
|
console.error(`${failMark()} ${terminalText(msg)}`);
|
|
@@ -1600,13 +1600,185 @@ var agentCommand = {
|
|
|
1600
1600
|
help: agentHelp
|
|
1601
1601
|
};
|
|
1602
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 MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
1610
|
+
var count = (value) => new Intl.NumberFormat("en-US").format(value);
|
|
1611
|
+
function parseDate(iso) {
|
|
1612
|
+
if (typeof iso !== "string" || !iso) return null;
|
|
1613
|
+
const date2 = new Date(iso);
|
|
1614
|
+
return Number.isNaN(date2.getTime()) ? null : date2;
|
|
1615
|
+
}
|
|
1616
|
+
var longDate = (date2) => `${date2.getUTCDate()} ${MONTHS[date2.getUTCMonth()]} ${date2.getUTCFullYear()}`;
|
|
1617
|
+
var shortDate = (date2) => `${date2.getUTCDate()} ${MONTHS[date2.getUTCMonth()]}`;
|
|
1618
|
+
function periodLabel(period, currentYear) {
|
|
1619
|
+
const match = /^(\d{4})-(\d{2})$/.exec(period);
|
|
1620
|
+
const month = match ? MONTHS[Number(match[2]) - 1] : void 0;
|
|
1621
|
+
if (!match || !month) return terminalText(period);
|
|
1622
|
+
return match[1] === currentYear ? month : `${month} ${match[1]}`;
|
|
1623
|
+
}
|
|
1624
|
+
function daysLeft(end, now = Date.now()) {
|
|
1625
|
+
const days = Math.max(0, Math.ceil((end.getTime() - now) / 864e5));
|
|
1626
|
+
return days === 0 ? "ends today" : days === 1 ? "1 day left" : `${days} days left`;
|
|
1627
|
+
}
|
|
1628
|
+
var tierName = (tier) => tier ? `Everything ${terminalText(tier)}` : "Everything";
|
|
1629
|
+
function planLine(body) {
|
|
1630
|
+
if (body.billingEnabled === false) {
|
|
1631
|
+
return body.plan === "free" ? "Free" : "Everything";
|
|
1632
|
+
}
|
|
1633
|
+
switch (body.plan) {
|
|
1634
|
+
case "free":
|
|
1635
|
+
return `Free \xB7 ${count(body.allowance)} responses a month`;
|
|
1636
|
+
case "trial": {
|
|
1637
|
+
const end = parseDate(body.trialEndsAt);
|
|
1638
|
+
const ends = end ? ` \xB7 ends ${longDate(end)} (${daysLeft(end)})` : "";
|
|
1639
|
+
return `Everything trial \xB7 ${count(body.allowance)} responses${ends}`;
|
|
1640
|
+
}
|
|
1641
|
+
case "complimentary":
|
|
1642
|
+
return `${tierName(body.tier)} \xB7 complimentary (no charge, no renewal date)`;
|
|
1643
|
+
case "subscribed": {
|
|
1644
|
+
const billed = body.interval === "year" ? "billed yearly" : "billed monthly";
|
|
1645
|
+
const end = parseDate(body.currentPeriodEnd);
|
|
1646
|
+
let next = "";
|
|
1647
|
+
if (body.status === "past_due") next = " \xB7 payment failed";
|
|
1648
|
+
else if (body.cancelAtPeriodEnd) next = end ? ` \xB7 cancels ${longDate(end)}` : " \xB7 canceling";
|
|
1649
|
+
else if (end)
|
|
1650
|
+
next = `${body.status === "trialing" ? " \xB7 first payment" : " \xB7 renews"} ${longDate(end)}`;
|
|
1651
|
+
return `${tierName(body.tier)} \xB7 ${billed}${next}`;
|
|
1652
|
+
}
|
|
1653
|
+
default:
|
|
1654
|
+
return terminalText(body.plan);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
function usageLine(body) {
|
|
1658
|
+
const { responses: responses2 } = body.usage;
|
|
1659
|
+
const reset = parseDate(body.usage.resetsAt);
|
|
1660
|
+
const resets = reset ? ` \xB7 resets ${shortDate(reset)}` : "";
|
|
1661
|
+
if (!(body.allowance > 0)) return `${count(responses2)} responses this month${resets}`;
|
|
1662
|
+
const percent = Math.floor(responses2 / body.allowance * 100);
|
|
1663
|
+
return `${count(responses2)} of ${count(body.allowance)} responses this month (${percent}%)${resets}`;
|
|
1664
|
+
}
|
|
1665
|
+
function historyLine(body) {
|
|
1666
|
+
const currentYear = body.usage.period?.slice(0, 4);
|
|
1667
|
+
const earlier = (body.history ?? []).filter(
|
|
1668
|
+
(row) => typeof row.period === "string" && row.period !== body.usage.period && typeof row.responses === "number"
|
|
1669
|
+
);
|
|
1670
|
+
if (!earlier.some((row) => row.responses > 0)) return null;
|
|
1671
|
+
return earlier.map((row) => `${periodLabel(row.period, currentYear)} ${count(row.responses)}`).join(" \xB7 ");
|
|
1672
|
+
}
|
|
1673
|
+
function manageUrl(body) {
|
|
1674
|
+
try {
|
|
1675
|
+
const url = new URL(body.manageUrl ?? "");
|
|
1676
|
+
if (url.protocol === "https:" || url.protocol === "http:") return terminalText(url.href);
|
|
1677
|
+
} catch {
|
|
1678
|
+
}
|
|
1679
|
+
return `${terminalText(API)}/settings/plan`;
|
|
1680
|
+
}
|
|
1681
|
+
function fittingVolume(body) {
|
|
1682
|
+
return VOLUMES.find(
|
|
1683
|
+
(volume) => volume.responses > body.allowance && volume.responses >= body.usage.responses
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
function notes(body) {
|
|
1687
|
+
const lines = [];
|
|
1688
|
+
const billing2 = body.billingEnabled !== false;
|
|
1689
|
+
if (billing2 && body.plan === "subscribed" && body.status === "past_due") {
|
|
1690
|
+
lines.push(
|
|
1691
|
+
"The last payment failed. Stripe is retrying it, and Everything stays on meanwhile.",
|
|
1692
|
+
"An owner or admin updates the card from Manage billing at the link above."
|
|
1693
|
+
);
|
|
1694
|
+
} else if (billing2 && body.plan === "subscribed" && body.cancelAtPeriodEnd) {
|
|
1695
|
+
lines.push(
|
|
1696
|
+
"Everything stays active until the period ends; the workspace returns to Free after that."
|
|
1697
|
+
);
|
|
1698
|
+
} else if (billing2 && body.plan === "trial") {
|
|
1699
|
+
lines.push("When the trial ends without a subscription, the workspace returns to Free.");
|
|
1700
|
+
}
|
|
1701
|
+
if (body.allowance > 0) {
|
|
1702
|
+
const ratio = body.usage.responses / body.allowance;
|
|
1703
|
+
if (ratio >= 0.8) {
|
|
1704
|
+
lines.push(
|
|
1705
|
+
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.`
|
|
1706
|
+
);
|
|
1707
|
+
if (billing2) {
|
|
1708
|
+
const next = fittingVolume(body);
|
|
1709
|
+
if (next) {
|
|
1710
|
+
lines.push(
|
|
1711
|
+
`${ratio >= 1 ? "The volume that fits" : "The next volume"} is Everything ${next.tier} (${count(next.responses)} a month).`
|
|
1712
|
+
);
|
|
1713
|
+
} else {
|
|
1714
|
+
lines.push(
|
|
1715
|
+
`For steady volume above ${count(body.allowance)} a month, write to hello@fillo.so.`
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
if (!billing2) lines.push("Billing isn't enabled on this Fillo server.");
|
|
1722
|
+
return lines;
|
|
1723
|
+
}
|
|
1724
|
+
async function billing(subcommand, flags) {
|
|
1725
|
+
if (subcommand === "help") return billingHelp();
|
|
1726
|
+
if (subcommand !== void 0) {
|
|
1727
|
+
die(`Unknown billing command: ${subcommand} (fillo billing takes no arguments).`);
|
|
1728
|
+
}
|
|
1729
|
+
const body = await callApi(
|
|
1730
|
+
"/cli/workspace/billing",
|
|
1731
|
+
{},
|
|
1732
|
+
{
|
|
1733
|
+
fallback: failed("billing"),
|
|
1734
|
+
expect: (b) => typeof b.plan === "string" && typeof b.allowance === "number" && typeof b.usage?.responses === "number"
|
|
1735
|
+
}
|
|
1736
|
+
);
|
|
1737
|
+
if (jsonMode(flags)) return emitResult(body);
|
|
1738
|
+
const rows = [
|
|
1739
|
+
["Plan:", planLine(body)],
|
|
1740
|
+
["Usage:", usageLine(body)]
|
|
1741
|
+
];
|
|
1742
|
+
const earlier = historyLine(body);
|
|
1743
|
+
if (earlier) rows.push(["Earlier:", earlier]);
|
|
1744
|
+
if (body.billingEnabled !== false) rows.push(["Manage:", `${manageUrl(body)} (owner or admin)`]);
|
|
1745
|
+
console.log("");
|
|
1746
|
+
for (const [label, value] of rows) console.log(` ${label.padEnd(9)}${value}`);
|
|
1747
|
+
const extra = notes(body);
|
|
1748
|
+
if (extra.length > 0) {
|
|
1749
|
+
console.log("");
|
|
1750
|
+
for (const line of extra) console.log(` ${dim(line)}`);
|
|
1751
|
+
}
|
|
1752
|
+
console.log("");
|
|
1753
|
+
}
|
|
1754
|
+
function billingHelp() {
|
|
1755
|
+
console.log(`
|
|
1756
|
+
${boldRaw("fillo billing")} \u2014 the workspace's plan and this month's response usage
|
|
1757
|
+
|
|
1758
|
+
${boldRaw("Usage")}
|
|
1759
|
+
billing Print the plan, usage against the monthly allowance,
|
|
1760
|
+
recent months, and the Manage link
|
|
1761
|
+
|
|
1762
|
+
${dimRaw("Read-only. Needs an owner or admin login. Subscribing, volume changes,")}
|
|
1763
|
+
${dimRaw("cards, and invoices are a human click in Settings \u2192 Billing & plan (the")}
|
|
1764
|
+
${dimRaw("printed Manage link). Allowances are soft: forms keep collecting past them.")}
|
|
1765
|
+
${dimRaw("--json prints the raw server response on stdout.")}
|
|
1766
|
+
`);
|
|
1767
|
+
}
|
|
1768
|
+
var billingCommand = {
|
|
1769
|
+
name: "billing",
|
|
1770
|
+
flags: [],
|
|
1771
|
+
run: (args, flags) => billing(args[0], flags),
|
|
1772
|
+
help: billingHelp
|
|
1773
|
+
};
|
|
1774
|
+
|
|
1603
1775
|
// src/commands/branding.ts
|
|
1604
1776
|
function printState(body) {
|
|
1605
1777
|
const plan = body.plan === "pro" ? "Everything" : "Free";
|
|
1606
1778
|
console.log(` Plan: ${plan}`);
|
|
1607
1779
|
console.log(` Badge: ${body.poweredBy ? "shown" : "hidden"}`);
|
|
1608
1780
|
if (body.canHide === false) {
|
|
1609
|
-
console.log(dim(" Turning it off needs the Everything plan (Settings \u2192
|
|
1781
|
+
console.log(dim(" Turning it off needs the Everything plan (Settings \u2192 Billing & plan)."));
|
|
1610
1782
|
}
|
|
1611
1783
|
}
|
|
1612
1784
|
async function branding(subcommand, flags) {
|
|
@@ -1639,8 +1811,9 @@ function brandingHelp() {
|
|
|
1639
1811
|
branding off Hide the badge on Fillo-rendered forms (Everything plan)
|
|
1640
1812
|
branding on Show the badge again
|
|
1641
1813
|
|
|
1642
|
-
${dimRaw("Hiding needs the Everything plan
|
|
1643
|
-
${dimRaw("
|
|
1814
|
+
${dimRaw("Hiding needs the Everything plan: a workspace owner or admin subscribes")}
|
|
1815
|
+
${dimRaw("in Settings \u2192 Billing & plan.")}
|
|
1816
|
+
${dimRaw("The CLI never buys the plan. Headless embeds never show a badge.")}
|
|
1644
1817
|
${dimRaw("--json prints the raw server response on stdout.")}
|
|
1645
1818
|
`);
|
|
1646
1819
|
}
|
|
@@ -4711,7 +4884,7 @@ var schemaShape = object({
|
|
|
4711
4884
|
});
|
|
4712
4885
|
var MAX_SCHEMA_VERSION = 1;
|
|
4713
4886
|
var FILLO_SCHEMA_VERSION = 1;
|
|
4714
|
-
var FILLO_SDK_VERSION = true ? "0.
|
|
4887
|
+
var FILLO_SDK_VERSION = true ? "0.24.1" : "0.0.0-dev";
|
|
4715
4888
|
function str(value, max, fallback = "") {
|
|
4716
4889
|
return typeof value === "string" ? value.trim().slice(0, max) : fallback;
|
|
4717
4890
|
}
|
|
@@ -7461,7 +7634,7 @@ async function summary(handle, flags) {
|
|
|
7461
7634
|
);
|
|
7462
7635
|
const distribution = Object.entries(field2.distribution ?? {});
|
|
7463
7636
|
if (distribution.length > 0) {
|
|
7464
|
-
const shown = distribution.slice(0, 5).map(([label,
|
|
7637
|
+
const shown = distribution.slice(0, 5).map(([label, count2]) => `${terminalText(label)} \u2014 ${count2}`).join(" \xB7 ");
|
|
7465
7638
|
const rest = distribution.length - Math.min(distribution.length, 5);
|
|
7466
7639
|
console.log(` ${shown}${rest > 0 ? dim(` +${rest} more`) : ""}`);
|
|
7467
7640
|
}
|
|
@@ -8790,10 +8963,10 @@ async function origins(subcommand, flags) {
|
|
|
8790
8963
|
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(", "))}.`
|
|
8791
8964
|
);
|
|
8792
8965
|
}
|
|
8793
|
-
function printProtectedForms(
|
|
8794
|
-
if (
|
|
8966
|
+
function printProtectedForms(count2) {
|
|
8967
|
+
if (count2 === 0) return;
|
|
8795
8968
|
console.log(
|
|
8796
|
-
` ${dim(`${
|
|
8969
|
+
` ${dim(`${count2} ${count2 === 1 ? "form requires" : "forms require"} verified respondents.`)}`
|
|
8797
8970
|
);
|
|
8798
8971
|
}
|
|
8799
8972
|
async function identityStatus(flags) {
|
|
@@ -9171,6 +9344,7 @@ var REGISTRY = [
|
|
|
9171
9344
|
webhooksCommand,
|
|
9172
9345
|
settingsCommand,
|
|
9173
9346
|
brandingCommand,
|
|
9347
|
+
billingCommand,
|
|
9174
9348
|
membersCommand,
|
|
9175
9349
|
workspaceCommand,
|
|
9176
9350
|
syncTokensCommand,
|
|
@@ -9262,6 +9436,7 @@ function help() {
|
|
|
9262
9436
|
${dimRaw("--auth none|bearer|x-api-key secret via prompt or env, never argv")}
|
|
9263
9437
|
settings <cmd> A form's operational settings: get, set key=value
|
|
9264
9438
|
branding [on|off] The workspace's "Powered by Fillo" badge (off needs Everything)
|
|
9439
|
+
billing Plan and this month's response usage (read-only)
|
|
9265
9440
|
members <cmd> Workspace members: list, invite, cancel-invite, role, remove
|
|
9266
9441
|
workspace rename Rename the workspace (ids, keys, and URLs stay put)
|
|
9267
9442
|
sync-tokens <cmd> Stage-only deploy credentials: list, create, revoke
|
|
@@ -160,17 +160,26 @@ 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 (default stays visible
|
|
164
|
-
someone turns it off), and never present in a fully headless layout
|
|
163
|
+
visible on Free, hideable on the paid Everything plan (default stays visible
|
|
164
|
+
until someone turns it off), and never present in a fully headless layout
|
|
165
165
|
(headless is free on every plan). Never hide or obscure it with CSS, DOM
|
|
166
166
|
edits, or style overrides — on Free that violates Fillo's terms. When the
|
|
167
|
-
user asks to remove it: with a CLI login on
|
|
167
|
+
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
|
-
|
|
173
|
-
|
|
170
|
+
the user choose: a workspace owner or admin subscribes to Everything in
|
|
171
|
+
Fillo Settings → Billing & plan, or rebuild the embed headless with the
|
|
172
|
+
host's own components. Do not bring up plans unprompted.
|
|
173
|
+
- Billing is a human's decision. Every plan includes every feature; Everything
|
|
174
|
+
is real paid billing through Stripe checkout for more monthly responses (5k,
|
|
175
|
+
25k, or 100k; Free is 200) or a hidden badge. Never select a plan, change a
|
|
176
|
+
volume, or open checkout on the user's behalf. When the user asks about
|
|
177
|
+
their plan or usage, run `npx @usefillo/cli@latest billing` (owner or admin
|
|
178
|
+
login; `--json` for the raw result) or the MCP `fillo_get_billing` tool,
|
|
179
|
+
report the plan and this month's usage against the allowance, and hand the
|
|
180
|
+
human the printed Manage link. Allowances are soft: forms never stop
|
|
181
|
+
collecting over them, so never describe going over as losing responses. For
|
|
182
|
+
prices, read `https://fillo.so/pricing.md` rather than quoting from memory.
|
|
174
183
|
- Use `onSubmitted` only for host-side follow-up after Fillo stores the
|
|
175
184
|
response. Use a webhook when another backend needs durable delivery.
|
|
176
185
|
- 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, changing a volume, and cards
|
|
308
|
+
or invoices stay a human click in Settings → Billing & plan; `billing` only
|
|
309
|
+
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.1",
|
|
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.1"
|
|
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
|
}
|