panoma 0.1.7 → 0.1.9

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.
Files changed (104) hide show
  1. package/app/BUILD-INFO.json +2 -2
  2. package/app/apps/web/.next-bundle/BUILD_ID +1 -1
  3. package/app/apps/web/.next-bundle/app-build-manifest.json +125 -125
  4. package/app/apps/web/.next-bundle/app-path-routes-manifest.json +28 -28
  5. package/app/apps/web/.next-bundle/build-manifest.json +2 -2
  6. package/app/apps/web/.next-bundle/prerender-manifest.json +3 -3
  7. package/app/apps/web/.next-bundle/server/app/(app)/agents/page_client-reference-manifest.js +1 -1
  8. package/app/apps/web/.next-bundle/server/app/(app)/ai/page_client-reference-manifest.js +1 -1
  9. package/app/apps/web/.next-bundle/server/app/(app)/bridge/page.js +1 -1
  10. package/app/apps/web/.next-bundle/server/app/(app)/bridge/page_client-reference-manifest.js +1 -1
  11. package/app/apps/web/.next-bundle/server/app/(app)/copies/page_client-reference-manifest.js +1 -1
  12. package/app/apps/web/.next-bundle/server/app/(app)/credentials/page_client-reference-manifest.js +1 -1
  13. package/app/apps/web/.next-bundle/server/app/(app)/disk/page_client-reference-manifest.js +1 -1
  14. package/app/apps/web/.next-bundle/server/app/(app)/hidden/page_client-reference-manifest.js +1 -1
  15. package/app/apps/web/.next-bundle/server/app/(app)/p/[slug]/page_client-reference-manifest.js +1 -1
  16. package/app/apps/web/.next-bundle/server/app/(app)/packages/page_client-reference-manifest.js +1 -1
  17. package/app/apps/web/.next-bundle/server/app/(app)/page_client-reference-manifest.js +1 -1
  18. package/app/apps/web/.next-bundle/server/app/(app)/runs/[id]/page_client-reference-manifest.js +1 -1
  19. package/app/apps/web/.next-bundle/server/app/(app)/runs/page_client-reference-manifest.js +1 -1
  20. package/app/apps/web/.next-bundle/server/app/(app)/search/page_client-reference-manifest.js +1 -1
  21. package/app/apps/web/.next-bundle/server/app/(app)/twin/look/page_client-reference-manifest.js +1 -1
  22. package/app/apps/web/.next-bundle/server/app/(app)/twin/page_client-reference-manifest.js +1 -1
  23. package/app/apps/web/.next-bundle/server/app/(app)/unsaved/page_client-reference-manifest.js +1 -1
  24. package/app/apps/web/.next-bundle/server/app/_not-found/page_client-reference-manifest.js +1 -1
  25. package/app/apps/web/.next-bundle/server/app/api/accounts/route_client-reference-manifest.js +1 -1
  26. package/app/apps/web/.next-bundle/server/app/api/agent/consult/route_client-reference-manifest.js +1 -1
  27. package/app/apps/web/.next-bundle/server/app/api/agent/context/route_client-reference-manifest.js +1 -1
  28. package/app/apps/web/.next-bundle/server/app/api/agent/hello/route_client-reference-manifest.js +1 -1
  29. package/app/apps/web/.next-bundle/server/app/api/agent/journal/route_client-reference-manifest.js +1 -1
  30. package/app/apps/web/.next-bundle/server/app/api/agent/keys/route_client-reference-manifest.js +1 -1
  31. package/app/apps/web/.next-bundle/server/app/api/agent/log/route_client-reference-manifest.js +1 -1
  32. package/app/apps/web/.next-bundle/server/app/api/agent/mcp/route_client-reference-manifest.js +1 -1
  33. package/app/apps/web/.next-bundle/server/app/api/agent/notes/route_client-reference-manifest.js +1 -1
  34. package/app/apps/web/.next-bundle/server/app/api/agent/tasks/[id]/route_client-reference-manifest.js +1 -1
  35. package/app/apps/web/.next-bundle/server/app/api/agent/tasks/route_client-reference-manifest.js +1 -1
  36. package/app/apps/web/.next-bundle/server/app/api/ai/route.js +1 -1
  37. package/app/apps/web/.next-bundle/server/app/api/ai/route_client-reference-manifest.js +1 -1
  38. package/app/apps/web/.next-bundle/server/app/api/assets/route_client-reference-manifest.js +1 -1
  39. package/app/apps/web/.next-bundle/server/app/api/assignments/launch/route_client-reference-manifest.js +1 -1
  40. package/app/apps/web/.next-bundle/server/app/api/assignments/route_client-reference-manifest.js +1 -1
  41. package/app/apps/web/.next-bundle/server/app/api/catalog/route_client-reference-manifest.js +1 -1
  42. package/app/apps/web/.next-bundle/server/app/api/check/route_client-reference-manifest.js +1 -1
  43. package/app/apps/web/.next-bundle/server/app/api/consultations/route_client-reference-manifest.js +1 -1
  44. package/app/apps/web/.next-bundle/server/app/api/describe/route_client-reference-manifest.js +1 -1
  45. package/app/apps/web/.next-bundle/server/app/api/disk/route_client-reference-manifest.js +1 -1
  46. package/app/apps/web/.next-bundle/server/app/api/enrich/route_client-reference-manifest.js +1 -1
  47. package/app/apps/web/.next-bundle/server/app/api/environment/route_client-reference-manifest.js +1 -1
  48. package/app/apps/web/.next-bundle/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  49. package/app/apps/web/.next-bundle/server/app/api/ingest/route_client-reference-manifest.js +1 -1
  50. package/app/apps/web/.next-bundle/server/app/api/md/apply/route_client-reference-manifest.js +1 -1
  51. package/app/apps/web/.next-bundle/server/app/api/md/context/route_client-reference-manifest.js +1 -1
  52. package/app/apps/web/.next-bundle/server/app/api/md/inspect/route_client-reference-manifest.js +1 -1
  53. package/app/apps/web/.next-bundle/server/app/api/md/repair/route_client-reference-manifest.js +1 -1
  54. package/app/apps/web/.next-bundle/server/app/api/md/review/route_client-reference-manifest.js +1 -1
  55. package/app/apps/web/.next-bundle/server/app/api/north/route_client-reference-manifest.js +1 -1
  56. package/app/apps/web/.next-bundle/server/app/api/notes/route_client-reference-manifest.js +1 -1
  57. package/app/apps/web/.next-bundle/server/app/api/open/route_client-reference-manifest.js +1 -1
  58. package/app/apps/web/.next-bundle/server/app/api/project/route_client-reference-manifest.js +1 -1
  59. package/app/apps/web/.next-bundle/server/app/api/rescan/route_client-reference-manifest.js +1 -1
  60. package/app/apps/web/.next-bundle/server/app/api/roots/route_client-reference-manifest.js +1 -1
  61. package/app/apps/web/.next-bundle/server/app/api/runs/[id]/route_client-reference-manifest.js +1 -1
  62. package/app/apps/web/.next-bundle/server/app/api/runs/route_client-reference-manifest.js +1 -1
  63. package/app/apps/web/.next-bundle/server/app/api/scale/route_client-reference-manifest.js +1 -1
  64. package/app/apps/web/.next-bundle/server/app/api/search/route_client-reference-manifest.js +1 -1
  65. package/app/apps/web/.next-bundle/server/app/api/secrets/route_client-reference-manifest.js +1 -1
  66. package/app/apps/web/.next-bundle/server/app/api/tasks/route_client-reference-manifest.js +1 -1
  67. package/app/apps/web/.next-bundle/server/app/api/today/route_client-reference-manifest.js +1 -1
  68. package/app/apps/web/.next-bundle/server/app/api/twin/assign/route_client-reference-manifest.js +1 -1
  69. package/app/apps/web/.next-bundle/server/app/api/twin/classify/route_client-reference-manifest.js +1 -1
  70. package/app/apps/web/.next-bundle/server/app/api/twin/critique/route_client-reference-manifest.js +1 -1
  71. package/app/apps/web/.next-bundle/server/app/api/twin/design/route_client-reference-manifest.js +1 -1
  72. package/app/apps/web/.next-bundle/server/app/api/twin/distill/route_client-reference-manifest.js +1 -1
  73. package/app/apps/web/.next-bundle/server/app/api/twin/look/route_client-reference-manifest.js +1 -1
  74. package/app/apps/web/.next-bundle/server/app/api/twin/mine/route_client-reference-manifest.js +1 -1
  75. package/app/apps/web/.next-bundle/server/app/api/twin/score/route_client-reference-manifest.js +1 -1
  76. package/app/apps/web/.next-bundle/server/app/api/twin/shot/route_client-reference-manifest.js +1 -1
  77. package/app/apps/web/.next-bundle/server/app/api/twin/sources/route_client-reference-manifest.js +1 -1
  78. package/app/apps/web/.next-bundle/server/app/api/twin/synthesize/route_client-reference-manifest.js +1 -1
  79. package/app/apps/web/.next-bundle/server/app/api/twin/taste/route_client-reference-manifest.js +1 -1
  80. package/app/apps/web/.next-bundle/server/app/api/twin/verdicts/route_client-reference-manifest.js +1 -1
  81. package/app/apps/web/.next-bundle/server/app/api/watch/route_client-reference-manifest.js +1 -1
  82. package/app/apps/web/.next-bundle/server/app/icon/[id]/route_client-reference-manifest.js +1 -1
  83. package/app/apps/web/.next-bundle/server/app-paths-manifest.json +28 -28
  84. package/app/apps/web/.next-bundle/server/chunks/2930.js +1 -1
  85. package/app/apps/web/.next-bundle/server/chunks/3610.js +6 -6
  86. package/app/apps/web/.next-bundle/server/chunks/3677.js +1 -1
  87. package/app/apps/web/.next-bundle/server/chunks/7876.js +1 -1
  88. package/app/apps/web/.next-bundle/server/functions-config-manifest.json +12 -12
  89. package/app/apps/web/.next-bundle/server/middleware-manifest.json +5 -5
  90. package/app/apps/web/.next-bundle/server/pages/500.html +1 -1
  91. package/app/apps/web/.next-bundle/server/server-reference-manifest.json +1 -1
  92. package/app/apps/web/.next-bundle/static/chunks/7911-1f2ce7063b83e142.js +1 -0
  93. package/app/apps/web/.next-bundle/static/chunks/app/(app)/bridge/{page-95d7d92ca0b30f20.js → page-9f270ffcd9da75ca.js} +1 -1
  94. package/app/node_modules/@panoma/ai/dist/index.d.ts +100 -1
  95. package/app/node_modules/@panoma/ai/dist/index.js +135 -57
  96. package/dist/{ai-command-ICITNELE.js → ai-command-W6FV3QW5.js} +2 -2
  97. package/dist/{chunk-N7EEJ4U6.js → chunk-ROPR3LUO.js} +133 -57
  98. package/dist/{dist-C7GE6UXH.js → dist-XTIHMRQM.js} +5 -1
  99. package/dist/index.js +1 -1
  100. package/npm-shrinkwrap.json +2 -2
  101. package/package.json +1 -1
  102. package/app/apps/web/.next-bundle/static/chunks/7911-c6622f219724e580.js +0 -1
  103. /package/app/apps/web/.next-bundle/static/{Ac5gZ0DbQ_SywHZXjduqm → GWFC4d9IgsD38wa3aUlsQ}/_buildManifest.js +0 -0
  104. /package/app/apps/web/.next-bundle/static/{Ac5gZ0DbQ_SywHZXjduqm → GWFC4d9IgsD38wa3aUlsQ}/_ssgManifest.js +0 -0
