@tarileo/avileo-catalog-cli 0.3.1 → 0.3.2

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 CHANGED
@@ -2,24 +2,43 @@
2
2
 
3
3
  The Avileo tenant-agent command line interface for a registered business with effective catalog-agent access.
4
4
 
5
- For a first-time human connection, use the temporary device authorization link:
5
+ The origin-pinning contract below describes this checkout. A package version in
6
+ `package.json` does not prove npm availability: `0.3.2` has not been published
7
+ by this release closure. Public handoff must wait for the operator's verified
8
+ package version/artifact and an activated destination. Never substitute an older
9
+ CLI that lacks these flags or silently pair with the integrated deployment.
10
+
11
+ For an authorized local-source session, build the workspace SDK first and run
12
+ the CLI from the repository root:
6
13
 
7
14
  ```sh
8
- npx --yes @tarileo/avileo-catalog-cli@0.3.1 auth login --json
15
+ bun run --filter @tarileo/avileo-sdk build
16
+ bun apps/avileo/agent-cli/src/index.ts auth login --base-url <origin> --json
9
17
  ```
10
18
 
11
- The CLI prints only the verification URI, user code and status, opens the link
12
- with a safe executable when available, polls until approval, and stores the new
13
- Agent Key locally with `0600` permissions. The key is never printed or placed
14
- in the URL. Use `auth status --json` to inspect the source (`environment` or
15
- `local`) without revealing the key, and `auth logout --json` to remove only the
16
- local credential; remote revocation remains an Avileo action.
19
+ Hereafter `avileo` means that same verified CLI command prefix. Pass only the
20
+ explicit API origin supplied by the operator; package source and API destination
21
+ are independent decisions.
22
+
23
+ The CLI prints only the verification URI, user code, status and pinned
24
+ `baseUrl`, opens the link with a safe executable when available, polls until
25
+ approval, and stores the new Agent Key locally with `0600` permissions. The key
26
+ is never printed or placed in the URL. Use `auth status --json` to inspect the
27
+ source (`environment` or `local`) and the pinned origin without revealing the
28
+ key, and `auth logout --json` to remove only the local credential; remote
29
+ revocation remains an Avileo action.
30
+
31
+ Origins must use HTTPS, except plain HTTP for local development hosts
32
+ (loopback, private or Tailscale addresses). Without `--base-url` or
33
+ `AVILEO_AGENT_BASE_URL`, the CLI pairs with production
34
+ (`https://avileo.theelena.me`) and pins it. If the pinned origin fails, fix the
35
+ target; the CLI never falls back to another origin on its own.
17
36
 
18
37
  If Avileo has already issued an Agent Key, configure it without putting the
19
38
  secret in shell history, process arguments or JSON:
20
39
 
21
40
  ```sh
22
- avileo auth configure --json
41
+ avileo auth configure --base-url <origin> --json
23
42
  ```
24
43
 
25
44
  The command reads the key without echoing it and stores it locally. For CI/CD
@@ -27,7 +46,19 @@ and automated agents, continue injecting `AVILEO_AGENT_API_KEY`.
27
46
  Environment credentials always take priority over the local credential file.
28
47
  Never pass an Agent Key through flags, prompts or JSON input.
29
48
 
30
- `AVILEO_AGENT_BASE_URL` remains the development/test endpoint override.
49
+ `--base-url` wins over `AVILEO_AGENT_BASE_URL`, which remains the advanced
50
+ development/test override: set it before `auth login` or `auth configure` so
51
+ the origin is pinned with the credential. Commands reject
52
+ `CLI_ORIGIN_CONFLICT` when the variable points to a different origin than the
53
+ pinned credential — remove the override or re-authenticate with
54
+ `auth login --base-url <origin>`; an existing key is never sent to another
55
+ host. Invalid or unsafe origins fail with `CLI_ORIGIN_UNSUPPORTED`.
56
+
57
+ Local credentials created before origin pinning must be authorized again with
58
+ `auth login --base-url <origin>`. A stored key without a bound origin fails
59
+ with `CLI_CONFIGURATION_ERROR` before any request; the CLI cannot infer its
60
+ destination safely and does not migrate it to production or an environment
61
+ override.
31
62
 
32
63
  Use `--from <json-file>` for structured mutation input. Direct mutations validate
33
64
  locally by default; pass `--apply` only after reviewing the result. The CLI is
