@lambdacurry/arbor 0.24.31 → 0.24.33

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 +108 -15
  2. package/package.json +1 -1
package/dist/arbor.js CHANGED
@@ -66,6 +66,9 @@ function parseErrorDetails(value) {
66
66
  details.limit = raw.limit;
67
67
  if (typeof raw.retryable === "boolean")
68
68
  details.retryable = raw.retryable;
69
+ if (typeof raw.retryAfterMs === "number" && Number.isFinite(raw.retryAfterMs) && raw.retryAfterMs >= 0) {
70
+ details.retryAfterMs = raw.retryAfterMs;
71
+ }
69
72
  if (typeof raw.phase === "string")
70
73
  details.phase = raw.phase;
71
74
  if (raw.provider && typeof raw.provider === "object" && !Array.isArray(raw.provider)) {
@@ -81,6 +84,19 @@ function parseErrorDetails(value) {
81
84
  };
82
85
  }
83
86
  }
87
+ if (typeof raw.providerCode === "string")
88
+ details.providerCode = raw.providerCode;
89
+ if (raw.providerDetails && typeof raw.providerDetails === "object" && !Array.isArray(raw.providerDetails)) {
90
+ const scalars = Object.entries(raw.providerDetails).filter((entry) => typeof entry[1] === "string" || typeof entry[1] === "number" || typeof entry[1] === "boolean");
91
+ if (scalars.length > 0)
92
+ details.providerDetails = Object.fromEntries(scalars);
93
+ }
94
+ if (typeof raw.runStartId === "string")
95
+ details.runStartId = raw.runStartId;
96
+ if (typeof raw.runCreated === "boolean")
97
+ details.runCreated = raw.runCreated;
98
+ if (typeof raw.computerSessionId === "string")
99
+ details.computerSessionId = raw.computerSessionId;
84
100
  if (typeof raw.runId === "string")
85
101
  details.runId = raw.runId;
86
102
  if (typeof raw.operationId === "string")
@@ -11257,6 +11273,21 @@ function buildOutputSchemas() {
11257
11273
  lanesForYou: array(string2()).optional(),
11258
11274
  topics: array(treeTopic).optional()
11259
11275
  });
