@vxil/cli 0.20.2 → 0.21.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.
@@ -0,0 +1,16 @@
1
+ export declare const AI_THINKING_LEVELS: readonly ["minimal", "low", "medium", "high"];
2
+ export type GeminiThinkingLevel = (typeof AI_THINKING_LEVELS)[number];
3
+ /** The lowest thinking level the model accepts. 3.x thinks by default; the
4
+ * 2.5-era `thinkingBudget:0` is INVALID on 3.x, and `thinkingLevel:'minimal'`
5
+ * (which zeroes thought tokens) is accepted by gemini-3-flash … 3.6-flash and
6
+ * every flash-lite, but REJECTED with a 400 ("Thinking level MINIMAL is not
7
+ * supported for this model") by gemini-3.7-flash and gemini-3.8-flash
8
+ * (measured 2026-10-03 on the staging key). Unknown model shapes (incl. the
9
+ * Pro models) get 'low': it is the one level measured as accepted on every
10
+ * 3.x model tried. */
11
+ export declare function minimalThinkingLevel(model: string): GeminiThinkingLevel;
12
+ /** Null when `model` is not known to reject `level`, else the reason. Only
13
+ * 'minimal' is checked (per the table above). Measured: 'low' on every 3.x
14
+ * model tried; medium/high are NOT verified per model — a model that rejects
15
+ * one fails as a provider 4xx after the reserve (refunded), not here. */
16
+ export declare function geminiThinkingLevelError(model: string, level: GeminiThinkingLevel): string | null;
@@ -1,4 +1,5 @@
1
1
  import { type Static, type TSchema } from '@sinclair/typebox';
2
+ export { AI_THINKING_LEVELS, geminiThinkingLevelError, minimalThinkingLevel, type GeminiThinkingLevel } from './_vxil-feature-configs-geminiThinking.js';
2
3
  export * from './_vxil-feature-configs-hooks.js';
3
4
  export * from './_vxil-feature-configs-readmodels.js';
4
5
  export * from './_vxil-feature-configs-apiState.js';
@@ -605,11 +606,12 @@ export declare const AiConfigSchema: import("@sinclair/typebox").TObject<{
605
606
  openaiBaseUrl: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
606
607
  }>>;
607
608
  }>;
608
- defaults: import("@sinclair/typebox").TObject<{
609
+ defaults: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
609
610
  model: import("@sinclair/typebox").TString;
610
611
  maxTokens: import("@sinclair/typebox").TInteger;
611
612
  temperature: import("@sinclair/typebox").TNumber;
612
- }>;
613
+ thinkingLevel: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<import("@sinclair/typebox").TLiteral<"minimal" | "low" | "medium" | "high">[]>>;
614
+ }>>;
613
615
  cache: import("@sinclair/typebox").TObject<{
614
616
  ttlSeconds: import("@sinclair/typebox").TInteger;
615
617
  }>;
package/dist/vxil.js CHANGED
@@ -3383,6 +3383,9 @@ var PublicAssetsConfigSchema = Type.Object({
3383
3383
  ))
3384
3384
  });
3385
3385
 
3386
+ // ../feature-configs/src/geminiThinking.ts
3387
+ var AI_THINKING_LEVELS = ["minimal", "low", "medium", "high"];
3388
+
3386
3389
  // ../feature-configs/src/canonicalJson.ts
3387
3390
  function canonicalJson(v) {
3388
3391
  if (Array.isArray(v)) return `[${v.map((x) => canonicalJson(x) ?? "null").join(",")}]`;
@@ -3963,8 +3966,8 @@ var FilesConfigSchema = Type.Object({
3963
3966
  // (pricing review 2026-07-10). 5 GiB/object, 1 TiB/tenant absolute.
3964
3967
  maxObjectBytes: Type.Integer({ default: 100 * 1024 * 1024, minimum: 1, maximum: 5 * 1024 * 1024 * 1024 }),
3965
3968
  // NO default (2026-10-04): UNSET means "the plan's storage ceiling"
3966
- // (control-plane plans.ts FILES_MAX_TOTAL_BYTES_BY_TIER — Free/Developer
3967
- // 10 GiB, Team 50 GiB, Business 256 GiB, Enterprise 1 TiB), resolved by
3969
+ // (control-plane plans.ts FILES_MAX_TOTAL_BYTES_BY_TIER — Free 10 GiB,
3970
+ // Developer 25 GiB, Team 150 GiB, Business 256 GiB, Enterprise 1 TiB), resolved by
3968
3971
  // files-v1 where the quota is enforced, so a plan change applies at once.
3969
3972
  // A value is the project's own LOWER cap (above the plan → 422 at config
3970
3973
  // write; a downgrade clamps it). It used to default to 10 GiB on every
@@ -4469,11 +4472,18 @@ var AiConfigSchema = Type.Object({
4469
4472
  openaiBaseUrl: Type.Optional(Type.String({ maxLength: 512 }))
4470
4473
  }))
4471
4474
  }, { default: {} }),
