@wervt/cli 0.2.8 → 0.2.10
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.mjs +90 -1
- package/package.json +1 -1
- package/skill/SKILL.md +9 -5
package/dist/index.mjs
CHANGED
|
@@ -204,6 +204,15 @@ const deploy = defineCommand({
|
|
|
204
204
|
default: true,
|
|
205
205
|
description: "Build before deploying"
|
|
206
206
|
},
|
|
207
|
+
check: {
|
|
208
|
+
type: "string",
|
|
209
|
+
description: "Pages to request once the release started, a 5xx fails (default: /)"
|
|
210
|
+
},
|
|
211
|
+
"rollback-on-fail": {
|
|
212
|
+
type: "boolean",
|
|
213
|
+
default: false,
|
|
214
|
+
description: "Go back to the previous release when a page check fails"
|
|
215
|
+
},
|
|
207
216
|
...connection
|
|
208
217
|
},
|
|
209
218
|
async run({ args }) {
|
|
@@ -227,7 +236,10 @@ const deploy = defineCommand({
|
|
|
227
236
|
spinner.stop(`Packed ${bundle.files} files (${(bundle.data.byteLength / 1024).toFixed(0)} kB)`);
|
|
228
237
|
spinner.start("Uploading");
|
|
229
238
|
const source = await deploySource(dir);
|
|
230
|
-
const
|
|
239
|
+
const query = new URLSearchParams();
|
|
240
|
+
if (args.check !== void 0) query.set("check", args.check);
|
|
241
|
+
if (args["rollback-on-fail"]) query.set("rollbackOnFail", "1");
|
|
242
|
+
const res = await api(`/v1/apps/${name}/deploys${query.size ? `?${query}` : ""}`, args, {
|
|
231
243
|
method: "POST",
|
|
232
244
|
headers: {
|
|
233
245
|
"content-type": "application/gzip",
|
|
@@ -273,6 +285,15 @@ const deploy = defineCommand({
|
|
|
273
285
|
process.exitCode = 1;
|
|
274
286
|
return;
|
|
275
287
|
}
|
|
288
|
+
if (event.step === "check-failed") {
|
|
289
|
+
const failed = event.failed.map((check) => `${check.path} (${check.status || "no answer"})`).join(", ");
|
|
290
|
+
p.log.error(`Release ${event.release} started, but failed: ${failed}`);
|
|
291
|
+
if (event.message) p.log.message(event.message);
|
|
292
|
+
if (event.restored) p.log.info(`${name} is back on ${event.restored}.`);
|
|
293
|
+
else p.log.warn(`It stays active. Fix it or run \`wervt rollback ${name}\`.`);
|
|
294
|
+
process.exitCode = 1;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
276
297
|
if (event.step === "storage-failed") {
|
|
277
298
|
p.log.warn(`Storage isn't set up: ${event.message}. The app runs; files won't work yet.`);
|
|
278
299
|
continue;
|
|
@@ -695,6 +716,73 @@ const logs = defineCommand({
|
|
|
695
716
|
for (const entry of entries.reverse()) printLog(entry, Boolean(args.json));
|
|
696
717
|
}
|
|
697
718
|
});
|
|
719
|
+
const SPARKS = "▁▂▃▄▅▆▇█";
|
|
720
|
+
const stats = defineCommand({
|
|
721
|
+
meta: {
|
|
722
|
+
name: "stats",
|
|
723
|
+
description: "Show an app's visitors, page views and top pages (cookieless analytics)"
|
|
724
|
+
},
|
|
725
|
+
args: {
|
|
726
|
+
app: {
|
|
727
|
+
type: "positional",
|
|
728
|
+
required: true,
|
|
729
|
+
description: "App name"
|
|
730
|
+
},
|
|
731
|
+
range: {
|
|
732
|
+
type: "string",
|
|
733
|
+
default: "7d",
|
|
734
|
+
description: "24h, 7d, 30d or 90d"
|
|
735
|
+
},
|
|
736
|
+
path: {
|
|
737
|
+
type: "string",
|
|
738
|
+
description: "Only views of this path"
|
|
739
|
+
},
|
|
740
|
+
referrer: {
|
|
741
|
+
type: "string",
|
|
742
|
+
description: "Only visits from this referrer host ('' for direct)"
|
|
743
|
+
},
|
|
744
|
+
country: {
|
|
745
|
+
type: "string",
|
|
746
|
+
description: "Only visits from this country (ISO code, e.g. DE)"
|
|
747
|
+
},
|
|
748
|
+
json: {
|
|
749
|
+
type: "boolean",
|
|
750
|
+
description: "Print JSON"
|
|
751
|
+
},
|
|
752
|
+
...connection
|
|
753
|
+
},
|
|
754
|
+
async run({ args }) {
|
|
755
|
+
const params = new URLSearchParams({ range: args.range });
|
|
756
|
+
for (const key of [
|
|
757
|
+
"path",
|
|
758
|
+
"referrer",
|
|
759
|
+
"country"
|
|
760
|
+
]) if (args[key] !== void 0) params.set(key, args[key]);
|
|
761
|
+
const report = await (await api(`/v1/apps/${args.app}/analytics?${params.toString()}`, args)).json();
|
|
762
|
+
if (args.json) return console.log(JSON.stringify(report));
|
|
763
|
+
const { totals, series } = report;
|
|
764
|
+
const peak = Math.max(1, ...series.map((point) => point.views));
|
|
765
|
+
const spark = series.map((point) => point.views ? SPARKS[Math.min(7, Math.floor(point.views / peak * 7.99))] : " ").join("");
|
|
766
|
+
console.log(`${styleText("bold", String(totals.visitors))} visitors ${styleText("bold", String(totals.views))} page views` + (totals.users ? ` ${styleText("bold", String(totals.users))} signed-in users` : "") + styleText("dim", ` (last ${report.range}, visitors unique per day)`));
|
|
767
|
+
console.log(styleText("cyan", spark), styleText("dim", `per ${report.bucket}, peak ${peak}`));
|
|
768
|
+
const section = (title, rows, none) => {
|
|
769
|
+
if (!rows.length) return;
|
|
770
|
+
console.log(`\n${styleText("bold", title)}`);
|
|
771
|
+
const width = Math.max(...rows.map((row) => String(row.visitors).length));
|
|
772
|
+
for (const row of rows.slice(0, 5)) console.log(` ${String(row.visitors).padStart(width)} ${row.value || styleText("dim", none)}`);
|
|
773
|
+
};
|
|
774
|
+
section("Pages", report.top.path, "(none)");
|
|
775
|
+
section("Referrers", report.top.referrer, "direct");
|
|
776
|
+
section("Countries", report.top.country, "unknown");
|
|
777
|
+
section("Devices", report.top.device, "unknown");
|
|
778
|
+
section("Browsers", report.top.browser, "unknown");
|
|
779
|
+
if (report.events.length) {
|
|
780
|
+
console.log(`\n${styleText("bold", "Events")}`);
|
|
781
|
+
const width = Math.max(...report.events.map((event) => String(event.count).length));
|
|
782
|
+
for (const event of report.events.slice(0, 10)) console.log(` ${String(event.count).padStart(width)} ${event.name}${event.label ? styleText("dim", ` ${event.label}`) : ""}`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
});
|
|
698
786
|
const whoami = defineCommand({
|
|
699
787
|
meta: {
|
|
700
788
|
name: "whoami",
|
|
@@ -854,6 +942,7 @@ runMain(defineCommand({
|
|
|
854
942
|
env,
|
|
855
943
|
tokens,
|
|
856
944
|
tasks,
|
|
945
|
+
stats,
|
|
857
946
|
delete: remove,
|
|
858
947
|
login,
|
|
859
948
|
whoami,
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -49,8 +49,10 @@ existing app, work in its repository instead.
|
|
|
49
49
|
individual designs, Nuxt UI + the `nuxt-ui-mobile` skill for pure phone apps. Details in the
|
|
50
50
|
app's `AGENTS.md`.
|
|
51
51
|
- Users: wervt signs people in. Use `useWervtUser()`; never build login, passwords or OAuth.
|
|
52
|
-
-
|
|
53
|
-
|
|
52
|
+
- AI agents: add a tool in `server/tools/` (`defineTool`) for each main thing a user does in the
|
|
53
|
+
app, so agents can do it too: in the browser (WebMCP) and from Claude Code, Cursor and other
|
|
54
|
+
MCP clients at `https://<app>.wervt.app/mcp`, signed in through wervt. Details in the app's
|
|
55
|
+
`AGENTS.md`.
|
|
54
56
|
- After changing `server/db/schema.ts`: `pnpm db:generate`, and commit the migration.
|
|
55
57
|
- `pnpm dev` runs against a local wervt stack, which the user may not have. When it isn't running,
|
|
56
58
|
don't set one up: `pnpm build` plus `pnpm exec vp check --fix` are the checks, and the
|
|
@@ -70,9 +72,11 @@ pnpm exec wervt env <name> --set KEY=value # server env vars, applied fro
|
|
|
70
72
|
decide that. Private, with the user's email allowed, is the safe default.
|
|
71
73
|
- Secrets for `wervt env` (API keys) come from the user. Ask them to run the command themselves
|
|
72
74
|
(`! pnpm exec wervt env <name> --set API_KEY=…`) instead of pasting the value to you.
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
- `wervt deploy` requests `/` once the release started and fails (exit 1, with the first logged
|
|
76
|
+
error) on a 5xx. Add the pages you changed: `--check /,/todos,/api/todos`. The checks are
|
|
77
|
+
anonymous, so pages behind a sign-in only show they don't crash without a user.
|
|
78
|
+
- Then open the URL `wervt deploy` printed and check
|
|
79
|
+
`pnpm exec wervt logs <name> 'level:error' --since 15m`. Fix and redeploy until both are clean.
|
|
76
80
|
|
|
77
81
|
## 5. Deploy on push (when the user wants a repository)
|
|
78
82
|
|