@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.
- package/dist/arbor.js +174 -171
- 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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
17145
|
-
|
|
17146
|
-
|
|
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