4472
- defaults: Type.Object({
4475
+ // `defaults` became an OPTIONAL bag (3 leaves → 1, the `streaming` trick) on
4476
+ // 2026-10-10 to carry `thinkingLevel` (AI-THINKING-LEVEL). It KEEPS
4477
+ // `default: {}`, so Value.Default still materializes
4478
+ // defaults.{model,maxTokens,temperature} exactly as before; `thinkingLevel`
4479
+ // has NO default (absent = the model's own default), so an existing manifest
4480
+ // stays byte-identical. Sent to Gemini only; validated per model below.
4481
+ defaults: Type.Optional(Type.Object({
4473
4482
  model: Type.String({ default: "mock-1" }),
4474
4483
  maxTokens: Type.Integer({ default: 1024, minimum: 1, maximum: 2e5 }),
4475
- temperature: Type.Number({ default: 0.7, minimum: 0, maximum: 2 })
4476
- }, { default: {} }),
4484
+ temperature: Type.Number({ default: 0.7, minimum: 0, maximum: 2 }),
4485
+ thinkingLevel: Type.Optional(Type.Union(AI_THINKING_LEVELS.map((l) => Type.Literal(l))))
4486
+ }, { default: {} })),
4477
4487
  cache: Type.Object({ ttlSeconds: Type.Integer({ default: 0, minimum: 0 }) }, { default: {} }),
4478
4488
  // 0 = off
