@lambdacurry/arbor 0.4.23 → 0.4.25

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 +61 -29
  2. package/package.json +1 -1
package/dist/arbor.js CHANGED
@@ -1401,8 +1401,14 @@ var invitations = sqliteTable("invitations", {
1401
1401
  email: text("email").notNull(),
1402
1402
  profileId: text("profile_id").notNull().references(() => profiles.id),
1403
1403
  status: text("status").$type().notNull().default("pending"),
1404
+ token: text("token"),
1405
+ expiresAt: ts("expires_at"),
1406
+ invitedByProfileId: text("invited_by_profile_id").references(() => profiles.id),
1404
1407
  createdAt: ts("created_at").notNull()
1405
- }, (t) => ({ emailIdx: index("invitations_email_idx").on(t.email, t.status) }));
1408
+ }, (t) => ({
1409
+ emailIdx: index("invitations_email_idx").on(t.email, t.status),
1410
+ tokenIdx: uniqueIndex("invitations_token_idx").on(t.token)
1411
+ }));
1406
1412
  var agentPairings = sqliteTable("agent_pairings", {
1407
1413
  id: text("id").primaryKey(),
1408
1414
  orgId: text("org_id").notNull().references(() => orgs.id),
@@ -1618,6 +1624,7 @@ var notificationCursors = sqliteTable("notification_cursors", {
1618
1624
  lastSeenAt: ts("last_seen_at").notNull()
1619
1625
  });
1620
1626
  // ../core/src/ops/onboarding.ts
1627
+ var INVITE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
1621
1628
  var EMOJI_RE = new RegExp("^\\p{RGI_Emoji}$", "v");
1622
1629
  // ../core/src/ops/agent-pairing.ts
1623
1630
  var PAIRING_TTL_MS = 15 * 60 * 1000;
@@ -16299,11 +16306,11 @@ var ACTIONS = [
16299
16306
  {
16300
16307
  name: "introduce",
16301
16308
  title: "Introduce yourself",
16302
- description: "Set your own identity (AD-029/114/150/152): an emoji that represents you, an identity color from the curated palette (indigo, teal, terracotta, plum, steel, olive, rose, cyan), a short `description` (your CHARTER: who you are + what you're here to do), and/or `capabilities` — free-form tags for what you can do, so teammates know who to route work to. Do this once when you first connect so people recognize you at a glance; change it anytime. Your description shows on your profile AND is read back at the start of every session (prepended to your orientation), so write it as your working brief; your capabilities show on your profile + the space roster. Your display name stays managed by your human.",
16309
+ description: 'Set your own identity (AD-029/114/150/152/160): an emoji that represents you, an identity color from the curated palette (indigo, teal, terracotta, plum, steel, olive, rose, cyan), a short `description` (your CHARTER: your ROLE + what you\'re here to do), and/or `capabilities` — free-form tags for what you can do, so teammates know who to route work to. Do this once when you first connect; change it anytime. STYLE (AD-160): write the charter ROLE-FIRST — start with what you ARE/do (e.g. "Design engineer who holds the quality bar; makes design decisions executable"), NOT with your name. It renders inside "You are {Name}, working in Arbor. Your charter: …", so "I\'m {Name} —" just doubles up; lead with the role. It shows on your profile AND is read back at the start of every session. Your capabilities show on your profile + the space roster. Your display name stays managed by your human.',
16303
16310
  inputSchema: {
16304
16311
  emoji: exports_external.string().optional().describe("exactly one emoji that represents you, e.g. \uD83D\uDD2D"),
16305
16312
  color: exports_external.string().optional().describe("an identity color key from the curated palette"),
16306
- description: exports_external.string().optional().describe("your charter (≤500 chars): who you are + what you do; read back in your orientation. Empty clears it."),
16313
+ description: exports_external.string().optional().describe("your charter (≤500 chars): ROLE-FIRST — what you are + do, NOT 'I'm {Name}' (it renders after 'You are {Name}'). Read back in your orientation. Empty clears it."),
16307
16314
  capabilities: exports_external.array(exports_external.string()).optional().describe("free-form skill tags (≤20, each ≤60 chars) for what you can do — e.g. ['market-research','data-viz']; how teammates route work to you. An empty array clears them.")
16308
16315
  },
16309
16316
  surfaces: ["mcp", "cli"],
@@ -16729,24 +16736,29 @@ var httpExecutor = {
16729
16736
 
16730
16737
  // src/output.ts
16731
16738
  import { randomUUID } from "node:crypto";
16739
+ function lastValue(value) {
16740
+ return Array.isArray(value) ? value[value.length - 1] : value;
16741
+ }
16732
16742
  var GLOBAL_BOOLEAN_FLAGS = new Set(["json", "quiet", "no-quiet", "version", "help"]);
16733
16743
  var RESERVED_FLAGS = new Set([...GLOBAL_BOOLEAN_FLAGS, "url", "fields"]);
16734
16744
  var TRUTHY = new Set(["true", "1", "yes", "on"]);
16735
16745
  function truthyFlag(value) {
16736
- return value === true || typeof value === "string" && TRUTHY.has(value.toLowerCase());
16746
+ const v = lastValue(value);
16747
+ return v === true || typeof v === "string" && TRUTHY.has(v.toLowerCase());
16737
16748
  }
16738
16749
  function stringFlag(value, name) {
16739
- if (value === undefined)
16750
+ const v = lastValue(value);
16751
+ if (v === undefined)
16740
16752
  return;
16741
- if (value === true)
16753
+ if (v === true)
16742
16754
  throw new UsageError(`--${name} expects a value`);
16743
- return String(value);
16755
+ return String(v);
16744
16756
  }
16745
16757
  function resolveOutput(flags, opts) {
16746
16758
  const json2 = truthyFlag(flags.json);
16747
16759
  const isTTY = opts?.isTTY ?? Boolean(process.stdout.isTTY);
16748
16760
  const quiet = truthyFlag(flags["no-quiet"]) ? false : truthyFlag(flags.quiet) || json2 || !isTTY;
16749
- const fieldsRaw = flags.fields;
16761
+ const fieldsRaw = lastValue(flags.fields);
16750
16762
  const fields = typeof fieldsRaw === "string" && fieldsRaw.trim() ? fieldsRaw.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
16751
16763
  return { json: json2, quiet, fields };
16752
16764
  }
@@ -16879,7 +16891,7 @@ function buildInput(inputSchema, flags) {
16879
16891
  const input = {};
16880
16892
  for (const spec of flagsForSchema(inputSchema)) {
16881
16893
  if (spec.kind === "string") {
16882
- const fileSrc = flags[`${spec.flag}-file`];
16894
+ const fileSrc = lastValue(flags[`${spec.flag}-file`]);
16883
16895
  if (fileSrc !== undefined) {
16884
16896
  if (flags[spec.flag] !== undefined) {
16885
16897
  throw new UsageError(`pass either --${spec.flag} or --${spec.flag}-file, not both`);
@@ -16901,33 +16913,41 @@ function buildInput(inputSchema, flags) {
16901
16913
  }
16902
16914
  switch (spec.kind) {
16903
16915
  case "number": {
16904
- const n = Number(raw);
16916
+ const n = Number(lastValue(raw));
16905
16917
  if (Number.isNaN(n))
16906
- throw new UsageError(`--${spec.flag} expects a number, got: ${String(raw)}`);
16918
+ throw new UsageError(`--${spec.flag} expects a number, got: ${String(lastValue(raw))}`);
16907
16919
  input[spec.field] = n;
16908
16920
  break;
16909
16921
  }
16910
16922
  case "array": {
16911
- const text3 = String(raw).trim();
16912
- if (text3.startsWith("[")) {
16913
- try {
16914
- const parsed = JSON.parse(text3);
16915
- if (Array.isArray(parsed)) {
16916
- input[spec.field] = parsed;
16917
- break;
16923
+ const tokens = Array.isArray(raw) ? raw : [String(raw)];
16924
+ const out = [];
16925
+ for (const token of tokens) {
16926
+ const text3 = String(token).trim();
16927
+ if (text3.startsWith("[")) {
16928
+ try {
16929
+ const parsed = JSON.parse(text3);
16930
+ if (Array.isArray(parsed)) {
16931
+ out.push(...parsed);
16932
+ continue;
16933
+ }
16934
+ } catch {
16935
+ throw new UsageError(`--${spec.flag} looks like JSON but failed to parse — check the quoting`);
16918
16936
  }
16919
- } catch {
16920
- throw new UsageError(`--${spec.flag} looks like JSON but failed to parse — check the quoting`);
16921
16937
  }
16938
+ for (const part of text3.split(",").map((s) => s.trim()).filter(Boolean))
16939
+ out.push(part);
16922
16940
  }
16923
- input[spec.field] = text3.split(",").map((s) => s.trim()).filter(Boolean);
16941
+ input[spec.field] = out;
16924
16942
  break;
16925
16943
  }
16926
- case "boolean":
16927
- input[spec.field] = raw === true ? true : raw !== "false";
16944
+ case "boolean": {
16945
+ const v = lastValue(raw);
16946
+ input[spec.field] = v === true ? true : v !== "false";
16928
16947
  break;
16948
+ }
16929
16949
  default:
16930
- input[spec.field] = String(raw);
16950
+ input[spec.field] = String(lastValue(raw));
16931
16951
  }
16932
16952
  }
16933
16953
  return input;
@@ -17089,6 +17109,14 @@ async function login(opts) {
17089
17109
  }
17090
17110
 
17091
17111
  // src/index.ts
17112
+ function setFlag(flags, key, value) {
17113
+ const prev = flags[key];
17114
+ if (prev === undefined || typeof value === "boolean" || typeof prev === "boolean") {
17115
+ flags[key] = value;
17116
+ return;
17117
+ }
17118
+ flags[key] = Array.isArray(prev) ? [...prev, value] : [prev, value];
17119
+ }
17092
17120
  function parseArgs(argv) {
17093
17121
  const positionals = [];
17094
17122
  const flags = {};
@@ -17098,11 +17126,11 @@ function parseArgs(argv) {
17098
17126
  const body = arg.slice(2);
17099
17127
  const eq = body.indexOf("=");
17100
17128
  if (eq !== -1) {
17101
- flags[body.slice(0, eq)] = body.slice(eq + 1);
17129
+ setFlag(flags, body.slice(0, eq), body.slice(eq + 1));
17102
17130
  } else if (!GLOBAL_BOOLEAN_FLAGS.has(body) && i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
17103
- flags[body] = argv[++i];
17131
+ setFlag(flags, body, argv[++i]);
17104
17132
  } else {
17105
- flags[body] = true;
17133
+ setFlag(flags, body, true);
17106
17134
  }
17107
17135
  } else {
17108
17136
  positionals.push(arg);
@@ -17122,13 +17150,17 @@ var CLI_VERSION = PKG.version;
17122
17150
  async function renderMe(ctx, action) {
17123
17151
  const me = await httpExecutor.call("me.get", {});
17124
17152
  const gaps = me.completeness?.unset ?? [];
17153
+ const spaceGaps = me.completeness?.spacesWithoutCharter ?? [];
17154
+ const spaceLines = spaceGaps.length > 0 ? ` spaces without your charter (say how you operate there):
17155
+ ` + spaceGaps.map((s) => ` • ${s.spaceTitle} — \`set_space_charter --space-id ${s.spaceId} --charter "…"\`
17156
+ `).join("") : "";
17125
17157
  const human = `${me.profile.displayName} · ${me.profile.kind} · org-role ${me.profile.orgRole}
17126
17158
  ` + ` org ${me.profile.orgId} · resolved via ${me.client.kind}
17127
17159
  ` + (gaps.length > 0 ? ` to be more legible to your team, set: ${gaps.join("; ")} (\`introduce\`)
17128
- ` : "");
17160
+ ` : "") + spaceLines;
17129
17161
  emitDual(me, human, action, ctx);
17130
17162
  }
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.`;
17163
+ 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. List inputs (e.g. \`--capabilities\`) accept EITHER a comma list (\`--capabilities a,b,c\`) OR a repeated flag (\`--capabilities a --capabilities b\`) — both build the array.`;
17132
17164
  function renderOrient(ctx) {
17133
17165
  emitDual({ orientation: ORIENTATION, cliNote: CLI_NOTE }, `${ORIENTATION}
17134
17166
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdacurry/arbor",
3
- "version": "0.4.23",
3
+ "version": "0.4.25",
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": {