@arkveil/cli 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -590,15 +590,15 @@ var require_help = __commonJS({
590
590
  * @return {string}
591
591
  *
592
592
  */
593
- wrap(str, width, indent, minColumnWidth = 40) {
593
+ wrap(str, width, indent2, minColumnWidth = 40) {
594
594
  const indents = " \\f\\t\\v\xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF";
595
595
  const manualIndent = new RegExp(`[\\n][${indents}]+`);
596
596
  if (str.match(manualIndent)) return str;
597
- const columnWidth = width - indent;
597
+ const columnWidth = width - indent2;
598
598
  if (columnWidth < minColumnWidth) return str;
599
- const leadingStr = str.slice(0, indent);
600
- const columnText = str.slice(indent).replace("\r\n", "\n");
601
- const indentString = " ".repeat(indent);
599
+ const leadingStr = str.slice(0, indent2);
600
+ const columnText = str.slice(indent2).replace("\r\n", "\n");
601
+ const indentString = " ".repeat(indent2);
602
602
  const zeroWidthSpace = "\u200B";
603
603
  const breaks = `\\s${zeroWidthSpace}`;
604
604
  const regex2 = new RegExp(
@@ -5276,14 +5276,14 @@ var init_open = __esm({
5276
5276
  }
5277
5277
  const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
5278
5278
  if (options.wait) {
5279
- return new Promise((resolve, reject) => {
5279
+ return new Promise((resolve2, reject) => {
5280
5280
  subprocess.once("error", reject);
5281
5281
  subprocess.once("close", (exitCode) => {
5282
5282
  if (!options.allowNonzeroExitCode && exitCode > 0) {
5283
5283
  reject(new Error(`Exited with code ${exitCode}`));
5284
5284
  return;
5285
5285
  }
5286
- resolve(subprocess);
5286
+ resolve2(subprocess);
5287
5287
  });
5288
5288
  });
5289
5289
  }
@@ -5413,9 +5413,9 @@ var require_src = __commonJS({
5413
5413
  });
5414
5414
 
5415
5415
  // src/index.ts
5416
- import { readFileSync as readFileSync4 } from "fs";
5417
- import { fileURLToPath as fileURLToPath2 } from "url";
5418
- import { dirname, join as join2 } from "path";
5416
+ import { readFileSync as readFileSync5 } from "fs";
5417
+ import { fileURLToPath as fileURLToPath4 } from "url";
5418
+ import { dirname as dirname2, join as join3 } from "path";
5419
5419
 
5420
5420
  // node_modules/.pnpm/commander@12.1.0/node_modules/commander/esm.mjs
5421
5421
  var import_index = __toESM(require_commander(), 1);
@@ -10913,11 +10913,11 @@ var Ora = class {
10913
10913
  get indent() {
10914
10914
  return this.#indent;
10915
10915
  }
10916
- set indent(indent = 0) {
10917
- if (!(indent >= 0 && Number.isInteger(indent))) {
10916
+ set indent(indent2 = 0) {
10917
+ if (!(indent2 >= 0 && Number.isInteger(indent2))) {
10918
10918
  throw new Error("The `indent` option must be an integer from 0 and up");
10919
10919
  }
10920
- this.#indent = indent;
10920
+ this.#indent = indent2;
10921
10921
  this.#updateLineCount();
10922
10922
  }
10923
10923
  get interval() {
@@ -11750,7 +11750,7 @@ import { randomUUID } from "crypto";
11750
11750
  var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
11751
11751
  var RETRYABLE_STATUS = /* @__PURE__ */ new Set([429, 502, 503, 504]);
11752
11752
  var MAX_BACKOFF_MS = 5e3;
11753
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
11753
+ var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
11754
11754
  function backoffDelay(attempt, retryAfter) {
11755
11755
  if (retryAfter) {
11756
11756
  const seconds = Number(retryAfter);
@@ -12205,7 +12205,7 @@ function extractOAuthErrorDescription(json) {
12205
12205
  }
12206
12206
  return void 0;
12207
12207
  }
12208
- var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
12208
+ var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
12209
12209
  async function parseJson(response, url) {
12210
12210
  const text = await safeText(response);
12211
12211
  if (!text) return {};
@@ -13256,7 +13256,10 @@ async function setSchema(ctx, type, options) {
13256
13256
  var TYPE_CHOICES = ["user", "context", "action"];
13257
13257
  function registerSchemas(program2) {
13258
13258
  const schemas = program2.command("schemas").description("Manage USER/CONTEXT/ACTION attribute JSON schemas");
13259
- schemas.command("get").description("Show the JSON Schema for an attribute type").addArgument(new Argument("<type>", "attribute schema type").choices(TYPE_CHOICES)).action(async (type, _options, command) => {
13259
+ schemas.command("get").description("Show the JSON Schema for an attribute type").addArgument(new Argument("<type>", "attribute schema type").choices(TYPE_CHOICES)).addHelpText(
13260
+ "after",
13261
+ "\nTo type the SDK from these schemas (user/context), see the recipe in\n`arkveil sdk info` \u2014 fetch with --json, generate TypeScript, and augment\nthe SDK's ArkveilUserRegistry / ArkveilContextRegistry.\n"
13262
+ ).action(async (type, _options, command) => {
13260
13263
  await run(command, (ctx) => getSchema(ctx, type));
13261
13264
  });
13262
13265
  schemas.command("set").description("Replace the JSON Schema for an attribute type").addArgument(new Argument("<type>", "attribute schema type").choices(TYPE_CHOICES)).option("--data <json>", "JSON Schema: inline JSON, @file, or - for stdin").addHelpText("after", "\nExample:\n $ arkveil schemas set user --data @user-schema.json\n").action(async (type, options, command) => {
@@ -13264,6 +13267,614 @@ function registerSchemas(program2) {
13264
13267
  });
13265
13268
  }
13266
13269
 
13270
+ // src/commands/sdk/catalog.ts
13271
+ var SDK_CATALOG = {
13272
+ language: "TypeScript / JavaScript",
13273
+ languages: ["TypeScript", "JavaScript"],
13274
+ registry: "npm",
13275
+ requirements: { node: ">=18", typescript: "^5 (for typed usage)" },
13276
+ note: "Only TypeScript / JavaScript is supported today, via three packages: the core SDK (arkveil), Node.js/Express (@arkveil/node), and NestJS (@arkveil/nest). There are no SDKs for other languages yet.",
13277
+ targets: [
13278
+ {
13279
+ id: "nest",
13280
+ title: "NestJS SDK",
13281
+ package: "@arkveil/nest",
13282
+ platform: "NestJS (Node.js)",
13283
+ frameworks: ["NestJS"],
13284
+ whenToUse: "Use in a NestJS application. Configure once with ArkveilModule.forRoot and protect routes declaratively with the @PermissionPoint decorator.",
13285
+ install: "npm install @arkveil/nest",
13286
+ quickStart: `import { Module } from "@nestjs/common";
13287
+ import { ArkveilModule } from "@arkveil/nest";
13288
+
13289
+ @Module({
13290
+ imports: [
13291
+ ArkveilModule.forRoot({
13292
+ serviceUrl: "https://api.arkveil.com",
13293
+ apiKey: process.env.ARKVEIL_API_KEY!,
13294
+ getUserAttributes: (req) => ({ id: req.user?.id, role: req.user?.role }),
13295
+ }),
13296
+ ],
13297
+ })
13298
+ export class AppModule {}
13299
+
13300
+ // Protect a route \u2014 the code is type-checked once the registry is augmented:
13301
+ import { Controller, Delete } from "@nestjs/common";
13302
+ import { PermissionPoint } from "@arkveil/nest";
13303
+
13304
+ @Controller("articles")
13305
+ export class ArticlesController {
13306
+ @Delete(":id")
13307
+ @PermissionPoint("content-service.article-delete")
13308
+ remove() {
13309
+ return "Protected content";
13310
+ }
13311
+ }`,
13312
+ docs: "https://www.npmjs.com/package/@arkveil/nest"
13313
+ },
13314
+ {
13315
+ id: "node",
13316
+ title: "Node.js / Express SDK",
13317
+ package: "@arkveil/node",
13318
+ platform: "Node.js (Express, Fastify, and other HTTP frameworks)",
13319
+ frameworks: ["Express", "Fastify", "Node.js HTTP"],
13320
+ whenToUse: "Use in a non-Nest Node.js HTTP server. Provides a `permissionPoint(code)` middleware for Express/Fastify-style apps.",
13321
+ install: "npm install @arkveil/node arkveil",
13322
+ quickStart: `import { Arkveil } from "@arkveil/node";
13323
+
13324
+ const arkveil = new Arkveil({
13325
+ serviceUrl: "https://api.arkveil.com",
13326
+ apiKey: process.env.ARKVEIL_API_KEY!,
13327
+ getUserAttributes: (req) => ({ id: req.user?.id, role: req.user?.role }),
13328
+ onDenied: (req, res) => res.status(403).json({ error: "Forbidden" }),
13329
+ });
13330
+
13331
+ app.post(
13332
+ "/api/admin",
13333
+ arkveil.permissionPoint("content-service.article-delete"),
13334
+ (req, res) => res.json({ message: "Protected content" }),
13335
+ );`,
13336
+ docs: "https://www.npmjs.com/package/@arkveil/node"
13337
+ },
13338
+ {
13339
+ id: "core",
13340
+ title: "Core SDK (runtime-agnostic)",
13341
+ package: "arkveil",
13342
+ platform: "Any JavaScript runtime (Node.js, edge, workers, browser)",
13343
+ frameworks: ["any"],
13344
+ whenToUse: "Use when you want to call Arkveil directly (no middleware), or to build your own platform integration. @arkveil/node and @arkveil/nest are built on top of this.",
13345
+ install: "npm install arkveil",
13346
+ quickStart: `import { Arkveil } from "arkveil";
13347
+
13348
+ const arkveil = new Arkveil({
13349
+ serviceUrl: "https://api.arkveil.com",
13350
+ apiKey: process.env.ARKVEIL_API_KEY!,
13351
+ });
13352
+
13353
+ const { granted } = await arkveil.checkPermission({
13354
+ code: "content-service.article-delete",
13355
+ user: { id: "user-123", role: "admin" },
13356
+ context: {},
13357
+ });
13358
+
13359
+ if (granted) {
13360
+ // allow
13361
+ } else {
13362
+ // deny
13363
+ }`,
13364
+ docs: "https://www.npmjs.com/package/arkveil"
13365
+ }
13366
+ ],
13367
+ typing: {
13368
+ summary: "The SDK is typed by declaration merging. `arkveil generate typescript` reads this project's permission codes and user/context JSON Schemas and writes one TypeScript file that augments the SDK registries. Until you import it, attributes are Record<string, any> and codes are string, so untyped usage keeps working.",
13369
+ command: "arkveil generate typescript -o src/arkveil.generated.ts",
13370
+ registries: [
13371
+ {
13372
+ interface: "ArkveilUserRegistry",
13373
+ key: "attributes",
13374
+ resolves: "ArkveilUser",
13375
+ source: "arkveil schemas get user --json"
13376
+ },
13377
+ {
13378
+ interface: "ArkveilContextRegistry",
13379
+ key: "attributes",
13380
+ resolves: "ArkveilContext",
13381
+ source: "arkveil schemas get context --json"
13382
+ },
13383
+ {
13384
+ interface: "ArkveilCodeRegistry",
13385
+ key: "codes",
13386
+ resolves: "ArkveilCode",
13387
+ source: "the permission/action codes defined in your project"
13388
+ }
13389
+ ],
13390
+ steps: [
13391
+ "Run `arkveil generate typescript -o src/arkveil.generated.ts` \u2014 it fetches the codes + attribute schemas and writes the typed file for you (use `--include user,context` to skip codes).",
13392
+ 'Import the generated file once as a side-effect import (`import "./arkveil.generated"`) so the `declare module "arkveil"` augmentation is in scope.',
13393
+ "Permission codes plus `getUserAttributes`, `getContextAttributes`, and `checkPermission` are now type-checked against this project.",
13394
+ "Re-run the command whenever the project's codes or attribute schemas change to keep the types in sync.",
13395
+ "Manual alternative: `arkveil schemas get user|context --json` returns the raw JSON Schema (under `.jsonSchema`) if you prefer to generate the types yourself."
13396
+ ],
13397
+ example: `// arkveil-attributes.generated.ts \u2014 generated from \`arkveil schemas get\`
13398
+ export interface ArkveilUserAttributes {
13399
+ id?: string;
13400
+ role: "admin" | "editor" | "viewer";
13401
+ }
13402
+
13403
+ export interface ArkveilContextAttributes {
13404
+ ipAddress?: string;
13405
+ region?: "EU" | "US";
13406
+ }
13407
+
13408
+ declare module "arkveil" {
13409
+ interface ArkveilUserRegistry {
13410
+ attributes: ArkveilUserAttributes;
13411
+ }
13412
+ interface ArkveilContextRegistry {
13413
+ attributes: ArkveilContextAttributes;
13414
+ }
13415
+ }`
13416
+ }
13417
+ };
13418
+ function findTarget(id) {
13419
+ return SDK_CATALOG.targets.find((t) => t.id === id);
13420
+ }
13421
+ var SDK_TARGET_IDS = SDK_CATALOG.targets.map((t) => t.id);
13422
+ function renderTarget(t) {
13423
+ return [
13424
+ `${t.title} (${t.package})`,
13425
+ ` Platform: ${t.platform}`,
13426
+ ` Frameworks: ${t.frameworks.join(", ")}`,
13427
+ ` When: ${t.whenToUse}`,
13428
+ ` Install: ${t.install}`,
13429
+ "",
13430
+ indent(t.quickStart, 2)
13431
+ ].join("\n");
13432
+ }
13433
+ function renderTyping(typing) {
13434
+ const registries = typing.registries.map(
13435
+ (r2) => ` \u2022 ${r2.interface} (key "${r2.key}") \u2192 ${r2.resolves}
13436
+ from: ${r2.source}`
13437
+ ).join("\n");
13438
+ const steps = typing.steps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
13439
+ return [
13440
+ "TYPED CODES, USER & CONTEXT ATTRIBUTES",
13441
+ ` ${typing.summary}`,
13442
+ "",
13443
+ ` Generate it: ${typing.command}`,
13444
+ "",
13445
+ " Registries to augment:",
13446
+ registries,
13447
+ "",
13448
+ " Steps:",
13449
+ steps,
13450
+ "",
13451
+ " Example generated file:",
13452
+ indent(typing.example, 4)
13453
+ ].join("\n");
13454
+ }
13455
+ function renderCatalog(target) {
13456
+ const c = SDK_CATALOG;
13457
+ const header = [
13458
+ "Arkveil SDK \u2014 install & usage",
13459
+ "",
13460
+ `Language: ${c.language}`,
13461
+ `Registry: ${c.registry}`,
13462
+ `Requirements: Node ${c.requirements.node}, TypeScript ${c.requirements.typescript}`,
13463
+ "",
13464
+ c.note
13465
+ ].join("\n");
13466
+ if (target) {
13467
+ return [header, "", renderTarget(target)].join("\n");
13468
+ }
13469
+ const targets = c.targets.map(renderTarget).join("\n\n");
13470
+ return [
13471
+ header,
13472
+ "",
13473
+ "TARGETS",
13474
+ "",
13475
+ targets,
13476
+ "",
13477
+ renderTyping(c.typing),
13478
+ "",
13479
+ "See also: `arkveil sdk install <core|node|nest>`, `arkveil schemas get <user|context>`."
13480
+ ].join("\n");
13481
+ }
13482
+ function indent(text, spaces) {
13483
+ const pad = " ".repeat(spaces);
13484
+ return text.split("\n").map((line) => line.length > 0 ? pad + line : line).join("\n");
13485
+ }
13486
+
13487
+ // src/commands/sdk/info.ts
13488
+ function sdkInfo(ctx, target) {
13489
+ if (target !== void 0 && !findTarget(target)) {
13490
+ throw new UsageError(
13491
+ `Unknown SDK target "${target}".`,
13492
+ `Choose one of: ${SDK_TARGET_IDS.join(", ")}.`
13493
+ );
13494
+ }
13495
+ const selected = target ? findTarget(target) : void 0;
13496
+ const jsonValue = selected ? { ...SDK_CATALOG, targets: [selected] } : SDK_CATALOG;
13497
+ ctx.out.data(jsonValue, () => renderCatalog(selected));
13498
+ return Promise.resolve();
13499
+ }
13500
+
13501
+ // src/commands/sdk/install.ts
13502
+ function sdkInstall(ctx, target) {
13503
+ const t = findTarget(target);
13504
+ if (!t) {
13505
+ throw new UsageError(
13506
+ `Unknown SDK target "${target}".`,
13507
+ `Choose one of: ${SDK_TARGET_IDS.join(", ")}.`
13508
+ );
13509
+ }
13510
+ ctx.out.data(
13511
+ { id: t.id, package: t.package, install: t.install, registry: SDK_CATALOG.registry },
13512
+ () => t.install
13513
+ );
13514
+ return Promise.resolve();
13515
+ }
13516
+
13517
+ // src/commands/sdk/index.ts
13518
+ var TARGET_CHOICES = SDK_TARGET_IDS;
13519
+ function registerSdk(program2) {
13520
+ const sdk = program2.command("sdk").description("How to install and use the Arkveil SDK (for humans and AI agents)");
13521
+ sdk.command("info").description("Print SDK install + usage info (all targets, or one)").addArgument(
13522
+ new Argument("[target]", "limit to one SDK target").choices(TARGET_CHOICES)
13523
+ ).addHelpText(
13524
+ "after",
13525
+ `
13526
+ Targets:
13527
+ nest NestJS app (@arkveil/nest)
13528
+ node Node.js / Express (@arkveil/node)
13529
+ core runtime-agnostic (arkveil)
13530
+
13531
+ For AI agents: \`arkveil sdk info --json\` emits the full machine-readable
13532
+ catalog \u2014 packages, install commands, usage snippets, and the recipe to type
13533
+ the SDK from your project's attribute schemas (\`arkveil schemas get user|context\`).
13534
+
13535
+ Examples:
13536
+ $ arkveil sdk info # everything
13537
+ $ arkveil sdk info nest # just the NestJS package
13538
+ $ arkveil sdk info --json | jq . # structured output for tooling/agents
13539
+ `
13540
+ ).action(async (target, _options, command) => {
13541
+ await run(command, (ctx) => sdkInfo(ctx, target));
13542
+ });
13543
+ sdk.command("install").description("Print the npm install command for an SDK target").addArgument(
13544
+ new Argument("<target>", "SDK target to install").choices(TARGET_CHOICES)
13545
+ ).addHelpText(
13546
+ "after",
13547
+ '\nExample:\n $ arkveil sdk install nest\n $ eval "$(arkveil sdk install node)"\n'
13548
+ ).action(async (target, _options, command) => {
13549
+ await run(command, (ctx) => sdkInstall(ctx, target));
13550
+ });
13551
+ }
13552
+
13553
+ // src/commands/generate/typescript.ts
13554
+ import { writeFileSync as writeFileSync2 } from "fs";
13555
+ import { resolve } from "path";
13556
+
13557
+ // src/lib/json-schema-to-ts.ts
13558
+ var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
13559
+ function tsLiteral(value) {
13560
+ if (typeof value === "string") return JSON.stringify(value);
13561
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
13562
+ if (value === null) return "null";
13563
+ return JSON.stringify(value);
13564
+ }
13565
+ function dedupe(values) {
13566
+ return [...new Set(values)];
13567
+ }
13568
+ function commentText(text) {
13569
+ return text.replace(/\*\//g, "*\\/").replace(/\s+/g, " ").trim();
13570
+ }
13571
+ function indentOf(spaces) {
13572
+ return " ".repeat(spaces);
13573
+ }
13574
+ function resolveRef(ref, ctx) {
13575
+ const local = ref.match(/^#\/(\$defs|definitions)\/(.+)$/);
13576
+ if (!local) return void 0;
13577
+ const bag = local[1] === "$defs" ? ctx.root.$defs : ctx.root.definitions;
13578
+ const key = decodeURIComponent(local[2]);
13579
+ return bag?.[key];
13580
+ }
13581
+ function needsParensForArray(t) {
13582
+ return t.includes("|") || t.includes("&");
13583
+ }
13584
+ function arrayType2(schema, indent2, ctx) {
13585
+ if (Array.isArray(schema.items)) {
13586
+ const tuple = schema.items.map((s) => schemaToType(s, indent2, ctx)).join(", ");
13587
+ return `[${tuple}]`;
13588
+ }
13589
+ const item = schema.items ? schemaToType(schema.items, indent2, ctx) : "unknown";
13590
+ return needsParensForArray(item) ? `(${item})[]` : `${item}[]`;
13591
+ }
13592
+ function objectType2(schema, indent2, ctx) {
13593
+ const props = schema.properties ?? {};
13594
+ const keys = Object.keys(props);
13595
+ const required = new Set(schema.required ?? []);
13596
+ const inner = indent2 + 2;
13597
+ const lines = [];
13598
+ for (const key of keys) {
13599
+ const propSchema = props[key];
13600
+ const optional = required.has(key) ? "" : "?";
13601
+ const safeKey = IDENTIFIER.test(key) ? key : JSON.stringify(key);
13602
+ if (typeof propSchema.description === "string" && propSchema.description.trim()) {
13603
+ lines.push(`${indentOf(inner)}/** ${commentText(propSchema.description)} */`);
13604
+ }
13605
+ lines.push(
13606
+ `${indentOf(inner)}${safeKey}${optional}: ${schemaToType(propSchema, inner, ctx)};`
13607
+ );
13608
+ }
13609
+ const ap = schema.additionalProperties;
13610
+ if (ap && ap !== true) {
13611
+ lines.push(`${indentOf(inner)}[key: string]: ${schemaToType(ap, inner, ctx)};`);
13612
+ } else if (ap === true) {
13613
+ lines.push(`${indentOf(inner)}[key: string]: unknown;`);
13614
+ }
13615
+ if (lines.length === 0) {
13616
+ return ap === false ? "Record<string, never>" : "Record<string, unknown>";
13617
+ }
13618
+ return `{
13619
+ ${lines.join("\n")}
13620
+ ${indentOf(indent2)}}`;
13621
+ }
13622
+ function unionOf(schemas, indent2, ctx) {
13623
+ const parts = dedupe(schemas.map((s) => schemaToType(s, indent2, ctx)));
13624
+ return parts.length > 0 ? parts.join(" | ") : "unknown";
13625
+ }
13626
+ function schemaToType(schema, indent2 = 0, ctx = {
13627
+ root: typeof schema === "object" && schema ? schema : {},
13628
+ seen: /* @__PURE__ */ new Set()
13629
+ }) {
13630
+ if (schema === void 0 || schema === true) return "unknown";
13631
+ if (schema === false) return "never";
13632
+ if (typeof schema.$ref === "string") {
13633
+ if (ctx.seen.has(schema.$ref)) return "unknown";
13634
+ const resolved = resolveRef(schema.$ref, ctx);
13635
+ if (!resolved) return "unknown";
13636
+ ctx.seen.add(schema.$ref);
13637
+ const t = schemaToType(resolved, indent2, ctx);
13638
+ ctx.seen.delete(schema.$ref);
13639
+ return t;
13640
+ }
13641
+ if (schema.const !== void 0) return tsLiteral(schema.const);
13642
+ if (Array.isArray(schema.enum)) {
13643
+ const union = dedupe(schema.enum.map(tsLiteral));
13644
+ return union.length > 0 ? union.join(" | ") : "never";
13645
+ }
13646
+ if (Array.isArray(schema.anyOf)) return unionOf(schema.anyOf, indent2, ctx);
13647
+ if (Array.isArray(schema.oneOf)) return unionOf(schema.oneOf, indent2, ctx);
13648
+ if (Array.isArray(schema.allOf)) {
13649
+ const parts = dedupe(schema.allOf.map((s) => schemaToType(s, indent2, ctx)));
13650
+ return parts.length > 0 ? parts.join(" & ") : "unknown";
13651
+ }
13652
+ const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
13653
+ const withNullable = (t) => schema.nullable && !t.split("|").map((s) => s.trim()).includes("null") ? `${t} | null` : t;
13654
+ if (types.length === 0) return withNullable("unknown");
13655
+ if (types.length > 1) {
13656
+ const parts = dedupe(
13657
+ types.map(
13658
+ (t) => t === "null" ? "null" : schemaToType({ ...schema, type: t }, indent2, ctx)
13659
+ )
13660
+ );
13661
+ return parts.join(" | ");
13662
+ }
13663
+ switch (types[0]) {
13664
+ case "string":
13665
+ return withNullable("string");
13666
+ case "integer":
13667
+ case "number":
13668
+ return withNullable("number");
13669
+ case "boolean":
13670
+ return withNullable("boolean");
13671
+ case "null":
13672
+ return "null";
13673
+ case "array":
13674
+ return withNullable(arrayType2(schema, indent2, ctx));
13675
+ case "object":
13676
+ return withNullable(objectType2(schema, indent2, ctx));
13677
+ default:
13678
+ return withNullable("unknown");
13679
+ }
13680
+ }
13681
+ function schemaToNamedType(name, schema, description) {
13682
+ const ctx = { root: schema, seen: /* @__PURE__ */ new Set() };
13683
+ const body = schemaToType(schema, 0, ctx);
13684
+ const doc = description && description.trim() ? `/** ${commentText(description)} */
13685
+ ` : "";
13686
+ if (body.startsWith("{")) {
13687
+ return `${doc}export interface ${name} ${body}`;
13688
+ }
13689
+ return `${doc}export type ${name} = ${body};`;
13690
+ }
13691
+
13692
+ // src/commands/generate/assemble.ts
13693
+ var TYPE_NAMES = {
13694
+ codes: "ArkveilCodes",
13695
+ user: "ArkveilUserAttributes",
13696
+ context: "ArkveilContextAttributes"
13697
+ };
13698
+ var HEADER = `// AUTO-GENERATED by \`arkveil generate typescript\`. Do not edit by hand.
13699
+ //
13700
+ // This file types the Arkveil SDK for TypeScript by declaration-merging into
13701
+ // the \`arkveil\` package. Import it once (a side-effect import is enough) and
13702
+ // permission codes plus \`user\` / \`context\` attributes become typed across
13703
+ // \`checkPermission\`, the Node \`permissionPoint\` middleware, and the NestJS
13704
+ // \`@PermissionPoint\` decorator. Regenerate whenever codes or schemas change.`;
13705
+ function renderCodes(codes) {
13706
+ if (codes.length === 0) {
13707
+ return `/** No permission codes were found in this project (falls back to \`string\`). */
13708
+ export type ${TYPE_NAMES.codes} = string;`;
13709
+ }
13710
+ const union = codes.map((c) => ` | ${JSON.stringify(c)}`).join("\n");
13711
+ return `/** Union of every permission code defined in this Arkveil project. */
13712
+ export type ${TYPE_NAMES.codes} =
13713
+ ${union};`;
13714
+ }
13715
+ function assembleTypeScript(input) {
13716
+ const blocks = [HEADER];
13717
+ const augment = [];
13718
+ if (input.codes !== void 0) {
13719
+ blocks.push(renderCodes(input.codes));
13720
+ augment.push(` interface ArkveilCodeRegistry {
13721
+ codes: ${TYPE_NAMES.codes};
13722
+ }`);
13723
+ }
13724
+ if (input.userSchema !== void 0) {
13725
+ blocks.push(
13726
+ schemaToNamedType(
13727
+ TYPE_NAMES.user,
13728
+ input.userSchema,
13729
+ "Shape of `user` attributes (from `arkveil schemas get user`)."
13730
+ )
13731
+ );
13732
+ augment.push(
13733
+ ` interface ArkveilUserRegistry {
13734
+ attributes: ${TYPE_NAMES.user};
13735
+ }`
13736
+ );
13737
+ }
13738
+ if (input.contextSchema !== void 0) {
13739
+ blocks.push(
13740
+ schemaToNamedType(
13741
+ TYPE_NAMES.context,
13742
+ input.contextSchema,
13743
+ "Shape of `context` attributes (from `arkveil schemas get context`)."
13744
+ )
13745
+ );
13746
+ augment.push(
13747
+ ` interface ArkveilContextRegistry {
13748
+ attributes: ${TYPE_NAMES.context};
13749
+ }`
13750
+ );
13751
+ }
13752
+ blocks.push(
13753
+ `// Register the generated types globally so the default \`Arkveil\` generics
13754
+ // pick them up automatically \u2014 no need to pass them everywhere.
13755
+ declare module "arkveil" {
13756
+ ${augment.join("\n")}
13757
+ }`
13758
+ );
13759
+ return `${blocks.join("\n\n")}
13760
+ `;
13761
+ }
13762
+
13763
+ // src/commands/generate/typescript.ts
13764
+ var ALL_INCLUDES = ["codes", "user", "context"];
13765
+ function parseInclude(value) {
13766
+ if (value === void 0) return ALL_INCLUDES;
13767
+ const items = value.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
13768
+ if (items.length === 0) {
13769
+ throw new UsageError(
13770
+ "--include must list at least one of: codes, user, context.",
13771
+ "Example: --include user,context"
13772
+ );
13773
+ }
13774
+ const invalid = items.filter((i) => !ALL_INCLUDES.includes(i));
13775
+ if (invalid.length > 0) {
13776
+ throw new UsageError(
13777
+ `Unknown --include value(s): ${invalid.join(", ")}.`,
13778
+ "Allowed values are: codes, user, context."
13779
+ );
13780
+ }
13781
+ return ALL_INCLUDES.filter((i) => items.includes(i));
13782
+ }
13783
+ function collectActionCodes(node, into) {
13784
+ if (node.resourceType === "ACTION" && node.resource && "code" in node.resource) {
13785
+ const code = node.resource.code;
13786
+ if (typeof code === "string" && code.length > 0) into.add(code);
13787
+ }
13788
+ for (const child of node.children ?? []) {
13789
+ collectActionCodes(child, into);
13790
+ }
13791
+ }
13792
+ async function fetchSchema(ctx, type) {
13793
+ const client = await ctx.getClient({ requireAuth: true });
13794
+ const res = await unwrap(
13795
+ client.GET("/api/v1/attribute-schemas/{type}", { params: { path: { type } } }),
13796
+ "GET"
13797
+ );
13798
+ return res.jsonSchema ?? {};
13799
+ }
13800
+ async function fetchActionCodes(ctx) {
13801
+ const client = await ctx.getClient({ requireAuth: true });
13802
+ const tree = await unwrap(
13803
+ client.GET("/api/v1/navigation/trees/actions"),
13804
+ "GET"
13805
+ );
13806
+ const codes = /* @__PURE__ */ new Set();
13807
+ if (tree?.root) collectActionCodes(tree.root, codes);
13808
+ return [...codes].sort();
13809
+ }
13810
+ async function generateTypeScript(ctx, options) {
13811
+ const include = parseInclude(options.include);
13812
+ const spinner = ctx.out.spinner("Generating TypeScript\u2026");
13813
+ const input = {};
13814
+ let codes = [];
13815
+ try {
13816
+ if (include.includes("codes")) {
13817
+ codes = await fetchActionCodes(ctx);
13818
+ input.codes = codes;
13819
+ }
13820
+ if (include.includes("user")) {
13821
+ input.userSchema = await fetchSchema(ctx, "user");
13822
+ }
13823
+ if (include.includes("context")) {
13824
+ input.contextSchema = await fetchSchema(ctx, "context");
13825
+ }
13826
+ spinner.stop();
13827
+ } catch (err) {
13828
+ spinner.fail("Could not generate TypeScript.");
13829
+ throw err;
13830
+ }
13831
+ const code = assembleTypeScript(input);
13832
+ if (include.includes("codes") && codes.length === 0) {
13833
+ ctx.out.warn(
13834
+ "No permission codes found; ArkveilCodes falls back to `string`. Define actions first."
13835
+ );
13836
+ }
13837
+ if (options.output) {
13838
+ const target = resolve(process.cwd(), options.output);
13839
+ writeFileSync2(target, code, "utf8");
13840
+ ctx.out.success(`Wrote ${include.join(", ")} TypeScript to ${target}`);
13841
+ ctx.out.data({ output: target, include, codeCount: codes.length }, () => void 0);
13842
+ return;
13843
+ }
13844
+ ctx.out.data(
13845
+ { language: "typescript", include, codeCount: codes.length, code },
13846
+ () => code
13847
+ );
13848
+ }
13849
+
13850
+ // src/commands/generate/index.ts
13851
+ function registerGenerate(program2) {
13852
+ const generate = program2.command("generate").alias("gen").description("Generate typed SDK code from this project (TypeScript)");
13853
+ generate.command("typescript").alias("ts").description("Generate a TypeScript file that types the Arkveil SDK").option(
13854
+ "--include <items>",
13855
+ "comma-separated subset of codes,user,context",
13856
+ "codes,user,context"
13857
+ ).option("-o, --output <file>", "write to a file instead of stdout").addHelpText(
13858
+ "after",
13859
+ `
13860
+ Generates TypeScript that types the Arkveil SDK by declaration-merging into the
13861
+ \`arkveil\` package: a permission-code union (ArkveilCodes) and \`user\` /
13862
+ \`context\` attribute types (ArkveilUserAttributes / ArkveilContextAttributes),
13863
+ sourced from this project's actions and attribute schemas. Import the file once
13864
+ (a side-effect import is enough) and the SDK becomes typed.
13865
+
13866
+ This emits TypeScript only \u2014 there is no codegen for other languages yet.
13867
+
13868
+ Examples:
13869
+ $ arkveil generate typescript -o src/arkveil.generated.ts
13870
+ $ arkveil gen ts --include user,context > src/arkveil.generated.ts
13871
+ $ arkveil generate typescript --json # { language, include, code, \u2026 }
13872
+ `
13873
+ ).action(async (options, command) => {
13874
+ await run(command, (ctx) => generateTypeScript(ctx, options));
13875
+ });
13876
+ }
13877
+
13267
13878
  // src/commands/folders/create.ts
13268
13879
  async function createFolder(ctx, options) {
13269
13880
  const body = {
@@ -13669,12 +14280,13 @@ async function deletePolicy(ctx, targetNodeId, policyId, options) {
13669
14280
  // src/commands/policies/index.ts
13670
14281
  var POLICY_TYPES = ["PERMISSION", "READ", "WRITE", "INVARIANT", "PROJECTION"];
13671
14282
  var POLICY_STATUSES = ["ENABLED", "DISABLED", "DRAFT", "DELETED"];
14283
+ var DSL_HELP = "\n--condition and --filter use the Arkveil formula DSL.\nRun `arkveil formula syntax` for the full reference, or `arkveil formula parse` to validate a formula.\n";
13672
14284
  function registerPolicies(program2) {
13673
14285
  const policies = program2.command("policies").description("Manage policies attached to a target");
13674
- policies.command("create <targetNodeId>").description("Create a policy under a target").addOption(new Option("--type <type>", "policy type").choices(POLICY_TYPES).makeOptionMandatory()).addOption(new Option("--status <status>", "policy status").choices(POLICY_STATUSES).makeOptionMandatory()).requiredOption("--title <title>", "policy title").option("--description <text>", "description").option("--condition <dsl>", "condition DSL").option("--filter <dsl>", "filter DSL (data policies)").option("--projection <json>", "projection: inline JSON, @file, or -").action(async (targetNodeId, options, command) => {
14286
+ policies.command("create <targetNodeId>").description("Create a policy under a target").addOption(new Option("--type <type>", "policy type").choices(POLICY_TYPES).makeOptionMandatory()).addOption(new Option("--status <status>", "policy status").choices(POLICY_STATUSES).makeOptionMandatory()).requiredOption("--title <title>", "policy title").option("--description <text>", "description").option("--condition <dsl>", "condition DSL").option("--filter <dsl>", "filter DSL (data policies)").option("--projection <json>", "projection: inline JSON, @file, or -").addHelpText("after", DSL_HELP).action(async (targetNodeId, options, command) => {
13675
14287
  await run(command, (ctx) => createPolicy(ctx, targetNodeId, options));
13676
14288
  });
13677
- policies.command("update <targetNodeId> <policyId>").description("Update a policy").addOption(new Option("--status <status>", "policy status").choices(POLICY_STATUSES).makeOptionMandatory()).requiredOption("--title <title>", "policy title").option("--description <text>", "description").option("--condition <dsl>", "condition DSL").option("--filter <dsl>", "filter DSL (data policies)").option("--projection <json>", "projection: inline JSON, @file, or -").action(
14289
+ policies.command("update <targetNodeId> <policyId>").description("Update a policy").addOption(new Option("--status <status>", "policy status").choices(POLICY_STATUSES).makeOptionMandatory()).requiredOption("--title <title>", "policy title").option("--description <text>", "description").option("--condition <dsl>", "condition DSL").option("--filter <dsl>", "filter DSL (data policies)").option("--projection <json>", "projection: inline JSON, @file, or -").addHelpText("after", DSL_HELP).action(
13678
14290
  async (targetNodeId, policyId, options, command) => {
13679
14291
  await run(command, (ctx) => updatePolicy(ctx, targetNodeId, policyId, options));
13680
14292
  }
@@ -14009,6 +14621,105 @@ async function parseFormula(ctx, options) {
14009
14621
  ctx.out.data(ast, () => JSON.stringify(ast, null, 2));
14010
14622
  }
14011
14623
 
14624
+ // src/commands/formula/syntax.ts
14625
+ var FORMULA_SYNTAX_REFERENCE = `Arkveil Formula DSL \u2014 syntax reference
14626
+
14627
+ A formula is a single boolean expression evaluated against the attributes of a
14628
+ request; it returns true or false. Formulas are used for policy conditions and
14629
+ filters, target conditions, and test selectors.
14630
+
14631
+ ATTRIBUTE REFERENCES
14632
+ Read request attributes through one of four roots. The dot is part of the
14633
+ keyword \u2014 write "user.role", not "user . role":
14634
+
14635
+ user.<path> e.g. user.role, user.profile.age
14636
+ context.<path> e.g. context.country, context.time.hour (note: "context.", not "ctx.")
14637
+ action.<path> e.g. action.name, action.tags
14638
+ request.<path> e.g. request.invoice.amount
14639
+
14640
+ Paths may be nested with dots: request.invoice.line.total
14641
+
14642
+ LITERALS
14643
+ String "double quoted" escape an inner quote with \\" e.g. "O\\"Brien"
14644
+ Integer 42 whole numbers only \u2014 no decimals, no negatives
14645
+ Boolean true false
14646
+ Array ["a","b"] [1,2,3] [true,false]
14647
+ \u2022 never empty
14648
+ \u2022 all elements must be the same type
14649
+ \u2022 elements are literals only (no attribute references inside an array)
14650
+
14651
+ COMPARISON
14652
+ = equal user.role = "admin"
14653
+ != not equal user.status != "blocked"
14654
+
14655
+ Equality is a single "=" (NOT "=="). "==" is not valid.
14656
+
14657
+ STRING PREDICATES left <op> right
14658
+ contains request.path contains "/admin"
14659
+ containsIgnoreCase user.email containsIgnoreCase "@Arkveil.com"
14660
+ startsWith action.name startsWith "delete"
14661
+ startsWithIgnoreCase action.name startsWithIgnoreCase "Delete"
14662
+ matches user.name matches "^A.*" (regular expression)
14663
+
14664
+ PRESENCE / SET / COLLECTION CHECKS
14665
+ is null user.manager is null
14666
+ is not null user.manager is not null
14667
+ in <array> user.role in ["admin","editor"]
14668
+ not in <array> context.region not in ["EU","UK"]
14669
+ is empty action.tags is empty
14670
+ is not empty action.tags is not empty
14671
+ is uniform request.amounts is uniform (all elements are equal)
14672
+ is diverse request.amounts is diverse (elements are not all equal)
14673
+
14674
+ BOOLEAN LOGIC precedence, lowest to highest: or < and < not
14675
+ and user.active = true and context.country = "US"
14676
+ or user.isOwner = true or user.role = "admin"
14677
+ not not (user.suspended = true)
14678
+
14679
+ Keywords are lowercase. Use parentheses to group: (a or b) and c
14680
+
14681
+ ITERATIVE PREDICATES OVER COLLECTIONS
14682
+ Test the elements of an array. <condition> is a full boolean expression in
14683
+ which "it" refers to the current element:
14684
+
14685
+ any <collection> [as <alias>] where <condition> at least one element matches
14686
+ all <collection> [as <alias>] where <condition> every element matches
14687
+ every <collection> [as <alias>] where <condition> every element matches
14688
+ none <collection> [as <alias>] where <condition> no element matches
14689
+ exists <collection> [as <alias>] where <condition> at least one element matches
14690
+
14691
+ \u2022 <collection> must be an array attribute (user./context./action./request.)
14692
+ or an array literal. It cannot be "it", a scalar literal, or a parenthesized
14693
+ expression.
14694
+ \u2022 Because <condition> is a full expression, "and"/"or" bind INSIDE the where:
14695
+ any user.tags where it = "a" or it = "b"
14696
+ To combine a whole iterative predicate with an outer expression, wrap it in
14697
+ parentheses:
14698
+ (any user.tags where it = "a") or user.active = true
14699
+ \u2022 Each <collection> must be one of those roots or an array literal \u2014 you cannot
14700
+ iterate an alias element (e.g. "g.members" is not a valid collection).
14701
+ \u2022 For nested iteration, name the outer collection with "as" and reference its
14702
+ current element as <alias>.it ("it" alone is always the innermost element):
14703
+ any user.groups as g where any context.allowedGroups where it = g.it
14704
+
14705
+ EXAMPLES
14706
+ user.role = "admin"
14707
+ user.role = "admin" and context.country = "US"
14708
+ not (user.suspended = true) and user.role in ["admin","editor"]
14709
+ request.path startsWith "/admin/" and user.clearance != "none"
14710
+ action.tags is not empty and none action.tags where it = "blocked"
14711
+ any user.permissions where it startsWith "billing:"
14712
+ all request.items as line where line.it != ""
14713
+ (any user.tags where it = "vip") or user.isOwner = true
14714
+ `;
14715
+ function formulaSyntax(ctx) {
14716
+ ctx.out.data(
14717
+ { dsl: "arkveil-formula", reference: FORMULA_SYNTAX_REFERENCE },
14718
+ () => FORMULA_SYNTAX_REFERENCE
14719
+ );
14720
+ return Promise.resolve();
14721
+ }
14722
+
14012
14723
  // src/commands/formula/index.ts
14013
14724
  var CONTEXTS = [
14014
14725
  "ACTION_PERMISSION",
@@ -14020,12 +14731,15 @@ var CONTEXTS = [
14020
14731
  ];
14021
14732
  function registerFormula(program2) {
14022
14733
  const formula = program2.command("formula").description("Work with the formula DSL");
14734
+ formula.command("syntax").description("Print the formula DSL syntax reference").addHelpText(
14735
+ "after",
14736
+ "\nThe full DSL reference (operators, predicates, collections, examples) is\nprinted to stdout. Pipe it anywhere, e.g. `arkveil formula syntax | less`.\n"
14737
+ ).action(async (_options, command) => {
14738
+ await run(command, formulaSyntax);
14739
+ });
14023
14740
  formula.command("parse").description("Parse a formula DSL string into its AST").requiredOption("--dsl <dsl>", "the formula DSL to parse").addOption(new Option("--context <context>", "evaluation context").choices(CONTEXTS).makeOptionMandatory()).option("--request-schema <json>", "request schema: inline JSON, @file, or -").addHelpText(
14024
14741
  "after",
14025
- `
14026
- Example:
14027
- $ arkveil formula parse --context ACTION_PERMISSION --dsl 'user.role == "admin"'
14028
- `
14742
+ "\nExample:\n $ arkveil formula parse --context ACTION_PERMISSION --dsl 'user.role = \"admin\"'\n\nSee `arkveil formula syntax` for the full DSL reference.\n"
14029
14743
  ).action(
14030
14744
  async (options, command) => {
14031
14745
  await run(command, (ctx) => parseFormula(ctx, options));
@@ -14240,12 +14954,255 @@ function registerAdmin(program2) {
14240
14954
  });
14241
14955
  }
14242
14956
 
14957
+ // src/commands/update/update.ts
14958
+ import { execFile as execFile6 } from "child_process";
14959
+ import { fileURLToPath as fileURLToPath3 } from "url";
14960
+
14961
+ // src/commands/update/npm.ts
14962
+ import { existsSync, readFileSync as readFileSync4 } from "fs";
14963
+ import { fileURLToPath as fileURLToPath2 } from "url";
14964
+ import { dirname, join as join2, parse } from "path";
14965
+ function readPackageMeta() {
14966
+ let dir = dirname(fileURLToPath2(import.meta.url));
14967
+ const { root } = parse(dir);
14968
+ while (true) {
14969
+ const candidate = join2(dir, "package.json");
14970
+ if (existsSync(candidate)) {
14971
+ const pkg = JSON.parse(readFileSync4(candidate, "utf8"));
14972
+ return { name: pkg.name ?? "@arkveil/cli", version: pkg.version ?? "0.0.0" };
14973
+ }
14974
+ if (dir === root) break;
14975
+ dir = dirname(dir);
14976
+ }
14977
+ return { name: "@arkveil/cli", version: "0.0.0" };
14978
+ }
14979
+ function parseSemver(version) {
14980
+ const cleaned = version.trim().replace(/^v/, "");
14981
+ const [main3 = "", pre = null] = cleaned.split("-", 2);
14982
+ const core = main3.split(".").map((p) => Number.parseInt(p, 10) || 0);
14983
+ while (core.length < 3) core.push(0);
14984
+ return { core: core.slice(0, 3), pre: pre ?? null };
14985
+ }
14986
+ function compareSemver(a3, b3) {
14987
+ const pa = parseSemver(a3);
14988
+ const pb = parseSemver(b3);
14989
+ for (let i = 0; i < 3; i++) {
14990
+ const diff = (pa.core[i] ?? 0) - (pb.core[i] ?? 0);
14991
+ if (diff !== 0) return diff < 0 ? -1 : 1;
14992
+ }
14993
+ if (pa.pre === pb.pre) return 0;
14994
+ if (pa.pre === null) return 1;
14995
+ if (pb.pre === null) return -1;
14996
+ return pa.pre < pb.pre ? -1 : 1;
14997
+ }
14998
+ var PACKAGE_MANAGERS = ["pnpm", "yarn", "bun", "npm"];
14999
+ function detectPackageManager(modulePath, env2 = process.env) {
15000
+ const path2 = modulePath.toLowerCase();
15001
+ if (path2.includes("pnpm")) return "pnpm";
15002
+ if (path2.includes(`${"/"}.bun${"/"}`) || path2.includes("/bun/")) return "bun";
15003
+ if (path2.includes(".yarn") || path2.includes("/yarn/")) return "yarn";
15004
+ const agent = (env2.npm_config_user_agent ?? "").toLowerCase();
15005
+ const fromAgent = PACKAGE_MANAGERS.find((pm) => agent.startsWith(pm));
15006
+ if (fromAgent) return fromAgent;
15007
+ return "npm";
15008
+ }
15009
+ function installCommand(pm, spec) {
15010
+ switch (pm) {
15011
+ case "pnpm":
15012
+ return { cmd: "pnpm", args: ["add", "-g", spec] };
15013
+ case "yarn":
15014
+ return { cmd: "yarn", args: ["global", "add", spec] };
15015
+ case "bun":
15016
+ return { cmd: "bun", args: ["add", "-g", spec] };
15017
+ case "npm":
15018
+ default:
15019
+ return { cmd: "npm", args: ["install", "-g", spec] };
15020
+ }
15021
+ }
15022
+ async function fetchLatestVersion(opts) {
15023
+ const tag = opts.tag ?? "latest";
15024
+ const registry = (opts.registry ?? "https://registry.npmjs.org").replace(/\/+$/, "");
15025
+ const encoded = opts.name.replace("/", "%2f");
15026
+ const url = `${registry}/${encoded}/${tag}`;
15027
+ const controller = new AbortController();
15028
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 1e4);
15029
+ try {
15030
+ const res = await fetch(url, {
15031
+ headers: { accept: "application/json" },
15032
+ signal: controller.signal
15033
+ });
15034
+ if (!res.ok) {
15035
+ throw new NetworkError(
15036
+ `npm registry returned ${res.status} for ${opts.name}@${tag}.`,
15037
+ "Check your network connection and that the package/tag exists."
15038
+ );
15039
+ }
15040
+ const body = await res.json();
15041
+ if (!body.version) {
15042
+ throw new NetworkError(
15043
+ `npm registry response for ${opts.name}@${tag} had no version.`
15044
+ );
15045
+ }
15046
+ return body.version;
15047
+ } catch (err) {
15048
+ if (err instanceof NetworkError) throw err;
15049
+ if (err instanceof Error && err.name === "AbortError") {
15050
+ throw new NetworkError(
15051
+ `Timed out contacting the npm registry for ${opts.name}@${tag}.`,
15052
+ "Retry, or increase the timeout with --timeout <ms>.",
15053
+ err
15054
+ );
15055
+ }
15056
+ throw new NetworkError(
15057
+ `Could not reach the npm registry: ${err instanceof Error ? err.message : String(err)}`,
15058
+ "Check your network connection.",
15059
+ err
15060
+ );
15061
+ } finally {
15062
+ clearTimeout(timer);
15063
+ }
15064
+ }
15065
+
15066
+ // src/commands/update/update.ts
15067
+ var VALID_PMS = ["npm", "pnpm", "yarn", "bun"];
15068
+ async function selfUpdate(ctx, options) {
15069
+ const { out, config } = ctx;
15070
+ const meta = readPackageMeta();
15071
+ const tag = options.tag ?? "latest";
15072
+ if (options.use && !VALID_PMS.includes(options.use)) {
15073
+ throw new CliError(`Unknown package manager "${options.use}".`, {
15074
+ exitCode: ExitCode.Usage,
15075
+ hint: `Choose one of: ${VALID_PMS.join(", ")}.`
15076
+ });
15077
+ }
15078
+ const spinner = out.spinner(`Checking npm for ${meta.name}@${tag}\u2026`);
15079
+ let latest;
15080
+ try {
15081
+ latest = await fetchLatestVersion({
15082
+ name: meta.name,
15083
+ tag,
15084
+ timeoutMs: config.timeoutMs
15085
+ });
15086
+ spinner.stop();
15087
+ } catch (err) {
15088
+ spinner.fail("Could not check for updates.");
15089
+ throw err;
15090
+ }
15091
+ const cmp = compareSemver(meta.version, latest);
15092
+ const upToDate = cmp >= 0;
15093
+ const willUpdate = !upToDate || Boolean(options.force);
15094
+ const pm = options.use ?? detectPackageManager(fileURLToPath3(import.meta.url));
15095
+ const spec = `${meta.name}@${tag}`;
15096
+ const { cmd, args } = installCommand(pm, spec);
15097
+ const commandLine = `${cmd} ${args.join(" ")}`;
15098
+ if (options.check || options.dryRun || !willUpdate) {
15099
+ out.data(
15100
+ {
15101
+ name: meta.name,
15102
+ current: meta.version,
15103
+ latest,
15104
+ tag,
15105
+ updateAvailable: !upToDate,
15106
+ upToDate,
15107
+ packageManager: pm,
15108
+ command: commandLine,
15109
+ action: options.dryRun ? "dry-run" : options.check ? "check" : "none"
15110
+ },
15111
+ (o2) => {
15112
+ if (upToDate) {
15113
+ return `${o2.c.green("\u2714")} ${meta.name} is up to date (${meta.version}).`;
15114
+ }
15115
+ const lines = [
15116
+ `${o2.c.yellow("!")} Update available: ${o2.c.dim(meta.version)} \u2192 ${o2.c.green(latest)}`
15117
+ ];
15118
+ if (options.dryRun) {
15119
+ lines.push(` Would run: ${o2.c.cyan(commandLine)}`);
15120
+ } else {
15121
+ lines.push(` Run ${o2.c.cyan("arkveil update")} to upgrade (uses: ${o2.c.cyan(commandLine)}).`);
15122
+ }
15123
+ return lines.join("\n");
15124
+ }
15125
+ );
15126
+ return;
15127
+ }
15128
+ const label = upToDate ? `Reinstalling ${spec}\u2026` : `Updating ${meta.name} ${meta.version} \u2192 ${latest}\u2026`;
15129
+ out.verbose(`Running: ${commandLine}`);
15130
+ const install = out.spinner(label);
15131
+ try {
15132
+ await runInstall(cmd, args);
15133
+ install.succeed(`Updated to ${latest}. Run \`arkveil --version\` to confirm.`);
15134
+ } catch (err) {
15135
+ install.fail("Update failed.");
15136
+ throw toInstallError(err, pm, commandLine);
15137
+ }
15138
+ out.data(
15139
+ {
15140
+ name: meta.name,
15141
+ previous: meta.version,
15142
+ latest,
15143
+ tag,
15144
+ packageManager: pm,
15145
+ command: commandLine,
15146
+ action: "updated"
15147
+ },
15148
+ () => {
15149
+ }
15150
+ );
15151
+ }
15152
+ function runInstall(cmd, args) {
15153
+ return new Promise((resolve2, reject) => {
15154
+ execFile6(cmd, args, { timeout: 12e4 }, (err) => {
15155
+ if (err) reject(err);
15156
+ else resolve2();
15157
+ });
15158
+ });
15159
+ }
15160
+ function toInstallError(err, pm, commandLine) {
15161
+ const e2 = err;
15162
+ if (e2?.code === "ENOENT") {
15163
+ return new CliError(`${pm} was not found on your PATH.`, {
15164
+ exitCode: ExitCode.Generic,
15165
+ hint: `Install ${pm}, or pass --use <npm|pnpm|yarn|bun> to pick another package manager.`,
15166
+ cause: err
15167
+ });
15168
+ }
15169
+ const detail = (e2?.stderr ?? e2?.message ?? String(err)).trim().split("\n").slice(-3).join(" ");
15170
+ return new CliError(`Upgrade command failed: ${detail || commandLine}`, {
15171
+ exitCode: ExitCode.Generic,
15172
+ hint: `Try running it yourself: ${commandLine}. A global install may require elevated permissions (sudo).`,
15173
+ cause: err
15174
+ });
15175
+ }
15176
+
15177
+ // src/commands/update/index.ts
15178
+ function registerUpdate(program2) {
15179
+ program2.command("update").aliases(["upgrade", "self-update"]).description("Update the Arkveil CLI to the latest published version").option("--check", "only report whether an update is available; don't install").option("--dry-run", "print the install command that would run, without executing it").option("--tag <tag>", "npm dist-tag to install (default latest)", "latest").option("--use <pm>", "package manager to use: npm, pnpm, yarn, or bun").option("--force", "reinstall even if already on the target version").addHelpText(
15180
+ "after",
15181
+ `
15182
+ Checks the npm registry for a newer release of the CLI and upgrades this install
15183
+ in place using the package manager that installed it (auto-detected; override
15184
+ with --use). Use --check in scripts/CI to detect a pending update via the exit
15185
+ output without changing anything.
15186
+
15187
+ Examples:
15188
+ $ arkveil update # upgrade to the latest release
15189
+ $ arkveil update --check # is a newer version available?
15190
+ $ arkveil update --dry-run # show the command without running it
15191
+ $ arkveil update --tag next # install the "next" dist-tag
15192
+ $ arkveil update --use pnpm # force pnpm for the global install
15193
+ $ arkveil update --json # machine-readable result
15194
+ `
15195
+ ).action(async (options, command) => {
15196
+ await run(command, (ctx) => selfUpdate(ctx, options));
15197
+ });
15198
+ }
15199
+
14243
15200
  // src/index.ts
14244
15201
  function readVersion() {
14245
15202
  try {
14246
- const here = dirname(fileURLToPath2(import.meta.url));
15203
+ const here = dirname2(fileURLToPath4(import.meta.url));
14247
15204
  const pkg = JSON.parse(
14248
- readFileSync4(join2(here, "..", "package.json"), "utf8")
15205
+ readFileSync5(join3(here, "..", "package.json"), "utf8")
14249
15206
  );
14250
15207
  return pkg.version ?? "0.0.0";
14251
15208
  } catch {
@@ -14273,10 +15230,13 @@ function buildProgram() {
14273
15230
  registerTests(program2);
14274
15231
  registerSettings(program2);
14275
15232
  registerSchemas(program2);
15233
+ registerSdk(program2);
15234
+ registerGenerate(program2);
14276
15235
  registerFormula(program2);
14277
15236
  registerEval(program2);
14278
15237
  registerAbac(program2);
14279
15238
  registerAdmin(program2);
15239
+ registerUpdate(program2);
14280
15240
  program2.addHelpText(
14281
15241
  "after",
14282
15242
  `
@@ -14285,6 +15245,9 @@ Examples:
14285
15245
  $ arkveil health Check API connectivity
14286
15246
  $ arkveil tags list --json List tags as JSON
14287
15247
  $ arkveil trees forest Show the full navigation forest
15248
+ $ arkveil sdk info How to install & use the SDK
15249
+ $ arkveil update Update the CLI to the latest release
15250
+ $ arkveil formula syntax Print the formula DSL reference
14288
15251
  $ arkveil eval explain -a orders:read \\
14289
15252
  --user '{"role":"admin"}' --context '{}' Explain an access decision
14290
15253