@lambdacurry/arbor 0.4.34 → 0.6.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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/arbor.js +80 -18
  3. package/package.json +18 -18
package/README.md CHANGED
@@ -32,7 +32,7 @@ arbor whoami
32
32
 
33
33
  ## Agent self-registration (AD-109)
34
34
 
35
- An agent can register *itself* with a device-code-style handshake. A human mints a one-time **pairing key** in the Arbor web app (Settings → "Have an agent register itself") and hands it to the agent. The agent then:
35
+ An agent can register _itself_ with a device-code-style handshake. A human mints a one-time **pairing key** in the Arbor web app (Settings → "Have an agent register itself") and hands it to the agent. The agent then:
36
36
 
37
37
  ```bash
38
38
  npx @lambdacurry/arbor connect <pairing-key>
package/dist/arbor.js CHANGED
@@ -1446,8 +1446,16 @@ var initiatives = sqliteTable("initiatives", {
1446
1446
  ownerProfileId: text("owner_profile_id").notNull().references(() => profiles.id),
1447
1447
  priority: text("priority").$type().notNull().default("normal"),
1448
1448
  status: text("status").$type().notNull().default("active"),
1449
+ homeSpaceId: text("home_space_id").references(() => spaces.id),
1449
1450
  createdAt: ts("created_at").notNull()
1450
1451
  }, (t) => ({ orgIdx: index("initiatives_org_idx").on(t.orgId) }));
1452
+ var initiativeSpaces = sqliteTable("initiative_spaces", {
1453
+ initiativeId: text("initiative_id").notNull().references(() => initiatives.id),
1454
+ spaceId: text("space_id").notNull().references(() => spaces.id)
1455
+ }, (t) => ({
1456
+ pk: primaryKey({ columns: [t.initiativeId, t.spaceId] }),
1457
+ spaceIdx: index("initiative_spaces_space_idx").on(t.spaceId)
1458
+ }));
1451
1459
  var threads = sqliteTable("threads", {
1452
1460
  id: text("id").primaryKey(),
1453
1461
  topicId: text("topic_id").notNull().references(() => topics.id),
@@ -16416,15 +16424,15 @@ var ACTIONS = [
16416
16424
  run: forward("space.leave")
16417
16425
  },
16418
16426
  {
16419
- name: "invite_to_space",
16420
- title: "Invite to a space",
16421
- description: "Invite a person/agent into a space you administer (space-admin or org-owner). The ONLY way into a private space.",
16427
+ name: "add_to_space",
16428
+ title: "Add a member to a space",
16429
+ description: 'Add a person or agent to a space you administer (space-admin or org-owner) the way into a private space. One move for both: an AGENT joins directly (returns effect "added"), a HUMAN gets a pending invite they accept (effect "invited").',
16422
16430
  inputSchema: {
16423
- spaceId: exports_external.string().describe("the space id to invite into, e.g. spc_…"),
16424
- profileId: exports_external.string().describe("the invitee's profile id, e.g. prof_…")
16431
+ spaceId: exports_external.string().describe("the space id to add into, e.g. spc_…"),
16432
+ profileId: exports_external.string().describe("the profile id to add, e.g. prof_… (agent → granted, human → invited)")
16425
16433
  },
16426
16434
  surfaces: ["mcp", "cli"],
16427
- run: forward("space.invite")
16435
+ run: forward("space.addMember")
16428
16436
  },
16429
16437
  {
16430
16438
  name: "accept_space_invite",
@@ -16455,9 +16463,9 @@ var ACTIONS = [
16455
16463
  {
16456
16464
  name: "create_goal",
16457
16465
  title: "Create a goal",
16458
- description: "Create an org-scoped GOAL (initiative) — the widest unit of work, the 'why' that threads serve toward. Reach for this when several threads ladder up to one outcome worth naming; then `link_thread_to_goal` the threads that contribute. Optionally seed it from a space's threads at creation. Prefer linking to an existing goal (see `list_goals`) over minting a near-duplicate.",
16466
+ description: "Create a GOAL (initiative) — the widest unit of work, the 'why' that threads serve toward. The space you create it from becomes its HOME (AD-180): the goal surfaces there immediately, and that space's threads can bind to it. To surface it in MORE spaces, `attach_goal_to_space`. Reach for this when several threads ladder up to one outcome worth naming; prefer linking to an existing goal (see `list_goals`) over minting a near-duplicate.",
16459
16467
  inputSchema: {
16460
- spaceId: exports_external.string().describe("the space you're creating it from, spc_… (gates access)"),
16468
+ spaceId: exports_external.string().describe("the space it's created in — becomes its HOME space, spc_…"),
16461
16469
  title: exports_external.string().min(1).describe("the goal's name — an outcome, not a task"),
16462
16470
  purpose: exports_external.string().optional().describe("one or two lines on what reaching it means"),
16463
16471
  threadIds: exports_external.array(exports_external.string()).optional().describe("optional threads IN THIS SPACE to link at creation (others are ignored)")
@@ -16465,6 +16473,28 @@ var ACTIONS = [
16465
16473
  surfaces: ["mcp", "cli"],
16466
16474
  run: forward("initiative.create")
16467
16475
  },
16476
+ {
16477
+ name: "attach_goal_to_space",
16478
+ title: "Attach a goal to a space",
16479
+ description: "Surface a goal in an ADDITIONAL space (AD-180) — visibility, not custody: it appears on that space's goal strip and its threads can then `link_thread_to_goal` to it. You must be a member of the target space. Use for a genuinely cross-space goal (a launch spanning product + marketing); a goal already surfaces in its home space without this. Idempotent.",
16480
+ inputSchema: {
16481
+ initiativeId: exports_external.string().describe("the goal to attach, ini_…"),
16482
+ spaceId: exports_external.string().describe("the space to surface it in, spc_… (you must be a member)")
16483
+ },
16484
+ surfaces: ["mcp", "cli"],
16485
+ run: forward("initiative.attachSpace")
16486
+ },
16487
+ {
16488
+ name: "detach_goal_from_space",
16489
+ title: "Detach a goal from a space",
16490
+ description: "Stop surfacing a goal in a space (the deliberate inverse of `attach_goal_to_space`). A goal can NOT be detached from its HOME space — archive it instead. Existing thread links are untouched; only where the goal surfaces changes.",
16491
+ inputSchema: {
16492
+ initiativeId: exports_external.string().describe("the goal to detach, ini_…"),
16493
+ spaceId: exports_external.string().describe("the space to stop surfacing it in, spc_…")
16494
+ },
16495
+ surfaces: ["mcp", "cli"],
16496
+ run: forward("initiative.detachSpace")
16497
+ },
16468
16498
  {
16469
16499
  name: "link_thread_to_goal",
16470
16500
  title: "Link a thread to a goal",
@@ -16490,7 +16520,7 @@ var ACTIONS = [
16490
16520
  {
16491
16521
  name: "list_goals",
16492
16522
  title: "List a thread's goals",
16493
- description: "For a thread, list the goals it already contributes to PLUS the org's goals you could link it to (the pick list). Reach for this before `link_thread_to_goal` to find the right goal id and avoid creating a duplicate; archived goals are omitted.",
16523
+ description: "For a thread, list the goals it already contributes to PLUS the pick list of goals attached to ITS space (AD-180 — a thread binds only to goals that surface where it lives; `attach_goal_to_space` widens a goal's reach). Reach for this before `link_thread_to_goal` to find the right goal id and avoid creating a duplicate; archived goals are omitted.",
16494
16524
  inputSchema: {
16495
16525
  threadId: exports_external.string().describe("the thread to read goals for, thr_…")
16496
16526
  },
@@ -16533,11 +16563,12 @@ var ACTIONS = [
16533
16563
  {
16534
16564
  name: "space_create",
16535
16565
  title: "Create a space",
16536
- description: "Create a top-level space — the org-foundation container topics and threads live under. ORG OWNER/ADMIN only (a non-admin gets a clean 403): stand up a new area of work, e.g. a 'Saffron' space for cross-agent infra/tooling deliberation. Pass visibility private for invite-only (default open).",
16566
+ description: "Create a top-level space — the org-foundation container topics and threads live under. ORG OWNER/ADMIN only (a non-admin gets a clean 403): stand up a new area of work, e.g. a 'Saffron' space for cross-agent infra/tooling deliberation. Pass visibility private for invite-only (default open). Pass memberProfileIds to seed the space with members in one step — agents are added directly, humans get a pending invite to accept.",
16537
16567
  inputSchema: {
16538
16568
  title: exports_external.string().min(1).describe("the space title"),
16539
16569
  purpose: exports_external.string().optional().describe("optional one-line purpose"),
16540
- visibility: exports_external.enum(["open", "private"]).optional().describe("open (default) or private (invite-only)")
16570
+ visibility: exports_external.enum(["open", "private"]).optional().describe("open (default) or private (invite-only)"),
16571
+ memberProfileIds: exports_external.array(exports_external.string()).optional().describe("org profile ids to seed as members — agents added directly, humans get a pending invite")
16541
16572
  },
16542
16573
  surfaces: ["mcp", "cli"],
16543
16574
  run: forward("space.create")
@@ -16775,8 +16806,18 @@ import { randomUUID } from "node:crypto";
16775
16806
  function lastValue(value) {
16776
16807
  return Array.isArray(value) ? value[value.length - 1] : value;
16777
16808
  }
16778
- var GLOBAL_BOOLEAN_FLAGS = new Set(["json", "quiet", "no-quiet", "version", "help"]);
16779
- var RESERVED_FLAGS = new Set([...GLOBAL_BOOLEAN_FLAGS, "url", "fields"]);
16809
+ var GLOBAL_BOOLEAN_FLAGS = new Set([
16810
+ "json",
16811
+ "quiet",
16812
+ "no-quiet",
16813
+ "version",
16814
+ "help"
16815
+ ]);
16816
+ var RESERVED_FLAGS = new Set([
16817
+ ...GLOBAL_BOOLEAN_FLAGS,
16818
+ "url",
16819
+ "fields"
16820
+ ]);
16780
16821
  var TRUTHY = new Set(["true", "1", "yes", "on"]);
16781
16822
  function truthyFlag(value) {
16782
16823
  const v = lastValue(value);
@@ -16823,7 +16864,11 @@ function applyFields(data, fields) {
16823
16864
  function emitData(data, action, ctx) {
16824
16865
  const shaped = ctx.fields ? applyFields(data, ctx.fields) : data;
16825
16866
  if (ctx.json) {
16826
- const envelope = { ok: true, data: shaped, meta: { action, requestId: randomUUID() } };
16867
+ const envelope = {
16868
+ ok: true,
16869
+ data: shaped,
16870
+ meta: { action, requestId: randomUUID() }
16871
+ };
16827
16872
  process.stdout.write(`${JSON.stringify(envelope)}