4479
4489
  limits: Type.Object({
@@ -5065,14 +5075,16 @@ function lowerAllTriggerBindings(def, name) {
5065
5075
  return bindings;
5066
5076
  }
5067
5077
  var CONFIG_FILENAMES = ["vxil.config.ts", "vxil.config.mjs", "vxil.config.js"];
5068
- var VXIL_CONFIG_PKG_VERSION = "0.13.0";
5069
- var VXIL_SDK_PKG_VERSION = "0.20.1";
5070
- var VXIL_CLI_PKG_VERSION = "0.20.2";
5078
+ var VXIL_CONFIG_PKG_VERSION = "0.14.0";
5079
+ var VXIL_SDK_PKG_VERSION = "0.21.0";
5080
+ var VXIL_CLI_PKG_VERSION = "0.21.0";
5071
5081
  function ensureScaffoldPackageJson(cwd, opts = {}) {
5072
5082
  const file = resolve(cwd, "package.json");
5073
5083
  const wanted = {
5074
5084
  "@vxil/config": `^${VXIL_CONFIG_PKG_VERSION}`,
5075
- ...opts.sdkTypes ? { "@vxil/sdk": `^${VXIL_SDK_PKG_VERSION}` } : {}
5085
+ ...opts.sdkTypes ? { "@vxil/sdk": `^${VXIL_SDK_PKG_VERSION}` } : {},
5086
+ // the scaffolded tsconfig.json names `types: ["node"]` (CLI-TS-SCAFFOLD)
5087
+ ...opts.nodeTypes ? { "@types/node": SCAFFOLD_NODE_TYPES_RANGE } : {}
5076
5088
  };
5077
5089
  if (!existsSync(file)) {
5078
5090
  writeFileSync(file, JSON.stringify({
@@ -5102,6 +5114,24 @@ function ensureScaffoldPackageJson(cwd, opts = {}) {
5102
5114
  writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n");
5103
5115
  return { action: "added" };
5104
5116
  }
5117
+ var SCAFFOLD_NODE_TYPES_RANGE = "^24.0.0";
5118
+ var SCAFFOLD_TSCONFIG = {
5119
+ compilerOptions: {
5120
+ target: "ES2022",
5121
+ module: "ESNext",
5122
+ moduleResolution: "bundler",
5123
+ strict: true,
5124
+ skipLibCheck: true,
5125
+ noEmit: true,
5126
+ types: ["node"]
5127
+ }
5128
+ };
5129
+ function ensureScaffoldTsconfig(cwd) {
5130
+ const file = resolve(cwd, "tsconfig.json");
5131
+ if (existsSync(file)) return "exists";
5132
+ writeFileSync(file, JSON.stringify(SCAFFOLD_TSCONFIG, null, 2) + "\n");
5133
+ return "created";
5134
+ }
5105
5135
  function apiErrText(r) {
5106
5136
  const e = r.body.error;
5107
5137
  if (!e) return `${r.status}`;
@@ -5353,6 +5383,49 @@ async function serverApply({ api, bundle, resume, allowDestructive, onPlan }) {
5353
5383
  );
5354
5384
  }
5355
5385
  var PUSH_READ_CONCURRENCY = 5;
5386
+ var CMS_ENABLE_MANIFEST = { enabled: true };
5387
+ function cmsEnableManifest(declared) {
5388
+ return declared.limits !== void 0 ? { ...CMS_ENABLE_MANIFEST, limits: declared.limits } : { ...CMS_ENABLE_MANIFEST };
5389
+ }
5390
+ async function enableCmsBeforeSchema({ api, cfg }) {
5391
+ const declared = cfg.features?.cms;
5392
+ if (!Object.keys(cfg.cms?.collections ?? {}).length || !declared || declared.enabled === false) return null;
5393
+ const cur = await api("GET", "/v1/config/cms");
5394
+ if (classifyRead(cur.status, cur.body?.error?.code) !== "empty") return null;
5395
+ const dry = await api("PUT", "/v1/config/cms?dry_run=true", declared);
5396
+ if (dry.status !== 200) {
5397
+ const e = dry.body.error ?? {};
5398
+ throw new Error(`invalid config for cms: ${e.code ?? dry.status} ${e.message ?? ""} ${e.hint ?? ""}`.trim() + "\n(nothing was pushed)");
5399
+ }
5400
+ const put = await api("PUT", "/v1/config/cms", cmsEnableManifest(declared));
5401
+ if (put.status !== 200) {
5402
+ const e = put.body.error ?? {};
5403
+ throw new Error(`cms: could not enable cms before its schema: ${e.code ?? put.status} ${e.message ?? ""} ${e.hint ?? ""}`.trim());
5404
+ }
5405
+ return put.body.data?.version ?? 0;
5406
+ }
5407
+ var CMS_PROPAGATION_ATTEMPTS = 4;
5408
+ var CMS_PROPAGATION_DELAY_MS = 1e4;
5409
+ function retryWhileCmsPropagates(api, opts = {}) {
5410
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
5411
+ const attempts = opts.attempts ?? CMS_PROPAGATION_ATTEMPTS;
5412
+ const delayMs = opts.delayMs ?? CMS_PROPAGATION_DELAY_MS;
5413
+ let waited = 0;
5414
+ return async (method, path, body) => {
5415
+ for (; ; ) {
5416
+ const res = await api(method, path, body);
5417
+ const propagating = path.startsWith("/v1/cms/") && res.status === 501 && (res.body?.error?.code ?? "capability_not_enabled") === "capability_not_enabled";
5418
+ if (!propagating || waited >= attempts) return res;
5419
+ waited++;
5420
+ opts.onWait?.(` \u2026 cms config propagating (just enabled) \u2014 retrying in ${Math.round(delayMs / 1e3)} s (${waited}/${attempts})`);
5421
+ await sleep(delayMs);
5422
+ }
5423
+ };
5424
+ }
5425
+ function onlyPropagationDeferred(steps) {
5426
+ const errored = steps.filter((st) => st.status === "pending" && st.error);
5427
+ return errored.length > 0 && errored.every((st) => (st.kind === "api-state" || st.kind === "cms.collection" || st.kind === "cms.field") && (st.error ?? "").startsWith("deferred:"));
5428
+ }
5356
5429
  async function planOrPush({ api, features, apply, allowDestructive }) {
5357
5430
  const results = [];
5358
5431
  const destructiveQs = allowDestructive ? "&allow_destructive=true" : "";
@@ -9561,7 +9634,7 @@ var API_CHEATSHEET = [
9561
9634
  { feature: "orgs", what: "add / remove a member", cmd: `vxil api POST /v1/orgs/<org_id>/members '{"user_id":"<id>","role":"member"}' \xB7 vxil api DELETE /v1/orgs/<org_id>/members/<user_id>` },
9562
9635
  { feature: "orgs", what: "invite by e-mail (the token is returned once) / revoke", cmd: `vxil api POST /v1/orgs/<org_id>/invitations '{"email":"a@example.com","role":"member"}' \xB7 vxil api DELETE /v1/orgs/invitations/<invite_id>` },
9563
9636
  // feeds
9564
- { feature: "activity-feed", what: "block or mute a feed for a user", cmd: `vxil api POST /v1/feeds/blocks '{"owner":"<user>","blocked":"<user>","mode":"block"}'` },
9637
+ { feature: "activity-feed", what: "block or mute a feed for a user", cmd: `vxil api POST /v1/feeds/blocks '{"owner":"timeline:<reader_id>","blocked":"user:<author_id>","mode":"block"}'` },
9565
9638
  { feature: "activity-feed", what: "remove an activity", cmd: "vxil api DELETE /v1/feeds/<group>/<feed_id>/activities/<ref>" }
9566
9639
  ];
9567
9640
  function apiCheatSheet() {
@@ -11876,7 +11949,9 @@ function genInputFromConfig(cfg) {
11876
11949
  }
11877
11950
  function typesCheckBody(src) {
11878
11951
  const i = src.indexOf("export interface VxilSchema");
11879
- return i >= 0 ? src.slice(i) : src;
11952
+ const body = i >= 0 ? src.slice(i) : src;
11953
+ const usage = body.indexOf("\n// Usage \u2014 ");
11954
+ return usage >= 0 ? body.slice(0, usage) : body;
11880
11955
  }
11881
11956
  function checkGeneratedTypes(existing, generated, opts) {
11882
11957
  let have = typesCheckBody(withCurrentApiVersionsHead(existing));
@@ -11947,7 +12022,7 @@ export type VxilFilterJson = string | number | boolean | { $eq?: string | number
11947
12022
  // Usage \u2014 narrow the client to THIS tenant's enabled features (a disabled feature
11948
12023
  // becomes a compile error):
11949
12024
  // import { Vxil } from '@vxil/sdk';
11950
- // import type { VxilSchema } from './vxil.types';
12025
+ // import type { VxilSchema } from './vxil.types.js';
11951
12026
  // const vx = Vxil.connect<VxilSchema>({ apiKey: process.env.VXIL_API_KEY! });
11952
12027
  // await vx.from('${colls[0]?.collection ?? "collection"}').create({ /* field-typed */ });
11953
12028
  `;
@@ -13552,7 +13627,7 @@ var TOOLS = [
13552
13627
  {
13553
13628
  name: "search_query",
13554
13629
  feature: "vector-search",
13555
- description: "Retrieve the top-k most relevant chunks from a vector-search collection. mode 'hybrid' (default) fuses semantic + keyword via RRF; 'vector' is pure semantic; 'keyword' is lexical. filter is a JSON object of equality constraints over chunk metadata. Returns ranked results with document/chunk attribution: score is the fused rank value (orders the hits, not a similarity); similarity is the cosine similarity to the query (-1..1, null in keyword mode) \u2014 threshold on similarity.",
13630
+ description: "Retrieve the top-k most relevant chunks from a vector-search collection. mode 'hybrid' (default) fuses semantic + keyword via RRF; 'vector' is pure semantic; 'keyword' is lexical. filter is a JSON object of equality constraints over chunk metadata. Returns ranked results with document/chunk attribution: score is the fused rank value (orders the hits, not a similarity); similarity is the cosine similarity to the query (-1..1, null in keyword mode) \u2014 threshold on similarity. embed_provider names the collection's embedder; 'mock' is the deterministic test embedder (ranks by a hash, not meaning), so tell the user search is not semantic until a real provider is set.",
13556
13631
  inputSchema: {
13557
13632
  type: "object",
13558
13633
  properties: {
@@ -13582,6 +13657,11 @@ var TOOLS = [
13582
13657
  model: { type: "string" },
13583
13658
  max_tokens: { type: "number", minimum: 1, maximum: 2e5 },
13584
13659
  temperature: { type: "number", minimum: 0, maximum: 2 },
13660
+ thinking_level: {
13661
+ type: "string",
13662
+ enum: ["minimal", "low", "medium", "high"],
13663
+ 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."
13664
+ },
13585
13665
  json_mode: { type: "boolean", description: "Constrain the output to JSON (no schema \u2014 use response_schema for a guaranteed shape)." },
13586
13666
  response_schema: {
13587
13667
  type: "object",
@@ -13712,7 +13792,7 @@ var TOOLS = [
13712
13792
  {
13713
13793
  name: "rag_answer",
13714
13794
  feature: "rag",
13715
- description: "Answer a question grounded in a vector-search collection: retrieve \u2192 budget context \u2192 generate \u2192 cite. Defaults the collection/template/top_k/mode from the rag config; override per call. Returns the answer plus citations to the source chunks that grounded it.",
13795
+ description: "Answer a question grounded in a vector-search collection: retrieve \u2192 budget context \u2192 generate \u2192 cite. Defaults the collection/template/top_k/mode from the rag config; override per call. Returns the answer plus citations to the source chunks that grounded it, and embed_provider (the collection's embedder; 'mock' = the deterministic test embedder, so retrieval is not semantic yet). The collection must exist first (POST /v1/search/collections, SDK vx.search.createCollection); a missing one fails retrieval with upstream collection_not_found.",
13716
13796
  inputSchema: {
13717
13797
  type: "object",
13718
13798
  properties: {
@@ -13736,7 +13816,7 @@ var TOOLS = [
13736
13816
  {
13737
13817
  name: "rag_search",
13738
13818
  feature: "rag",
13739
- description: "Retrieval-only grounding preview: the exact chunks rag_answer would ground on, with rerank + metadata boosts applied \u2014 no generation, no token spend. Use it to inspect/tune retrieval (boost weights, rerank, min_similarity) before paying for an answer.",
13819
+ description: "Retrieval-only grounding preview: the exact chunks rag_answer would ground on, with rerank + metadata boosts applied \u2014 no generation, no token spend. Use it to inspect/tune retrieval (boost weights, rerank, min_similarity) before paying for an answer. embed_provider names the collection's embedder (mock = the deterministic test embedder, not semantic).",
13740
13820
  inputSchema: {
13741
13821
  type: "object",
13742
13822
  properties: {
@@ -13882,7 +13962,7 @@ var TOOLS = [
13882
13962
  // Substrate tool (no `feature` — always-on like users_upsert): usage is a
13883
13963
  // platform property, not a feature you enable.
13884
13964
  name: "usage_current",
13885
- description: "Current-month usage for this project: requests used, the plan's included quota, remaining (null when the plan has no fixed cap), month-to-date per-feature metered detail (may lag \u2014 it is aggregated periodically), and `routes` \u2014 the same request count split by the surface that served it (the resolved upstream; /v1/fn/* reports as 'fn-invoke', busiest first, observational only). Read-only. Call this before a bulk run to self-check remaining quota: the Free tier is hard-capped (429 quota_exceeded when exhausted); paid tiers are never cut off mid-month. Needs usage:read or features:read.",
13965
+ description: "Current-month usage for this project: requests used, the plan's included quota, remaining (null when the plan has no fixed cap), month-to-date per-feature metered detail (may lag \u2014 it is aggregated periodically), and `routes` \u2014 the same request count split by the surface that served it (the resolved upstream; /v1/fn/* reports as 'fn-invoke', busiest first, observational only), `files` (live private objects and bytes vs the storage `limit` uploads are held to \u2014 422 quota_exceeded at it) and `functions` (today's UTC call count vs the plan's `limit_per_day`; a snapshot that can trail by a minute or two, null when there is no snapshot for today). Read-only. Call this before a bulk run to self-check remaining quota: the Free tier is hard-capped (429 quota_exceeded when exhausted); paid tiers are never cut off mid-month. Needs usage:read or features:read.",
13886
13966
  inputSchema: { type: "object", properties: {} },
13887
13967
  method: "GET",
13888
13968
  path: "/v1/usage"
@@ -14440,6 +14520,21 @@ function notificationsProviderChecks(manifest) {
14440
14520
  }
14441
14521
  return [{ name: "notifications provider", ok: true, warn: true, detail: MOCK_PROVIDER_NOTE }];
14442
14522
  }
14523
+ var EMBED_MOCK_NOTE = "vector-search embed provider is 'mock': the deterministic TEST embedder ranks by a hash of the text, not by meaning, so search and rag results look real but are not semantic (responses carry embed_provider: 'mock'). Store a provider key (`vxil secrets set vector-search/<name>`) and set embed: { provider: 'openai' | 'cohere', apiKeyRef } in your vector-search config (or 'byov' to supply your own vectors) before you ship";
14524
+ function embedProviderChecks(manifest, collections) {
14525
+ if (!manifest || manifest.enabled === false) return [];
14526
+ const def = manifest.embed?.provider ?? "mock";
14527
+ const mockCols = (collections ?? []).filter((c) => (c.provider ?? def) === "mock").map((c) => c.collection);
14528
+ if (def !== "mock" && !mockCols.length) {
14529
+ return [{ name: "vector-search embed provider", ok: true, detail: def }];
14530
+ }
14531
+ return [{
14532
+ name: "vector-search embed provider",
14533
+ ok: true,
14534
+ warn: true,
14535
+ detail: EMBED_MOCK_NOTE + (mockCols.length ? `. Collections on 'mock': ${mockCols.join(", ")} \u2014 a collection keeps the provider it was created with: create it again with the real embed provider (same name and dimensions) and re-ingest its documents` : "")
14536
+ }];
14537
+ }
14443
14538
  var PAYMENTS_WEBHOOK_SECRET_NOTE = "payments provider is real but no per-tenant webhook secret is declared: every inbound provider delivery will be rejected 401 bad_signature and recorded as a sig_failed row (those rows can never be reprocessed, so the money events behind them are lost). There is NO platform-wide fallback. Declare <provider>.webhookSecretRef in your payments config and store the value with `vxil secrets set payments/<name>`";
14444
14539
  function paymentsWebhookSecretChecks(manifest) {
14445
14540
  if (!manifest || manifest.enabled === false) return [];
@@ -15219,10 +15314,10 @@ function renderTryReport(result, opts) {
15219
15314
  if (genOk) {
15220
15315
  lines.push("\n try it (types were generated in ./vxil.types.ts):");
15221
15316
  lines.push(" import { Vxil } from '@vxil/sdk';");
15222
- lines.push(" import type { VxilSchema } from './vxil.types';");
15317
+ lines.push(" import type { VxilSchema } from './vxil.types.js';");
15223
15318
  lines.push(` const vx = Vxil.connect<VxilSchema>({ apiKey: '${result.api_key}', baseUrl: '${opts.base}' });`);
15224
15319
  } else {
15225
- lines.push("\n try it (typed client skipped \u2014 `npm i -D vxil && npm i @vxil/sdk`, then `npx vxil gen`):");
15320
+ lines.push("\n try it (typed client skipped \u2014 `npm i -D @vxil/cli && npm i @vxil/sdk`, then `npx vxil gen`):");
15226
15321
  lines.push(" import { Vxil } from '@vxil/sdk';");
15227
15322
  lines.push(` const vx = new Vxil({ apiKey: '${result.api_key}', baseUrl: '${opts.base}' });`);
15228
15323
  }
@@ -21455,10 +21550,19 @@ async function runApply(apply, sel = "prod", opts = {}) {
21455
21550
  report.errors++;
21456
21551
  }
21457
21552
  };
21553
+ let cmsApi = api;
21554
+ if (apply) {
21555
+ const enabledAt = await enableCmsBeforeSchema({ api, cfg });
21556
+ if (enabledAt !== null) {
21557
+ console.log(`
21558
+ cms: enabled (v${enabledAt}) before its schema \u2014 the declared cms config follows in the config step`);
21559
+ cmsApi = retryWhileCmsPropagates(api, { onWait: (line) => console.log(line) });
21560
+ }
21561
+ }
21458
21562
  await runSection("cms.schema", async () => {
21459
21563
  if (!cfg.cms?.collections || !Object.keys(cfg.cms.collections).length) return;
21460
21564
  const r = await planCmsSchema({
21461
- api,
21565
+ api: cmsApi,
21462
21566
  collections: cfg.cms.collections,
21463
21567
  apply,
21464
21568
  allowDestructive: hasFlag("allow-destructive"),
@@ -21873,15 +21977,11 @@ ${formatTriggerWiring(w).join("\n")}`);
21873
21977
  say("\napplying server-side\u2026");
21874
21978
  }
21875
21979
  });
21876
- if (done.status === "running") {
21877
- const errored = done.steps.filter((st) => st.status === "pending" && st.error);
21878
- const onlyDeferredApiState = errored.length > 0 && errored.every((st) => st.kind === "api-state" && (st.error ?? "").startsWith("deferred:"));
21879
- if (onlyDeferredApiState) {
21880
- report(done, false);
21881
- say(" \u2026 declared API state deferred (the feature config is still propagating) \u2014 waiting 30 s, then resuming the same apply");
21882
- await new Promise((r) => setTimeout(r, 3e4));
21883
- done = await serverApply({ api, resume: done.apply_id });
21884
- }
21980
+ for (let attempt = 1; done.status === "running" && onlyPropagationDeferred(done.steps) && attempt <= CMS_PROPAGATION_ATTEMPTS; attempt++) {
21981
+ if (attempt === 1) report(done, false);
21982
+ say(` \u2026 deferred: the feature config is still propagating \u2014 waiting ${CMS_PROPAGATION_DELAY_MS / 1e3} s, then resuming the same apply (${attempt}/${CMS_PROPAGATION_ATTEMPTS})`);
21983
+ await new Promise((r) => setTimeout(r, CMS_PROPAGATION_DELAY_MS));
21984
+ done = await serverApply({ api, resume: done.apply_id });
21885
21985
  }
21886
21986
  report(done, true);
21887
21987
  const problems = { hard: [], strict: [] };
@@ -22142,6 +22242,16 @@ async function runDoctor() {
22142
22242
  ));
22143
22243
  }
22144
22244
  }
22245
+ if (live.has("vector-search")) {
22246
+ const vres = await api("GET", "/v1/config/vector-search");
22247
+ if (vres.status === 200) {
22248
+ const cres = await api("GET", "/v1/search/collections");
22249
+ checks.push(...embedProviderChecks(
22250
+ vres.body.data?.manifest,
22251
+ cres.status === 200 ? cres.body.data?.collections ?? [] : null
22252
+ ));
22253
+ }
22254
+ }
22145
22255
  if (live.has("payments")) {
22146
22256
  const pres = await api("GET", "/v1/config/payments");
22147
22257
  if (pres.status === 200) {
@@ -22279,11 +22389,19 @@ function scaffoldProject(configSrc, fns) {
22279
22389
  const gi = resolve7(cwd, ".gitignore");
22280
22390
  const ignore = ["", "# vxil", ".vxil/", ".env.local", ""].join("\n");
22281
22391
  if (!existsSync8(gi) || !readFileSync8(gi, "utf8").includes(".vxil/")) appendFileSync2(gi, ignore);
22282
- const pkg = ensureScaffoldPackageJson(cwd, { sdkTypes: entries.some(([, src]) => src.includes("from '@vxil/sdk'")) });
22392
+ const freshFolder = !existsSync8(resolve7(cwd, "package.json"));
22393
+ const tsconfig = freshFolder ? ensureScaffoldTsconfig(cwd) : existsSync8(resolve7(cwd, "tsconfig.json")) ? "exists" : "suggested";
22394
+ const pkg = ensureScaffoldPackageJson(cwd, {
22395
+ sdkTypes: entries.some(([, src]) => src.includes("from '@vxil/sdk'")),
22396
+ nodeTypes: tsconfig === "created"
22397
+ });
22283
22398
  if (pkg.action === "skipped") console.log(` ! package.json not patched: ${pkg.reason}`);
22284
- return pkg;
22399
+ if (tsconfig === "suggested") {
22400
+ console.log(' (no tsconfig.json written into an existing app \u2014 to type-check vxil.config.ts / functions, use moduleResolution "bundler" and add @types/node)');
22401
+ }
22402
+ return { ...pkg, tsconfig };
22285
22403
  }
22286
- function initProject() {
22404
+ function initProject(opts = {}) {
22287
22405
  const cwd = process.cwd();
22288
22406
  const cfgPath = resolve7(cwd, "vxil.config.ts");
22289
22407
  if (existsSync8(cfgPath) && !hasFlag("force")) fail("vxil.config.ts already exists (use --force to overwrite)");
@@ -22302,8 +22420,10 @@ function initProject() {
22302
22420
  const sdk = Object.values(tpl.functions ?? {}).some((src) => src.includes("from '@vxil/sdk'"));
22303
22421
  console.log(` package.json # declares @vxil/config (the config import)${sdk ? " + @vxil/sdk (the functions' envelope types)" : ""}`);
22304
22422
  }
22423
+ if (pkgRes.tsconfig === "created") console.log(' tsconfig.json # minimal: moduleResolution bundler, types ["node"]');
22424
+ if (opts.fromQuickstart) return;
22305
22425
  console.log("\nnext: `vxil quickstart` (new tenant) or `vxil link <slug>` (existing), then `vxil push`.");
22306
- console.log(" (`npm install` is optional \u2014 it gives your editor the types; the CLI runs vxil.config.ts and bundles functions without it.)");
22426
+ console.log(" (the CLI runs vxil.config.ts and bundles functions without `npm install`; run it to type-check \u2014 tsc and your editor need the installed types.)");
22307
22427
  }
22308
22428
  function listTemplates() {
22309
22429
  console.log("vxil templates \u2014 the Blueprint Gallery. Scaffold one with `vxil init --template <id>`.\n");
@@ -22381,7 +22501,7 @@ try {
22381
22501
  saveProject(hasFlag("dev") ? { ...merged, dev: { tenant_id: slot.tenant_id, slug: slot.slug, edge_url: slot.edge_url } } : merged);
22382
22502
  const envNotice = quickstartEnvNotice(flag("env"), base2, d.app_slug, { dev: hasFlag("dev") });
22383
22503
  if (envNotice) console.error(`vxil: ${envNotice}`);
22384
- if (!existsSync8(resolve7(process.cwd(), "vxil.config.ts"))) initProject();
22504
+ if (!existsSync8(resolve7(process.cwd(), "vxil.config.ts"))) initProject({ fromQuickstart: true });
22385
22505
  console.log(`
22386
22506
  \u2713 tenant '${d.app_slug}' created \xB7 enabled: ${(d.enabled ?? []).join(", ")}`);
22387
22507
  if (d.expires_at) console.log(` dev tenant \u2014 expires ${d.expires_at} (the nightly reaper tears it down; extend by recreating)`);
@@ -22411,11 +22531,12 @@ try {
22411
22531
  console.log(` types: ./vxil.types.ts (generated \u2014 re-run \`vxil gen\` after a schema change)`);
22412
22532
  console.log(` try it:`);
22413
22533
  console.log(` import { Vxil } from '@vxil/sdk';`);
22414
- console.log(` import type { VxilSchema } from './vxil.types';`);
22415
- console.log(` const vx = Vxil.connect<VxilSchema>({ apiKey }); // narrows to YOUR enabled features`);
22534
+ console.log(` import type { VxilSchema } from './vxil.types.js';`);
22535
+ console.log(` const vx = Vxil.connect<VxilSchema>({ apiKey: process.env.VXIL_API_KEY! }); // narrows to YOUR enabled features`);
22416
22536
  console.log(` await vx.from('${firstCollection}').create({ /* field-typed */ });`);
22417
22537
  console.log(`
22418
22538
  key stored in ~/.vxil/credentials.json \xB7 project bound in .vxil/project.json`);
22539
+ console.log(" next: `vxil env pull` writes VXIL_API_KEY (+ VXIL_BASE_URL, VXIL_TENANT_ID) to .env.local for the snippet above");
22419
22540
  break;
22420
22541
  }
22421
22542
  case "try": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vxil/cli",
3
- "version": "0.20.2",
3
+ "version": "0.21.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.13.0",
49
- "@vxil/feature-configs": "0.11.0",
48
+ "@vxil/config": "0.14.0",
49
+ "@vxil/feature-configs": "0.12.0",
50
50
  "@vxil/runtime": "0.0.1",
51
51
  "@vxil/types": "0.0.1",
52
52
  "miniflare": "^4.20260730.0"