@@ -1,3 +1,102 @@
1
+ /**
2
+ * What went wrong, said once and readable by both audiences.
3
+ *
4
+ * This package threw sentences in fixed Spanish, and both of its readers were wrong ones. The
5
+ * terminal prints `error.message` raw and is a machine surface, where the house rule is English.
6
+ * The browser is bilingual and got Spanish whichever language it was showing. `model-errors.ts`
7
+ * had already patched the two worst cases, and its own comment admits how: one by a typed error,
8
+ * the other by matching a Spanish substring — a sentence used as a key, which stops working the
9
+ * day somebody rewords it and says nothing.
10
+ *
11
+ * So the failure travels as data. `message` is English, because that is what a machine reads and
12
+ * what the CLI prints; `failure` carries the code and its parts, so the web can say the same thing
13
+ * in the language in front of it without parsing prose.
14
+ *
15
+ * What is NOT here: anything a provider said. A 429, a refusal, the text of somebody else's API —
16
+ * those are quotes, and translating a quote is inventing one. They travel inside `detail`, whole.
17
+ */
18
+ type AiFailure = {
19
+ code: "noCommand";
20
+ provider: string;
21
+ } | {
22
+ code: "launchFailed";
23
+ command: string;
24
+ reason: string;
25
+ } | {
26
+ code: "exited";
27
+ provider: string;
28
+ status: number;
29
+ output: string;
30
+ } | {
31
+ code: "emptyBody";
32
+ provider: string;
33
+ } | {
34
+ code: "providerRefused";
35
+ provider: string;
36
+ status: number;
37
+ detail: string;
38
+ } | {
39
+ code: "tokenRefused";
40
+ provider: string;
41
+ status: number;
42
+ detail: string;
43
+ } | {
44
+ code: "neverAnswered";
45
+ provider: string;
46
+ last: string;
47
+ attempts: number;
48
+ } | {
49
+ code: "configShape";
50
+ } | {
51
+ code: "configLocked";
52
+ lock: string;
53
+ holder: string | undefined;
54
+ } | {
55
+ code: "noProvider";
56
+ } | {
57
+ code: "unknownProvider";
58
+ id: string;
59
+ } | {
60
+ code: "oauthTimeout";
61
+ } | {
62
+ code: "noOauth";
63
+ provider: string;
64
+ } | {
65
+ code: "badUrl";
66
+ provider: string;
67
+ value: string;
68
+ where: string;
69
+ } | {
70
+ code: "notHttp";
71
+ provider: string;
72
+ } | {
73
+ code: "urlHasCredentials";
74
+ provider: string;
75
+ } | {
76
+ code: "insecureHost";
77
+ provider: string;
78
+ host: string;
79
+ };
80
+ /**
81
+ * The English sentence for a failure, written in one place.
82
+ *
83
+ * One place because two would drift: the whole point of carrying the code is that the message and
84
+ * the translation cannot disagree about what happened.
85
+ */
86
+ declare function failureMessage(failure: AiFailure): string;
87
+ /**
88
+ * An error this package raised about itself.
89
+ *
90
+ * `instanceof` rather than a string check, so that whoever translates it is reading a type and not
91
+ * guessing at prose. The two typed errors that already existed —`NoCredentialError` and
92
+ * `ConfigCorruptError`— keep their own classes: they carry more than a code and were already
93
+ * being read that way.
94
+ */
95
+ declare class AiError extends Error {
96
+ readonly failure: AiFailure;
97
+ constructor(failure: AiFailure);
98
+ }
99
+
1
100
  /**
2
101
  * AI suppliers catalog.
3
102
  *
@@ -487,4 +586,4 @@ interface CompleteResult {
487
586
  }
488
587
  declare function complete(request: CompleteRequest): Promise<CompleteResult>;
489
588
 
490
- export { ATTEMPTS, type AgentAvailability, type AiConfig, type ApiFamily, type AuthType, type CallOptions, type Challenge, type CompleteImage, type CompleteRequest, type CompleteResult, ConfigCorruptError, NoCredentialError, type OauthConfig, type OauthToken, PROVIDERS, type Provider, REDACTED, type ResolvedCredential, VisionUnsupportedError, accountFrom, authorizeUrl, awaitCallback, callProvider, checkBaseUrl, complete, completeWithCliAgent, configPath, createChallenge, detectCliAgents, exchangeCode, expired, findProvider, forgetCredential, listModels, maskKey, pointsElsewhere, providersByAuth, readConfig, redact, redirectUri, refresh, resolveCredential, saveKey, saveToken, transportFailure, updateConfig, writeConfig };
589
+ export { ATTEMPTS, type AgentAvailability, type AiConfig, AiError, type AiFailure, type ApiFamily, type AuthType, type CallOptions, type Challenge, type CompleteImage, type CompleteRequest, type CompleteResult, ConfigCorruptError, NoCredentialError, type OauthConfig, type OauthToken, PROVIDERS, type Provider, REDACTED, type ResolvedCredential, VisionUnsupportedError, accountFrom, authorizeUrl, awaitCallback, callProvider, checkBaseUrl, complete, completeWithCliAgent, configPath, createChallenge, detectCliAgents, exchangeCode, expired, failureMessage, findProvider, forgetCredential, listModels, maskKey, pointsElsewhere, providersByAuth, readConfig, redact, redirectUri, refresh, resolveCredential, saveKey, saveToken, transportFailure, updateConfig, writeConfig };
@@ -1,3 +1,53 @@
1
+ // src/failures.ts
2
+ function failureMessage(failure) {
3
+ switch (failure.code) {
4
+ case "noCommand":
5
+ return `${failure.provider} declares no command to run.`;
6
+ case "launchFailed":
7
+ return `Could not launch ${failure.command}: ${failure.reason}`;
8
+ case "exited":
9
+ return `${failure.provider} exited with code ${failure.status}. ${failure.output}`.trim();
10
+ case "emptyBody":
11
+ return `${failure.provider} answered with no body.`;
12
+ case "providerRefused":
13
+ return `${failure.provider} answered ${failure.status}: ${failure.detail}`;
14
+ case "tokenRefused":
15
+ return `${failure.provider} refused the token request (${failure.status}): ${failure.detail}`;
16
+ case "neverAnswered":
17
+ return `${failure.provider} never answered: ${failure.last}. Attempts: ${failure.attempts}. The model did not say no \u2014 the request never left this machine.`;
18
+ case "configShape":
19
+ return "the contents are not shaped like a panoma configuration";
20
+ case "configLocked":
21
+ return `Another panoma process is writing the configuration${failure.holder ? ` (${failure.holder})` : ""}.
22
+ If none is running, the lock was left behind by an earlier run:
23
+ rm ${failure.lock}`;
24
+ case "noProvider":
25
+ return "No AI provider is configured. Run 'panoma ai' to see the options.";
26
+ case "unknownProvider":
27
+ return `Unknown provider: ${failure.id}`;
28
+ case "oauthTimeout":
29
+ return "Timed out waiting for you to come back from the browser.";
30
+ case "noOauth":
31
+ return `${failure.provider} does not use sign-in.`;
32
+ case "badUrl":
33
+ return `${failure.provider}'s address is not a valid URL: \xAB${failure.value}\xBB. Check ${failure.where}.`;
34
+ case "notHttp":
35
+ return `${failure.provider}'s address has to be http or https.`;
36
+ case "urlHasCredentials":
37
+ return `${failure.provider}'s address carries a user or a password inside. Take them out and use the key.`;
38
+ case "insecureHost":
39
+ return `panoma will not send ${failure.provider}'s credential unencrypted to ${failure.host}. Use https, or a server on your own machine.`;
40
+ }
41
+ }
42
+ var AiError = class extends Error {
43
+ failure;
44
+ constructor(failure) {
45
+ super(failureMessage(failure));
46
+ this.name = "AiError";
47
+ this.failure = failure;
48
+ }
49
+ };
50
+
1
51
  // src/providers.ts
2
52
  import { homedir } from "os";
3
53
  import { join } from "path";
@@ -246,8 +296,8 @@ var PROVIDERS = [
246
296
  id: "openai-codex",
247
297
  name: "ChatGPT (Codex)",
248
298
  auth: "oauth",
249
- description: "Tu suscripci\xF3n de ChatGPT Plus o Pro, sin clave. Uso personal: se apoya en el inicio de sesi\xF3n del CLI de Codex y en un endpoint privado de OpenAI, as\xED que puede dejar de funcionar el d\xEDa que ellos lo cambien.",
250
- descriptionEn: "Your ChatGPT Plus or Pro subscription, no key. Personal use: it leans on the Codex CLI sign-in and a private OpenAI endpoint, so it can stop working the day they change it.",
299
+ description: "Tu suscripci\xF3n de ChatGPT Plus o Pro, sin clave y sin binario local. Uso personal: se apoya en el inicio de sesi\xF3n del CLI de Codex y en un endpoint privado de OpenAI, as\xED que puede dejar de funcionar el d\xEDa que ellos lo cambien.",
300
+ descriptionEn: "Your ChatGPT Plus or Pro subscription, no key and no local binary. Personal use: it leans on the Codex CLI sign-in and a private OpenAI endpoint, so it can stop working the day they change it.",
251
301
  api: "codex",
252
302
  // The backup of ChatGPT, not `api.openai.com`: the subscription token is the only thing from
253
303
  // OpenAI that is charged from the plan, and it is only valid against this gate.
@@ -298,11 +348,23 @@ var PROVIDERS = [
298
348
  ]
299
349
  },
300
350
  {
351
+ /*
352
+ «Codex CLI» and not «Codex», which is what it said.
353
+
354
+ Two entries away sits `openai-codex`, called «ChatGPT (Codex)», and a list showing both
355
+ offered the reader the same word twice with no way to tell them apart. They are not variants:
356
+ this one runs the `codex` binary on this machine as a subprocess, and that one talks to
357
+ OpenAI over your ChatGPT sign-in without any local binary at all. Whoever picked wrong was
358
+ not being careless — the screen gave them nothing to pick with.
359
+
360
+ The descriptions say the mechanism now, for the same reason: «uses your signed-in session,
361
+ no keys» was true of both.
362
+ */
301
363
  id: "codex-cli",