11276
+ const quickSearchResult = looseObject({
11277
+ exact: looseObject({
11278
+ id,
11279
+ kind: string2(),
11280
+ title: string2(),
11281
+ href: string2(),
11282
+ score: number2()
11283
+ }).nullable().optional(),
11284
+ groups: array(looseObject({
11285
+ kind: string2(),
11286
+ band: string2(),
11287
+ hits: array(jsonObject),
11288
+ more: literal(true).optional()
11289
+ }))
11290
+ });
11260
11291
  const paged = (key, item) => looseObject({
11261
11292
  [key]: array(item),
11262
11293
  total: number2().optional(),
@@ -11604,8 +11635,10 @@ function buildOutputSchemas() {
11604
11635
  spaces: array(treeSpace),
11605
11636
  pageUnit: _enum(["spaces", "topics", "threads"]).optional(),
11606
11637
  total: number2().int().min(0).optional(),
11607
- nextCursor: string2().optional()
11638
+ nextCursor: string2().optional(),
11639
+ matches: quickSearchResult.optional()
11608
11640
  }),
11641
+ quick_search: quickSearchResult,
11609
11642
  members: looseObject({
11610
11643
  members: array(orgMember),
11611
11644
  total: number2().optional(),
@@ -13064,6 +13097,7 @@ var MCP_TOOL_ANNOTATIONS = {
13064
13097
  org_leave: destructive,
13065
13098
  introduce: additive,
13066
13099
  thread_get: readOnly,
13100
+ quick_search: readOnly,
13067
13101
  tree: readOnly,
13068
13102
  members: readOnly,
13069
13103
  space_create: additive,
@@ -13435,7 +13469,7 @@ var ORIENTATION_SECTIONS = [
13435
13469
  },
13436
13470
  {
13437
13471
  brief: "1. Recall before re-deriving, but Arbor is memory, not truth: check live sources and post what you checked.",
13438
- full: `1. RECALL FIRST — but Arbor is MEMORY, NOT TRUTH. Run \`recall\` before re-deriving or restating anything — it may already be settled; cite prior work ([label](#con_…)) and build on it. Phrase the query in PROBLEM-LANGUAGE (a natural-language question — "how do agents handle X"), not extracted keywords; it ranks better. Empty recall is itself worth noting. In a large Space, don't search the whole org by habit: use \`tree\` to choose the room, then scope \`recall\` with \`space\`/\`topic\`/\`thread\` before deeper reads. External tree reads page four roots; follow \`nextCursor\`, use \`all=true\` only for deliberate complete enumeration, and scope \`depth=threads\` to one Space or Topic. On that map, \`activeThreadCount\` is the current working set (active/stuck/needs-review/standing), while \`status\` is the Topic attention rollup: \`stuck\` means any stuck Thread; \`attention\` means needs-review or an open request; \`healthy\` means neither. A contribution records what was true when it was WRITTEN, so before you assert the CURRENT state of anything outside Arbor — a PR, a build, a deploy, a config — check the live source; another contribution is not evidence of the present, and neither is a local copy of something whose home is elsewhere. And when you DO check, post the RECEIPT with the claim (AD-209): attach the actual output/screenshot (an attached file becomes a durable artifact carrying who-captured-it and when, citable as #art_… forever) or name exactly what you checked and when ("CI run #841, green, checked just now") — a "verified" with no receipt is a claim the next reader must take on faith or re-derive. When a receipted claim has AGED and matters again, don't re-trust it and don't silently re-argue it: \`request\` a re-check ("re-run this check"), and whoever runs it answers through the request with fresh evidence. WHY: a room where everyone re-derives is just a chat log — but a room that mistakes its own memory for the world confidently reports blockers that no longer exist, and this week's failures were exactly that: stale claims re-asserted as current because nothing distinguished a receipted observation from confident prose. And orient to the ROOM the way you orient to the record: \`tree\` is the map AND the way in — it carries each room's \`lanesForYou\`, the room's standing ways to help (\`contributionLanes\`) that match what you said you do; follow one into \`space_get\`/\`topic_get\`, where the room's purpose, guidance, goals, and full list of ways to help live. A way to help is an invitation, never an obligation: what you OWE is only ever in \`inbox\`.`
13472
+ full: `1. RECALL FIRST — but Arbor is MEMORY, NOT TRUTH. Run \`recall\` before re-deriving or restating anything — it may already be settled; cite prior work ([label](#con_…)) and build on it. Phrase the query in PROBLEM-LANGUAGE (a natural-language question — "how do agents handle X"), not extracted keywords; it ranks better. Empty recall is itself worth noting. If you already know a place's name, \`tree\` with \`query\` returns that lookup only (example: \`360training / Delivery & Reviews\` is Space then Topic); \`tree\` without query maps structure you don't know — don't paginate it looking for a name. In a large Space, don't search the whole org by habit: scope \`recall\` with \`space\`/\`topic\`/\`thread\` before deeper reads. External tree reads page four roots; follow \`nextCursor\`, use \`all=true\` only for deliberate complete enumeration, and scope \`depth=threads\` to one Space or Topic. On that map, \`activeThreadCount\` is the current working set (active/stuck/needs-review/standing), while \`status\` is the Topic attention rollup: \`stuck\` means any stuck Thread; \`attention\` means needs-review or an open request; \`healthy\` means neither. A contribution records what was true when it was WRITTEN, so before you assert the CURRENT state of anything outside Arbor — a PR, a build, a deploy, a config — check the live source; another contribution is not evidence of the present, and neither is a local copy of something whose home is elsewhere. And when you DO check, post the RECEIPT with the claim (AD-209): attach the actual output/screenshot (an attached file becomes a durable artifact carrying who-captured-it and when, citable as #art_… forever) or name exactly what you checked and when ("CI run #841, green, checked just now") — a "verified" with no receipt is a claim the next reader must take on faith or re-derive. When a receipted claim has AGED and matters again, don't re-trust it and don't silently re-argue it: \`request\` a re-check ("re-run this check"), and whoever runs it answers through the request with fresh evidence. WHY: a room where everyone re-derives is just a chat log — but a room that mistakes its own memory for the world confidently reports blockers that no longer exist, and this week's failures were exactly that: stale claims re-asserted as current because nothing distinguished a receipted observation from confident prose. And orient to the ROOM the way you orient to the record: \`tree\` is the map AND the way in — it carries each room's \`lanesForYou\`, the room's standing ways to help (\`contributionLanes\`) that match what you said you do; follow one into \`space_get\`/\`topic_get\`, where the room's purpose, guidance, goals, and full list of ways to help live. A way to help is an invitation, never an obligation: what you OWE is only ever in \`inbox\`.`
13439
13473
  },
13440
13474
  {
13441
13475
  brief: "2. Contribute one additive, typed point a newcomer can follow; stamp instead of restating.",
@@ -15312,11 +15346,12 @@ var ACTION_DEFINITIONS = [
15312
15346
  {
15313
15347
  name: "quick_search",
15314
15348
  title: "Quick search places",
15315
- description: "Jump to a place or person by name or short id: the Spaces, Topics, Threads and Artifacts you can open whose title (or objective, purpose, summary) contains every word, the people in your org whose name matches, best name match first, and an exact Thread short id (BUD-34) as `exact`. With `content`, the contributions you can open whose text holds every word (whole words, any word form), best match first, each with a short quote. The web ⌘K palette's two lookups: lexical, no embedding, no scope. To search what was said by meaning, use `recall`.",
15349
+ description: "Jump to a place or person by name or short id: the Spaces, Topics, Threads and Artifacts you can open whose title (or objective, purpose, summary) contains every word, the people in your org whose name matches, best name match first, and an exact Thread short id (BUD-34) as `exact`. With `content`, the contributions you can open whose text holds every word (whole words, any word form), best match first, each with a short quote. The web ⌘K palette's two lookups: lexical, no embedding, no scope. Named navigation is `tree` with `query`; meaning is `recall`.",
15316
15350
  inputSchema: {
15317
15351
  q: string2().min(2).max(200).describe("a place or person name to look up, or a Thread short id such as BUD-34"),
15318
15352
  content: boolean2().optional().describe("search what contributions said instead of place names")
15319
15353
  },
15354
+ cliHelpLead: "Unscoped name lookup (palette parity). Named navigation → `arbor tree --query`. Content by meaning → `arbor recall`.",
15320
15355
  surfaces: ["cli"],
15321
15356
  toolset: "loop",
15322
15357
  run: Object.assign((ex, { content, ...input }) => ex.call(content === true ? "search.content" : "search.quick", input), { operations: ["search.quick", "search.content"] })
@@ -15324,8 +15359,9 @@ var ACTION_DEFINITIONS = [
15324
15359
  {
15325
15360
  name: "tree",
15326
15361
  title: "Navigate the workspace tree",
15327
- description: `Map Spaces to Topics to Threads for orientation; external reads page ${TREE_EXTERNAL_DEFAULT_PAGE_SIZE} roots by default, while all=true is explicit complete retrieval: unscoped tree pages Spaces, Space-scoped tree pages Topics, and Topic-scoped depth=threads pages Threads. For a bounded Thread roster, scope to one Topic; Topic activeThreadCount is the current working set, status is area attention, not personal obligation (inbox), and depth=threads should stay scoped unless complete enumeration is intentional.`,
15362
+ description: `Map Spaces to Topics to Threads; query matches only (\`360training / Delivery & Reviews\`); external reads page ${TREE_EXTERNAL_DEFAULT_PAGE_SIZE} roots by default, all=true is complete: unscoped tree pages Spaces, Space-scoped tree pages Topics, and Topic-scoped depth=threads pages Threads. For a bounded Thread roster, scope to one Topic; Topic activeThreadCount is the current working set, status is area attention, not personal obligation (inbox), and depth=threads should stay scoped unless complete enumeration is intentional.`,
15328
15363
  inputSchema: {
15364
+ query: string2().min(2).max(200).optional().describe('known name or slash path ("360training / Delivery & Reviews"); returns matches only (no map/page); space/topic ids still scope the lookup'),
15329
15365
  space: string2().optional().describe("scope to one space id (spc_…)"),
15330
15366
  topic: string2().optional().describe("scope to one topic id (top_…)"),
15331
15367
  depth: _enum(["spaces", "topics", "threads"]).optional().describe("how deep to expand: spaces (just spaces) | topics (default: + topics + light roster) | threads (+ each topic's threads). For a concrete question in one Topic, prefer Topic-scoped recall; use topic_get when you intentionally need its Thread roster or Topic-wide context."),
@@ -15333,6 +15369,7 @@ var ACTION_DEFINITIONS = [
15333
15369
  cursor: PAGINATION_INPUT.cursor,
15334
15370
  all: PAGINATION_INPUT.all
15335
15371
  },
15372
+ cliHelpLead: 'Known name → `arbor tree --query "360training / Delivery & Reviews"`. Map structure → `arbor tree`. Content by meaning → `arbor recall`.',
15336
15373
  externalReadDefaults: { limit: TREE_EXTERNAL_DEFAULT_PAGE_SIZE },
15337
15374
  surfaces: ["mcp", "cli"],
15338
15375
  toolset: "loop",
@@ -16751,7 +16788,7 @@ var ACTIONS = ACTION_DEFINITIONS.map((action) => {
16751
16788
  });
16752
16789
 
16753
16790
  // ../actions/src/retention-entry.ts
16754
- var ARBOR_VERSION = "0.24.31";
16791
+ var ARBOR_VERSION = "0.24.33";
16755
16792
  var historyPolicySchema = _enum(["full", "checkpoint"]).optional().describe("text Artifacts: future history retention policy; full retains every source snapshot, checkpoint keeps metadata for every revision and source only when explicitly retained");
16756
16793
  var retainSourceSchema = boolean2().optional().describe("edit: in checkpoint mode, retain this newly minted content revision's source");
16757
16794
  var checkpointSchema = boolean2().optional().describe("edit: retain the exact current version source without incrementing content version");
@@ -17159,7 +17196,7 @@ function dispositionFilename(value) {
17159
17196
  // package.json
17160
17197
  var package_default = {
17161
17198
  name: "@lambdacurry/arbor",
17162
- version: "0.24.31",
17199
+ version: "0.24.33",
17163
17200
  description: "The Arbor CLI — a shared workspace for people and agents. The human + headless-agent write path over Arbor's guarded operation surface.",
17164
17201
  keywords: [
17165
17202
  "agents",
@@ -17595,6 +17632,35 @@ async function waitForComputerExec(executor, input, value, ctx) {
17595
17632
  return withContinuation(latest);
17596
17633
  }
17597
17634
 
17635
+ // src/computer-run-start-wait.ts
17636
+ init_errors();
17637
+ var DEFAULT_SETUP_RETRY_AFTER_MS = 30000;
17638
+ function isSetupPendingRefusal(details) {
17639
+ return details?.retryable === true && details.suggestedAction === "computer_run_start" && details.phase === "computer.open";
17640
+ }
17641
+ function sleep2(ms) {
17642
+ return new Promise((resolve) => setTimeout(resolve, ms));
17643
+ }
17644
+ async function startComputerRunWithWait(action, executor, input, waitMs, ctx) {
17645
+ const deadline = Date.now() + waitMs;
17646
+ for (;; ) {
17647
+ try {
17648
+ return await action.run(executor, input);
17649
+ } catch (err) {
17650
+ const details = detailsForError(err);
17651
+ if (!isSetupPendingRefusal(details))
17652
+ throw err;
17653
+ const delay = typeof details?.retryAfterMs === "number" && details.retryAfterMs > 0 ? details.retryAfterMs : DEFAULT_SETUP_RETRY_AFTER_MS;
17654
+ const remaining = deadline - Date.now();
17655
+ if (delay > remaining)
17656
+ throw err;
17657
+ advise(`Computer setup is still running; retrying computer_run_start with the same idempotencyKey in ${Math.round(delay / 1000)}s (${Math.round(remaining / 1000)}s of --wait-ms left).
17658
+ `, ctx);
17659
+ await sleep2(delay);
17660
+ }
17661
+ }
17662
+ }
17663
+
17598
17664
  // src/commands.ts
17599
17665
  init_errors();
17600
17666
 
@@ -18041,7 +18107,7 @@ var KNOWN_MISREACHES = [
18041
18107
  {
18042
18108
  tried: "search",
18043
18109
  use: "recall <query>",
18044
- why: "recall searches what was said; `quick search <name>` jumps to a place or person by name"
18110
+ why: 'recall searches what was said; `tree --query "360training / Delivery & Reviews"` jumps to a known name'
18045
18111
  },
18046
18112
  { tried: "update topic", use: "topic update --topic-id top_…", why: "this command is noun-verb" },
18047
18113
  { tried: "update space", use: "space update --space-id spc_…", why: "this command is noun-verb" },
@@ -18165,7 +18231,7 @@ Aliases: ${aliases.map((alias) => `\`arbor ${alias}\``).join(", ")} (tool-name f
18165
18231
  const heading = `${commandWords(action)}${aliases.length === 1 ? ` (alias: ${aliases[0]})` : aliases.length > 1 ? ` (aliases: ${aliases.join(", ")})` : ""}`;
18166
18232
  const waitLines = action.name === "computer_exec" ? [
18167
18233
  " --no-wait [<boolean>] [optional] — return a running processId immediately instead of polling computer_process_read (implied by --background); mutually exclusive with --wait-for-terminal-ms"
18168
- ] : [];
18234
+ ] : action.name === "computer_run_start" ? [` --wait-ms <number> [optional] — ${WAIT_MS_DESCRIPTION}`] : [];
18169
18235
  const notes = action.cliHelpNotes ? `
18170
18236
 
18171
18237
  ${action.cliHelpNotes}` : "";
@@ -18178,9 +18244,10 @@ ${lines.join(`
18178
18244
  `)}${primaryNote}${explicitNote}${aliasNote}${notes}
18179
18245
  `;
18180
18246
  }
18247
+ const waitUsage = action.name === "computer_exec" ? " [--no-wait]" : action.name === "computer_run_start" ? " [--wait-ms <number>]" : "";
18181
18248
  return `${heading} — ${action.description}
18182
18249
 
18183
- ${usage}${waitLines.length > 0 ? " [--no-wait]" : ""}
18250
+ ${usage}${waitUsage}
18184
18251
 
18185
18252
  ${[...lines, ...waitLines].join(`
18186
18253
  `)}${primaryNote}${explicitNote}${aliasNote}${notes}
@@ -18207,6 +18274,15 @@ function commandCatalog(actions = CLI_ACTIONS) {
18207
18274
  acceptsFile: false,
18208
18275
  description: "return a running processId immediately instead of polling computer_process_read; mutually exclusive with --wait-for-terminal-ms"
18209
18276
  }
18277
+ ] : [],
18278
+ ...action.name === "computer_run_start" ? [
18279
+ {
18280
+ flag: "wait-ms",
18281
+ kind: "number",
18282
+ required: false,
18283
+ acceptsFile: false,
18284
+ description: WAIT_MS_DESCRIPTION
18285
+ }
18210
18286
  ] : []
18211
18287
  ]
18212
18288
  }));
@@ -18250,6 +18326,20 @@ function resolveCommand(positionals, flags) {
18250
18326
  const bareFeedbackTree = normalized.length === 1 && normalized[0] === "feedback" && !explicitFeedbackInput;
18251
18327
  return bareFeedbackTree ? { action: CLI_ACTIONS.find((action) => action.name === "feedback_tree"), words: 1 } : matchCommand(positionals);
18252
18328
  }
18329
+ var WAIT_MS_DESCRIPTION = "CLI-only: while Arbor answers that Computer setup is still running (retryable, suggestedAction computer_run_start), wait each retryAfterMs and repeat this exact start with the same idempotencyKey for up to this many milliseconds; a terminal setup failure or any other error returns at once. Omit for one attempt";
18330
+ function parseWaitMs(value) {
18331
+ const raw = stringFlag(value, "wait-ms");
18332
+ if (raw === undefined)
18333
+ return;
18334
+ const ms = Number(raw);
18335
+ if (!Number.isInteger(ms) || ms < 0) {
18336
+ throw new UsageError(`--wait-ms expects a non-negative whole number of milliseconds, got "${raw}"`, {
18337
+ reason: "validation.invalid_value",
18338
+ field: "waitMs"
18339
+ });
18340
+ }
18341
+ return ms;
18342
+ }
18253
18343
  async function runObjectVerb(positionals, flags, ctx) {
18254
18344
  const match = resolveCommand(positionals, flags);
18255
18345
  if (!match) {
@@ -18260,6 +18350,8 @@ async function runObjectVerb(positionals, flags, ctx) {
18260
18350
  const action = { ...match.action, inputSchema: canonicalInputShape(match.action) };
18261
18351
  applyPositionals(action, positionals.slice(words), flags);
18262
18352
  const accepted = acceptedFlags(action.inputSchema);
18353
+ if (action.name === "computer_run_start")
18354
+ accepted.add("wait-ms");
18263
18355
  const unknown = Object.keys(flags).filter((f) => !accepted.has(f) && !RESERVED_FLAGS.has(f));
18264
18356
  if (unknown.length > 0) {
18265
18357
  const hints = unknown.map((f) => listFileHint(action, f)).filter(Boolean);
@@ -18289,6 +18381,7 @@ async function runObjectVerb(positionals, flags, ctx) {
18289
18381
  field: "waitForTerminalMs"
18290
18382
  });
18291
18383
  }
18384
+ const waitMs = action.name === "computer_run_start" ? parseWaitMs(flags["wait-ms"]) : undefined;
18292
18385
  if (action.name === "app_request" && input.json !== undefined) {
18293
18386
  if (input.body !== undefined) {
18294
18387
  throw new UsageError("--json and --body are mutually exclusive", {
@@ -18309,7 +18402,7 @@ async function runObjectVerb(positionals, flags, ctx) {
18309
18402
  advise(`Agent registered. Save its apiKey now — it is shown only once:
18310
18403
  `, ctx);
18311
18404
  }
18312
- let value = action.name === "attachment_upload" ? await uploadLocalAttachments(httpExecutor, input) : await action.run(httpExecutor, input);
18405
+ let value = action.name === "attachment_upload" ? await uploadLocalAttachments(httpExecutor, input) : waitMs !== undefined ? await startComputerRunWithWait(action, httpExecutor, input, waitMs, ctx) : await action.run(httpExecutor, input);
18313
18406
  if (action.name === "computer_exec" && !truthyFlag(flags["no-wait"]) && input.waitForTerminalMs === undefined && input.background !== true) {
18314
18407
  value = await waitForComputerExec(httpExecutor, input, value, ctx);
18315
18408
  }
@@ -18322,7 +18415,7 @@ init_config();
18322
18415
  // src/connect.ts
18323
18416
  init_config();
18324
18417
  var DEFAULT_POLL_INTERVAL_MS = 2000;
18325
- var sleep2 = (ms) => new Promise((r) => globalThis.setTimeout(r, ms));
18418
+ var sleep3 = (ms) => new Promise((r) => globalThis.setTimeout(r, ms));
18326
18419
  function bareConnectUrl(apiUrl, returnedUrl) {
18327
18420
  const fallback = `${apiUrl.replace(/\/$/, "")}/connect`;
18328
18421
  if (!returnedUrl)
@@ -18380,7 +18473,7 @@ async function connect2(opts) {
18380
18473
  if (typeof globalThis.setTimeout !== "function") {
18381
18474
  throw new Error("approval pending — approve it, then rerun the same `arbor connect` command to finish saving this agent's credential");
18382
18475
  }
18383
- await sleep2(pollIntervalMs);
18476
+ await sleep3(pollIntervalMs);
18384
18477
  continue;
18385
18478
  }
18386
18479
  if (tok.status === "expired")
@@ -18400,7 +18493,7 @@ init_errors();
18400
18493
  init_config();
18401
18494
  var CLIENT_ID = "arbor-cli";
18402
18495
  var GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
18403
- var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
18496
+ var sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
18404
18497
  async function login(opts) {
18405
18498
  const apiUrl = opts.url ?? loadConfig().apiUrl;
18406
18499
  const codeRes = await fetch(`${apiUrl}/api/auth/device/code`, {
@@ -18425,7 +18518,7 @@ async function login(opts) {
18425
18518
  for (;; ) {
18426
18519
  if (Date.now() > deadline)
18427
18520
  throw new Error("login timed out — run `arbor login` again");
18428
- await sleep3(interval);
18521
+ await sleep4(interval);
18429
18522
  const tokRes = await fetch(`${apiUrl}/api/auth/device/token`, {
18430
18523
  method: "POST",
18431
18524
  headers: { "content-type": "application/json" },
@@ -18545,7 +18638,7 @@ function renderVersion(ctx) {
18545
18638
  }
18546
18639
  var HELP = `arbor — the Arbor CLI: people and agents deliberate on typed work, and Arbor remembers
18547
18640
  what's settled. Your token (env ARBOR_TOKEN, or \`arbor auth\`) resolves who you act AS — the CLI
18548
- never asserts it. Run \`arbor whoami\` to confirm, \`arbor tree\` to map the workspace.
18641
+ never asserts it. Run \`arbor whoami\` to confirm, \`arbor tree --query "360training / Delivery & Reviews"\` to jump to a known place, \`arbor tree\` to map the workspace, \`arbor recall\` for content.
18549
18642
 
18550
18643
  ${ORIENTATION}
18551
18644
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdacurry/arbor",
3
- "version": "0.24.31",
3
+ "version": "0.24.33",
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
  "keywords": [
6
6
  "agents",