@lambdacurry/arbor 0.11.6 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,7 +42,7 @@ This prints a URL + short code to relay to the human, who approves it in their l
42
42
 
43
43
  ## Output contract (agent-friendly)
44
44
 
45
- Every command takes `--json` to emit a stable `{ ok, data | error, meta }` envelope on stdout (with structured error codes and agent-mode exit codes); advisory/human text goes to stderr. `--quiet` (auto under `--json` or a pipe) silences the advisory stream. `--fields a,b,list:N` projects a result; `--limit`/`--cursor` paginate list commands.
45
+ Every command takes `--json` to emit a stable `{ ok, data | error, meta }` envelope on stdout (with structured error codes and agent-mode exit codes); advisory/human text goes to stderr. A failure envelope adds the server's granular `reason` — `validation.missing_field`, `limit.exceeded`, `conflict.state`, … — beside the coarse `code`, optionally with `field`, `limit`, `retryable`, and `phase` (AD-237). `--quiet` (auto under `--json` or a pipe) silences the advisory stream. `--fields a,b,list:N` projects a result; `--limit`/`--cursor` paginate list commands.
46
46
 
47
47
  ```bash
48
48
  arbor inbox --json --limit 20
package/dist/arbor.js CHANGED
@@ -31,6 +31,18 @@ var CONTRIBUTION_TYPES = [
31
31
  "decision"
32
32
  ];
33
33
  var STAMP_FACETS = ["quality", "impact", "fit", "originality"];
34
+ // ../core/src/errors/contract.ts
35
+ var ERROR_REASONS = new Set([
36
+ "validation.missing_field",
37
+ "validation.invalid_value",
38
+ "authorization.denied",
39
+ "resource.not_found",
40
+ "conflict.state",
41
+ "limit.exceeded",
42
+ "provider.timeout",
43
+ "provider.failure",
44
+ "internal"
45
+ ]);
34
46
  // ../../node_modules/.pnpm/drizzle-orm@0.38.4_@cloudflare+workers-types@4.20260529.1_@prisma+client@5.22.0_@types+_885285c61ee4b1c788385eea24f801ee/node_modules/drizzle-orm/entity.js
35
47
  var entityKind = Symbol.for("drizzle:entityKind");
36
48
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
@@ -17600,13 +17612,37 @@ function codeForError(err) {
17600
17612
  }
17601
17613
  return "INTERNAL";
17602
17614
  }
17615
+ function parseErrorDetails(value) {
17616
+ if (!value || typeof value !== "object")
17617
+ return;
17618
+ const raw = value;
17619
+ if (typeof raw.reason !== "string" || raw.reason === "")
17620
+ return;
17621
+ const details = { reason: raw.reason };
17622
+ if (typeof raw.field === "string")
17623
+ details.field = raw.field;
17624
+ if (typeof raw.limit === "number")
17625
+ details.limit = raw.limit;
17626
+ if (typeof raw.retryable === "boolean")
17627
+ details.retryable = raw.retryable;
17628
+ if (typeof raw.phase === "string")
17629
+ details.phase = raw.phase;
17630
+ return details;
17631
+ }
17632
+ function detailsForError(err) {
17633
+ if (!err || typeof err !== "object" || !("details" in err))
17634
+ return;
17635
+ return parseErrorDetails(err.details);
17636
+ }
17603
17637
 
17604
17638
  class ApiError extends Error {
17605
17639
  status;
17640
+ details;
17606
17641
  code;
17607
- constructor(message, status) {
17642
+ constructor(message, status, details) {
17608
17643
  super(message);
17609
17644
  this.status = status;
17645
+ this.details = details;
17610
17646
  this.name = "ApiError";
17611
17647
  this.code = codeForStatus(status);
17612
17648
  }
@@ -17614,9 +17650,11 @@ class ApiError extends Error {
17614
17650
 
17615
17651
  class UsageError extends Error {
17616
17652
  code = "VALIDATION";
17617
- constructor(message) {
17653
+ details;
17654
+ constructor(message, details) {
17618
17655
  super(message);
17619
17656
  this.name = "UsageError";
17657
+ this.details = details ?? { reason: "validation.invalid_value" };
17620
17658
  }
17621
17659
  }
17622
17660
 
@@ -17642,6 +17680,7 @@ function detectSource(env = process.env) {
17642
17680
  // src/client.ts
17643
17681
  class NotLoggedInError extends Error {
17644
17682
  code = "UNAUTHORIZED";
17683
+ details = { reason: "authorization.denied" };
17645
17684
  constructor() {
17646
17685
  super("Not logged in. Run: arbor login");
17647
17686
  this.name = "NotLoggedInError";
@@ -17676,7 +17715,7 @@ var httpExecutor = {
17676
17715
  }
17677
17716
  const body = await res.json().catch(() => ({}));
17678
17717
  if (!res.ok || !body.success)
17679
- throw new ApiError(body.error ?? `request failed (${res.status})`, res.status);
17718
+ throw new ApiError(body.error ?? `request failed (${res.status})`, res.status, parseErrorDetails(body.errorDetails));
17680
17719
  return body.data;
17681
17720
  }
17682
17721
  };
@@ -17707,7 +17746,10 @@ function stringFlag(value, name) {
17707
17746
  if (v === undefined)
17708
17747
  return;
17709
17748
  if (v === true)
17710
- throw new UsageError(`--${name} expects a value`);
17749
+ throw new UsageError(`--${name} expects a value`, {
17750
+ reason: "validation.missing_field",
17751
+ field: name
17752
+ });
17711
17753
  return String(v);
17712
17754
  }
17713
17755
  function resolveOutput(flags, opts) {
@@ -17761,7 +17803,7 @@ function emitError(err, action, ctx) {
17761
17803
  if (ctx.json) {
17762
17804
  const envelope = {
17763
17805
  ok: false,
17764
- error: { code, message },
17806
+ error: { code, message, ...detailsForError(err) },
17765
17807
  meta: { action, requestId: randomUUID() }
17766
17808
  };
17767
17809
  process.stdout.write(`${JSON.stringify(envelope)}
