@vxil/cli 0.29.0 → 0.31.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 +1 -1
- package/dist/_vxil-feature-configs.d.ts +32 -2
- package/dist/config.d.ts +1 -1
- package/dist/vxil-main.js +127 -30
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ Verbs: `init templates quickstart try login link keys account logout invites mem
|
|
|
52
52
|
- `vxil doctor` — preflight: secrets referenced-vs-stored (advisory, names only) plus a served / published / local hash verdict per function. Under `--json` stdout is one JSON document; notices go to stderr.
|
|
53
53
|
- `vxil link <slug>` — reuses the key already stored for the slug on this machine; `--mint` for a fresh one, `--key` to hand one in.
|
|
54
54
|
- `vxil keys mint --name <name> --scopes <a,b,c> [--env live|test] [--store]` — mint a **narrow** key for a server (scopes validated locally; `admin` refused); the key prints **once** to stdout (`--json` → `{ api_key, key_id, scopes }`) and is never stored unless `--store`. `vxil keys list` shows names, ids, scopes, created and last-used (never secrets); `vxil keys revoke <key_id> [--yes]` asks you to type the id, and refuses the key the CLI itself is using unless `--yes`. Needs a dashboard session (`vxil login`, or it prompts at a terminal; a piped / `--json` run fails with that remedy instead — pass `--email`/`--password` to script it, which never stores the session).
|
|
55
|
-
- `vxil cms drop <collection> [--yes]` — remove one collection definition through the same guarded ack `push --allow-destructive` uses (rows are retained).
|
|
55
|
+
- `vxil cms drop <collection> [--purge-rows] [--yes]` — remove one collection definition through the same guarded ack `push --allow-destructive` uses (rows are retained, and a re-create brings them back; `--purge-rows` hard-deletes them too, and on a collection dropped earlier deletes its retained rows).
|
|
56
56
|
- `vxil members list|invite|add|rm|uninvite` and `vxil invites accept <token|url>` — the bound project's team from the terminal (your `vxil login` session): invite by e-mail with a role, add an existing account (also how you change a role), remove (typed e-mail, refuses the last owner), revoke a pending invitation, accept one.
|
|
57
57
|
- `vxil projects list|create|rm` and `vxil billing status|upgrade --tier <t>` — your projects and the bound project's plan. `projects rm` deletes any project you own after you type its slug; `billing upgrade` prints the hosted checkout URL (never opens a browser) and also downgrades (`--tier free`).
|
|
58
58
|
- `vxil files put <path> --user <id>`, `vxil webhooks sources add|list|rm|test`, `vxil webhooks events replay|runs`, `vxil cms bulk-rm <collection> --filter '<json>' [--dry-run]`, `vxil search sync run|reset <key> [--purge]`, `vxil users import <file>` — the operator actions where the CLI adds something a raw call cannot (bytes from disk, a print-once receiver URL, a dry run before a typed confirm, a chunked bulk). Everything else the dashboard does per feature is one `vxil api` line: `vxil api --help`.
|
|
@@ -105,6 +105,7 @@ export declare const NotificationsConfigSchema: import("@sinclair/typebox").TObj
|
|
|
105
105
|
html: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
106
106
|
text: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
107
107
|
}>>>>;
|
|
108
|
+
retainSentRender: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
108
109
|
}>>;
|
|
109
110
|
/** in-app inbox channel (send with channel: 'inbox' | 'both') */
|
|
110
111
|
inboxEnabled: import("@sinclair/typebox").TBoolean;
|
|
@@ -616,6 +617,13 @@ export declare const DeclaredSearchCollectionSchema: import("@sinclair/typebox")
|
|
|
616
617
|
orgScoped: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
617
618
|
}>;
|
|
618
619
|
export type DeclaredSearchCollection = Static<typeof DeclaredSearchCollectionSchema>;
|
|
620
|
+
/** VECTOR-HYBRID-LANE-WEIGHTS: the two RRF lane weights (weighted RRF —
|
|
621
|
+
* each lane's 1/(k+rank) is scaled by its weight). 0..100 each; a request
|
|
622
|
+
* with both 0 is refused at query time. */
|
|
623
|
+
export declare const HybridLaneWeights: import("@sinclair/typebox").TObject<{
|
|
624
|
+
vector: import("@sinclair/typebox").TNumber;
|
|
625
|
+
keyword: import("@sinclair/typebox").TNumber;
|
|
626
|
+
}>;
|
|
619
627
|
export declare const VectorSearchConfigSchema: import("@sinclair/typebox").TObject<{
|
|
620
628
|
enabled: import("@sinclair/typebox").TBoolean;
|
|
621
629
|
backend: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"auto">, import("@sinclair/typebox").TLiteral<"lakebase">, import("@sinclair/typebox").TLiteral<"pgvector">]>;
|
|
@@ -629,10 +637,14 @@ export declare const VectorSearchConfigSchema: import("@sinclair/typebox").TObje
|
|
|
629
637
|
maxTokens: import("@sinclair/typebox").TInteger;
|
|
630
638
|
overlap: import("@sinclair/typebox").TInteger;
|
|
631
639
|
}>;
|
|
632
|
-
hybrid: import("@sinclair/typebox").TObject<{
|
|
640
|
+
hybrid: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
633
641
|
defaultMode: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"hybrid">, import("@sinclair/typebox").TLiteral<"vector">, import("@sinclair/typebox").TLiteral<"keyword">]>;
|
|
634
642
|
rrfK: import("@sinclair/typebox").TInteger;
|
|
635
|
-
|
|
643
|
+
weights: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
644
|
+
vector: import("@sinclair/typebox").TNumber;
|
|
645
|
+
keyword: import("@sinclair/typebox").TNumber;
|
|
646
|
+
}>>;
|
|
647
|
+
}>>;
|
|
636
648
|
limits: import("@sinclair/typebox").TObject<{
|
|
637
649
|
maxDocsPerCollection: import("@sinclair/typebox").TInteger;
|
|
638
650
|
maxChunksPerDoc: import("@sinclair/typebox").TInteger;
|
|
@@ -673,6 +685,11 @@ export declare const DeclaredAiTemplateSchema: import("@sinclair/typebox").TObje
|
|
|
673
685
|
user: import("@sinclair/typebox").TString;
|
|
674
686
|
system: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
675
687
|
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnknown>>;
|
|
688
|
+
/** MX-1023-MEDIA-RESOLUTION: this template's Gemini media resolution (sent
|
|
689
|
+
* to ai-v1 as `media_resolution`). A request's media_resolution wins; absent
|
|
690
|
+
* falls back to defaults.mediaResolution. Part of the content hash only
|
|
691
|
+
* when set, so an existing declared template is not re-versioned. */
|
|
692
|
+
mediaResolution: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"low">, import("@sinclair/typebox").TLiteral<"medium">, import("@sinclair/typebox").TLiteral<"high">]>>;
|
|
676
693
|
}>;
|
|
677
694
|
export type DeclaredAiTemplate = Static<typeof DeclaredAiTemplateSchema>;
|
|
678
695
|
export declare const AiConfigSchema: import("@sinclair/typebox").TObject<{
|
|
@@ -692,6 +709,7 @@ export declare const AiConfigSchema: import("@sinclair/typebox").TObject<{
|
|
|
692
709
|
maxTokens: import("@sinclair/typebox").TInteger;
|
|
693
710
|
temperature: import("@sinclair/typebox").TNumber;
|
|
694
711
|
thinkingLevel: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<import("@sinclair/typebox").TLiteral<"minimal" | "low" | "medium" | "high">[]>>;
|
|
712
|
+
mediaResolution: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"low">, import("@sinclair/typebox").TLiteral<"medium">, import("@sinclair/typebox").TLiteral<"high">]>>;
|
|
695
713
|
}>>;
|
|
696
714
|
cache: import("@sinclair/typebox").TObject<{
|
|
697
715
|
ttlSeconds: import("@sinclair/typebox").TInteger;
|
|
@@ -710,6 +728,11 @@ export declare const AiConfigSchema: import("@sinclair/typebox").TObject<{
|
|
|
710
728
|
user: import("@sinclair/typebox").TString;
|
|
711
729
|
system: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
712
730
|
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnknown>>;
|
|
731
|
+
/** MX-1023-MEDIA-RESOLUTION: this template's Gemini media resolution (sent
|
|
732
|
+
* to ai-v1 as `media_resolution`). A request's media_resolution wins; absent
|
|
733
|
+
* falls back to defaults.mediaResolution. Part of the content hash only
|
|
734
|
+
* when set, so an existing declared template is not re-versioned. */
|
|
735
|
+
mediaResolution: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"low">, import("@sinclair/typebox").TLiteral<"medium">, import("@sinclair/typebox").TLiteral<"high">]>>;
|
|
713
736
|
}>>>;
|
|
714
737
|
testMarkers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
715
738
|
}>;
|
|
@@ -1025,7 +1048,14 @@ export declare function isDeclaredRetainDays(v: unknown): v is number | null | u
|
|
|
1025
1048
|
export declare function retainDaysShortens(declared: number | null, remote: number | null): boolean;
|
|
1026
1049
|
/** = cms-v1 core.ts DELETED_RETAIN_DAYS_MAX (a parity test pins the two). */
|
|
1027
1050
|
export declare const CMS_DELETED_RETAIN_DAYS_MAX = 29;
|
|
1051
|
+
/** = cms-v1 core.ts DELETED_RETAIN_DAYS_DEFAULT: the platform window. Writing
|
|
1052
|
+
* it explicitly is accepted and stored as null (MX-1023-SMALL — the
|
|
1053
|
+
* documented default must never be a 422). */
|
|
1054
|
+
export declare const CMS_DELETED_RETAIN_DAYS_DEFAULT = 30;
|
|
1028
1055
|
export declare function isDeclaredDeletedRetainDays(v: unknown): v is number | null | undefined;
|
|
1056
|
+
/** The stored form of a declared window: the explicit default (30) is null,
|
|
1057
|
+
* so a push declaring 30 against a null remote plans nothing. */
|
|
1058
|
+
export declare function normalizeDeletedRetainDays<T extends number | null | undefined>(v: T): T | null;
|
|
1029
1059
|
export declare function isDeclaredOnOwnerErase(v: unknown): v is 'delete' | 'keep' | undefined;
|
|
1030
1060
|
/** Does moving from `remote` to `declared` make an erase delete MORE (turning
|
|
1031
1061
|
* 'delete' on)? On an existing collection that applies only under
|
package/dist/config.d.ts
CHANGED
|
@@ -143,7 +143,7 @@ export interface CollectionDef {
|
|
|
143
143
|
/** Recovery window for DELETED items, in days (guide ch. 4): a soft-deleted
|
|
144
144
|
* item stays restorable this long before the nightly cleanup purges it for
|
|
145
145
|
* good. An integer 0..29 (0 = purged at the next nightly run, ≤ 24 h), or
|
|
146
|
-
* `null` for the platform default of 30 days. ABSENT = not managed by
|
|
146
|
+
* `null` (or `30`, stored as null) for the platform default of 30 days. ABSENT = not managed by
|
|
147
147
|
* config. Shortening it on an existing collection needs
|
|
148
148
|
* `vxil push --allow-destructive`. Carried by BOTH push paths. */
|
|
149
149
|
deletedRetainDays?: number | null;
|
package/dist/vxil-main.js
CHANGED
|
@@ -3699,7 +3699,14 @@ var NotificationsConfigSchema = Type.Object({
|
|
|
3699
3699
|
html: Type.Optional(Type.String({ maxLength: NOTIF_OVERRIDE_BODY_MAX })),
|
|
3700
3700
|
text: Type.Optional(Type.String({ maxLength: NOTIF_OVERRIDE_BODY_MAX }))
|
|
3701
3701
|
}))
|
|
3702
|
-
))
|
|
3702
|
+
)),
|
|
3703
|
+
// CVS-1022-1 (2026-10-23): keep the render of a REAL send on its delivery
|
|
3704
|
+
// row for 7 days (GET /v1/notifications/deliveries/{id}, rendered_from
|
|
3705
|
+
// send | send_redacted). Opt-in, no default (absent = off, manifests stay
|
|
3706
|
+
// byte-identical); auth-class mail is kept WITHOUT its data; a render
|
|
3707
|
+
// over 64 KiB is dropped (row kept). Inside the ONE optional templates
|
|
3708
|
+
// leaf — the schema is at the 15-leaf cap.
|
|
3709
|
+
retainSentRender: Type.Optional(Type.Boolean())
|
|
3703
3710
|
},
|
|
3704
3711
|
{ default: {} }
|
|
3705
3712
|
)),
|
|
@@ -4598,6 +4605,10 @@ var DeclaredSearchCollectionSchema = Type.Object({
|
|
|
4598
4605
|
// filtered to the active org; immutable after create (inside the array leaf).
|
|
4599
4606
|
orgScoped: Type.Optional(Type.Boolean())
|
|
4600
4607
|
});
|
|
4608
|
+
var HybridLaneWeights = Type.Object({
|
|
4609
|
+
vector: Type.Number({ minimum: 0, maximum: 100 }),
|
|
4610
|
+
keyword: Type.Number({ minimum: 0, maximum: 100 })
|
|
4611
|
+
}, { additionalProperties: false });
|
|
4601
4612
|
var VectorSearchConfigSchema = Type.Object({
|
|
4602
4613
|
enabled: Type.Boolean({ default: true }),
|
|
4603
4614
|
// 'auto' resolves to the default managed vector backend for the tier.
|
|
@@ -4633,16 +4644,22 @@ var VectorSearchConfigSchema = Type.Object({
|
|
|
4633
4644
|
},
|
|
4634
4645
|
{ default: {} }
|
|
4635
4646
|
),
|
|
4636
|
-
hybrid
|
|
4647
|
+
// The hybrid-fusion knobs — ONE optional bag (VECTOR-HYBRID-LANE-WEIGHTS
|
|
4648
|
+
// folded it from 2 leaves to 1, so `weights` rides it at no leaf cost);
|
|
4649
|
+
// `{ default: {} }` keeps defaultMode / rrfK filled exactly as before.
|
|
4650
|
+
hybrid: Type.Optional(Type.Object(
|
|
4637
4651
|
{
|
|
4638
4652
|
defaultMode: Type.Union(
|
|
4639
4653
|
[Type.Literal("hybrid"), Type.Literal("vector"), Type.Literal("keyword")],
|
|
4640
4654
|
{ default: "hybrid" }
|
|
4641
4655
|
),
|
|
4642
|
-
rrfK: Type.Integer({ default: 60, minimum: 1, maximum: 1e3 })
|
|
4656
|
+
rrfK: Type.Integer({ default: 60, minimum: 1, maximum: 1e3 }),
|
|
4657
|
+
// Per-lane RRF weights for hybrid queries (absent = 1 / 1, today's
|
|
4658
|
+
// fusion); a request's `weights` overrides. At least one must be > 0.
|
|
4659
|
+
weights: Type.Optional(HybridLaneWeights)
|
|
4643
4660
|
},
|
|
4644
4661
|
{ default: {} }
|
|
4645
|
-
),
|
|
4662
|
+
)),
|
|
4646
4663
|
limits: Type.Object(
|
|
4647
4664
|
{
|
|
4648
4665
|
maxDocsPerCollection: Type.Integer({ default: 1e5, minimum: 1 }),
|
|
@@ -4700,7 +4717,12 @@ var DeclaredAiTemplateSchema = Type.Object({
|
|
|
4700
4717
|
template: Type.String({ minLength: 1, maxLength: 128, pattern: AI_TEMPLATE_NAME_PATTERN }),
|
|
4701
4718
|
user: Type.String({ minLength: 1, maxLength: 64e3 }),
|
|
4702
4719
|
system: Type.Optional(Type.String({ maxLength: 32e3 })),
|
|
4703
|
-
schema: Type.Optional(Type.Record(Type.String(), Type.Unknown()))
|
|
4720
|
+
schema: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
4721
|
+
/** MX-1023-MEDIA-RESOLUTION: this template's Gemini media resolution (sent
|
|
4722
|
+
* to ai-v1 as `media_resolution`). A request's media_resolution wins; absent
|
|
4723
|
+
* falls back to defaults.mediaResolution. Part of the content hash only
|
|
4724
|
+
* when set, so an existing declared template is not re-versioned. */
|
|
4725
|
+
mediaResolution: Type.Optional(Type.Union([Type.Literal("low"), Type.Literal("medium"), Type.Literal("high")]))
|
|
4704
4726
|
});
|
|
4705
4727
|
var AiConfigSchema = Type.Object({
|
|
4706
4728
|
enabled: Type.Boolean({ default: true }),
|
|
@@ -4750,7 +4772,11 @@ var AiConfigSchema = Type.Object({
|
|
|
4750
4772
|
model: Type.String({ default: "mock-1" }),
|
|
4751
4773
|
maxTokens: Type.Integer({ default: 1024, minimum: 1, maximum: 2e5 }),
|
|
4752
4774
|
temperature: Type.Number({ default: 0.7, minimum: 0, maximum: 2 }),
|
|
4753
|
-
thinkingLevel: Type.Optional(Type.Union(AI_THINKING_LEVELS.map((l) => Type.Literal(l))))
|
|
4775
|
+
thinkingLevel: Type.Optional(Type.Union(AI_THINKING_LEVELS.map((l) => Type.Literal(l)))),
|
|
4776
|
+
// MX-1023-MEDIA-RESOLUTION (2026-10-23): Gemini's per-image / per-page
|
|
4777
|
+
// token budget, sent to Gemini only (ignored elsewhere). No default, so an
|
|
4778
|
+
// existing manifest stays byte-identical; inside the bag, so no new leaf.
|
|
4779
|
+
mediaResolution: Type.Optional(Type.Union([Type.Literal("low"), Type.Literal("medium"), Type.Literal("high")]))
|
|
4754
4780
|
}, { default: {} })),
|
|
4755
4781
|
cache: Type.Object({ ttlSeconds: Type.Integer({ default: 0, minimum: 0 }) }, { default: {} }),
|
|
4756
4782
|
// 0 = off
|
|
@@ -4781,8 +4807,9 @@ var AiConfigSchema = Type.Object({
|
|
|
4781
4807
|
// aiTemplateContentSha256) against the `content_sha256` GET /v1/ai/templates
|
|
4782
4808
|
// stamps on the latest version, and POSTs a new version ONLY when the content
|
|
4783
4809
|
// differs — so a push is idempotent and versions stay monotonic per name.
|
|
4784
|
-
// Item shape mirrors ai-v1 core.ts TemplateBody
|
|
4785
|
-
//
|
|
4810
|
+
// Item shape mirrors ai-v1 core.ts TemplateBody (`template` is the name;
|
|
4811
|
+
// `mediaResolution` is sent as the body's `media_resolution`).
|
|
4812
|
+
// Stored templates the config does not declare are reported and left
|
|
4786
4813
|
// in place — RETIRED (soft: hidden from list + render, history kept) only
|
|
4787
4814
|
// under --allow-destructive (2026-10-01). Bounded to 50 entries: the
|
|
4788
4815
|
// manifest rides the 1 MiB config body cap. An Optional ARRAY is ONE leaf.
|
|
@@ -5412,8 +5439,12 @@ function retainDaysShortens(declared, remote) {
|
|
|
5412
5439
|
return remote === null || declared < remote;
|
|
5413
5440
|
}
|
|
5414
5441
|
var CMS_DELETED_RETAIN_DAYS_MAX = 29;
|
|
5442
|
+
var CMS_DELETED_RETAIN_DAYS_DEFAULT = 30;
|
|
5415
5443
|
function isDeclaredDeletedRetainDays(v) {
|
|
5416
|
-
return v === void 0 || v === null || typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= CMS_DELETED_RETAIN_DAYS_MAX;
|
|
5444
|
+
return v === void 0 || v === null || typeof v === "number" && Number.isInteger(v) && v >= 0 && (v <= CMS_DELETED_RETAIN_DAYS_MAX || v === CMS_DELETED_RETAIN_DAYS_DEFAULT);
|
|
5445
|
+
}
|
|
5446
|
+
function normalizeDeletedRetainDays(v) {
|
|
5447
|
+
return v === CMS_DELETED_RETAIN_DAYS_DEFAULT ? null : v;
|
|
5417
5448
|
}
|
|
5418
5449
|
function isDeclaredOnOwnerErase(v) {
|
|
5419
5450
|
return v === void 0 || v === "delete" || v === "keep";
|
|
@@ -5641,9 +5672,9 @@ function lowerAllTriggerBindings(def, name) {
|
|
|
5641
5672
|
return bindings;
|
|
5642
5673
|
}
|
|
5643
5674
|
var CONFIG_FILENAMES = ["vxil.config.ts", "vxil.config.mjs", "vxil.config.js"];
|
|
5644
|
-
var VXIL_CONFIG_PKG_VERSION = "0.
|
|
5645
|
-
var VXIL_SDK_PKG_VERSION = "0.
|
|
5646
|
-
var VXIL_CLI_PKG_VERSION = "0.
|
|
5675
|
+
var VXIL_CONFIG_PKG_VERSION = "0.23.0";
|
|
5676
|
+
var VXIL_SDK_PKG_VERSION = "0.30.0";
|
|
5677
|
+
var VXIL_CLI_PKG_VERSION = "0.31.0";
|
|
5647
5678
|
function ensureScaffoldPackageJson(cwd, opts = {}) {
|
|
5648
5679
|
const file = resolve(cwd, "package.json");
|
|
5649
5680
|
const wanted = {
|
|
@@ -8544,6 +8575,7 @@ async function aiTemplateContentSha256(t) {
|
|
|
8544
8575
|
return Object.fromEntries(Object.keys(o).sort().map((k) => [k, canon(o[k], depth + 1)]));
|
|
8545
8576
|
};
|
|
8546
8577
|
return sha256Hex(JSON.stringify({
|
|
8578
|
+
...t.mediaResolution ? { media_resolution: t.mediaResolution } : {},
|
|
8547
8579
|
schema: canon(t.schema ?? null, 1),
|
|
8548
8580
|
system: t.system ?? null,
|
|
8549
8581
|
user: t.user
|
|
@@ -8989,7 +9021,7 @@ async function readOnlyPlan(api, block, allowDestructive) {
|
|
|
8989
9021
|
} else {
|
|
8990
9022
|
const hashed = await Promise.all(manifest.templates.map(async (t) => ({
|
|
8991
9023
|
template: t.template,
|
|
8992
|
-
content_sha256: await aiTemplateContentSha256({ system: t.system ?? null, user: t.user, schema: t.schema ?? null })
|
|
9024
|
+
content_sha256: await aiTemplateContentSha256({ system: t.system ?? null, user: t.user, schema: t.schema ?? null, mediaResolution: t.mediaResolution ?? null })
|
|
8993
9025
|
})));
|
|
8994
9026
|
const plan = planAiTemplates(hashed, listOf(data2.templates));
|
|
8995
9027
|
s.created.push(...plan.create);
|
|
@@ -9846,7 +9878,7 @@ async function planCmsSchema({ api, collections, apply, allowDestructive = false
|
|
|
9846
9878
|
}
|
|
9847
9879
|
const drd = def.deletedRetainDays;
|
|
9848
9880
|
if (!isDeclaredDeletedRetainDays(drd)) {
|
|
9849
|
-
throw new Error(`cms.collections.${name}.deletedRetainDays must be an integer 0..29 (days) or null (got ${JSON.stringify(drd)})`);
|
|
9881
|
+
throw new Error(`cms.collections.${name}.deletedRetainDays must be an integer 0..29 (days), or 30 / null for the default (got ${JSON.stringify(drd)})`);
|
|
9850
9882
|
}
|
|
9851
9883
|
const ooe = def.onOwnerErase;
|
|
9852
9884
|
if (!isDeclaredOnOwnerErase(ooe)) {
|
|
@@ -10089,7 +10121,7 @@ async function planCmsSchema({ api, collections, apply, allowDestructive = false
|
|
|
10089
10121
|
});
|
|
10090
10122
|
}
|
|
10091
10123
|
}
|
|
10092
|
-
const declaredWindow = def.deletedRetainDays;
|
|
10124
|
+
const declaredWindow = normalizeDeletedRetainDays(def.deletedRetainDays);
|
|
10093
10125
|
if (declaredWindow !== void 0 && declaredWindow !== rc.deletedRetainDays) {
|
|
10094
10126
|
const fmtWin = (d) => d === null ? "30 days (default)" : `${d} days`;
|
|
10095
10127
|
const shortens = retainDaysShortens(declaredWindow, rc.deletedRetainDays);
|
|
@@ -10195,7 +10227,8 @@ async function planCmsSchema({ api, collections, apply, allowDestructive = false
|
|
|
10195
10227
|
const orgRoles = writeRolesOf(def.orgReadRoles);
|
|
10196
10228
|
if (orgRoles.length > 0) body.org_read_roles = orgRoles;
|
|
10197
10229
|
if (typeof def.retainDays === "number") body.retain_days = def.retainDays;
|
|
10198
|
-
|
|
10230
|
+
const createWindow = normalizeDeletedRetainDays(def.deletedRetainDays);
|
|
10231
|
+
if (typeof createWindow === "number") body.deleted_retain_days = createWindow;
|
|
10199
10232
|
if (def.onOwnerErase === "delete") body.on_owner_erase = "delete";
|
|
10200
10233
|
if (def.actions && def.actions.length > 0) body.actions = normalizeActions(def.actions);
|
|
10201
10234
|
const res = await api("POST", "/v1/cms/collections", body);
|
|
@@ -10280,7 +10313,7 @@ async function planCmsSchema({ api, collections, apply, allowDestructive = false
|
|
|
10280
10313
|
applied++;
|
|
10281
10314
|
} else if (c.kind === "set-deleted-retain-days" || c.kind === "set-on-owner-erase") {
|
|
10282
10315
|
const def = collections[c.collection];
|
|
10283
|
-
const body = c.kind === "set-deleted-retain-days" ? { deleted_retain_days: def.deletedRetainDays ?? null } : { on_owner_erase: def.onOwnerErase ?? "keep" };
|
|
10316
|
+
const body = c.kind === "set-deleted-retain-days" ? { deleted_retain_days: normalizeDeletedRetainDays(def.deletedRetainDays) ?? null } : { on_owner_erase: def.onOwnerErase ?? "keep" };
|
|
10284
10317
|
const res = await api("POST", `/v1/cms/collections/${encodeURIComponent(c.collection)}/fields`, body);
|
|
10285
10318
|
if (res.status !== 200 && res.status !== 201) {
|
|
10286
10319
|
const e = res.body.error ?? {};
|
|
@@ -10370,14 +10403,43 @@ async function dropCollection(api, collection) {
|
|
|
10370
10403
|
throw new Error(`cms: drop collection ${collection} failed: ${e.code ?? res.status} ${e.message ?? ""}`);
|
|
10371
10404
|
}
|
|
10372
10405
|
}
|
|
10373
|
-
function
|
|
10406
|
+
async function dropCollectionPurgingRows(api, collection) {
|
|
10407
|
+
let purgedRows = 0;
|
|
10408
|
+
for (let round = 0; ; round++) {
|
|
10409
|
+
if (round > 0) {
|
|
10410
|
+
const read = await readRemote2(api);
|
|
10411
|
+
if (read.unavailable) {
|
|
10412
|
+
throw new Error(`cms: purge of ${collection} stopped after ${purgedRows} purged row(s): cannot read ${read.unavailable.route}`);
|
|
10413
|
+
}
|
|
10414
|
+
if (read.map.has(collection)) {
|
|
10415
|
+
throw new Error(`cms: purge of ${collection} stopped after ${purgedRows} purged row(s): the collection was re-created meanwhile \u2014 its rows are kept`);
|
|
10416
|
+
}
|
|
10417
|
+
}
|
|
10418
|
+
const res = await api("POST", "/v1/cms/collections", { drop_collection: collection, allow_destructive: true, purge_rows: true });
|
|
10419
|
+
if (res.body.error?.code === "not_found") return { purgedRows };
|
|
10420
|
+
if (res.status < 200 || res.status >= 300) {
|
|
10421
|
+
const e = res.body.error ?? {};
|
|
10422
|
+
throw new Error(`cms: drop collection ${collection} failed after ${purgedRows} purged row(s): ${e.code ?? res.status} ${e.message ?? ""}`);
|
|
10423
|
+
}
|
|
10424
|
+
const d = res.body.data ?? {};
|
|
10425
|
+
const n = typeof d.purged_rows === "number" ? d.purged_rows : 0;
|
|
10426
|
+
purgedRows += n;
|
|
10427
|
+
if (d.purge_complete === true) return { purgedRows };
|
|
10428
|
+
if (n === 0) throw new Error(`cms: purge of ${collection} made no progress after ${purgedRows} purged row(s) \u2014 re-run \`vxil cms drop ${collection} --purge-rows\``);
|
|
10429
|
+
}
|
|
10430
|
+
}
|
|
10431
|
+
function formatCollectionDrop(collection, plan, declaredLocally, purgeRows = false) {
|
|
10374
10432
|
const rows = plan.rows === null ? "an unknown number of live rows" : `${plan.rows} live row(s)`;
|
|
10375
|
-
const lines = [
|
|
10376
|
-
` ! drop collection ${collection} (${plan.fields} field(s), ${rows}) \u2014 the definition is removed; stored item rows are NOT deleted (they become unreachable through the API until a collection of that name exists again)
|
|
10433
|
+
const lines = !plan.exists && purgeRows ? [` ! collection ${collection} has no definition \u2014 its retained rows from an earlier drop (live and deleted) are HARD-DELETED; they cannot be restored`] : [
|
|
10434
|
+
` ! drop collection ${collection} (${plan.fields} field(s), ${rows}) \u2014 the definition is removed; ` + (purgeRows ? "its stored item rows (live and deleted) are HARD-DELETED with it; they cannot be restored" : "stored item rows are NOT deleted (they become unreachable through the API until a collection of that name exists again)")
|
|
10377
10435
|
];
|
|
10378
10436
|
if (declaredLocally) {
|
|
10379
|
-
|
|
10380
|
-
|
|
10437
|
+
if (purgeRows) {
|
|
10438
|
+
lines.push(` \u26A0 '${collection}' is still declared in vxil.config.ts \u2014 the next \`vxil push\` re-creates it empty; remove it from the config too`);
|
|
10439
|
+
} else {
|
|
10440
|
+
const back = plan.rows === null ? "the retained rows" : `the ${plan.rows} retained row(s)`;
|
|
10441
|
+
lines.push(` \u26A0 '${collection}' is still declared in vxil.config.ts \u2014 the next \`vxil push\` re-creates it and ${back} come back with it; remove it from the config too`);
|
|
10442
|
+
}
|
|
10381
10443
|
}
|
|
10382
10444
|
return lines.join("\n");
|
|
10383
10445
|
}
|
|
@@ -14852,6 +14914,13 @@ var TOOLS = [
|
|
|
14852
14914
|
mode: { type: "string", enum: ["hybrid", "vector", "keyword"], default: "hybrid" },
|
|
14853
14915
|
filter: { type: "object", additionalProperties: true, description: 'Metadata filter, keys ANDed: equality {"user_id":"u_42"} or any-of {"folder":{"$in":["a","b"]}} (1\u201350 values, \u22644 $in keys). null / arrays / other operators are refused 422.' },
|
|
14854
14916
|
rerank: { type: "boolean", description: "Apply the configured BYO reranker (config rerank block) to the fused results before top-k. Fail-open: a provider fault returns the RRF order with reranked:false." },
|
|
14917
|
+
weights: {
|
|
14918
|
+
type: "object",
|
|
14919
|
+
additionalProperties: false,
|
|
14920
|
+
required: ["vector", "keyword"],
|
|
14921
|
+
properties: { vector: { type: "number", minimum: 0, maximum: 100 }, keyword: { type: "number", minimum: 0, maximum: 100 } },
|
|
14922
|
+
description: `Hybrid mode only: per-lane RRF weights, e.g. {"vector":0.8,"keyword":0.2} to favour meaning over exact words. Not both 0. Default: the config's hybrid.weights, else equal.`
|
|
14923
|
+
},
|
|
14855
14924
|
org_id: { type: "string", description: "Workspace-scoped (org_scoped) collections only: the org to search. Required with a server key (422 org_id_required without); an end-user session always searches its active workspace. Refused 422 on a collection that is not org-scoped." }
|
|
14856
14925
|
},
|
|
14857
14926
|
required: ["collection", "query"]
|
|
@@ -14862,7 +14931,7 @@ var TOOLS = [
|
|
|
14862
14931
|
{
|
|
14863
14932
|
name: "ai_generate",
|
|
14864
14933
|
feature: "ai",
|
|
14865
|
-
description: "Generate a completion from a prompt or a named tenant template. Pass either prompt (raw text) or template + input (vars for a stored template). Defaults to the tenant-configured provider/model; override with provider/model. response_schema makes the output schema-guaranteed JSON (parsed into data.output). Returns the synchronous completion (set stream only via the REST API).",
|
|
14934
|
+
description: "Generate a completion from a prompt or a named tenant template. Pass either prompt (raw text) or template + input (vars for a stored template). Defaults to the tenant-configured provider/model; override with provider/model. response_schema makes the output schema-guaranteed JSON (parsed into data.output). Returns the synchronous completion by default (set stream only via the REST API); mode:'job' instead returns a 202 handle (generation_id, run_id) \u2014 read the answer with ai_generation_get and wait:25.",
|
|
14866
14935
|
inputSchema: {
|
|
14867
14936
|
type: "object",
|
|
14868
14937
|
properties: {
|
|
@@ -14878,6 +14947,22 @@ var TOOLS = [
|
|
|
14878
14947
|
enum: ["minimal", "low", "medium", "high"],
|
|
14879
14948
|
description: "Gemini thinking depth (ignored by other providers). Omitted: the tenant's defaults.thinkingLevel, then the model default. 'minimal' on a model that rejects it is a 422 thinking_level_unsupported. Thinking tokens bill as output."
|
|
14880
14949
|
},
|
|
14950
|
+
media_resolution: {
|
|
14951
|
+
type: "string",
|
|
14952
|
+
enum: ["low", "medium", "high"],
|
|
14953
|
+
description: "Gemini token budget per image or PDF page (ignored by other providers): lower is faster and bills fewer input tokens. Omitted: the stored template's own level (when template is set), then the tenant's defaults.mediaResolution, then the model default."
|
|
14954
|
+
},
|
|
14955
|
+
mode: {
|
|
14956
|
+
type: "string",
|
|
14957
|
+
enum: ["sync", "job"],
|
|
14958
|
+
description: "'sync' (default) answers with the completion. 'job' routes the generation through a jobs run and answers 202 with generation_id + run_id; read it with ai_generation_get (wait:25)."
|
|
14959
|
+
},
|
|
14960
|
+
deadline_ms: {
|
|
14961
|
+
type: "integer",
|
|
14962
|
+
minimum: 1e3,
|
|
14963
|
+
maximum: 17e3,
|
|
14964
|
+
description: "mode:'job' only (422 otherwise): bound the run's first provider call at this many ms so it can start without the queue hop. A first call that passes it is retried on the queue with the normal job-lane bound."
|
|
14965
|
+
},
|
|
14881
14966
|
json_mode: { type: "boolean", description: "Constrain the output to JSON (no schema \u2014 use response_schema for a guaranteed shape)." },
|
|
14882
14967
|
response_schema: {
|
|
14883
14968
|
type: "object",
|
|
@@ -15168,14 +15253,15 @@ var TOOLS = [
|
|
|
15168
15253
|
properties: {
|
|
15169
15254
|
since: { type: "string", description: "ISO-8601 lower bound on created_at (inclusive; no zone = UTC). Tail with an overlap (last stored created_at minus a minute) and dedupe on ledger_entry_id." },
|
|
15170
15255
|
cursor: { type: "string", description: "Opaque keyset cursor from a prior page (next_cursor)." },
|
|
15171
|
-
user_id: { type: "string", maxLength: 512, description: "Only this end user's rows." },
|
|
15256
|
+
user_id: { type: "string", maxLength: 512, description: "Only this end user's rows. Give this OR org_id." },
|
|
15257
|
+
org_id: { type: "string", maxLength: 64, description: "WORKSPACE BILLING: only this workspace's rows (an orgs id; the same as user_id 'org:<id>'). Give this OR user_id." },
|
|
15172
15258
|
credit_type: { type: "string", maxLength: 200, description: "Only rows of this credit type, e.g. 'scans'." },
|
|
15173
15259
|
limit: { type: "number", default: 100, description: "Page size, clamped 1..500." }
|
|
15174
15260
|
}
|
|
15175
15261
|
},
|
|
15176
15262
|
method: "GET",
|
|
15177
15263
|
path: "/v1/payments/credits/ledger",
|
|
15178
|
-
queryArgs: ["since", "cursor", "user_id", "credit_type", "limit"]
|
|
15264
|
+
queryArgs: ["since", "cursor", "user_id", "org_id", "credit_type", "limit"]
|
|
15179
15265
|
},
|
|
15180
15266
|
{
|
|
15181
15267
|
name: "payments_list_refunds",
|
|
@@ -16882,7 +16968,7 @@ var VERB_USAGE = {
|
|
|
16882
16968
|
import: "usage: vxil import <feature> --file <bundle.json|export.ndjson> [--mode merge|replace] [--allow-split-links]",
|
|
16883
16969
|
migrate: "usage: vxil migrate plan|schema|data|verify|payments \u2026 (run `vxil migrate --help`)",
|
|
16884
16970
|
functions: "usage: vxil functions new <name> [--force]|deploy [<name>] [--force]|list|delete|enable|disable [--yes]|invoke ['<json>' | --data '<json>'] [--async]|logs [--tail]|dev <name> [--port <n>] [--trigger <lane>] [--event <file.json>]",
|
|
16885
|
-
cms: "usage: vxil cms status|pull|reindex <collection> [--field <field>]|drop <collection> [--yes]|bulk-rm <collection> --filter '<json>' [--limit <1..100>] [--dry-run] [--yes]",
|
|
16971
|
+
cms: "usage: vxil cms status|pull|reindex <collection> [--field <field>]|drop <collection> [--purge-rows] [--yes]|bulk-rm <collection> --filter '<json>' [--limit <1..100>] [--dry-run] [--yes]",
|
|
16886
16972
|
payments: "usage: vxil payments simulate --scenario <refund-pair|cross-platform-unlock|renewal|expiry|past-due-grace|transfer> --user <id> [--tier <t>] [--dev] \u2014 mock/dev projects only",
|
|
16887
16973
|
dev: "usage: vxil dev up [--ttl <h>] [--new] [--name <n>] [--reuse-slug <slug>] [--email <e> --password <p>] [--invite <code>] | down [--yes] | seed | reset | branch [<name>] [--ttl <h=24>] [--no-env] [--print] | --list | --rm <name> [--yes] | --watch [--target <name>] \u2014 dev up signs in with --email/--password (or VXIL_DEV_EMAIL/VXIL_DEV_PASSWORD, + VXIL_DEV_INVITE), else your `vxil login` session, else a prompt",
|
|
16888
16974
|
config: "usage: vxil config get <feature> \xB7 vxil config draft show|save [--file <f>]|publish|discard <feature> \xB7 vxil config draft duplicate <feature> --from <version>",
|
|
@@ -17002,6 +17088,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
17002
17088
|
"check",
|
|
17003
17089
|
"all-projects",
|
|
17004
17090
|
"allow-destructive",
|
|
17091
|
+
"purge-rows",
|
|
17005
17092
|
"exit-code",
|
|
17006
17093
|
"show-runs",
|
|
17007
17094
|
"skip-functions",
|
|
@@ -26084,25 +26171,35 @@ export default defineConfig(${JSON.stringify({ features: featBlocks, cms: { coll
|
|
|
26084
26171
|
const [sub] = positional(0);
|
|
26085
26172
|
if (sub === "drop") {
|
|
26086
26173
|
const [collection] = positional(1);
|
|
26087
|
-
if (!collection) fail("usage: vxil cms drop <collection> [--yes]");
|
|
26174
|
+
if (!collection) fail("usage: vxil cms drop <collection> [--purge-rows] [--yes]");
|
|
26175
|
+
const purgeRows = hasFlag("purge-rows");
|
|
26088
26176
|
const { api: api2, target } = requireApi(selector(), { write: `cms drop ${collection}` });
|
|
26089
26177
|
const plan = await planCollectionDrop(api2, collection);
|
|
26090
26178
|
if (plan.remoteUnavailable) {
|
|
26091
26179
|
fail(`cannot read ${plan.remoteUnavailable.route} (${plan.remoteUnavailable.code ?? plan.remoteUnavailable.status}) \u2014 refusing to drop against an unread target`);
|
|
26092
26180
|
}
|
|
26093
|
-
if (!plan.exists) fail(`collection '${collection}' does not exist on tenant '${target.tenantSlug ?? "(env key)"}' \u2014 nothing to drop`);
|
|
26181
|
+
if (!plan.exists && !purgeRows) fail(`collection '${collection}' does not exist on tenant '${target.tenantSlug ?? "(env key)"}' \u2014 nothing to drop (\`--purge-rows\` deletes the rows an earlier drop retained)`);
|
|
26094
26182
|
let declaredLocally = false;
|
|
26095
26183
|
try {
|
|
26096
26184
|
const cfg = await loadVxilConfig();
|
|
26097
26185
|
declaredLocally = Object.prototype.hasOwnProperty.call(cfg.cms?.collections ?? {}, collection);
|
|
26098
26186
|
} catch {
|
|
26099
26187
|
}
|
|
26100
|
-
console.error(formatCollectionDrop(collection, plan, declaredLocally));
|
|
26188
|
+
console.error(formatCollectionDrop(collection, plan, declaredLocally, purgeRows));
|
|
26101
26189
|
if (!hasFlag("yes")) {
|
|
26102
26190
|
if (!process.stdin.isTTY) fail("cms drop is destructive \u2014 pass --yes to confirm in a non-interactive run");
|
|
26103
|
-
const ans = await prompt(
|
|
26191
|
+
const ans = await prompt(`${purgeRows ? "drop collection and hard-delete the rows of" : "drop collection"} '${collection}'? type the collection name to confirm: `);
|
|
26104
26192
|
if (ans !== collection) fail("confirmation did not match \u2014 aborted.");
|
|
26105
26193
|
}
|
|
26194
|
+
if (purgeRows) {
|
|
26195
|
+
const { purgedRows } = await dropCollectionPurgingRows(api2, collection);
|
|
26196
|
+
if (jsonOut) {
|
|
26197
|
+
console.log(JSON.stringify({ ok: true, collection, dropped: plan.exists, fields: plan.fields, purged_rows: purgedRows, declared_locally: declaredLocally }));
|
|
26198
|
+
} else {
|
|
26199
|
+
console.log(`\u2713 ${plan.exists ? `dropped collection ${collection} (${plan.fields} field(s)) and ` : `${collection}: `}hard-deleted ${purgedRows} stored row(s)`);
|
|
26200
|
+
}
|
|
26201
|
+
break;
|
|
26202
|
+
}
|
|
26106
26203
|
await dropCollection(api2, collection);
|
|
26107
26204
|
if (jsonOut) {
|
|
26108
26205
|
console.log(JSON.stringify({ ok: true, collection, fields: plan.fields, rows: plan.rows, declared_locally: declaredLocally }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vxil/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The vxil CLI — init, quickstart, push, gen, secrets, keys, migrate, doctor, diff, functions, payments; installs the `vxil` command (npm i -g @vxil/cli).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@vxil/config": "0.
|
|
49
|
-
"@vxil/feature-configs": "0.
|
|
48
|
+
"@vxil/config": "0.23.0",
|
|
49
|
+
"@vxil/feature-configs": "0.21.0",
|
|
50
50
|
"@vxil/runtime": "0.0.1",
|
|
51
51
|
"@vxil/types": "0.0.1",
|
|
52
52
|
"miniflare": "^4.20260730.0"
|