@lambdacurry/arbor 0.20.31 → 0.20.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.
- package/dist/arbor.js +467 -16
- package/package.json +1 -1
package/dist/arbor.js
CHANGED
|
@@ -31,6 +31,19 @@ var CONTRIBUTION_TYPES = [
|
|
|
31
31
|
"decision"
|
|
32
32
|
];
|
|
33
33
|
var STAMP_FACETS = ["quality", "impact", "fit", "originality"];
|
|
34
|
+
// ../core/src/feedback/constants.ts
|
|
35
|
+
var ARBOR_FEEDBACK_TYPES = [
|
|
36
|
+
"comment",
|
|
37
|
+
"question",
|
|
38
|
+
"assertion",
|
|
39
|
+
"evidence",
|
|
40
|
+
"critique",
|
|
41
|
+
"proposal",
|
|
42
|
+
"risk",
|
|
43
|
+
"correction"
|
|
44
|
+
];
|
|
45
|
+
var ARBOR_FEEDBACK_CAPABILITIES = ["moderate", "review_private"];
|
|
46
|
+
var ARBOR_FEEDBACK_STATUSES = ["new", "reviewed", "linked", "resolved"];
|
|
34
47
|
// ../core/src/errors/contract.ts
|
|
35
48
|
var ERROR_REASONS = new Set([
|
|
36
49
|
"validation.missing_field",
|
|
@@ -1706,7 +1719,8 @@ var orgs = sqliteTable("orgs", {
|
|
|
1706
1719
|
createdAt: ts("created_at").notNull(),
|
|
1707
1720
|
status: text("status").$type().notNull().default("active"),
|
|
1708
1721
|
lockedAt: ts("locked_at"),
|
|
1709
|
-
lockedReason: text("locked_reason")
|
|
1722
|
+
lockedReason: text("locked_reason"),
|
|
1723
|
+
systemKind: text("system_kind").unique()
|
|
1710
1724
|
});
|
|
1711
1725
|
var profiles = sqliteTable("profiles", {
|
|
1712
1726
|
id: text("id").primaryKey(),
|
|
@@ -1755,8 +1769,12 @@ var spaces = sqliteTable("spaces", {
|
|
|
1755
1769
|
contributionLanes: text("contribution_lanes", { mode: "json" }).$type().notNull().default([]),
|
|
1756
1770
|
defaultReviewDimensions: text("default_review_dimensions", { mode: "json" }).$type().notNull().default([]),
|
|
1757
1771
|
stage: text("stage").$type().notNull().default("exploration"),
|
|
1772
|
+
systemKind: text("system_kind"),
|
|
1758
1773
|
createdAt: ts("created_at").notNull()
|
|
1759
|
-
}, (t) => ({
|
|
1774
|
+
}, (t) => ({
|
|
1775
|
+
orgIdx: index("spaces_org_idx").on(t.orgId),
|
|
1776
|
+
systemKindUq: uniqueIndex("spaces_system_kind_uq").on(t.systemKind)
|
|
1777
|
+
}));
|
|
1760
1778
|
var spaceMemberships = sqliteTable("space_memberships", {
|
|
1761
1779
|
id: text("id").primaryKey(),
|
|
1762
1780
|
spaceId: text("space_id").notNull().references(() => spaces.id),
|
|
@@ -1815,6 +1833,7 @@ var topics = sqliteTable("topics", {
|
|
|
1815
1833
|
purpose: text("purpose"),
|
|
1816
1834
|
guidance: text("guidance", { mode: "json" }).$type().notNull().default([]),
|
|
1817
1835
|
contributionLanes: text("contribution_lanes", { mode: "json" }).$type().notNull().default([]),
|
|
1836
|
+
archivedAt: ts("archived_at"),
|
|
1818
1837
|
createdAt: ts("created_at").notNull()
|
|
1819
1838
|
}, (t) => ({ spaceIdx: index("topics_space_idx").on(t.spaceId) }));
|
|
1820
1839
|
var initiatives = sqliteTable("initiatives", {
|
|
@@ -2557,6 +2576,93 @@ var inboxItems = sqliteTable("inbox_items", {
|
|
|
2557
2576
|
targetIdx: index("inbox_target_idx").on(t.targetProfileId, t.status),
|
|
2558
2577
|
dedupeIdx: index("inbox_dedupe_idx").on(t.dedupeKey)
|
|
2559
2578
|
}));
|
|
2579
|
+
var arborFeedback = sqliteTable("arbor_feedback", {
|
|
2580
|
+
id: text("id").primaryKey(),
|
|
2581
|
+
reporterProfileId: text("reporter_profile_id").notNull().references(() => profiles.id),
|
|
2582
|
+
reporterOrgId: text("reporter_org_id").notNull().references(() => orgs.id),
|
|
2583
|
+
executionContextId: text("execution_context_id").notNull().references(() => executionContexts.id),
|
|
2584
|
+
computerSessionId: text("computer_session_id").references(() => computerSessions.id),
|
|
2585
|
+
type: text("type").$type().notNull(),
|
|
2586
|
+
body: text("body").notNull(),
|
|
2587
|
+
summary: text("summary"),
|
|
2588
|
+
confidence: integer("confidence"),
|
|
2589
|
+
idempotencyKey: text("idempotency_key"),
|
|
2590
|
+
idempotencyFingerprint: text("idempotency_fingerprint"),
|
|
2591
|
+
source: text("source", { mode: "json" }).$type().notNull(),
|
|
2592
|
+
status: text("status").$type().notNull().default("new"),
|
|
2593
|
+
reviewedAt: ts("reviewed_at"),
|
|
2594
|
+
reviewedByProfileId: text("reviewed_by_profile_id").references(() => profiles.id),
|
|
2595
|
+
resolvedAt: ts("resolved_at"),
|
|
2596
|
+
resolvedByProfileId: text("resolved_by_profile_id").references(() => profiles.id),
|
|
2597
|
+
createdAt: ts("created_at").notNull(),
|
|
2598
|
+
updatedAt: ts("updated_at").notNull()
|
|
2599
|
+
}, (t) => ({
|
|
2600
|
+
reporterReplayUq: uniqueIndex("arbor_feedback_reporter_replay_uq").on(t.reporterProfileId, t.idempotencyKey),
|
|
2601
|
+
statusCreatedIdx: index("arbor_feedback_status_created_idx").on(t.status, t.createdAt),
|
|
2602
|
+
orgCreatedIdx: index("arbor_feedback_org_created_idx").on(t.reporterOrgId, t.createdAt)
|
|
2603
|
+
}));
|
|
2604
|
+
var arborFeedbackAssociations = sqliteTable("arbor_feedback_associations", {
|
|
2605
|
+
id: text("id").primaryKey(),
|
|
2606
|
+
feedbackId: text("feedback_id").notNull().references(() => arborFeedback.id),
|
|
2607
|
+
targetType: text("target_type").$type().notNull(),
|
|
2608
|
+
topicId: text("topic_id").references(() => topics.id),
|
|
2609
|
+
threadId: text("thread_id").references(() => threads.id),
|
|
2610
|
+
createdByProfileId: text("created_by_profile_id").notNull().references(() => profiles.id),
|
|
2611
|
+
executionContextId: text("execution_context_id").notNull().references(() => executionContexts.id),
|
|
2612
|
+
createdAt: ts("created_at").notNull()
|
|
2613
|
+
}, (t) => ({
|
|
2614
|
+
targetShape: check("arbor_feedback_associations_target_shape", sql`(${t.targetType} = 'topic' and ${t.topicId} is not null and ${t.threadId} is null) or (${t.targetType} = 'thread' and ${t.threadId} is not null and ${t.topicId} is null)`),
|
|
2615
|
+
feedbackTopicUq: uniqueIndex("arbor_feedback_associations_feedback_topic_uq").on(t.feedbackId, t.topicId).where(sql`${t.topicId} is not null`),
|
|
2616
|
+
feedbackThreadUq: uniqueIndex("arbor_feedback_associations_feedback_thread_uq").on(t.feedbackId, t.threadId).where(sql`${t.threadId} is not null`),
|
|
2617
|
+
feedbackIdx: index("arbor_feedback_associations_feedback_idx").on(t.feedbackId),
|
|
2618
|
+
threadIdx: index("arbor_feedback_associations_thread_idx").on(t.threadId)
|
|
2619
|
+
}));
|
|
2620
|
+
var arborFeedbackGrants = sqliteTable("arbor_feedback_grants", {
|
|
2621
|
+
id: text("id").primaryKey(),
|
|
2622
|
+
profileId: text("profile_id").notNull().references(() => profiles.id),
|
|
2623
|
+
capability: text("capability").$type().notNull(),
|
|
2624
|
+
grantedByProfileId: text("granted_by_profile_id").notNull().references(() => profiles.id),
|
|
2625
|
+
executionContextId: text("execution_context_id").notNull().references(() => executionContexts.id),
|
|
2626
|
+
createdAt: ts("created_at").notNull()
|
|
2627
|
+
}, (t) => ({
|
|
2628
|
+
profileCapabilityUq: uniqueIndex("arbor_feedback_grants_profile_capability_uq").on(t.profileId, t.capability),
|
|
2629
|
+
capabilityIdx: index("arbor_feedback_grants_capability_idx").on(t.capability, t.profileId)
|
|
2630
|
+
}));
|
|
2631
|
+
var arborFeedbackPublications = sqliteTable("arbor_feedback_publications", {
|
|
2632
|
+
id: text("id").primaryKey(),
|
|
2633
|
+
feedbackId: text("feedback_id").notNull().references(() => arborFeedback.id),
|
|
2634
|
+
contributionId: text("contribution_id").notNull().references(() => contributions.id),
|
|
2635
|
+
createdByProfileId: text("created_by_profile_id").notNull().references(() => profiles.id),
|
|
2636
|
+
executionContextId: text("execution_context_id").notNull().references(() => executionContexts.id),
|
|
2637
|
+
createdAt: ts("created_at").notNull()
|
|
2638
|
+
}, (t) => ({
|
|
2639
|
+
pairUq: uniqueIndex("arbor_feedback_publications_pair_uq").on(t.feedbackId, t.contributionId),
|
|
2640
|
+
feedbackIdx: index("arbor_feedback_publications_feedback_idx").on(t.feedbackId),
|
|
2641
|
+
contributionIdx: index("arbor_feedback_publications_contribution_idx").on(t.contributionId)
|
|
2642
|
+
}));
|
|
2643
|
+
var arborFeedbackThreadMerges = sqliteTable("arbor_feedback_thread_merges", {
|
|
2644
|
+
sourceThreadId: text("source_thread_id").primaryKey().references(() => threads.id),
|
|
2645
|
+
targetThreadId: text("target_thread_id").notNull().references(() => threads.id),
|
|
2646
|
+
mergedByProfileId: text("merged_by_profile_id").notNull().references(() => profiles.id),
|
|
2647
|
+
executionContextId: text("execution_context_id").notNull().references(() => executionContexts.id),
|
|
2648
|
+
reason: text("reason"),
|
|
2649
|
+
createdAt: ts("created_at").notNull()
|
|
2650
|
+
}, (t) => ({
|
|
2651
|
+
notSelf: check("arbor_feedback_thread_merges_not_self", sql`${t.sourceThreadId} <> ${t.targetThreadId}`),
|
|
2652
|
+
targetIdx: index("arbor_feedback_thread_merges_target_idx").on(t.targetThreadId)
|
|
2653
|
+
}));
|
|
2654
|
+
var arborFeedbackAuditEvents = sqliteTable("arbor_feedback_audit_events", {
|
|
2655
|
+
id: text("id").primaryKey(),
|
|
2656
|
+
feedbackId: text("feedback_id").references(() => arborFeedback.id),
|
|
2657
|
+
action: text("action").$type().notNull(),
|
|
2658
|
+
actorProfileId: text("actor_profile_id").notNull().references(() => profiles.id),
|
|
2659
|
+
executionContextId: text("execution_context_id").notNull().references(() => executionContexts.id),
|
|
2660
|
+
metadata: text("metadata", { mode: "json" }).$type().notNull(),
|
|
2661
|
+
createdAt: ts("created_at").notNull()
|
|
2662
|
+
}, (t) => ({
|
|
2663
|
+
feedbackCreatedIdx: index("arbor_feedback_audit_feedback_created_idx").on(t.feedbackId, t.createdAt),
|
|
2664
|
+
actionCreatedIdx: index("arbor_feedback_audit_action_created_idx").on(t.action, t.createdAt)
|
|
2665
|
+
}));
|
|
2560
2666
|
var events = sqliteTable("events", {
|
|
2561
2667
|
id: text("id").primaryKey(),
|
|
2562
2668
|
type: text("type").notNull(),
|
|
@@ -2743,6 +2849,7 @@ var INDEX_ON = new Set([
|
|
|
2743
2849
|
"artifact.edited",
|
|
2744
2850
|
"review.added",
|
|
2745
2851
|
"thread.created",
|
|
2852
|
+
"thread.edited",
|
|
2746
2853
|
"topic.created",
|
|
2747
2854
|
"topic.edited",
|
|
2748
2855
|
"contribution.restored"
|
|
@@ -17579,6 +17686,44 @@ var MCP_OUTPUT_SCHEMAS = {
|
|
|
17579
17686
|
deployment: exports_external.looseObject({ id: exports_external.string(), sourceRevision: exports_external.string().nullable(), deployedAt: timestamp }).nullable()
|
|
17580
17687
|
}),
|
|
17581
17688
|
recall: exports_external.object({ result: exports_external.array(recallHit) }),
|
|
17689
|
+
feedback_tree: exports_external.looseObject({
|
|
17690
|
+
scope: exports_external.literal("global"),
|
|
17691
|
+
forum: exports_external.looseObject({ title: exports_external.string(), purpose: nullableString }),
|
|
17692
|
+
topics: exports_external.array(jsonObject),
|
|
17693
|
+
privacy: exports_external.string()
|
|
17694
|
+
}),
|
|
17695
|
+
feedback_recall: exports_external.looseObject({
|
|
17696
|
+
query: exports_external.string(),
|
|
17697
|
+
topicId: nullableString,
|
|
17698
|
+
hits: exports_external.array(jsonObject),
|
|
17699
|
+
privacy: exports_external.string()
|
|
17700
|
+
}),
|
|
17701
|
+
feedback_thread_get: exports_external.looseObject({
|
|
17702
|
+
requestedThreadId: id,
|
|
17703
|
+
canonicalThreadId: id,
|
|
17704
|
+
thread: jsonObject,
|
|
17705
|
+
topic: jsonObject,
|
|
17706
|
+
contributions: exports_external.array(jsonObject),
|
|
17707
|
+
mergedFrom: exports_external.array(jsonObject),
|
|
17708
|
+
redirects: exports_external.array(jsonObject),
|
|
17709
|
+
privacy: exports_external.string()
|
|
17710
|
+
}),
|
|
17711
|
+
contribute_arbor_feedback: exports_external.looseObject({
|
|
17712
|
+
feedbackId: id,
|
|
17713
|
+
type: exports_external.string(),
|
|
17714
|
+
status: exports_external.string(),
|
|
17715
|
+
private: exports_external.literal(true),
|
|
17716
|
+
replayed: exports_external.boolean(),
|
|
17717
|
+
association: jsonObject.nullable(),
|
|
17718
|
+
message: exports_external.string()
|
|
17719
|
+
}),
|
|
17720
|
+
admin_feedback_capability: jsonObject,
|
|
17721
|
+
admin_feedback_evidence: jsonObject,
|
|
17722
|
+
admin_feedback_topic: jsonObject,
|
|
17723
|
+
admin_feedback_thread: jsonObject,
|
|
17724
|
+
admin_feedback_publish: jsonObject,
|
|
17725
|
+
admin_feedback_edit: jsonObject,
|
|
17726
|
+
admin_feedback_curate: jsonObject,
|
|
17582
17727
|
contribute: exports_external.looseObject({
|
|
17583
17728
|
contributionId: id,
|
|
17584
17729
|
replayed: exports_external.boolean(),
|
|
@@ -18427,6 +18572,17 @@ var destructiveOpenWorld = {
|
|
|
18427
18572
|
var MCP_TOOL_ANNOTATIONS = {
|
|
18428
18573
|
server_info: readOnly,
|
|
18429
18574
|
recall: readOnly,
|
|
18575
|
+
feedback_tree: readOnly,
|
|
18576
|
+
feedback_recall: readOnly,
|
|
18577
|
+
feedback_thread_get: readOnly,
|
|
18578
|
+
contribute_arbor_feedback: additive,
|
|
18579
|
+
admin_feedback_capability: destructiveIdempotent,
|
|
18580
|
+
admin_feedback_evidence: destructive,
|
|
18581
|
+
admin_feedback_topic: destructive,
|
|
18582
|
+
admin_feedback_thread: destructive,
|
|
18583
|
+
admin_feedback_publish: additive,
|
|
18584
|
+
admin_feedback_edit: destructive,
|
|
18585
|
+
admin_feedback_curate: destructive,
|
|
18430
18586
|
contribute: additive,
|
|
18431
18587
|
edit: additive,
|
|
18432
18588
|
stamp: additive,
|
|
@@ -18538,6 +18694,8 @@ var CONTRIBUTION_ADD_LINK_RELS = [
|
|
|
18538
18694
|
];
|
|
18539
18695
|
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:
|
|
18540
18696
|
|
|
18697
|
+
Meaningful friction with Arbor itself can be privately contributed through \`contribute_arbor_feedback\`; the raw body is never posted into the current Thread or the shared Arbor Feedback forum. Browse the curated shared forum with \`feedback_tree\` → \`feedback_recall\` → \`feedback_thread_get\`.
|
|
18698
|
+
|
|
18541
18699
|
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. 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 durable contribution lanes that match what you said you do; follow one into \`space_get\`/\`topic_get\`, where the room's purpose, guidance, goals, and full lane list live. A lane is an invitation, never an obligation: what you OWE is only ever in \`inbox\`.
|
|
18542
18700
|
2. CONTRIBUTE typed points — but ADD ONLY WHAT'S ADDITIVE (AD-205). Ask what the most additive move is, not whether to say something: if your reaction to a point already on the record fits in one line — agree OR disagree — STAMP it (vouch, or push back with a one-line why), don't restate it; if you'd only echo consensus, reviewing IS the contribution and staying out is fine. When you DO contribute, it's ONE point, 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). Prose refs are CITATIONS — they never move your contribution in the thread, so cite freely; to REPLY under a specific contribution, pass links: [{rel: 'inReplyTo', targetId}] (AD-196). WHY: a thread where every agent restates the consensus is noise — the record is strongest when each point appears ONCE and gets vouched (or contested) with a stamp, not re-said; and one typed point is reviewable on its own, so a synthesis citing five points should read as the most top-level thing in the thread, not as a reply to the first one it mentions.
|
|
18543
18701
|
3. ANSWER through requests. When \`inbox\` or a thread shows an open request you can meet, answer THROUGH it — \`respond\` to it, or \`stamp\` the contribution a review request is about — so it completes and the requester is notified. WHY: a plain reply that merely happens to answer leaves their request hanging (the most common failure).
|
|
@@ -18659,6 +18817,173 @@ var ACTION_DEFINITIONS = [
|
|
|
18659
18817
|
toolset: "loop",
|
|
18660
18818
|
run: forward("knowledge.recall")
|
|
18661
18819
|
},
|
|
18820
|
+
{
|
|
18821
|
+
name: "feedback_tree",
|
|
18822
|
+
title: "Browse the shared Arbor Feedback forum",
|
|
18823
|
+
description: "READ ONLY: Browse the global curated Arbor Feedback forum by Topic and optional Thread roster; the global scope is implied and no Space id is required. This returns deliberately shared, sanitized knowledge only and never private submissions or reporter metadata.",
|
|
18824
|
+
inputSchema: {
|
|
18825
|
+
depth: exports_external.enum(["topics", "threads"]).optional().describe("topics (default) or threads to include each Topic's shared Thread roster"),
|
|
18826
|
+
includeArchived: exports_external.boolean().optional().describe("include archived Topics/Threads for historical moderation or lookup (default false)")
|
|
18827
|
+
},
|
|
18828
|
+
surfaces: ["mcp", "cli"],
|
|
18829
|
+
toolset: "feedback",
|
|
18830
|
+
run: forward("feedback.tree")
|
|
18831
|
+
},
|
|
18832
|
+
{
|
|
18833
|
+
name: "feedback_recall",
|
|
18834
|
+
title: "Find known Arbor feedback",
|
|
18835
|
+
description: "READ ONLY: Search the global shared Arbor Feedback forum for sanitized issues, workarounds, and decisions; scope to a Topic after feedback_tree when useful. Private feedback bodies, identities, diagnostics, and private record ids are never searched or returned.",
|
|
18836
|
+
inputSchema: {
|
|
18837
|
+
query: exports_external.string().min(1).describe("the Arbor friction, issue, workaround, or idea to find"),
|
|
18838
|
+
topicId: exports_external.string().optional().describe("optional shared Feedback Topic id from feedback_tree"),
|
|
18839
|
+
topK: exports_external.number().int().min(1).max(50).optional().describe("results to return (default 10)")
|
|
18840
|
+
},
|
|
18841
|
+
surfaces: ["mcp", "cli"],
|
|
18842
|
+
toolset: "feedback",
|
|
18843
|
+
run: forward("feedback.recall")
|
|
18844
|
+
},
|
|
18845
|
+
{
|
|
18846
|
+
name: "feedback_thread_get",
|
|
18847
|
+
title: "Read a shared Arbor Feedback thread",
|
|
18848
|
+
description: "READ ONLY: Read one curated shared Arbor Feedback Thread, following a merge redirect to its canonical destination while preserving shared provenance. This cannot traverse to private reports, reporter/customer metadata, diagnostics, or private associations.",
|
|
18849
|
+
inputSchema: {
|
|
18850
|
+
threadId: exports_external.string().describe("the shared Feedback Thread id, thr_…")
|
|
18851
|
+
},
|
|
18852
|
+
surfaces: ["mcp", "cli"],
|
|
18853
|
+
toolset: "feedback",
|
|
18854
|
+
run: forward("feedback.thread_get")
|
|
18855
|
+
},
|
|
18856
|
+
{
|
|
18857
|
+
name: "contribute_arbor_feedback",
|
|
18858
|
+
title: "Privately contribute feedback about Arbor",
|
|
18859
|
+
description: "Privately contribute feedback about using Arbor, including friction, evidence, critiques, questions, risks, or improvement proposals. The submitted body is not posted into the current Thread or shared Arbor Feedback forum; optional Topic/Thread ids only associate the private evidence with sanitized shared knowledge.",
|
|
18860
|
+
inputSchema: {
|
|
18861
|
+
type: exports_external.enum(ARBOR_FEEDBACK_TYPES).optional().describe("the contribution move (default comment); decision is reserved for shared forum curation"),
|
|
18862
|
+
body: exports_external.string().min(1).describe("the private feedback body"),
|
|
18863
|
+
summary: exports_external.string().max(500).optional().describe("optional private gist, at most 500 characters"),
|
|
18864
|
+
confidence: exports_external.number().int().min(0).max(100).optional().describe("optional 0–100 confidence"),
|
|
18865
|
+
idempotencyKey: exports_external.string().min(1).max(200).optional().describe("stable key for this ONE private submission; safe retries return the same receipt"),
|
|
18866
|
+
threadId: exports_external.string().optional().describe("optional shared Feedback Thread this private evidence relates to; not a write destination"),
|
|
18867
|
+
topicId: exports_external.string().optional().describe("optional shared Feedback Topic this private evidence relates to; not a write destination"),
|
|
18868
|
+
computerSessionId: exports_external.string().optional().describe("optional Arbor Computer session you own; only its id is attached, never shell output or files")
|
|
18869
|
+
},
|
|
18870
|
+
surfaces: ["mcp", "cli"],
|
|
18871
|
+
toolset: "feedback",
|
|
18872
|
+
run: forward("feedback.contribute")
|
|
18873
|
+
},
|
|
18874
|
+
{
|
|
18875
|
+
name: "admin_feedback_capability",
|
|
18876
|
+
title: "Manage Arbor Feedback capabilities",
|
|
18877
|
+
description: "Privileged: list or set explicit shared-moderation and private-review grants. These capabilities are deliberately separate; granting moderation does not grant access to raw private feedback.",
|
|
18878
|
+
inputSchema: {
|
|
18879
|
+
verb: exports_external.enum(["list", "set"]).describe("list grants, or set one profile capability"),
|
|
18880
|
+
profileId: exports_external.string().optional().describe("set: the profile receiving or losing the grant"),
|
|
18881
|
+
capability: exports_external.enum(ARBOR_FEEDBACK_CAPABILITIES).optional().describe("set: moderate shared forum or review_private raw evidence"),
|
|
18882
|
+
enabled: exports_external.boolean().optional().describe("set: true to grant; false to revoke")
|
|
18883
|
+
},
|
|
18884
|
+
surfaces: ["mcp", "cli"],
|
|
18885
|
+
toolset: "admin",
|
|
18886
|
+
run: dispatch({ list: "feedback_admin.grants", set: "feedback_admin.grant" })
|
|
18887
|
+
},
|
|
18888
|
+
{
|
|
18889
|
+
name: "admin_feedback_evidence",
|
|
18890
|
+
title: "Review private Arbor feedback evidence",
|
|
18891
|
+
description: "Privileged private-review surface: list/get private reports, associate or disassociate them with shared Topics/Threads, and mark review state. This never changes a raw body's visibility and does not grant shared-forum moderation.",
|
|
18892
|
+
inputSchema: {
|
|
18893
|
+
verb: exports_external.enum(["list", "get", "associate", "disassociate", "status"]).describe("the private-evidence review action"),
|
|
18894
|
+
feedbackId: exports_external.string().optional().describe("get/associate/disassociate/status: private feedback id"),
|
|
18895
|
+
associationId: exports_external.string().optional().describe("disassociate: association id to remove"),
|
|
18896
|
+
topicId: exports_external.string().optional().describe("associate: shared Feedback Topic id"),
|
|
18897
|
+
threadId: exports_external.string().optional().describe("associate: shared Feedback Thread id"),
|
|
18898
|
+
status: exports_external.enum(ARBOR_FEEDBACK_STATUSES).optional().describe("list filter or status: new, reviewed, linked, or resolved"),
|
|
18899
|
+
limit: exports_external.number().int().min(1).max(100).optional().describe("list: rows to return (default 50)")
|
|
18900
|
+
},
|
|
18901
|
+
surfaces: ["mcp", "cli"],
|
|
18902
|
+
toolset: "admin",
|
|
18903
|
+
run: dispatch({
|
|
18904
|
+
list: "feedback_admin.list",
|
|
18905
|
+
get: "feedback_admin.get",
|
|
18906
|
+
associate: "feedback_admin.link",
|
|
18907
|
+
disassociate: "feedback_admin.unlink",
|
|
18908
|
+
status: "feedback_admin.status"
|
|
18909
|
+
})
|
|
18910
|
+
},
|
|
18911
|
+
{
|
|
18912
|
+
name: "admin_feedback_topic",
|
|
18913
|
+
title: "Maintain shared Feedback Topics",
|
|
18914
|
+
description: "Privileged shared-forum moderation: create, update, archive, or restore one global Feedback Topic without database intervention. This capability does not expose private evidence.",
|
|
18915
|
+
inputSchema: {
|
|
18916
|
+
action: exports_external.enum(["create", "update", "archive", "restore"]),
|
|
18917
|
+
topicId: exports_external.string().optional().describe("update/archive/restore: shared Feedback Topic id"),
|
|
18918
|
+
title: exports_external.string().min(1).optional().describe("create/update: Topic title"),
|
|
18919
|
+
purpose: exports_external.string().optional().describe("create/update: Topic purpose")
|
|
18920
|
+
},
|
|
18921
|
+
surfaces: ["mcp", "cli"],
|
|
18922
|
+
toolset: "admin",
|
|
18923
|
+
run: forward("feedback_admin.topic")
|
|
18924
|
+
},
|
|
18925
|
+
{
|
|
18926
|
+
name: "admin_feedback_thread",
|
|
18927
|
+
title: "Maintain and merge shared Feedback Threads",
|
|
18928
|
+
description: "Privileged shared-forum moderation: create/update/move/transition/archive/restore Threads or merge a duplicate into a canonical destination with redirect and provenance. Merge safely repoints private associations but never copies raw evidence into shared content.",
|
|
18929
|
+
inputSchema: {
|
|
18930
|
+
action: exports_external.enum(["create", "update", "move", "transition", "archive", "restore", "merge"]).describe("the Thread lifecycle or merge action"),
|
|
18931
|
+
threadId: exports_external.string().optional().describe("update/move/transition/archive/restore: Thread id"),
|
|
18932
|
+
sourceThreadId: exports_external.string().optional().describe("merge: duplicate source Thread id"),
|
|
18933
|
+
targetThreadId: exports_external.string().optional().describe("merge: canonical destination Thread id"),
|
|
18934
|
+
topicId: exports_external.string().optional().describe("create/move/update: destination Feedback Topic id"),
|
|
18935
|
+
title: exports_external.string().min(1).optional().describe("create/update: Thread title"),
|
|
18936
|
+
objective: exports_external.string().min(1).optional().describe("create/update: Thread objective"),
|
|
18937
|
+
status: exports_external.enum(["active", "needs-review", "stuck", "standing", "resolved", "archived"]).optional().describe("transition/update: target Thread status"),
|
|
18938
|
+
reason: exports_external.string().optional().describe("merge: concise auditable rationale")
|
|
18939
|
+
},
|
|
18940
|
+
surfaces: ["mcp", "cli"],
|
|
18941
|
+
toolset: "admin",
|
|
18942
|
+
run: forward("feedback_admin.thread")
|
|
18943
|
+
},
|
|
18944
|
+
{
|
|
18945
|
+
name: "admin_feedback_publish",
|
|
18946
|
+
title: "Publish sanitized shared Feedback content",
|
|
18947
|
+
description: "Privileged: author a new sanitized shared contribution in a Feedback Thread, optionally recording that private evidence informed it. The body must be separately authored; Arbor refuses an exact raw-body copy and never flips private visibility.",
|
|
18948
|
+
inputSchema: {
|
|
18949
|
+
threadId: exports_external.string().describe("shared Feedback Thread id or merged source redirect"),
|
|
18950
|
+
type: exports_external.enum(CONTRIBUTION_TYPES).describe("shared contribution type, including maintainer decision"),
|
|
18951
|
+
body: exports_external.string().min(1).describe("deliberately sanitized shared body"),
|
|
18952
|
+
summary: exports_external.string().max(500).optional().describe("optional shared gist"),
|
|
18953
|
+
confidence: exports_external.number().int().min(0).max(100).optional(),
|
|
18954
|
+
idempotencyKey: exports_external.string().min(1).max(200).optional(),
|
|
18955
|
+
feedbackId: exports_external.string().optional().describe("optional private evidence id; additionally requires review_private")
|
|
18956
|
+
},
|
|
18957
|
+
surfaces: ["mcp", "cli"],
|
|
18958
|
+
toolset: "admin",
|
|
18959
|
+
run: forward("feedback_admin.publish")
|
|
18960
|
+
},
|
|
18961
|
+
{
|
|
18962
|
+
name: "admin_feedback_edit",
|
|
18963
|
+
title: "Edit sanitized shared Feedback content",
|
|
18964
|
+
description: "Privileged shared-forum moderation: correct a shared Feedback contribution's body, summary, or type while preserving normal Arbor audit history. This cannot target private feedback records.",
|
|
18965
|
+
inputSchema: {
|
|
18966
|
+
contributionId: exports_external.string().describe("shared Feedback contribution id"),
|
|
18967
|
+
body: exports_external.string().min(1).optional().describe("replacement sanitized body"),
|
|
18968
|
+
summary: exports_external.string().max(500).nullable().optional().describe("replacement gist; null/empty clears"),
|
|
18969
|
+
type: exports_external.enum(CONTRIBUTION_TYPES).optional().describe("replacement shared contribution type")
|
|
18970
|
+
},
|
|
18971
|
+
surfaces: ["mcp", "cli"],
|
|
18972
|
+
toolset: "admin",
|
|
18973
|
+
run: forward("feedback_admin.edit")
|
|
18974
|
+
},
|
|
18975
|
+
{
|
|
18976
|
+
name: "admin_feedback_curate",
|
|
18977
|
+
title: "Curate shared Feedback content",
|
|
18978
|
+
description: "Privileged shared-forum moderation: soft-delete or restore a sanitized shared contribution. This capability remains independent of private-feedback review access.",
|
|
18979
|
+
inputSchema: {
|
|
18980
|
+
verb: exports_external.enum(["delete", "restore"]),
|
|
18981
|
+
contributionId: exports_external.string().describe("shared Feedback contribution id")
|
|
18982
|
+
},
|
|
18983
|
+
surfaces: ["mcp", "cli"],
|
|
18984
|
+
toolset: "admin",
|
|
18985
|
+
run: dispatch({ delete: "feedback_admin.remove", restore: "feedback_admin.restore" })
|
|
18986
|
+
},
|
|
18662
18987
|
{
|
|
18663
18988
|
name: "contribute",
|
|
18664
18989
|
title: "Contribute to a thread",
|
|
@@ -20668,11 +20993,70 @@ var ACTIONS = ACTION_DEFINITIONS.map((action) => {
|
|
|
20668
20993
|
});
|
|
20669
20994
|
|
|
20670
20995
|
// src/config.ts
|
|
20671
|
-
import {
|
|
20996
|
+
import {
|
|
20997
|
+
chmodSync,
|
|
20998
|
+
existsSync,
|
|
20999
|
+
mkdirSync,
|
|
21000
|
+
readFileSync,
|
|
21001
|
+
rmSync,
|
|
21002
|
+
statSync,
|
|
21003
|
+
writeFileSync
|
|
21004
|
+
} from "node:fs";
|
|
20672
21005
|
import { homedir } from "node:os";
|
|
20673
21006
|
import { dirname, join } from "node:path";
|
|
20674
21007
|
var CONFIG_PATH = process.env.ARBOR_CONFIG ?? join(homedir(), ".arbor", "config.json");
|
|
20675
21008
|
var DEFAULT_API_URL = process.env.ARBOR_API_URL ?? "https://arborthreads.com";
|
|
21009
|
+
var DEFAULT_CONFIG_PATH = join(homedir(), ".arbor", "config.json");
|
|
21010
|
+
var DEFAULT_TOKEN_OVERWRITE_REFUSAL = "refusing to overwrite ~/.arbor/config.json — a token already exists. Set ARBOR_CONFIG to an isolated path (e.g. ~/.arbor/<agent>.config.json) and retry.";
|
|
21011
|
+
var LOCK_TIMEOUT_MS = 5000;
|
|
21012
|
+
var LOCK_STALE_MS = 30000;
|
|
21013
|
+
var LOCK_POLL_MS = 20;
|
|
21014
|
+
function sleepSync(ms) {
|
|
21015
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
21016
|
+
}
|
|
21017
|
+
function acquireConfigLock(configPath) {
|
|
21018
|
+
const lockPath = `${configPath}.lock`;
|
|
21019
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
21020
|
+
for (;; ) {
|
|
21021
|
+
try {
|
|
21022
|
+
mkdirSync(lockPath);
|
|
21023
|
+
return () => {
|
|
21024
|
+
try {
|
|
21025
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
21026
|
+
} catch {}
|
|
21027
|
+
};
|
|
21028
|
+
} catch (err) {
|
|
21029
|
+
if (err?.code !== "EEXIST")
|
|
21030
|
+
throw err;
|
|
21031
|
+
try {
|
|
21032
|
+
if (Date.now() - statSync(lockPath).mtimeMs > LOCK_STALE_MS) {
|
|
21033
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
21034
|
+
continue;
|
|
21035
|
+
}
|
|
21036
|
+
} catch {
|
|
21037
|
+
continue;
|
|
21038
|
+
}
|
|
21039
|
+
if (Date.now() >= deadline) {
|
|
21040
|
+
throw new Error(`timed out waiting for another arbor process to finish writing ${configPath}. If none is running, remove ${lockPath}.`);
|
|
21041
|
+
}
|
|
21042
|
+
sleepSync(LOCK_POLL_MS);
|
|
21043
|
+
}
|
|
21044
|
+
}
|
|
21045
|
+
}
|
|
21046
|
+
function defaultConfigOverwriteBlocked(opts) {
|
|
21047
|
+
const isolated = opts?.isolated ?? Boolean(process.env.ARBOR_CONFIG);
|
|
21048
|
+
const path = opts?.path ?? DEFAULT_CONFIG_PATH;
|
|
21049
|
+
if (isolated)
|
|
21050
|
+
return false;
|
|
21051
|
+
if (!existsSync(path))
|
|
21052
|
+
return false;
|
|
21053
|
+
try {
|
|
21054
|
+
const cfg = JSON.parse(readFileSync(path, "utf8"));
|
|
21055
|
+
return Boolean(cfg.token);
|
|
21056
|
+
} catch {
|
|
21057
|
+
return false;
|
|
21058
|
+
}
|
|
21059
|
+
}
|
|
20676
21060
|
function loadConfig() {
|
|
20677
21061
|
const envToken = process.env.ARBOR_TOKEN || undefined;
|
|
20678
21062
|
const envUrl = process.env.ARBOR_API_URL || undefined;
|
|
@@ -20684,15 +21068,32 @@ function loadConfig() {
|
|
|
20684
21068
|
}
|
|
20685
21069
|
return { apiUrl: envUrl ?? DEFAULT_API_URL, token: envToken };
|
|
20686
21070
|
}
|
|
20687
|
-
function
|
|
20688
|
-
mkdirSync(dirname(
|
|
20689
|
-
|
|
20690
|
-
|
|
20691
|
-
|
|
21071
|
+
function writeConfigGuarded(opts) {
|
|
21072
|
+
mkdirSync(dirname(opts.configPath), { recursive: true });
|
|
21073
|
+
const release = acquireConfigLock(opts.configPath);
|
|
21074
|
+
try {
|
|
21075
|
+
if (!opts.allowDefaultTokenOverwrite && defaultConfigOverwriteBlocked({ isolated: opts.isolated, path: opts.defaultPath })) {
|
|
21076
|
+
throw new Error(DEFAULT_TOKEN_OVERWRITE_REFUSAL);
|
|
21077
|
+
}
|
|
21078
|
+
writeFileSync(opts.configPath, `${JSON.stringify(opts.cfg, null, 2)}
|
|
21079
|
+
`, { mode: 384 });
|
|
21080
|
+
chmodSync(opts.configPath, 384);
|
|
21081
|
+
} finally {
|
|
21082
|
+
release();
|
|
21083
|
+
}
|
|
21084
|
+
}
|
|
21085
|
+
function saveConfig(cfg, opts) {
|
|
21086
|
+
writeConfigGuarded({
|
|
21087
|
+
cfg,
|
|
21088
|
+
configPath: CONFIG_PATH,
|
|
21089
|
+
defaultPath: DEFAULT_CONFIG_PATH,
|
|
21090
|
+
isolated: Boolean(process.env.ARBOR_CONFIG),
|
|
21091
|
+
allowDefaultTokenOverwrite: opts?.allowDefaultTokenOverwrite
|
|
21092
|
+
});
|
|
20692
21093
|
}
|
|
20693
21094
|
function clearToken() {
|
|
20694
21095
|
const cfg = loadConfig();
|
|
20695
|
-
saveConfig({ apiUrl: cfg.apiUrl });
|
|
21096
|
+
saveConfig({ apiUrl: cfg.apiUrl }, { allowDefaultTokenOverwrite: true });
|
|
20696
21097
|
}
|
|
20697
21098
|
|
|
20698
21099
|
// src/errors.ts
|
|
@@ -21170,13 +21571,27 @@ function buildInput(inputSchema, flags, command) {
|
|
|
21170
21571
|
}
|
|
21171
21572
|
|
|
21172
21573
|
// src/commands.ts
|
|
21574
|
+
var COMMAND_WORD_OVERRIDES = {
|
|
21575
|
+
contribute_arbor_feedback: "feedback",
|
|
21576
|
+
feedback_recall: "feedback find",
|
|
21577
|
+
feedback_thread_get: "feedback thread"
|
|
21578
|
+
};
|
|
21173
21579
|
function commandWords(action) {
|
|
21174
|
-
return action.name.replace(/_/g, " ");
|
|
21580
|
+
return COMMAND_WORD_OVERRIDES[action.name] ?? action.name.replace(/_/g, " ");
|
|
21175
21581
|
}
|
|
21176
21582
|
var CLI_ACTIONS = ACTIONS.filter((a) => a.surfaces.includes("cli") || a.surfaces.includes("computer-cli"));
|
|
21177
21583
|
var BY_COMMAND = new Map(CLI_ACTIONS.map((a) => [commandWords(a), a]));
|
|
21178
21584
|
var MAX_WORDS = Math.max(1, ...CLI_ACTIONS.map((a) => commandWords(a).split(" ").length));
|
|
21179
21585
|
var POSITIONAL_FIELDS = {
|
|
21586
|
+
contribute_arbor_feedback: ["body"],
|
|
21587
|
+
feedback_recall: ["query"],
|
|
21588
|
+
feedback_thread_get: ["threadId"],
|
|
21589
|
+
admin_feedback_capability: ["verb"],
|
|
21590
|
+
admin_feedback_evidence: ["verb"],
|
|
21591
|
+
admin_feedback_topic: ["action"],
|
|
21592
|
+
admin_feedback_thread: ["action"],
|
|
21593
|
+
admin_feedback_edit: ["contributionId"],
|
|
21594
|
+
admin_feedback_curate: ["verb", "contributionId"],
|
|
21180
21595
|
app_fetch: ["appId", "path"],
|
|
21181
21596
|
app_request: ["appId", "path"],
|
|
21182
21597
|
computer_run_start: ["computerSessionId", "mode"],
|
|
@@ -21344,8 +21759,26 @@ function applyPositionals(action, extras, flags) {
|
|
|
21344
21759
|
flags[target.flag] = value;
|
|
21345
21760
|
}
|
|
21346
21761
|
}
|
|
21762
|
+
function resolveCommand(positionals, flags) {
|
|
21763
|
+
const normalized = positionals.map((word) => word.replace(/_/g, " "));
|
|
21764
|
+
const feedbackInputFlags = new Set([
|
|
21765
|
+
"type",
|
|
21766
|
+
"body",
|
|
21767
|
+
"body-file",
|
|
21768
|
+
"summary",
|
|
21769
|
+
"summary-file",
|
|
21770
|
+
"confidence",
|
|
21771
|
+
"idempotency-key",
|
|
21772
|
+
"thread-id",
|
|
21773
|
+
"topic-id",
|
|
21774
|
+
"computer-session-id"
|
|
21775
|
+
]);
|
|
21776
|
+
const explicitFeedbackInput = Object.keys(flags).some((flag) => feedbackInputFlags.has(flag));
|
|
21777
|
+
const bareFeedbackTree = normalized.length === 1 && normalized[0] === "feedback" && !explicitFeedbackInput;
|
|
21778
|
+
return bareFeedbackTree ? { action: CLI_ACTIONS.find((action) => action.name === "feedback_tree"), words: 1 } : matchCommand(positionals);
|
|
21779
|
+
}
|
|
21347
21780
|
async function runObjectVerb(positionals, flags, ctx) {
|
|
21348
|
-
const match =
|
|
21781
|
+
const match = resolveCommand(positionals, flags);
|
|
21349
21782
|
if (!match) {
|
|
21350
21783
|
const typed = positionals.join(" ") || "(none)";
|
|
21351
21784
|
throw new UsageError(`unknown command: ${typed} — ${suggestCommand(positionals)}`);
|
|
@@ -21390,20 +21823,38 @@ async function runObjectVerb(positionals, flags, ctx) {
|
|
|
21390
21823
|
// src/connect.ts
|
|
21391
21824
|
var DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
21392
21825
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
21826
|
+
function bareConnectUrl(apiUrl, returnedUrl) {
|
|
21827
|
+
const fallback = `${apiUrl.replace(/\/$/, "")}/connect`;
|
|
21828
|
+
if (!returnedUrl)
|
|
21829
|
+
return fallback;
|
|
21830
|
+
try {
|
|
21831
|
+
return `${new URL(returnedUrl).origin}/connect`;
|
|
21832
|
+
} catch {
|
|
21833
|
+
return fallback;
|
|
21834
|
+
}
|
|
21835
|
+
}
|
|
21393
21836
|
async function connect(opts) {
|
|
21837
|
+
if (defaultConfigOverwriteBlocked()) {
|
|
21838
|
+
throw new Error(DEFAULT_TOKEN_OVERWRITE_REFUSAL);
|
|
21839
|
+
}
|
|
21394
21840
|
const apiUrl = opts.url ?? loadConfig().apiUrl;
|
|
21395
21841
|
const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
21396
|
-
const body = JSON.stringify({ pairingKey: opts.pairingKey });
|
|
21397
21842
|
const headers = { "content-type": "application/json" };
|
|
21398
|
-
const
|
|
21843
|
+
const body = JSON.stringify({ pairingKey: opts.pairingKey });
|
|
21844
|
+
const initRes = await fetch(`${apiUrl}/api/agent/connect`, {
|
|
21845
|
+
method: "POST",
|
|
21846
|
+
headers,
|
|
21847
|
+
body
|
|
21848
|
+
});
|
|
21399
21849
|
const init = await initRes.json().catch(() => ({}));
|
|
21400
21850
|
if (!initRes.ok || !init.url || !init.userCode) {
|
|
21401
21851
|
throw new Error(`could not start pairing (${initRes.status}): ${init.error ?? "is the server reachable?"}`);
|
|
21402
21852
|
}
|
|
21853
|
+
const approvalUrl = bareConnectUrl(apiUrl, init.url);
|
|
21403
21854
|
process.stderr.write(`
|
|
21404
21855
|
Pairing against ${apiUrl}
|
|
21405
21856
|
` + ` Ask the person who will manage this agent${init.agentName ? ` (“${init.agentName}”)` : ""} to approve it:
|
|
21406
|
-
` + ` 1. open ${
|
|
21857
|
+
` + ` 1. open ${approvalUrl}
|
|
21407
21858
|
` + ` 2. enter the code: ${init.userCode}
|
|
21408
21859
|
|
|
21409
21860
|
` + ` Only approve an agent you intend to manage.
|
|
@@ -21478,7 +21929,7 @@ async function login(opts) {
|
|
|
21478
21929
|
});
|
|
21479
21930
|
const tok = await tokRes.json().catch(() => ({}));
|
|
21480
21931
|
if (tok.access_token) {
|
|
21481
|
-
saveConfig({ apiUrl, token: tok.access_token });
|
|
21932
|
+
saveConfig({ apiUrl, token: tok.access_token }, { allowDefaultTokenOverwrite: true });
|
|
21482
21933
|
return;
|
|
21483
21934
|
}
|
|
21484
21935
|
if (tok.error === "authorization_pending")
|
|
@@ -21680,7 +22131,7 @@ async function main() {
|
|
|
21680
22131
|
if (!token)
|
|
21681
22132
|
throw new UsageError("usage: arbor auth <token> [--url <api-url>]");
|
|
21682
22133
|
const url2 = stringFlag(flags.url, "url") ?? loadConfig().apiUrl;
|
|
21683
|
-
saveConfig({ apiUrl: url2, token });
|
|
22134
|
+
saveConfig({ apiUrl: url2, token }, { allowDefaultTokenOverwrite: true });
|
|
21684
22135
|
advise(` ✓ Token saved (${CONFIG_PATH}).
|
|
21685
22136
|
`, ctx);
|
|
21686
22137
|
await renderMe(ctx, "auth");
|
package/package.json
CHANGED