@@ -17875,18 +17917,27 @@ function buildInput(inputSchema, flags, command) {
17875
17917
  if (raw === undefined) {
17876
17918
  if (spec.required) {
17877
17919
  const positional = primary && primary.field === spec.field && command ? ` — or pass it as the argument: arbor ${command} <${primary.field}>` : "";
17878
- throw new UsageError(`missing required flag: --${spec.flag}${positional}`);
17920
+ throw new UsageError(`missing required flag: --${spec.flag}${positional}`, {
17921
+ reason: "validation.missing_field",
17922
+ field: spec.field
17923
+ });
17879
17924
  }
17880
17925
  continue;
17881
17926
  }
17882
17927
  if (spec.kind !== "boolean" && raw === true) {
17883
- throw new UsageError(`--${spec.flag} expects a value`);
17928
+ throw new UsageError(`--${spec.flag} expects a value`, {
17929
+ reason: "validation.missing_field",
17930
+ field: spec.field
17931
+ });
17884
17932
  }
17885
17933
  switch (spec.kind) {
17886
17934
  case "number": {
17887
17935
  const n = Number(lastValue(raw));
17888
17936
  if (Number.isNaN(n))
17889
- throw new UsageError(`--${spec.flag} expects a number, got: ${String(lastValue(raw))}`);
17937
+ throw new UsageError(`--${spec.flag} expects a number, got: ${String(lastValue(raw))}`, {
17938
+ reason: "validation.invalid_value",
17939
+ field: spec.field
17940
+ });
17890
17941
  input[spec.field] = n;
17891
17942
  break;
17892
17943
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdacurry/arbor",
3
- "version": "0.11.6",
3
+ "version": "0.12.0",
4
4
  "description": "The Arbor CLI — a shared workspace for people and agents. The human + headless-agent write path over Arbor's guarded operation surface.",
5
5
  "keywords": [
6
6
  "agents",