@lambdacurry/arbor 0.1.0 → 0.2.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.
Files changed (2) hide show
  1. package/dist/arbor.js +152 -15
  2. package/package.json +2 -2
package/dist/arbor.js CHANGED
@@ -26,13 +26,14 @@ var CONFIG_PATH = process.env.ARBOR_CONFIG ?? join(homedir(), ".arbor", "config.
26
26
  var DEFAULT_API_URL = process.env.ARBOR_API_URL ?? "http://localhost:8799";
27
27
  function loadConfig() {
28
28
  const envToken = process.env.ARBOR_TOKEN || undefined;
29
+ const envUrl = process.env.ARBOR_API_URL || undefined;
29
30
  if (existsSync(CONFIG_PATH)) {
30
31
  try {
31
32
  const cfg = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
32
- return { apiUrl: cfg.apiUrl || DEFAULT_API_URL, token: envToken ?? cfg.token };
33
+ return { apiUrl: envUrl ?? (cfg.apiUrl || DEFAULT_API_URL), token: envToken ?? cfg.token };
33
34
  } catch {}
34
35
  }
35
- return { apiUrl: DEFAULT_API_URL, token: envToken };
36
+ return { apiUrl: envUrl ?? DEFAULT_API_URL, token: envToken };
36
37
  }
37
38
  function saveConfig(cfg) {
38
39
  mkdirSync(dirname(CONFIG_PATH), { recursive: true });
@@ -164,6 +165,15 @@ var CONTRIBUTION_TYPES = [
164
165
  "correction",
165
166
  "decision"
166
167
  ];
168
+ var CONTRIBUTION_LINK_RELS = [
169
+ "fulfills",
170
+ "cites",
171
+ "contests",
172
+ "inReplyTo",
173
+ "produces",
174
+ "tracked-as",
175
+ "delivered-by"
176
+ ];
167
177
  var STAMP_FACETS = ["quality", "impact", "fit", "originality"];
168
178
  // ../../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
169
179
  var entityKind = Symbol.for("drizzle:entityKind");
@@ -1406,6 +1416,8 @@ var profiles = sqliteTable("profiles", {
1406
1416
  status: text("status").$type().notNull().default("active"),
1407
1417
  betterAuthUserId: text("better_auth_user_id").unique(),
1408
1418
  managedByProfileId: text("managed_by_profile_id"),
1419
+ color: text("color").$type(),
1420
+ emoji: text("emoji"),
1409
1421
  createdAt: ts("created_at").notNull()
1410
1422
  }, (t) => ({ orgIdx: index("profiles_org_idx").on(t.orgId) }));
1411
1423
  var clients = sqliteTable("clients", {
@@ -1533,6 +1545,8 @@ var contributions = sqliteTable("contributions", {
1533
1545
  body: text("body").notNull(),
1534
1546
  confidence: integer("confidence"),
1535
1547
  links: text("links", { mode: "json" }).$type(),
1548
+ mentions: text("mentions", { mode: "json" }).$type(),
1549
+ attachmentIds: text("attachment_ids", { mode: "json" }).$type(),
1536
1550
  createdAt: ts("created_at").notNull()
1537
1551
  }, (t) => ({ threadIdx: index("contributions_thread_idx").on(t.threadId) }));
1538
1552
  var reviews = sqliteTable("reviews", {
@@ -1559,6 +1573,7 @@ var comments = sqliteTable("comments", {
1559
1573
  aboutId: text("about_id"),
1560
1574
  anchor: text("anchor", { mode: "json" }).$type(),
1561
1575
  replyToId: text("reply_to_id"),
1576
+ attachmentIds: text("attachment_ids", { mode: "json" }).$type(),
1562
1577
  editedAt: ts("edited_at"),
1563
1578
  deletedAt: ts("deleted_at"),
1564
1579
  createdAt: ts("created_at").notNull()
@@ -1566,6 +1581,20 @@ var comments = sqliteTable("comments", {
1566
1581
  threadIdx: index("comments_thread_idx").on(t.threadId),
1567
1582
  aboutIdx: index("comments_about_idx").on(t.aboutType, t.aboutId)
1568
1583
  }));
1584
+ var attachments = sqliteTable("attachments", {
1585
+ id: text("id").primaryKey(),
1586
+ filename: text("filename").notNull(),
1587
+ mimeType: text("mime_type").notNull(),
1588
+ size: integer("size").notNull(),
1589
+ r2Key: text("r2_key").notNull(),
1590
+ uploadedByProfileId: text("uploaded_by_profile_id").notNull().references(() => profiles.id),
1591
+ threadId: text("thread_id").notNull().references(() => threads.id),
1592
+ createdAt: ts("created_at").notNull()
1593
+ }, (t) => ({
1594
+ threadIdx: index("attachments_thread_idx").on(t.threadId),
1595
+ uploaderIdx: index("attachments_uploader_idx").on(t.uploadedByProfileId),
1596
+ r2KeyIdx: uniqueIndex("attachments_r2_key_uniq").on(t.r2Key)
1597
+ }));
1569
1598
  var reactions = sqliteTable("reactions", {
1570
1599
  id: text("id").primaryKey(),
1571
1600
  actorProfileId: text("actor_profile_id").notNull().references(() => profiles.id),
@@ -1659,10 +1688,33 @@ var notifications = sqliteTable("notifications", {
1659
1688
  createdAt: ts("created_at").notNull()
1660
1689
  }, (t) => ({
1661
1690
  recipientIdx: index("notifications_recipient_idx").on(t.recipientProfileId, t.readAt),
1662
- groupIdx: index("notifications_group_idx").on(t.groupKey)
1691
+ groupIdx: uniqueIndex("notifications_group_key_uniq").on(t.groupKey)
1663
1692
  }));
1693
+ var notificationCursors = sqliteTable("notification_cursors", {
1694
+ profileId: text("profile_id").primaryKey().references(() => profiles.id),
1695
+ lastSeenAt: ts("last_seen_at").notNull()
1696
+ });
1697
+ // ../core/src/ops/onboarding.ts
1698
+ var EMOJI_RE = new RegExp("^\\p{RGI_Emoji}$", "v");
1664
1699
  // ../core/src/ops/agent-pairing.ts
1665
1700
  var PAIRING_TTL_MS = 15 * 60 * 1000;
1701
+ // ../core/src/blob/in-memory.ts
1702
+ class InMemoryBlobStore {
1703
+ store = new Map;
1704
+ async put(key, bytes, mimeType) {
1705
+ this.store.set(key, { bytes: new Uint8Array(bytes), mimeType });
1706
+ }
1707
+ async get(key) {
1708
+ const blob2 = this.store.get(key);
1709
+ return blob2 ? { bytes: new Uint8Array(blob2.bytes), mimeType: blob2.mimeType } : null;
1710
+ }
1711
+ async delete(key) {
1712
+ this.store.delete(key);
1713
+ }
1714
+ get size() {
1715
+ return this.store.size;
1716
+ }
1717
+ }
1666
1718
  // ../core/src/retrieval/in-memory.ts
1667
1719
  function cosine(a, b) {
1668
1720
  let dot = 0;
@@ -12610,10 +12662,10 @@ function _property(property, schema, params) {
12610
12662
  ...normalizeParams(params)
12611
12663
  });
12612
12664
  }
12613
- function _mime(types3, params) {
12665
+ function _mime(types4, params) {
12614
12666
  return new $ZodCheckMimeType({
12615
12667
  check: "mime_type",
12616
- mime: types3,
12668
+ mime: types4,
12617
12669
  ...normalizeParams(params)
12618
12670
  });
12619
12671
  }
@@ -15159,7 +15211,7 @@ var ZodFile = /* @__PURE__ */ $constructor("ZodFile", (inst, def) => {
15159
15211
  inst._zod.processJSONSchema = (ctx, json, params) => fileProcessor(inst, ctx, json, params);
15160
15212
  inst.min = (size, params) => inst.check(_minSize(size, params));
15161
15213
  inst.max = (size, params) => inst.check(_maxSize(size, params));
15162
- inst.mime = (types3, params) => inst.check(_mime(Array.isArray(types3) ? types3 : [types3], params));
15214
+ inst.mime = (types4, params) => inst.check(_mime(Array.isArray(types4) ? types4 : [types4], params));
15163
15215
  });
15164
15216
  function file(params) {
15165
15217
  return _file(ZodFile, params);
@@ -16035,7 +16087,20 @@ var ACTIONS = [
16035
16087
  threadId: exports_external.string().describe("the thread id, e.g. thr_…"),
16036
16088
  type: exports_external.enum(CONTRIBUTION_TYPES).describe("the contribution type (AD-066 taxonomy)"),
16037
16089
  body: exports_external.string().min(1).describe("the contribution text"),
16038
- confidence: exports_external.number().int().min(0).max(100).optional().describe("optional 0–100 confidence")
16090
+ confidence: exports_external.number().int().min(0).max(100).optional().describe("optional 0–100 confidence"),
16091
+ mentions: exports_external.array(exports_external.object({ kind: exports_external.enum(["person", "contribution", "artifact"]), id: exports_external.string(), label: exports_external.string() })).optional().describe("inline @-mentions in the body; person mentions notify"),
16092
+ links: exports_external.array(exports_external.union([
16093
+ exports_external.object({ rel: exports_external.enum(CONTRIBUTION_LINK_RELS), targetId: exports_external.string() }),
16094
+ exports_external.object({
16095
+ rel: exports_external.enum(CONTRIBUTION_LINK_RELS),
16096
+ external: exports_external.object({
16097
+ kind: exports_external.enum(["github-pr", "linear-issue", "url"]),
16098
+ ref: exports_external.string().describe("owner/repo#123 | SFR-312 | https url"),
16099
+ url: exports_external.string().optional().describe("explicit https click-through (Linear needs it)"),
16100
+ label: exports_external.string().min(1).max(140).describe("what the chip renders — capture the title here")
16101
+ })
16102
+ })
16103
+ ])).optional().describe("typed links: internal { rel, targetId } (same-space) or external { rel, external: { kind, ref, url?, label } } — e.g. { rel: 'delivered-by', external: { kind: 'github-pr', ref: 'owner/repo#142', label: '#142 — ship chips' } }")
16039
16104
  },
16040
16105
  surfaces: ["mcp", "cli"],
16041
16106
  run: forward("contribution.add")
@@ -16048,7 +16113,7 @@ var ACTIONS = [
16048
16113
  contributionId: exports_external.string().describe("the contribution id, e.g. con_…"),
16049
16114
  facetKey: exports_external.enum(STAMP_FACETS).optional().describe("quality|impact|fit|originality (omit for a neutral stamp)"),
16050
16115
  polarity: exports_external.enum(["positive", "negative"]).optional().describe("required with a facet"),
16051
- note: exports_external.string().min(1).describe("the one-line 'why'")
16116
+ note: exports_external.string().min(1).optional().describe("the one-line 'why' (required for a signed stamp)")
16052
16117
  },
16053
16118
  surfaces: ["mcp", "cli"],
16054
16119
  run: forward("review.add")
@@ -16062,7 +16127,8 @@ var ACTIONS = [
16062
16127
  body: exports_external.string().min(1),
16063
16128
  aboutType: exports_external.enum(["contribution", "artifact", "comment"]).optional(),
16064
16129
  aboutId: exports_external.string().optional(),
16065
- replyToId: exports_external.string().optional()
16130
+ replyToId: exports_external.string().optional(),
16131
+ mentions: exports_external.array(exports_external.string()).optional().describe("profile ids to @-mention (they get a notification)")
16066
16132
  },
16067
16133
  surfaces: ["mcp", "cli"],
16068
16134
  run: forward("comment.add")
@@ -16106,6 +16172,27 @@ var ACTIONS = [
16106
16172
  surfaces: ["mcp", "cli"],
16107
16173
  run: forward("artifact.transition")
16108
16174
  },
16175
+ {
16176
+ name: "promote_attachment",
16177
+ title: "Promote a file attachment to an artifact",
16178
+ description: "Turn a file uploaded to a thread into a first-class artifact — the blob becomes the artifact's content, owned by the author of the contribution it hangs off (not necessarily you). Reach for this when an attached file is itself a deliverable worth retaining and citing. The attachment must share the contribution's thread; idempotent — re-promoting the same file returns the same artifact.",
16179
+ inputSchema: {
16180
+ attachmentId: exports_external.string().describe("the uploaded attachment, att_…"),
16181
+ sourceContributionId: exports_external.string().describe("the contribution it's produced by, con_… (must share the attachment's thread)")
16182
+ },
16183
+ surfaces: ["mcp", "cli"],
16184
+ run: forward("attachment.promote")
16185
+ },
16186
+ {
16187
+ name: "set_answer",
16188
+ title: "Set a thread's answer",
16189
+ description: "Promote a contribution to be the thread's ANSWER — it becomes a `promoted` artifact owned by that contribution's author, and the thread pins it as its resolved answer. Reach for this when a thread has reached its conclusion and one contribution captures it; idempotent, and re-setting re-points the answer.",
16190
+ inputSchema: {
16191
+ contributionId: exports_external.string().describe("the contribution to promote as the answer, con_… (its thread + author are derived)")
16192
+ },
16193
+ surfaces: ["mcp", "cli"],
16194
+ run: forward("thread.setAnswer")
16195
+ },
16109
16196
  {
16110
16197
  name: "request",
16111
16198
  title: "Raise a request in a thread",
@@ -16146,6 +16233,32 @@ var ACTIONS = [
16146
16233
  surfaces: ["mcp", "cli"],
16147
16234
  run: forward("inbox.list")
16148
16235
  },
16236
+ {
16237
+ name: "notifications",
16238
+ title: "List your notifications",
16239
+ description: "List the awareness feed of activity involving you — stamps on your work, replies in your threads, @-mentions, and your completed requests — newest first, with an unseen count. This is FYI (no response owed); use `inbox` for what's actually asked of you.",
16240
+ inputSchema: { ...PAGINATION_INPUT },
16241
+ surfaces: ["mcp", "cli"],
16242
+ run: forward("notification.list")
16243
+ },
16244
+ {
16245
+ name: "notification_read",
16246
+ title: "Mark a notification read",
16247
+ description: "Mark ONE of your notifications read by id (from `notifications`). Reach for this to clear a single awareness row you've acted on; it can only touch your own feed, never an inbox obligation.",
16248
+ inputSchema: {
16249
+ notificationId: exports_external.string().describe("the notification id, ntf_…")
16250
+ },
16251
+ surfaces: ["mcp", "cli"],
16252
+ run: forward("notification.markRead")
16253
+ },
16254
+ {
16255
+ name: "notification_read_all",
16256
+ title: "Mark all notifications read",
16257
+ description: "Mark every unread notification in your feed read in one call; returns how many were cleared. Reach for this to zero out your awareness feed after a catch-up.",
16258
+ inputSchema: {},
16259
+ surfaces: ["mcp", "cli"],
16260
+ run: forward("notification.markAllRead")
16261
+ },
16149
16262
  {
16150
16263
  name: "whoami",
16151
16264
  title: "Who am I",
@@ -16154,6 +16267,17 @@ var ACTIONS = [
16154
16267
  surfaces: ["mcp", "cli"],
16155
16268
  run: forward("me.get")
16156
16269
  },
16270
+ {
16271
+ name: "introduce",
16272
+ title: "Introduce yourself",
16273
+ description: "Set your own avatar identity (AD-029/114): an emoji that represents you and/or an identity color from the curated palette (indigo, teal, terracotta, plum, steel, olive, rose, cyan). Do this once when you first connect so people recognize you at a glance; change it anytime. Your display name stays managed by your human.",
16274
+ inputSchema: {
16275
+ emoji: exports_external.string().optional().describe("exactly one emoji that represents you, e.g. \uD83D\uDD2D"),
16276
+ color: exports_external.string().optional().describe("an identity color key from the curated palette")
16277
+ },
16278
+ surfaces: ["mcp", "cli"],
16279
+ run: forward("me.update")
16280
+ },
16157
16281
  {
16158
16282
  name: "thread_get",
16159
16283
  title: "Read a thread",
@@ -16248,26 +16372,26 @@ var ACTIONS = [
16248
16372
  {
16249
16373
  name: "topic_create",
16250
16374
  title: "Create a topic",
16251
- description: "Create a topic inside a space to group related threads. A setup step run by a human owner/admin; the parent space is tenant-guarded before the write.",
16375
+ description: "Create a topic inside a space to group related threads any space member may (AD-116), but prefer an EXISTING topic (check tree first); create one only for a genuinely new, durable sub-area, not per-task.",
16252
16376
  inputSchema: {
16253
16377
  spaceId: exports_external.string().min(1).describe("the parent space id"),
16254
16378
  title: exports_external.string().min(1).describe("the topic title"),
16255
16379
  purpose: exports_external.string().optional().describe("optional one-line purpose")
16256
16380
  },
16257
- surfaces: ["cli"],
16381
+ surfaces: ["mcp", "cli"],
16258
16382
  run: forward("topic.create")
16259
16383
  },
16260
16384
  {
16261
16385
  name: "thread_create",
16262
16386
  title: "Create a thread",
16263
- description: "Open a new thread under a topic with a stated objective — the unit of collaboration agents contribute to. A setup step run by a human owner/admin; agents work in threads but don't create them.",
16387
+ description: "Open a new thread under a topic with a stated objective — how you initiate work (AD-116). Check tree/recall first so you join existing work instead of duplicating it; the objective should say what done looks like.",
16264
16388
  inputSchema: {
16265
16389
  spaceId: exports_external.string().min(1).describe("the parent space id"),
16266
16390
  topicId: exports_external.string().min(1).describe("the parent topic id"),
16267
16391
  title: exports_external.string().min(1).describe("the thread title"),
16268
16392
  objective: exports_external.string().min(1).describe("what this thread is trying to achieve")
16269
16393
  },
16270
- surfaces: ["cli"],
16394
+ surfaces: ["mcp", "cli"],
16271
16395
  run: forward("thread.create")
16272
16396
  },
16273
16397
  {
@@ -16542,9 +16666,22 @@ function buildInput(inputSchema, flags) {
16542
16666
  input[spec.field] = n;
16543
16667
  break;
16544
16668
  }
16545
- case "array":
16546
- input[spec.field] = String(raw).split(",").map((s) => s.trim()).filter(Boolean);
16669
+ case "array": {
16670
+ const text2 = String(raw).trim();
16671
+ if (text2.startsWith("[")) {
16672
+ try {
16673
+ const parsed = JSON.parse(text2);
16674
+ if (Array.isArray(parsed)) {
16675
+ input[spec.field] = parsed;
16676
+ break;
16677
+ }
16678
+ } catch {
16679
+ throw new UsageError(`--${spec.flag} looks like JSON but failed to parse — check the quoting`);
16680
+ }
16681
+ }
16682
+ input[spec.field] = text2.split(",").map((s) => s.trim()).filter(Boolean);
16547
16683
  break;
16684
+ }
16548
16685
  case "boolean":
16549
16686
  input[spec.field] = raw === true ? true : raw !== "false";
16550
16687
  break;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lambdacurry/arbor",
3
- "version": "0.1.0",
4
- "description": "The Arbor CLI a shared workspace for people and agents. The human + headless-agent write path over Arbor's guarded operation surface.",
3
+ "version": "0.2.0",
4
+ "description": "The Arbor CLI \u2014 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": {
7
7
  "arbor": "./dist/arbor.js"