302
- name: "Codex",
364
+ name: "Codex CLI",
303
365
  auth: "cli",
304
- description: "Usa tu sesi\xF3n de Codex ya iniciada. Sin claves.",
305
- descriptionEn: "Uses your signed-in Codex session. No keys.",
366
+ description: "Ejecuta el binario `codex` de esta m\xE1quina. Usa su sesi\xF3n ya iniciada, sin claves.",
367
+ descriptionEn: "Runs the `codex` binary on this machine, using its signed-in session. No keys.",
306
368
  command: "codex",
307
369
  args: ["exec"],
308
370
  bundles: ["/Applications/ChatGPT.app/Contents/Resources/codex"]
@@ -431,7 +493,7 @@ function awaitCallback(provider, challenge) {
431
493
  const close = () => server?.close();
432
494
  const deadline = setTimeout(() => {
433
495
  close();
434
- reject(new Error("Se agot\xF3 el plazo esperando a que volvieras del navegador."));
496
+ reject(new AiError({ code: "oauthTimeout" }));
435
497
  }, DEADLINE_MS);
436
498
  const finish = (error, code) => {
437
499
  clearTimeout(deadline);
@@ -510,9 +572,13 @@ async function requestToken(provider, fields) {
510
572
  });
511
573
  const body = await response.json().catch(() => ({}));
512
574
  if (!response.ok || !body.access_token) {
513
- throw new Error(
514
- `${provider.name} rechaz\xF3 la petici\xF3n de token (${response.status}): ${body.error_description ?? body.error ?? response.statusText}`
515
- );
575
+ throw new AiError({
576
+ code: "tokenRefused",
577
+ provider: provider.name,
578
+ status: response.status,
579
+ /* The provider's own words, whole: translating somebody else's refusal invents one. */
580
+ detail: body.error_description ?? body.error ?? response.statusText
581
+ });
516
582
  }
517
583
  return {
518
584
  access: body.access_token,
@@ -539,7 +605,7 @@ function expired(token) {
539
605
  return token.expiresAt !== void 0 && Date.now() > token.expiresAt - MARGIN_MS;
540
606
  }
541
607
  function requireOauth(provider) {
542
- if (!provider.oauth) throw new Error(`${provider.name} no usa inicio de sesi\xF3n.`);
608
+ if (!provider.oauth) throw new AiError({ code: "noOauth", provider: provider.name });
543
609
  return provider.oauth;
544
610
  }
545
611
 
@@ -549,22 +615,25 @@ function checkBaseUrl(provider, value) {
549
615
  try {
550
616
  url = new URL(value);
551
617
  } catch {
552
- throw new Error(
553
- `La direcci\xF3n de ${provider.name} no es una URL v\xE1lida: \xAB${value}\xBB. Revisa ${provider.baseUrlEnvVar ?? "la configuraci\xF3n"}.`
554
- );
618
+ throw new AiError({
619
+ code: "badUrl",
620
+ provider: provider.name,
621
+ value,
622
+ where: provider.baseUrlEnvVar ?? "the configuration"
623
+ });
555
624
  }
556
625
  if (url.protocol !== "http:" && url.protocol !== "https:") {
557
- throw new Error(`La direcci\xF3n de ${provider.name} tiene que ser http o https.`);
626
+ throw new AiError({ code: "notHttp", provider: provider.name });
558
627
  }
559
628
  if (url.username || url.password) {
560
- throw new Error(
561
- `La direcci\xF3n de ${provider.name} lleva usuario o contrase\xF1a dentro. Qu\xEDtalos y usa la clave.`
562
- );
629
+ throw new AiError({ code: "urlHasCredentials", provider: provider.name });
563
630
  }
564
631
  if (url.protocol === "http:" && !isLocal(url.hostname)) {
565
- throw new Error(
566
- `Panoma no manda la credencial de ${provider.name} sin cifrar a ${url.hostname}. Usa https, o un servidor en tu propia m\xE1quina.`
567
- );
632
+ throw new AiError({
633
+ code: "insecureHost",
634
+ provider: provider.name,
635
+ host: url.hostname
636
+ });
568
637
  }
569
638
  return value;
570
639
  }
@@ -612,17 +681,21 @@ function backupPath() {
612
681
  }
613
682
  var ConfigCorruptError = class extends Error {
614
683
  constructor(path, problem, recovery) {
615
- const lines = [`No se pudo leer la configuraci\xF3n de IA en ${path}: ${problem.message}`];
684
+ const lines = [`Could not read the AI configuration at ${path}: ${problem.message}`];
616
685
  if (recovery) {
617
686
  lines.push(
618
- `Hay una copia anterior en ${recovery.path} con ${recovery.keys} clave${recovery.keys === 1 ? "" : "s"} dentro. Recup\xE9rala con:`,
687
+ /*
688
+ Both forms written whole rather than an `s` glued to the figure. The house rule: never
689
+ attach an inflected word to a digit, and this line only ever reads wrong at one.
690
+ */
691
+ recovery.keys === 1 ? `There is an earlier copy at ${recovery.path} holding one key. Recover it with:` : `There is an earlier copy at ${recovery.path} holding ${recovery.keys} keys. Recover it with:`,
619
692
  ` mv ${recovery.path} ${path}`,
620
- "No ejecutes 'panoma ai key' antes: sobrescribir\xEDa el fichero y con \xE9l lo que quede."
693
+ "Do not run 'panoma ai key' first: it would overwrite the file and whatever is left in it."
621
694
  );
622
695
  } else {
623
696
  lines.push(
624
- "No hay copia anterior. \xC1brelo y arr\xE9glalo a mano, o b\xF3rralo si prefieres empezar",
625
- "de cero \u2014 perder\xE1s las claves que hubiera guardadas dentro."
697
+ "There is no earlier copy. Open it and fix it by hand, or delete it to start over \u2014",
698
+ "you will lose whatever keys were saved inside."
626
699
  );
627
700
  }
628
701
  super(lines.join("\n"), { cause: problem });
@@ -664,7 +737,7 @@ async function readFileConfig(path) {
664
737
  }
665
738
  const parsed = JSON.parse(raw);
666
739
  if (!isConfig(parsed)) {
667
- throw new Error("el contenido no tiene la forma de una configuraci\xF3n de Panoma");
740
+ throw new AiError({ code: "configShape" });
668
741
  }
669
742
  return parsed;
670
743
  }
@@ -752,12 +825,11 @@ async function withLock(action) {
752
825
  if (error.code !== "EEXIST") throw error;
753
826
  if (Date.now() > deadline) {
754
827
  const owner = await readFile(lock, "utf8").catch(() => "");
755
- throw new Error(
756
- `Otro proceso de Panoma est\xE1 escribiendo la configuraci\xF3n${owner.trim() ? ` (pid ${owner.trim()})` : ""}.
757
- Si no hay ninguno corriendo, el cerrojo se qued\xF3 de una ejecuci\xF3n anterior:
758
- rm ${lock}`,
759
- { cause: error }
760
- );
828
+ throw new AiError({
829
+ code: "configLocked",
830
+ lock,
831
+ holder: owner.trim() ? `pid ${owner.trim()}` : void 0
832
+ });
761
833
  }
762
834
  await new Promise((resolve) => setTimeout(resolve, 50));
763
835
  }
@@ -771,8 +843,8 @@ Si no hay ninguno corriendo, el cerrojo se qued\xF3 de una ejecuci\xF3n anterior
771
843
  }
772
844
  var NoCredentialError = class extends Error {
773
845
  constructor(provider) {
774
- const how = provider.auth === "cli" ? `Instala ${provider.name} e inicia sesi\xF3n; Panoma llamar\xE1 a '${provider.command}'.` : provider.auth === "oauth" ? `Inicia sesi\xF3n en ${provider.name} desde la p\xE1gina del modelo en Panoma.` : `Ejecuta 'panoma ai key ${provider.id}' o exporta ${provider.apiKeyEnvVars?.[0]}. La clave se saca en ${provider.signupUrl}.`;
775
- super(`Sin credencial para ${provider.name}. ${how}`);
846
+ const how = provider.auth === "cli" ? `Install ${provider.name} and sign in; panoma will call '${provider.command}'.` : provider.auth === "oauth" ? `Sign in to ${provider.name} from the model page in panoma.` : `Run 'panoma ai key ${provider.id}' or export ${provider.apiKeyEnvVars?.[0]}. The key comes from ${provider.signupUrl}.`;
847
+ super(`No credential for ${provider.name}. ${how}`);
776
848
  this.provider = provider;
777
849
  this.name = "NoCredentialError";
778
850
  }
@@ -782,12 +854,10 @@ async function resolveCredential(providerId, config) {
782
854
  const settings = config ?? await readConfig();
783
855
  const id = providerId ?? settings.provider;
784
856
  if (!id) {
785
- throw new Error(
786
- "No hay proveedor de IA configurado. Ejecuta 'panoma ai' para ver las opciones."
787
- );
857
+ throw new AiError({ code: "noProvider" });
788
858
  }
789
859
  const provider = findProvider(id);
790
- if (!provider) throw new Error(`Proveedor desconocido: ${id}`);
860
+ if (!provider) throw new AiError({ code: "unknownProvider", id });
791
861
  if (provider.auth === "cli") {
792
862
  return { provider, model: settings.model ?? "", source: "agent-session" };
793
863
  }
@@ -914,7 +984,7 @@ async function detectCliAgents(providers) {
914
984
  var MAX_OUTPUT = 2e5;
915
985
  async function completeWithCliAgent(provider, prompt, options = {}) {
916
986
  const command = options.command ?? provider.command;
917
- if (!command) throw new Error(`${provider.name} no declara ning\xFAn comando.`);
987
+ if (!command) throw new AiError({ code: "noCommand", provider: provider.name });
918
988
  return new Promise((resolve, reject) => {
919
989
  const launch = resolveExecutable(command, provider.args ?? []);
920
990
  const child = spawn(launch.file, launch.args, {
@@ -934,7 +1004,7 @@ async function completeWithCliAgent(provider, prompt, options = {}) {
934
1004
  const timer = setTimeout(() => child.kill("SIGKILL"), options.timeoutMs ?? 18e4);
935
1005
  child.on("error", (error) => {
936
1006
  clearTimeout(timer);
937
- reject(new Error(`No se pudo lanzar ${command}: ${error.message}`));
1007
+ reject(new AiError({ code: "launchFailed", command, reason: error.message }));
938
1008
  });
939
1009
  child.on("close", (code) => {
940
1010
  clearTimeout(timer);
@@ -942,7 +1012,12 @@ async function completeWithCliAgent(provider, prompt, options = {}) {
942
1012
  reject(
943
1013
  new Error(
944
1014
  redact(
945
- `${provider.name} termin\xF3 con c\xF3digo ${code}. ${stderr.trim().slice(0, 600)}`.trim()
1015
+ failureMessage({
1016
+ code: "exited",
1017
+ provider: provider.name,
1018
+ status: code ?? -1,
1019
+ output: stderr.trim().slice(0, 600)
1020
+ })
946
1021
  )
947
1022
  )
948
1023
  );
@@ -1025,9 +1100,7 @@ async function callProvider(name, url, init, options = {}) {
1025
1100
  if (attempt < attempts) await wait(WAITS[Math.min(attempt - 1, WAITS.length - 1)] ?? 0);
1026
1101
  }
1027
1102
  }
1028
- throw new Error(
1029
- `${name} no lleg\xF3 a contestar: ${last}. Intentos: ${attempts}. No es que el modelo dijera que no: la petici\xF3n no sali\xF3 de esta m\xE1quina.`
1030
- );
1103
+ throw new AiError({ code: "neverAnswered", provider: name, last: String(last), attempts });
1031
1104
  }
1032
1105
  function sleep(ms) {
1033
1106
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -1037,7 +1110,7 @@ function sleep(ms) {
1037
1110
  var VisionUnsupportedError = class extends Error {
1038
1111
  constructor(provider) {
1039
1112
  super(
1040
- `${provider} no sabe recibir im\xE1genes. Configura un proveedor con clave \u2014'panoma ai key'\u2014 o p\xE1sale uno con --provider.`
1113
+ `${provider} cannot take images. Configure a provider with a key \u2014'panoma ai key'\u2014 or pass one with --provider.`
1041
1114
  );
1042
1115
  this.provider = provider;
1043
1116
  this.name = "VisionUnsupportedError";
@@ -1054,7 +1127,7 @@ async function complete(request) {
1054
1127
 
1055
1128
  ${request.prompt}` : request.prompt;
1056
1129
  const text = await completeWithCliAgent(credential.provider, prompt);
1057
- return { text, provider: credential.provider.id, model: credential.model || "sesi\xF3n" };
1130
+ return { text, provider: credential.provider.id, model: credential.model || "session" };
1058
1131
  }
1059
1132
  if (credential.provider.api === "anthropic") return completeAnthropic(credential, request);
1060
1133
  if (credential.provider.api === "codex") return completeCodex(credential, request);
@@ -1135,14 +1208,17 @@ async function completeCodex(credential, request) {
1135
1208
  });
1136
1209
  if (!response.ok) {
1137
1210
  const failure = await response.json().catch(() => ({}));
1138
- throw new Error(
1139
- redact(
1140
- `${credential.provider.name} respondi\xF3 ${response.status}: ${failure.error?.message ?? failure.detail ?? response.statusText}`,
1211
+ throw new AiError({
1212
+ code: "providerRefused",
1213
+ provider: credential.provider.name,
1214
+ status: response.status,
1215
+ detail: redact(
1216
+ String(failure.error?.message ?? failure.detail ?? response.statusText),
1141
1217
  [credential.apiKey]
1142
1218
  )
1143
- );
1219
+ });
1144
1220
  }
1145
- if (!response.body) throw new Error(`${credential.provider.name} contest\xF3 sin cuerpo.`);
1221
+ if (!response.body) throw new AiError({ code: "emptyBody", provider: credential.provider.name });
1146
1222
  const { text, final } = await readCodexStream(response.body);
1147
1223
  const outputText = (final?.output ?? []).flatMap((block) => block.content ?? []).flatMap((chunk) => chunk.text ? [chunk.text] : []).join("\n");
1148
1224
  const shortcut = Array.isArray(final?.output_text) ? final.output_text.join("\n") : final?.output_text;
@@ -1231,12 +1307,12 @@ async function completeOpenAi(credential, request) {
1231
1307
  );
1232
1308
  const body = await response.json().catch(() => ({}));
1233
1309
  if (!response.ok) {
1234
- throw new Error(
1235
- redact(
1236
- `${credential.provider.name} respondi\xF3 ${response.status}: ${body.error?.message ?? response.statusText}`,
1237
- [credential.apiKey]
1238
- )
1239
- );
1310
+ throw new AiError({
1311
+ code: "providerRefused",
1312
+ provider: credential.provider.name,
1313
+ status: response.status,
1314
+ detail: redact(String(body.error?.message ?? response.statusText), [credential.apiKey])
1315
+ });
1240
1316
  }
1241
1317
  return {
1242
1318
  text: (body.choices?.[0]?.message?.content ?? "").trim(),
@@ -1250,6 +1326,7 @@ function dataUrl(image) {
1250
1326
  }
1251
1327
  export {
1252
1328
  ATTEMPTS,
1329
+ AiError,
1253
1330
  ConfigCorruptError,
1254
1331
  NoCredentialError,
1255
1332
  PROVIDERS,
@@ -1267,6 +1344,7 @@ export {
1267
1344
  detectCliAgents,
1268
1345
  exchangeCode,
1269
1346
  expired,
1347
+ failureMessage,
1270
1348
  findProvider,
1271
1349
  forgetCredential,
1272
1350
  listModels,
@@ -10,7 +10,7 @@ import {
10
10
  readConfig,
11
11
  resolveCredential,
12
12
  updateConfig
13
- } from "./chunk-N7EEJ4U6.js";
13
+ } from "./chunk-ROPR3LUO.js";
14
14
  import {
15
15
  say
16
16
  } from "./chunk-FN63LYZH.js";
@@ -154,7 +154,7 @@ ${say("ai.keyEmpty")}
154
154
  `));
155
155
  return 1;
156
156
  }
157
- const { saveKey } = await import("./dist-C7GE6UXH.js");
157
+ const { saveKey } = await import("./dist-XTIHMRQM.js");
158
158
  await saveKey(provider.id, key);
159
159
  process.stdout.write(
160
160
  `