package/dist/index.js CHANGED
@@ -15670,6 +15670,53 @@ import { chmodSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSy
15670
15670
  import { randomUUID } from "crypto";
15671
15671
  import { homedir } from "os";
15672
15672
  import { dirname, join } from "path";
15673
+ import { isIP } from "net";
15674
+ var LOCAL_IPV4_HOST_PATTERN = /^(?:127\.|10\.|192\.168\.|169\.254\.|172\.(?:1[6-9]|2\d|3[01])\.|100\.(?:6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.)/;
15675
+ function isLocalHostname(hostname3) {
15676
+ const host = hostname3.replace(/^\[|\]$/g, "").toLowerCase();
15677
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) return true;
15678
+ if (host === "::1" || host === "::" || /^f[cd][0-9a-f]{2}:/.test(host)) return true;
15679
+ if (host.includes(":")) return false;
15680
+ if (!host.includes(".")) return true;
15681
+ return isIP(host) === 4 && LOCAL_IPV4_HOST_PATTERN.test(host);
15682
+ }
15683
+ function normalizeApiBaseUrl(rawUrl) {
15684
+ const candidate = rawUrl.trim().replace(/\/+$/, "");
15685
+ if (!candidate) {
15686
+ throw new CliOriginUnsupportedError("La URL base de la API est\xE1 vac\xEDa");
15687
+ }
15688
+ let parsed;
15689
+ try {
15690
+ parsed = new URL(candidate);
15691
+ } catch {
15692
+ throw new CliOriginUnsupportedError(`La URL base de la API no es v\xE1lida: ${candidate}`);
15693
+ }
15694
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
15695
+ throw new CliOriginUnsupportedError(
15696
+ `La URL base debe usar HTTPS (o HTTP local de desarrollo); se recibi\xF3 ${parsed.protocol}//`
15697
+ );
15698
+ }
15699
+ if (parsed.protocol === "http:" && !isLocalHostname(parsed.hostname)) {
15700
+ throw new CliOriginUnsupportedError(
15701
+ "Solo se permite HTTP para hosts locales de desarrollo; usa un origen HTTPS"
15702
+ );
15703
+ }
15704
+ if (parsed.username || parsed.password) {
15705
+ throw new CliOriginUnsupportedError("La URL base no debe incluir credenciales embebidas");
15706
+ }
15707
+ if (parsed.search || parsed.hash) {
15708
+ throw new CliOriginUnsupportedError("La URL base no debe incluir query ni fragmento");
15709
+ }
15710
+ return `${parsed.protocol}//${parsed.host}${parsed.pathname.replace(/\/+$/, "")}`;
15711
+ }
15712
+ function apiOrigin(baseUrl) {
15713
+ const parsed = new URL(baseUrl);
15714
+ return `${parsed.protocol}//${parsed.host}`;
15715
+ }
15716
+ function overrideBaseUrl(env) {
15717
+ const raw = env.AVILEO_AGENT_BASE_URL?.trim();
15718
+ return raw ? normalizeApiBaseUrl(raw) : void 0;
15719
+ }
15673
15720
  var AGENT_KEY_PATTERN = /^avileo_sk_[^\s]+$/;
15674
15721
  function normalizeAgentKey(value, message) {
15675
15722
  const key = value.trim();
@@ -15700,10 +15747,15 @@ function readLocalCredential(env = process.env) {
15700
15747
  throw new CliConfigurationError("La credencial local no es una Agent Key v\xE1lida");
15701
15748
  }
15702
15749
  const apiKey = normalizeAgentKey(record2.apiKey, "La credencial local no es una Agent Key v\xE1lida");
15703
- const baseUrl = typeof record2.baseUrl === "string" && record2.baseUrl.trim() ? record2.baseUrl.trim() : void 0;
15704
- return { apiKey, ...baseUrl ? { baseUrl } : {} };
15750
+ if (typeof record2.baseUrl !== "string" || !record2.baseUrl.trim()) {
15751
+ throw new CliConfigurationError(
15752
+ "La credencial local no tiene un origen vinculado. Vuelve a autorizar con auth login --base-url <URL_DEL_PORTAL> antes de usarla."
15753
+ );
15754
+ }
15755
+ return { apiKey, baseUrl: normalizeApiBaseUrl(record2.baseUrl) };
15705
15756
  }
