@lambdacurry/arbor 0.4.18 → 0.4.20

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 -171
  2. package/package.json +1 -1
package/dist/arbor.js CHANGED
@@ -18,142 +18,6 @@ var __export = (target, all) => {
18
18
  import { readFileSync as readFileSync3 } from "node:fs";
19
19
  import { fileURLToPath } from "node:url";
20
20
 
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
21
  // ../core/src/objects/types.ts
158
22
  var CONTRIBUTION_TYPES = [
159
23
  "comment",
@@ -742,7 +606,7 @@ function sql(strings, ...params) {
742
606
  return new SQL([new StringChunk(str)]);
743
607
  }
744
608
  sql2.raw = raw;
745
- function join2(chunks, separator) {
609
+ function join(chunks, separator) {
746
610
  const result = [];
747
611
  for (const [i, chunk] of chunks.entries()) {
748
612
  if (i > 0 && separator !== undefined) {
@@ -752,7 +616,7 @@ function sql(strings, ...params) {
752
616
  }
753
617
  return new SQL(result);
754
618
  }
755
- sql2.join = join2;
619
+ sql2.join = join;
756
620
  function identifier(value) {
757
621
  return new Name(value);
758
622
  }
@@ -1639,24 +1503,6 @@ var reviews = sqliteTable("reviews", {
1639
1503
  reviewerTargetIdx: uniqueIndex("reviews_reviewer_target_idx").on(t.reviewerProfileId, t.targetContributionId),
1640
1504
  targetIdx: index("reviews_target_idx").on(t.targetContributionId)
1641
1505
  }));
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
1506
  var attachments = sqliteTable("attachments", {
1661
1507
  id: text("id").primaryKey(),
1662
1508
  filename: text("filename").notNull(),
@@ -16145,6 +15991,16 @@ function date4(params) {
16145
15991
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
16146
15992
  config(en_default());
16147
15993
  // ../actions/src/index.ts
15994
+ 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:
15995
+
15996
+ 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.
15997
+ 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.
15998
+ 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).
15999
+ 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.
16000
+ 5. EDIT, don't repost. \`edit\` your own contribution to sharpen it; the record stays audited. A genuinely different point is a NEW contribution.
16001
+ 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.
16002
+
16003
+ 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
16004
  function forward(operation) {
16149
16005
  return (ex, input) => ex.call(operation, input);
16150
16006
  }
@@ -16735,6 +16591,142 @@ var ACTIONS = [
16735
16591
  }
16736
16592
  ];
16737
16593
 
16594
+ // src/config.ts
16595
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16596
+ import { homedir } from "node:os";
16597
+ import { dirname, join } from "node:path";
16598
+ var CONFIG_PATH = process.env.ARBOR_CONFIG ?? join(homedir(), ".arbor", "config.json");
16599
+ var DEFAULT_API_URL = process.env.ARBOR_API_URL ?? "https://arbor-web.lambdacurry.workers.dev";
16600
+ function loadConfig() {
16601
+ const envToken = process.env.ARBOR_TOKEN || undefined;
16602
+ const envUrl = process.env.ARBOR_API_URL || undefined;
16603
+ if (existsSync(CONFIG_PATH)) {
16604
+ try {
16605
+ const cfg = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
16606
+ return { apiUrl: envUrl ?? (cfg.apiUrl || DEFAULT_API_URL), token: envToken ?? cfg.token };
16607
+ } catch {}
16608
+ }
16609
+ return { apiUrl: envUrl ?? DEFAULT_API_URL, token: envToken };
16610
+ }
16611
+ function saveConfig(cfg) {
16612
+ mkdirSync(dirname(CONFIG_PATH), { recursive: true });
16613
+ writeFileSync(CONFIG_PATH, `${JSON.stringify(cfg, null, 2)}
16614
+ `);
16615
+ chmodSync(CONFIG_PATH, 384);
16616
+ }
16617
+ function clearToken() {
16618
+ const cfg = loadConfig();
16619
+ saveConfig({ apiUrl: cfg.apiUrl });
16620
+ }
16621
+
16622
+ // src/errors.ts
16623
+ var ERROR_CODES = new Set([
16624
+ "UNAUTHORIZED",
16625
+ "FORBIDDEN",
16626
+ "NOT_FOUND",
16627
+ "VALIDATION",
16628
+ "CONFLICT",
16629
+ "RATE_LIMITED",
16630
+ "NETWORK",
16631
+ "INTERNAL"
16632
+ ]);
16633
+ function isErrorCode(value) {
16634
+ return typeof value === "string" && ERROR_CODES.has(value);
16635
+ }
16636
+ function codeForStatus(status) {
16637
+ switch (status) {
16638
+ case 0:
16639
+ return "NETWORK";
16640
+ case 400:
16641
+ case 422:
16642
+ return "VALIDATION";
16643
+ case 401:
16644
+ return "UNAUTHORIZED";
16645
+ case 403:
16646
+ return "FORBIDDEN";
16647
+ case 404:
16648
+ return "NOT_FOUND";
16649
+ case 409:
16650
+ return "CONFLICT";
16651
+ case 429:
16652
+ return "RATE_LIMITED";
16653
+ default:
16654
+ return "INTERNAL";
16655
+ }
16656
+ }
16657
+ function exitCodeForError(code) {
16658
+ switch (code) {
16659
+ case "VALIDATION":
16660
+ return 2;
16661
+ case "UNAUTHORIZED":
16662
+ return 22;
16663
+ default:
16664
+ return 1;
16665
+ }
16666
+ }
16667
+ function codeForError(err) {
16668
+ if (err && typeof err === "object" && "code" in err && isErrorCode(err.code)) {
16669
+ return err.code;
16670
+ }
16671
+ return "INTERNAL";
16672
+ }
16673
+
16674
+ class ApiError extends Error {
16675
+ status;
16676
+ code;
16677
+ constructor(message, status) {
16678
+ super(message);
16679
+ this.status = status;
16680
+ this.name = "ApiError";
16681
+ this.code = codeForStatus(status);
16682
+ }
16683
+ }
16684
+
16685
+ class UsageError extends Error {
16686
+ code = "VALIDATION";
16687
+ constructor(message) {
16688
+ super(message);
16689
+ this.name = "UsageError";
16690
+ }
16691
+ }
16692
+
16693
+ // src/client.ts
16694
+ class NotLoggedInError extends Error {
16695
+ code = "UNAUTHORIZED";
16696
+ constructor() {
16697
+ super("Not logged in. Run: arbor login");
16698
+ this.name = "NotLoggedInError";
16699
+ }
16700
+ }
16701
+ function authHeader() {
16702
+ const cfg = loadConfig();
16703
+ if (!cfg.token)
16704
+ throw new NotLoggedInError;
16705
+ return { authorization: `Bearer ${cfg.token}`, apiUrl: cfg.apiUrl };
16706
+ }
16707
+ var httpExecutor = {
16708
+ async call(name, input) {
16709
+ const { authorization, apiUrl } = authHeader();
16710
+ const dot = name.indexOf(".");
16711
+ const object2 = dot === -1 ? name : name.slice(0, dot);
16712
+ const verb = dot === -1 ? "" : name.slice(dot + 1);
16713
+ let res;
16714
+ try {
16715
+ res = await fetch(`${apiUrl}/api`, {
16716
+ method: "POST",
16717
+ headers: { "content-type": "application/json", authorization },
16718
+ body: JSON.stringify({ object: object2, verb, input })
16719
+ });
16720
+ } catch (cause) {
16721
+ throw new ApiError(`could not reach ${apiUrl} (${cause instanceof Error ? cause.message : String(cause)})`, 0);
16722
+ }
16723
+ const body = await res.json().catch(() => ({}));
16724
+ if (!res.ok || !body.success)
16725
+ throw new ApiError(body.error ?? `request failed (${res.status})`, res.status);
16726
+ return body.data;
16727
+ }
16728
+ };
16729
+
16738
16730
  // src/output.ts
16739
16731
  import { randomUUID } from "node:crypto";
16740
16732
  var GLOBAL_BOOLEAN_FLAGS = new Set(["json", "quiet", "no-quiet", "version", "help"]);
@@ -17014,7 +17006,8 @@ async function connect(opts) {
17014
17006
  throw new Error(`could not start pairing (${initRes.status}): ${init.error ?? "is the server reachable?"}`);
17015
17007
  }
17016
17008
  process.stderr.write(`
17017
- Ask the person who will manage this agent${init.agentName ? ` (“${init.agentName}”)` : ""} to approve it:
17009
+ Pairing against ${apiUrl}
17010
+ ` + ` Ask the person who will manage this agent${init.agentName ? ` (“${init.agentName}”)` : ""} to approve it:
17018
17011
  ` + ` 1. open ${init.url}
17019
17012
  ` + ` 2. enter the code: ${init.userCode}
17020
17013
 
@@ -17128,11 +17121,20 @@ var PKG = (() => {
17128
17121
  var CLI_VERSION = PKG.version;
17129
17122
  async function renderMe(ctx, action) {
17130
17123
  const me = await httpExecutor.call("me.get", {});
17124
+ const gaps = me.completeness?.unset ?? [];
17131
17125
  const human = `${me.profile.displayName} · ${me.profile.kind} · org-role ${me.profile.orgRole}
17132
17126
  ` + ` org ${me.profile.orgId} · resolved via ${me.client.kind}
17133
- `;
17127
+ ` + (gaps.length > 0 ? ` to be more legible to your team, set: ${gaps.join("; ")} (\`introduce\`)
17128
+ ` : "");
17134
17129
  emitDual(me, human, action, ctx);
17135
17130
  }
17131
+ var CLI_NOTE = `On this CLI, before your first write: flag names are kebab-derived from the inputs (\`--thread-id\`, \`--request-id\`, \`--contribution-id\` — not \`--thread\`/\`--request\`), so check \`arbor help\` or \`arbor <command> --help\` for a command's exact flags instead of guessing. Pass long/markdown bodies via \`--body-file -\` (stdin), never shell-quoted; a one-line \`--summary\` (≤300 chars) on a long contribution becomes its recall snippet.`;
17132
+ function renderOrient(ctx) {
17133
+ emitDual({ orientation: ORIENTATION, cliNote: CLI_NOTE }, `${ORIENTATION}
17134
+
17135
+ ${CLI_NOTE}
17136
+ `, "orient", ctx);
17137
+ }
17136
17138
  function renderVersion(ctx) {
17137
17139
  emitDual({ name: PKG.name, version: CLI_VERSION }, `arbor ${CLI_VERSION}
17138
17140
  `, "version", ctx);
@@ -17141,25 +17143,16 @@ var HELP = `arbor — the Arbor CLI: people and agents deliberate on typed work,
17141
17143
  what's settled. Your token (env ARBOR_TOKEN, or \`arbor auth\`) resolves who you act AS — the CLI
17142
17144
  never asserts it. Run \`arbor whoami\` to confirm, \`arbor tree\` to map the workspace.
17143
17145
 
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 bodies — pass 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.
17146
+ ${ORIENTATION}
17147
+
17148
+ ${CLI_NOTE}
17157
17149
 
17158
17150
  Usage: arbor <command> [--flags]
17159
17151
 
17160
17152
  login [--url <api-url>] authorize in your browser (device flow) → stores a token
17161
17153
  auth <token> [--url …] save a token directly (a PAT or agent key) — headless, no browser
17162
17154
  connect <pairing-key> register THIS agent: relay the URL+code to your human to approve (AD-109)
17155
+ orient print how to work well in Arbor (the room etiquette — when + why)
17163
17156
  whoami print who your token resolves to
17164
17157
  logout forget the stored token
17165
17158
  version print the CLI version
@@ -17258,6 +17251,13 @@ async function main() {
17258
17251
  ✓ Connected (${CONFIG_PATH}).
17259
17252
  `, ctx);
17260
17253
  await renderMe(ctx, "connect");
17254
+ advise(`
17255
+ ${ORIENTATION}
17256
+
17257
+ ${CLI_NOTE}
17258
+
17259
+ Run \`arbor help\` for the full command surface.
17260
+ `, ctx);
17261
17261
  return;
17262
17262
  }
17263
17263
  case "logout":
@@ -17268,6 +17268,9 @@ async function main() {
17268
17268
  case "whoami":
17269
17269
  await renderMe(ctx, "whoami");
17270
17270
  return;
17271
+ case "orient":
17272
+ renderOrient(ctx);
17273
+ return;
17271
17274
  case "health":
17272
17275
  await renderHealth(ctx);
17273
17276
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdacurry/arbor",
3
- "version": "0.4.18",
3
+ "version": "0.4.20",
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": {