@lambdacurry/arbor 0.4.17 → 0.4.19

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 (2) hide show
  1. package/dist/arbor.js +174 -179
  2. package/package.json +1 -1
package/dist/arbor.js CHANGED
@@ -1,16 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
- var __returnValue = (v) => v;
4
- function __exportSetter(name, newValue) {
5
- this[name] = __returnValue.bind(null, newValue);
6
- }
7
3
  var __export = (target, all) => {
8
4
  for (var name in all)
9
5
  __defProp(target, name, {
10
6
  get: all[name],
11
7
  enumerable: true,
12
8
  configurable: true,
13
- set: __exportSetter.bind(all, name)
9
+ set: (newValue) => all[name] = () => newValue
14
10
  });
15
11
  };
16
12
 
@@ -18,142 +14,6 @@ var __export = (target, all) => {
18
14
  import { readFileSync as readFileSync3 } from "node:fs";
19
15
  import { fileURLToPath } from "node:url";
20
16
 
21
- // src/config.ts
22
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
23
- import { homedir } from "node:os";
24
- import { dirname, join } from "node:path";
25
- var CONFIG_PATH = process.env.ARBOR_CONFIG ?? join(homedir(), ".arbor", "config.json");
26
- var DEFAULT_API_URL = process.env.ARBOR_API_URL ?? "http://localhost:8799";
27
- function loadConfig() {
28
- const envToken = process.env.ARBOR_TOKEN || undefined;
29
- const envUrl = process.env.ARBOR_API_URL || undefined;
30
- if (existsSync(CONFIG_PATH)) {
31
- try {
32
- const cfg = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
33
- return { apiUrl: envUrl ?? (cfg.apiUrl || DEFAULT_API_URL), token: envToken ?? cfg.token };
34
- } catch {}
35
- }
36
- return { apiUrl: envUrl ?? DEFAULT_API_URL, token: envToken };
37
- }
38
- function saveConfig(cfg) {
39
- mkdirSync(dirname(CONFIG_PATH), { recursive: true });
40
- writeFileSync(CONFIG_PATH, `${JSON.stringify(cfg, null, 2)}
41
- `);
42
- chmodSync(CONFIG_PATH, 384);
43
- }
44
- function clearToken() {
45
- const cfg = loadConfig();
46
- saveConfig({ apiUrl: cfg.apiUrl });
47
- }
48
-
49
- // src/errors.ts
50
- var ERROR_CODES = new Set([
51
- "UNAUTHORIZED",
52
- "FORBIDDEN",
53
- "NOT_FOUND",
54
- "VALIDATION",
55
- "CONFLICT",
56
- "RATE_LIMITED",
57
- "NETWORK",
58
- "INTERNAL"
59
- ]);
60
- function isErrorCode(value) {
61
- return typeof value === "string" && ERROR_CODES.has(value);
62
- }
63
- function codeForStatus(status) {
64
- switch (status) {
65
- case 0:
66
- return "NETWORK";
67
- case 400:
68
- case 422:
69
- return "VALIDATION";
70
- case 401:
71
- return "UNAUTHORIZED";
72
- case 403:
73
- return "FORBIDDEN";
74
- case 404:
75
- return "NOT_FOUND";
76
- case 409:
77
- return "CONFLICT";
78
- case 429:
79
- return "RATE_LIMITED";
80
- default:
81
- return "INTERNAL";
82
- }
83
- }
84
- function exitCodeForError(code) {
85
- switch (code) {
86
- case "VALIDATION":
87
- return 2;
88
- case "UNAUTHORIZED":
89
- return 22;
90
- default:
91
- return 1;
92
- }
93
- }
94
- function codeForError(err) {
95
- if (err && typeof err === "object" && "code" in err && isErrorCode(err.code)) {
96
- return err.code;
97
- }
98
- return "INTERNAL";
99
- }
100
-
101
- class ApiError extends Error {
102
- status;
103
- code;
104
- constructor(message, status) {
105
- super(message);
106
- this.status = status;
107
- this.name = "ApiError";
108
- this.code = codeForStatus(status);
109
- }
110
- }
111
-
112
- class UsageError extends Error {
113
- code = "VALIDATION";
114
- constructor(message) {
115
- super(message);
116
- this.name = "UsageError";
117
- }
118
- }
119
-
120
- // src/client.ts
121
- class NotLoggedInError extends Error {
122
- code = "UNAUTHORIZED";
123
- constructor() {
124
- super("Not logged in. Run: arbor login");
125
- this.name = "NotLoggedInError";
126
- }
127
- }
128
- function authHeader() {
129
- const cfg = loadConfig();
130
- if (!cfg.token)
131
- throw new NotLoggedInError;
132
- return { authorization: `Bearer ${cfg.token}`, apiUrl: cfg.apiUrl };
133
- }
134
- var httpExecutor = {
135
- async call(name, input) {
136
- const { authorization, apiUrl } = authHeader();
137
- const dot = name.indexOf(".");
138
- const object = dot === -1 ? name : name.slice(0, dot);
139
- const verb = dot === -1 ? "" : name.slice(dot + 1);
140
- let res;
141
- try {
142
- res = await fetch(`${apiUrl}/api`, {
143
- method: "POST",
144
- headers: { "content-type": "application/json", authorization },
145
- body: JSON.stringify({ object, verb, input })
146
- });
147
- } catch (cause) {
148
- throw new ApiError(`could not reach ${apiUrl} (${cause instanceof Error ? cause.message : String(cause)})`, 0);
149
- }
150
- const body = await res.json().catch(() => ({}));
151
- if (!res.ok || !body.success)
152
- throw new ApiError(body.error ?? `request failed (${res.status})`, res.status);
153
- return body.data;
154
- }
155
- };
156
-
157
17
  // ../core/src/objects/types.ts
158
18
  var CONTRIBUTION_TYPES = [
159
19
  "comment",
@@ -742,7 +602,7 @@ function sql(strings, ...params) {
742
602
  return new SQL([new StringChunk(str)]);
743
603
  }
744
604
  sql2.raw = raw;
745
- function join2(chunks, separator) {
605
+ function join(chunks, separator) {
746
606
  const result = [];
747
607
  for (const [i, chunk] of chunks.entries()) {
748
608
  if (i > 0 && separator !== undefined) {
@@ -752,7 +612,7 @@ function sql(strings, ...params) {
752
612
  }
753
613
  return new SQL(result);
754
614
  }
755
- sql2.join = join2;
615
+ sql2.join = join;
756
616
  function identifier(value) {
757
617
  return new Name(value);
758
618
  }
@@ -1639,24 +1499,6 @@ var reviews = sqliteTable("reviews", {
1639
1499
  reviewerTargetIdx: uniqueIndex("reviews_reviewer_target_idx").on(t.reviewerProfileId, t.targetContributionId),
1640
1500
  targetIdx: index("reviews_target_idx").on(t.targetContributionId)
1641
1501
  }));
1642
- var comments = sqliteTable("comments", {
1643
- id: text("id").primaryKey(),
1644
- threadId: text("thread_id").notNull().references(() => threads.id),
1645
- authorProfileId: text("author_profile_id").notNull().references(() => profiles.id),
1646
- executionContextId: text("execution_context_id").references(() => executionContexts.id),
1647
- body: text("body").notNull(),
1648
- aboutType: text("about_type").$type(),
1649
- aboutId: text("about_id"),
1650
- anchor: text("anchor", { mode: "json" }).$type(),
1651
- replyToId: text("reply_to_id"),
1652
- attachmentIds: text("attachment_ids", { mode: "json" }).$type(),
1653
- editedAt: ts("edited_at"),
1654
- deletedAt: ts("deleted_at"),
1655
- createdAt: ts("created_at").notNull()
1656
- }, (t) => ({
1657
- threadIdx: index("comments_thread_idx").on(t.threadId),
1658
- aboutIdx: index("comments_about_idx").on(t.aboutType, t.aboutId)
1659
- }));
1660
1502
  var attachments = sqliteTable("attachments", {
1661
1503
  id: text("id").primaryKey(),
1662
1504
  filename: text("filename").notNull(),
@@ -13332,7 +13174,7 @@ function finalize(ctx, schema) {
13332
13174
  result.$schema = "http://json-schema.org/draft-07/schema#";
13333
13175
  } else if (ctx.target === "draft-04") {
13334
13176
  result.$schema = "http://json-schema.org/draft-04/schema#";
13335
- } else if (ctx.target === "openapi-3.0") {}
13177
+ } else if (ctx.target === "openapi-3.0") {} else {}
13336
13178
  if (ctx.external?.uri) {
13337
13179
  const id = ctx.external.registry.get(schema)?.id;
13338
13180
  if (!id)
@@ -13576,7 +13418,7 @@ var literalProcessor = (schema, ctx, json, _params) => {
13576
13418
  if (val === undefined) {
13577
13419
  if (ctx.unrepresentable === "throw") {
13578
13420
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
13579
- }
13421
+ } else {}
13580
13422
  } else if (typeof val === "bigint") {
13581
13423
  if (ctx.unrepresentable === "throw") {
13582
13424
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -16145,6 +15987,16 @@ function date4(params) {
16145
15987
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
16146
15988
  config(en_default());
16147
15989
  // ../actions/src/index.ts
15990
+ var ORIENTATION = `Arbor is your team's deliberation room and shared memory — people and agents settle typed work here, and Arbor remembers what's decided. To work well:
15991
+
15992
+ 1. RECALL FIRST. Run \`recall\` before re-deriving or restating anything — it may already be settled; cite prior work ([label](#con_…)) and build on it. Empty recall is itself worth noting. WHY: a room where everyone re-derives is just a chat log.
15993
+ 2. CONTRIBUTE typed points — ONE point per contribution, with the type that names your move (proposal / critique / question / evidence / risk / correction / assertion / decision). Markdown welcome; put references IN your prose (a URL or [label](#con_…) becomes a navigable reference — there's no separate links field). WHY: one typed point is reviewable on its own.
15994
+ 3. ANSWER through asks. When \`inbox\` or a thread shows an open request you can meet, answer THROUGH it — \`respond\` to it, or \`stamp\` the contribution a review-ask is about — so it completes and the requester is notified. WHY: a plain reply that merely happens to answer leaves their ask hanging (the most common failure).
15995
+ 4. REVIEW honestly. \`stamp\` to vouch or push back on a point with a one-line why (you can't stamp your own work — ask via \`request\`). Promote sparingly. WHY: a standout is curation, not applause — if everything's promoted, nothing is.
15996
+ 5. EDIT, don't repost. \`edit\` your own contribution to sharpen it; the record stays audited. A genuinely different point is a NEW contribution.
15997
+ 6. OPEN STRUCTURE sparingly. Check \`tree\` first and prefer an existing thread; \`create_topic\`/\`create_thread\` only when work truly doesn't fit, named by its OBJECTIVE with your first contribution. Filed somewhere wrong or concluded? \`transition_thread --to archived\` and reopen where it belongs. WHY: structure is scarce — clutter makes the room harder to read.
15998
+
15999
+ Run \`arbor help\` (or read the MCP tool list) for the exact command/flags — those stay generated from the live action surface, so they're always current.`;
16148
16000
  function forward(operation) {
16149
16001
  return (ex, input) => ex.call(operation, input);
16150
16002
  }
@@ -16613,7 +16465,7 @@ var ACTIONS = [
16613
16465
  {
16614
16466
  name: "set_goal_status",
16615
16467
  title: "Set a goal's status",
16616
- description: "Move a goal through its lifecycle — active paused completed → archived (the goal's OWNER or an org admin). `completed` marks it reached; `archived` retires it (drops out of pick lists). This is the SOFT retire path; prefer it over deleting. Status is a deliberate signal — set it when the goal's state genuinely changes, not as routine bookkeeping.",
16468
+ description: "Move a goal through its lifecycle — active paused completed, any → archived, and archived → active to restore (the goal's OWNER or an org admin). `completed` marks it reached; `archived` retires it (drops out of pick lists). This is the SOFT retire path; prefer it over deleting. Illegal jumps are rejected. Status is a deliberate signal — set it when the goal's state genuinely changes, not as routine bookkeeping.",
16617
16469
  inputSchema: {
16618
16470
  id: exports_external.string().describe("the goal, ini_…"),
16619
16471
  status: exports_external.enum(["active", "paused", "completed", "archived"]).describe("the new lifecycle state")
@@ -16735,6 +16587,142 @@ var ACTIONS = [
16735
16587
  }
16736
16588
  ];
16737
16589
 
16590
+ // src/config.ts
16591
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16592
+ import { homedir } from "node:os";
16593
+ import { dirname, join } from "node:path";
16594
+ var CONFIG_PATH = process.env.ARBOR_CONFIG ?? join(homedir(), ".arbor", "config.json");
16595
+ var DEFAULT_API_URL = process.env.ARBOR_API_URL ?? "https://arbor-web.lambdacurry.workers.dev";
16596
+ function loadConfig() {
16597
+ const envToken = process.env.ARBOR_TOKEN || undefined;
16598
+ const envUrl = process.env.ARBOR_API_URL || undefined;
16599
+ if (existsSync(CONFIG_PATH)) {
16600
+ try {
16601
+ const cfg = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
16602
+ return { apiUrl: envUrl ?? (cfg.apiUrl || DEFAULT_API_URL), token: envToken ?? cfg.token };
16603
+ } catch {}
16604
+ }
16605
+ return { apiUrl: envUrl ?? DEFAULT_API_URL, token: envToken };
16606
+ }
16607
+ function saveConfig(cfg) {
16608
+ mkdirSync(dirname(CONFIG_PATH), { recursive: true });
16609
+ writeFileSync(CONFIG_PATH, `${JSON.stringify(cfg, null, 2)}
16610
+ `);
16611
+ chmodSync(CONFIG_PATH, 384);
16612
+ }
16613
+ function clearToken() {
16614
+ const cfg = loadConfig();
16615
+ saveConfig({ apiUrl: cfg.apiUrl });
16616
+ }
16617
+
16618
+ // src/errors.ts
16619
+ var ERROR_CODES = new Set([
16620
+ "UNAUTHORIZED",
16621
+ "FORBIDDEN",
16622
+ "NOT_FOUND",
16623
+ "VALIDATION",
16624
+ "CONFLICT",
16625
+ "RATE_LIMITED",
16626
+ "NETWORK",
16627
+ "INTERNAL"
16628
+ ]);
16629
+ function isErrorCode(value) {
16630
+ return typeof value === "string" && ERROR_CODES.has(value);
16631
+ }
16632
+ function codeForStatus(status) {
16633
+ switch (status) {
16634
+ case 0:
16635
+ return "NETWORK";
16636
+ case 400:
16637
+ case 422:
16638
+ return "VALIDATION";
16639
+ case 401:
16640
+ return "UNAUTHORIZED";
16641
+ case 403:
16642
+ return "FORBIDDEN";
16643
+ case 404:
16644
+ return "NOT_FOUND";
16645
+ case 409:
16646
+ return "CONFLICT";
16647
+ case 429:
16648
+ return "RATE_LIMITED";
16649
+ default:
16650
+ return "INTERNAL";
16651
+ }
16652
+ }
16653
+ function exitCodeForError(code) {
16654
+ switch (code) {
16655
+ case "VALIDATION":
16656
+ return 2;
16657
+ case "UNAUTHORIZED":
16658
+ return 22;
16659
+ default:
16660
+ return 1;
16661
+ }
16662
+ }
16663
+ function codeForError(err) {
16664
+ if (err && typeof err === "object" && "code" in err && isErrorCode(err.code)) {
16665
+ return err.code;
16666
+ }
16667
+ return "INTERNAL";
16668
+ }
16669
+
16670
+ class ApiError extends Error {
16671
+ status;
16672
+ code;
16673
+ constructor(message, status) {
16674
+ super(message);
16675
+ this.status = status;
16676
+ this.name = "ApiError";
16677
+ this.code = codeForStatus(status);
16678
+ }
16679
+ }
16680
+
16681
+ class UsageError extends Error {
16682
+ code = "VALIDATION";
16683
+ constructor(message) {
16684
+ super(message);
16685
+ this.name = "UsageError";
16686
+ }
16687
+ }
16688
+
16689
+ // src/client.ts
16690
+ class NotLoggedInError extends Error {
16691
+ code = "UNAUTHORIZED";
16692
+ constructor() {
16693
+ super("Not logged in. Run: arbor login");
16694
+ this.name = "NotLoggedInError";
16695
+ }
16696
+ }
16697
+ function authHeader() {
16698
+ const cfg = loadConfig();
16699
+ if (!cfg.token)
16700
+ throw new NotLoggedInError;
16701
+ return { authorization: `Bearer ${cfg.token}`, apiUrl: cfg.apiUrl };
16702
+ }
16703
+ var httpExecutor = {
16704
+ async call(name, input) {
16705
+ const { authorization, apiUrl } = authHeader();
16706
+ const dot = name.indexOf(".");
16707
+ const object2 = dot === -1 ? name : name.slice(0, dot);
16708
+ const verb = dot === -1 ? "" : name.slice(dot + 1);
16709
+ let res;
16710
+ try {
16711
+ res = await fetch(`${apiUrl}/api`, {
16712
+ method: "POST",
16713
+ headers: { "content-type": "application/json", authorization },
16714
+ body: JSON.stringify({ object: object2, verb, input })
16715
+ });
16716
+ } catch (cause) {
16717
+ throw new ApiError(`could not reach ${apiUrl} (${cause instanceof Error ? cause.message : String(cause)})`, 0);
16718
+ }
16719
+ const body = await res.json().catch(() => ({}));
16720
+ if (!res.ok || !body.success)
16721
+ throw new ApiError(body.error ?? `request failed (${res.status})`, res.status);
16722
+ return body.data;
16723
+ }
16724
+ };
16725
+
16738
16726
  // src/output.ts
16739
16727
  import { randomUUID } from "node:crypto";
16740
16728
  var GLOBAL_BOOLEAN_FLAGS = new Set(["json", "quiet", "no-quiet", "version", "help"]);
@@ -17014,7 +17002,8 @@ async function connect(opts) {
17014
17002
  throw new Error(`could not start pairing (${initRes.status}): ${init.error ?? "is the server reachable?"}`);
17015
17003
  }
17016
17004
  process.stderr.write(`
17017
- Ask the person who will manage this agent${init.agentName ? ` (“${init.agentName}”)` : ""} to approve it:
17005
+ Pairing against ${apiUrl}
17006
+ ` + ` Ask the person who will manage this agent${init.agentName ? ` (“${init.agentName}”)` : ""} to approve it:
17018
17007
  ` + ` 1. open ${init.url}
17019
17008
  ` + ` 2. enter the code: ${init.userCode}
17020
17009
 
@@ -17128,11 +17117,17 @@ var PKG = (() => {
17128
17117
  var CLI_VERSION = PKG.version;
17129
17118
  async function renderMe(ctx, action) {
17130
17119
  const me = await httpExecutor.call("me.get", {});
17120
+ const gaps = me.completeness?.unset ?? [];
17131
17121
  const human = `${me.profile.displayName} · ${me.profile.kind} · org-role ${me.profile.orgRole}
17132
17122
  ` + ` org ${me.profile.orgId} · resolved via ${me.client.kind}
17133
- `;
17123
+ ` + (gaps.length > 0 ? ` to be more legible to your team, set: ${gaps.join("; ")} (\`introduce\`)
17124
+ ` : "");
17134
17125
  emitDual(me, human, action, ctx);
17135
17126
  }
17127
+ function renderOrient(ctx) {
17128
+ emitDual({ orientation: ORIENTATION }, `${ORIENTATION}
17129
+ `, "orient", ctx);
17130
+ }
17136
17131
  function renderVersion(ctx) {
17137
17132
  emitDual({ name: PKG.name, version: CLI_VERSION }, `arbor ${CLI_VERSION}
17138
17133
  `, "version", ctx);
@@ -17141,25 +17136,17 @@ var HELP = `arbor — the Arbor CLI: people and agents deliberate on typed work,
17141
17136
  what's settled. Your token (env ARBOR_TOKEN, or \`arbor auth\`) resolves who you act AS — the CLI
17142
17137
  never asserts it. Run \`arbor whoami\` to confirm, \`arbor tree\` to map the workspace.
17143
17138
 
17144
- Etiquette — WHEN to act (the judgment; per-command mechanics are listed below):
17145
- · Recall first — \`arbor recall --query "<question>"\` before you contribute; cite prior work, don't repeat it.
17146
- · One typed point per contribution (proposal / critique / question / evidence / risk / correction /
17147
- assertion / decision). Markdown bodiespass long/multiline bodies with \`--body-file -\` (stdin),
17148
- don't shell-quote them. For a long contribution add a one-line \`--summary\` (≤300 chars): it becomes
17149
- the recall snippet AND sharpens recall (it's embedded with the body).
17150
- · References live in your prose — a URL or \`[label](#con_…)\` in the body becomes a typed reference;
17151
- there is no separate links field.
17152
- · Respond to requests THROUGH them — \`arbor respond\` (or \`arbor stamp\` a review request) so the
17153
- request completes and the requester is notified; a plain reply that skips \`respond\` leaves it hanging.
17154
- · Review honestly (\`arbor stamp\` = vouch / push back with a why), promote sparingly (curation, not
17155
- applause), and \`arbor edit\` rather than repost.
17156
- Full norms: GET /agent.md on your Arbor server, or the saffron arbor-collaboration shared skill.
17139
+ ${ORIENTATION}
17140
+
17141
+ CLI tips: pass long/multiline bodies with \`--body-file -\` (stdin), never shell-quote them; add a one-line
17142
+ \`--summary\` (≤300 chars) to a long contribution it's the recall snippet AND sharpens recall.
17157
17143
 
17158
17144
  Usage: arbor <command> [--flags]
17159
17145
 
17160
17146
  login [--url <api-url>] authorize in your browser (device flow) → stores a token
17161
17147
  auth <token> [--url …] save a token directly (a PAT or agent key) — headless, no browser
17162
17148
  connect <pairing-key> register THIS agent: relay the URL+code to your human to approve (AD-109)
17149
+ orient print how to work well in Arbor (the room etiquette — when + why)
17163
17150
  whoami print who your token resolves to
17164
17151
  logout forget the stored token
17165
17152
  version print the CLI version
@@ -17258,6 +17245,11 @@ async function main() {
17258
17245
  ✓ Connected (${CONFIG_PATH}).
17259
17246
  `, ctx);
17260
17247
  await renderMe(ctx, "connect");
17248
+ advise(`
17249
+ ${ORIENTATION}
17250
+
17251
+ Run \`arbor help\` for the full command surface.
17252
+ `, ctx);
17261
17253
  return;
17262
17254
  }
17263
17255
  case "logout":
@@ -17268,6 +17260,9 @@ async function main() {
17268
17260
  case "whoami":
17269
17261
  await renderMe(ctx, "whoami");
17270
17262
  return;
17263
+ case "orient":
17264
+ renderOrient(ctx);
17265
+ return;
17271
17266
  case "health":
17272
17267
  await renderHealth(ctx);
17273
17268
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdacurry/arbor",
3
- "version": "0.4.17",
3
+ "version": "0.4.19",
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
  "type": "module",
6
6
  "bin": {