15706
15757
  function resolveCredential(env = process.env) {
15758
+ const override = overrideBaseUrl(env);
15707
15759
  const environmentValue = env.AVILEO_AGENT_API_KEY;
15708
15760
  if (environmentValue !== void 0) {
15709
15761
  return {
@@ -15712,31 +15764,29 @@ function resolveCredential(env = process.env) {
15712
15764
  "AVILEO_AGENT_API_KEY no es una Agent Key v\xE1lida"
15713
15765
  ),
15714
15766
  source: "environment",
15715
- ...env.AVILEO_AGENT_BASE_URL?.trim() ? { baseUrl: env.AVILEO_AGENT_BASE_URL.trim() } : {}
15767
+ baseUrl: override ?? AVILEO_AGENT_API_URL
15716
15768
  };
15717
15769
  }
15718
15770
  const local = readLocalCredential(env);
15719
15771
  if (!local) return null;
15720
- return {
15721
- ...local,
15722
- source: "local",
15723
- ...env.AVILEO_AGENT_BASE_URL?.trim() ? { baseUrl: env.AVILEO_AGENT_BASE_URL.trim() } : {}
15724
- };
15772
+ if (override && apiOrigin(local.baseUrl) !== apiOrigin(override)) {
15773
+ throw new CliOriginConflictError(
15774
+ `La credencial local est\xE1 vinculada al origen ${apiOrigin(local.baseUrl)}, pero AVILEO_AGENT_BASE_URL apunta a ${apiOrigin(override)}. La clave nunca se env\xEDa a otro origen: quita la variable de entorno o vuelve a autenticar con auth login --base-url ${apiOrigin(override)} para emitir una clave en ese origen.`
15775
+ );
15776
+ }
15777
+ return { ...local, source: "local" };
15725
15778
  }