16828
16873
  `);
16829
16874
  } else {
@@ -16835,7 +16880,11 @@ function emitError(err, action, ctx) {
16835
16880
  const code = codeForError(err);
16836
16881
  const message = err instanceof Error ? err.message : String(err);
16837
16882
  if (ctx.json) {
16838
- const envelope = { ok: false, error: { code, message }, meta: { action, requestId: randomUUID() } };
16883
+ const envelope = {
16884
+ ok: false,
16885
+ error: { code, message },
16886
+ meta: { action, requestId: randomUUID() }
16887
+ };
16839
16888
  process.stdout.write(`${JSON.stringify(envelope)}
16840
16889
  `);
16841
16890
  process.exitCode = exitCodeForError(code);
@@ -17138,7 +17187,11 @@ async function connect(opts) {
17138
17187
  throw new Error("pairing timed out — ask for a new pairing key and run `arbor connect` again");
17139
17188
  }
17140
17189
  await sleep(pollIntervalMs);
17141
- const tokRes = await fetch(`${apiUrl}/api/agent/connect/token`, { method: "POST", headers, body });
17190
+ const tokRes = await fetch(`${apiUrl}/api/agent/connect/token`, {
17191
+ method: "POST",
17192
+ headers,
17193
+ body
17194
+ });
17142
17195
  const tok = await tokRes.json().catch(() => ({}));
17143
17196
  if (tok.status === "approved" && tok.apiKey) {
17144
17197
  saveConfig({ apiUrl, token: tok.apiKey });
@@ -17188,7 +17241,11 @@ async function login(opts) {
17188
17241
  const tokRes = await fetch(`${apiUrl}/api/auth/device/token`, {
17189
17242
  method: "POST",
17190
17243
  headers: { "content-type": "application/json" },
17191
- body: JSON.stringify({ grant_type: GRANT_TYPE, device_code: code.device_code, client_id: CLIENT_ID })
17244
+ body: JSON.stringify({
17245
+ grant_type: GRANT_TYPE,
17246
+ device_code: code.device_code,
17247
+ client_id: CLIENT_ID
17248
+ })
17192
17249
  });
17193
17250
  const tok = await tokRes.json().catch(() => ({}));
17194
17251
  if (tok.access_token) {
@@ -17333,7 +17390,12 @@ async function renderHealth(ctx) {
17333
17390
  const me = await httpExecutor.call("me.get", {});
17334
17391
  auth = {
17335
17392
  authenticated: true,
17336
- profile: { id: me.profile.id, displayName: me.profile.displayName, kind: me.profile.kind, orgId: me.profile.orgId }
17393
+ profile: {
17394
+ id: me.profile.id,
17395
+ displayName: me.profile.displayName,
17396
+ kind: me.profile.kind,
17397
+ orgId: me.profile.orgId
17398
+ }
17337
17399
  };
17338
17400
  } catch (e) {
17339
17401
  auth = { authenticated: false, error: e instanceof Error ? e.message : String(e) };
package/package.json CHANGED
@@ -1,27 +1,13 @@
1
1
  {
2
2
  "name": "@lambdacurry/arbor",
3
- "version": "0.4.34",
3
+ "version": "0.6.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
- "type": "module",
6
- "bin": {
7
- "arbor": "./dist/arbor.js"
8
- },
9
- "files": [
10
- "dist",
11
- "README.md"
12
- ],
13
- "publishConfig": {
14
- "access": "public"
15
- },
16
- "engines": {
17
- "node": ">=18"
18
- },
19
5
  "keywords": [
6
+ "agents",
20
7
  "arbor",
21
8
  "cli",
22
- "agents",
23
- "mcp",
24
- "collaboration"
9
+ "collaboration",
10
+ "mcp"
25
11
  ],
26
12
  "license": "MIT",
27
13
  "repository": {
@@ -29,6 +15,17 @@
29
15
  "url": "git+https://github.com/lambda-curry/arbor.git",
30
16
  "directory": "packages/cli"
31
17
  },
18
+ "bin": {
19
+ "arbor": "./dist/arbor.js"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "type": "module",
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
32
29
  "scripts": {
33
30
  "arbor": "tsx src/bin.ts",
34
31
  "build": "bun build ./src/bin.ts --target=node --outfile=dist/arbor.js",
@@ -44,5 +41,8 @@
44
41
  "typescript": "^5.8.0",
45
42
  "vitest": "^4.1.4",
46
43
  "zod": "^4.4.3"
44
+ },
45
+ "engines": {
46
+ "node": ">=18"
47
47
  }
48
48
  }