15726
15779
  function saveLocalCredential(credential, env = process.env) {
15727
15780
  const apiKey = normalizeAgentKey(
15728
15781
  credential.apiKey,
15729
15782
  "Solo se pueden guardar Agent Keys de Avileo"
15730
15783
  );
15784
+ const baseUrl = normalizeApiBaseUrl(credential.baseUrl);
15731
15785
  const path = localCredentialPath(env);
15732
15786
  mkdirSync(dirname(path), { recursive: true, mode: 448 });
15733
15787
  const temporaryPath = `${path}.${randomUUID()}.tmp`;
15734
- writeFileSync(
15735
- temporaryPath,
15736
- `${JSON.stringify({ apiKey, ...credential.baseUrl ? { baseUrl: credential.baseUrl } : {} })}
15737
- `,
15738
- { mode: 384 }
15739
- );
15788
+ writeFileSync(temporaryPath, `${JSON.stringify({ apiKey, baseUrl })}
15789
+ `, { mode: 384 });
15740
15790
  chmodSync(temporaryPath, 384);
15741
15791
  renameSync(temporaryPath, path);
15742
15792
  chmodSync(path, 384);
@@ -15760,7 +15810,7 @@ function createClientFromEnvironment({
15760
15810
  }
15761
15811
  return clientFactory({
15762
15812
  apiKey: credential.apiKey,
15763
- ...credential.baseUrl ? { baseUrl: credential.baseUrl } : {}
15813
+ baseUrl: credential.baseUrl
15764
15814
  });
15765
15815
  }
15766
15816
  var CliConfigurationError = class extends Error {
@@ -15770,6 +15820,20 @@ var CliConfigurationError = class extends Error {
15770
15820
  this.name = "CliConfigurationError";
15771
15821
  }
15772
15822
  };
15823
+ var CliOriginUnsupportedError = class extends CliConfigurationError {
15824
+ code = "CLI_ORIGIN_UNSUPPORTED";
15825
+ constructor(message) {
15826
+ super(message);
15827
+ this.name = "CliOriginUnsupportedError";
15828
+ }
15829
+ };
15830
+ var CliOriginConflictError = class extends CliConfigurationError {
15831
+ code = "CLI_ORIGIN_CONFLICT";
15832
+ constructor(message) {
15833
+ super(message);
15834
+ this.name = "CliOriginConflictError";
15835
+ }
15836
+ };
15773
15837
 
15774
15838
  // src/output.ts
15775
15839
  var CliProtocolError = class extends Error {
@@ -16426,9 +16490,15 @@ async function openVerificationUri(uri) {
16426
16490
  }
16427
16491
  return false;
16428
16492
  }
16493
+ function resolveSelectedBaseUrl(options) {
16494
+ const explicit = options.baseUrl?.trim();
16495
+ if (explicit) return normalizeApiBaseUrl(explicit);
16496
+ const fromEnvironment = options.env.AVILEO_AGENT_BASE_URL?.trim();
16497
+ return fromEnvironment ? normalizeApiBaseUrl(fromEnvironment) : AVILEO_AGENT_API_URL;
16498
+ }
16429
16499
  async function login(options) {
16430
- const baseUrl = options.env.AVILEO_AGENT_BASE_URL?.trim();
16431
- const deviceAuth = options.deviceAuthFactory(baseUrl ? { baseUrl } : {});
16500
+ const baseUrl = resolveSelectedBaseUrl(options);
16501
+ const deviceAuth = options.deviceAuthFactory({ baseUrl });
16432
16502
  const start = await deviceAuth.start({ name: "Avileo CLI" });
16433
16503
  const browserOpened = await options.openBrowser(start.verificationUri);
16434
16504
  process.stderr.write(
@@ -16445,7 +16515,7 @@ async function login(options) {
16445
16515
  continue;
16446
16516
  }
16447
16517
  if (result.status === "denied" || result.status === "expired") {
16448
- emitSuccess(safeFinalResult(start, browserOpened, result));
16518
+ emitSuccess({ ...safeFinalResult(start, browserOpened, result), baseUrl });
16449
16519
  return;
16450
16520
  }
16451
16521
  if (!result.agentKey || !result.keyId || !result.businessId) {
@@ -16454,30 +16524,20 @@ async function login(options) {
16454
16524
  "La autorizaci\xF3n aprobada no devolvi\xF3 una credencial v\xE1lida"
16455
16525
  );
16456
16526
  }
16457
- saveLocalCredential(
16458
- {
16459
- apiKey: result.agentKey,
16460
- ...baseUrl ? { baseUrl } : {}
16461
- },
16462
- options.env
16463
- );
16464
- emitSuccess(safeFinalResult(start, browserOpened, result));
16527
+ saveLocalCredential({ apiKey: result.agentKey, baseUrl }, options.env);
16528
+ emitSuccess({ ...safeFinalResult(start, browserOpened, result), baseUrl });
16465
16529
  return;
16466
16530
  }
16467
16531
  throw new CliProtocolError("CLI_AUTH_TIMEOUT", "La autorizaci\xF3n expir\xF3 antes de ser aprobada");
16468
16532
  }
16469
16533
  async function configure(options) {
16534
+ const baseUrl = resolveSelectedBaseUrl(options);
16470
16535
  const apiKey = await options.readSecret("Agent Key de Avileo (no se mostrar\xE1): ");
16471
- saveLocalCredential(
16472
- {
16473
- apiKey,
16474
- ...options.env.AVILEO_AGENT_BASE_URL?.trim() ? { baseUrl: options.env.AVILEO_AGENT_BASE_URL.trim() } : {}
16475
- },
16476
- options.env
16477
- );
16536
+ saveLocalCredential({ apiKey, baseUrl }, options.env);
16478
16537
  emitSuccess({
16479
16538
  configured: true,
16480
16539
  source: "local",
16540
+ baseUrl,
16481
16541
  credentialPath: localCredentialPath(options.env)
16482
16542
  });
16483
16543
  }
@@ -16498,7 +16558,7 @@ async function status(options) {
16498
16558
  emitSuccess({
16499
16559
  configured: true,
16500
16560
  source: credential.source,
16501
- credentialPath: credential.source === "local" ? localCredentialPath(options.env) : void 0,
16561
+ baseUrl: credential.baseUrl ?? AVILEO_AGENT_API_URL,
16502
16562
  business,
16503
16563
  ...businessLookupError ? { businessLookupError } : {}
16504
16564
  });
@@ -16514,8 +16574,15 @@ async function logout(options) {
16514
16574
  }
16515
16575
  function registerAuthCommands(root, options) {
16516
16576
  const auth = root.command("auth").description("Conecta la CLI con Avileo sin copiar claves en el chat");
16517
- auth.command("configure").description("Guarda una Agent Key existente sin mostrarla en pantalla").action(() => configure(options));
16518
- auth.command("login").description("Autoriza esta CLI mediante un link temporal").action(() => login(options));
16577
+ auth.command("configure").description("Guarda una Agent Key existente sin mostrarla en pantalla").option("--base-url <url>", "Origen de la API donde se usar\xE1 la clave").action(
16578
+ (commandOptions) => configure({ ...options, baseUrl: commandOptions.baseUrl })
16579
+ );
16580
+ auth.command("login").description("Autoriza esta CLI mediante un link temporal").option(
16581
+ "--base-url <url>",
16582
+ "Origen de la API a vincular; tiene prioridad sobre AVILEO_AGENT_BASE_URL"
16583
+ ).action(
16584
+ (commandOptions) => login({ ...options, baseUrl: commandOptions.baseUrl })
16585
+ );
16519
16586
  auth.command("status").description("Muestra el estado de la credencial sin revelar secretos").action(() => status(options));
16520
16587
  auth.command("logout").description("Elimina solamente la credencial local").action(() => logout(options));
16521
16588
  }