borgmcp 1.0.5 → 1.0.7

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 (157) hide show
  1. package/dist/assimilate-cmd.js +39 -497
  2. package/dist/assimilate-deps.js +3 -177
  3. package/dist/assimilate-welcome.js +2 -24
  4. package/dist/auth-env.js +1 -107
  5. package/dist/auth.js +23 -612
  6. package/dist/claude.js +11 -281
  7. package/dist/cli-help.js +29 -50
  8. package/dist/cli-platform.js +4 -94
  9. package/dist/codex-app-server.js +4 -228
  10. package/dist/codex-app-wake.js +2 -122
  11. package/dist/codex-launch.js +1 -81
  12. package/dist/codex-remote.js +1 -250
  13. package/dist/config-utils.js +3 -385
  14. package/dist/config.js +1 -190
  15. package/dist/console-prefix.js +1 -86
  16. package/dist/cube-name.js +1 -65
  17. package/dist/cubes.js +4 -269
  18. package/dist/debug.js +1 -71
  19. package/dist/device-auth.js +1 -167
  20. package/dist/direct-log.js +1 -11
  21. package/dist/health-beat.js +1 -168
  22. package/dist/inbox-monitor.js +1 -129
  23. package/dist/index.js +26 -1378
  24. package/dist/lifecycle-log-guard.js +2 -93
  25. package/dist/list-roles-render.js +6 -39
  26. package/dist/log-audit.js +3 -186
  27. package/dist/log-stream.js +9 -848
  28. package/dist/name-validator.js +1 -22
  29. package/dist/parse-assimilate-args.js +1 -82
  30. package/dist/postinstall.js +8 -22
  31. package/dist/regen-format.js +11 -329
  32. package/dist/regen.js +5 -83
  33. package/dist/remote-client.js +1 -695
  34. package/dist/role-resolver.js +1 -36
  35. package/dist/role-section.js +8 -208
  36. package/dist/roster-render.js +3 -96
  37. package/dist/setup.js +36 -251
  38. package/dist/shell-escape.js +1 -22
  39. package/dist/spawn.js +10 -29
  40. package/dist/stale-version-check.js +1 -102
  41. package/dist/stream-owner.js +2 -202
  42. package/dist/stream-status.js +3 -211
  43. package/dist/subscription-retry.js +1 -23
  44. package/dist/sync-roles-render.js +3 -118
  45. package/dist/sync.js +22 -286
  46. package/dist/templates.js +120 -563
  47. package/dist/terminal-title.js +1 -68
  48. package/dist/token-crypto.js +1 -91
  49. package/dist/token-store.js +1 -222
  50. package/dist/types.js +0 -5
  51. package/dist/version.js +2 -78
  52. package/dist/worktree-lifecycle.js +2 -173
  53. package/package.json +11 -2
  54. package/dist/assimilate-cmd.d.ts.map +0 -1
  55. package/dist/assimilate-cmd.js.map +0 -1
  56. package/dist/assimilate-deps.d.ts.map +0 -1
  57. package/dist/assimilate-deps.js.map +0 -1
  58. package/dist/assimilate-welcome.d.ts.map +0 -1
  59. package/dist/assimilate-welcome.js.map +0 -1
  60. package/dist/auth-env.d.ts.map +0 -1
  61. package/dist/auth-env.js.map +0 -1
  62. package/dist/auth.d.ts.map +0 -1
  63. package/dist/auth.js.map +0 -1
  64. package/dist/claude.d.ts.map +0 -1
  65. package/dist/claude.js.map +0 -1
  66. package/dist/cli-help.d.ts.map +0 -1
  67. package/dist/cli-help.js.map +0 -1
  68. package/dist/cli-platform.d.ts.map +0 -1
  69. package/dist/cli-platform.js.map +0 -1
  70. package/dist/codex-app-server.d.ts.map +0 -1
  71. package/dist/codex-app-server.js.map +0 -1
  72. package/dist/codex-app-wake.d.ts.map +0 -1
  73. package/dist/codex-app-wake.js.map +0 -1
  74. package/dist/codex-launch.d.ts.map +0 -1
  75. package/dist/codex-launch.js.map +0 -1
  76. package/dist/codex-remote.d.ts.map +0 -1
  77. package/dist/codex-remote.js.map +0 -1
  78. package/dist/config-utils.d.ts.map +0 -1
  79. package/dist/config-utils.js.map +0 -1
  80. package/dist/config.d.ts.map +0 -1
  81. package/dist/config.js.map +0 -1
  82. package/dist/console-prefix.d.ts.map +0 -1
  83. package/dist/console-prefix.js.map +0 -1
  84. package/dist/cube-name.d.ts.map +0 -1
  85. package/dist/cube-name.js.map +0 -1
  86. package/dist/cubes.d.ts.map +0 -1
  87. package/dist/cubes.js.map +0 -1
  88. package/dist/debug.d.ts.map +0 -1
  89. package/dist/debug.js.map +0 -1
  90. package/dist/device-auth.d.ts.map +0 -1
  91. package/dist/device-auth.js.map +0 -1
  92. package/dist/direct-log.d.ts.map +0 -1
  93. package/dist/direct-log.js.map +0 -1
  94. package/dist/health-beat.d.ts.map +0 -1
  95. package/dist/health-beat.js.map +0 -1
  96. package/dist/inbox-monitor.d.ts.map +0 -1
  97. package/dist/inbox-monitor.js.map +0 -1
  98. package/dist/index.d.ts.map +0 -1
  99. package/dist/index.js.map +0 -1
  100. package/dist/lifecycle-log-guard.d.ts.map +0 -1
  101. package/dist/lifecycle-log-guard.js.map +0 -1
  102. package/dist/list-roles-render.d.ts.map +0 -1
  103. package/dist/list-roles-render.js.map +0 -1
  104. package/dist/log-audit.d.ts.map +0 -1
  105. package/dist/log-audit.js.map +0 -1
  106. package/dist/log-stream.d.ts.map +0 -1
  107. package/dist/log-stream.js.map +0 -1
  108. package/dist/name-validator.d.ts.map +0 -1
  109. package/dist/name-validator.js.map +0 -1
  110. package/dist/parse-assimilate-args.d.ts.map +0 -1
  111. package/dist/parse-assimilate-args.js.map +0 -1
  112. package/dist/postinstall.d.ts.map +0 -1
  113. package/dist/postinstall.js.map +0 -1
  114. package/dist/regen-format.d.ts.map +0 -1
  115. package/dist/regen-format.js.map +0 -1
  116. package/dist/regen.d.ts.map +0 -1
  117. package/dist/regen.js.map +0 -1
  118. package/dist/remote-client.d.ts.map +0 -1
  119. package/dist/remote-client.js.map +0 -1
  120. package/dist/role-resolver.d.ts.map +0 -1
  121. package/dist/role-resolver.js.map +0 -1
  122. package/dist/role-section.d.ts.map +0 -1
  123. package/dist/role-section.js.map +0 -1
  124. package/dist/roster-render.d.ts.map +0 -1
  125. package/dist/roster-render.js.map +0 -1
  126. package/dist/setup.d.ts.map +0 -1
  127. package/dist/setup.js.map +0 -1
  128. package/dist/shell-escape.d.ts.map +0 -1
  129. package/dist/shell-escape.js.map +0 -1
  130. package/dist/spawn.d.ts.map +0 -1
  131. package/dist/spawn.js.map +0 -1
  132. package/dist/stale-version-check.d.ts.map +0 -1
  133. package/dist/stale-version-check.js.map +0 -1
  134. package/dist/stream-owner.d.ts.map +0 -1
  135. package/dist/stream-owner.js.map +0 -1
  136. package/dist/stream-status.d.ts.map +0 -1
  137. package/dist/stream-status.js.map +0 -1
  138. package/dist/subscription-retry.d.ts.map +0 -1
  139. package/dist/subscription-retry.js.map +0 -1
  140. package/dist/sync-roles-render.d.ts.map +0 -1
  141. package/dist/sync-roles-render.js.map +0 -1
  142. package/dist/sync.d.ts.map +0 -1
  143. package/dist/sync.js.map +0 -1
  144. package/dist/templates.d.ts.map +0 -1
  145. package/dist/templates.js.map +0 -1
  146. package/dist/terminal-title.d.ts.map +0 -1
  147. package/dist/terminal-title.js.map +0 -1
  148. package/dist/token-crypto.d.ts.map +0 -1
  149. package/dist/token-crypto.js.map +0 -1
  150. package/dist/token-store.d.ts.map +0 -1
  151. package/dist/token-store.js.map +0 -1
  152. package/dist/types.d.ts.map +0 -1
  153. package/dist/types.js.map +0 -1
  154. package/dist/version.d.ts.map +0 -1
  155. package/dist/version.js.map +0 -1
  156. package/dist/worktree-lifecycle.d.ts.map +0 -1
  157. package/dist/worktree-lifecycle.js.map +0 -1
@@ -1,36 +1 @@
1
- /**
2
- * Normalize a role-name argument or stored name into a slug used for
3
- * both worktree-path construction and role lookup. Single source of
4
- * truth so the path and the matched role always agree.
5
- *
6
- * Path-safe: strips characters outside `[a-z0-9-]` after the
7
- * underscore/whitespace collapse. The no-arg path bypasses
8
- * validateName (the matched role's name comes from the DB, which
9
- * has no charset constraint on role names — CreateRoleSchema is
10
- * `min(1).max(64)` only), so the safety has to live here.
11
- */
12
- export function roleSlug(raw) {
13
- return raw.toLowerCase().replace(/[_\s]+/g, '-').replace(/[^a-z0-9-]/g, '');
14
- }
15
- /**
16
- * Case- and separator-insensitive lookup against the cube's roles.
17
- */
18
- export function matchRoleByName(roles, query) {
19
- const slug = roleSlug(query);
20
- return roles.find((r) => roleSlug(r.name) === slug);
21
- }
22
- /**
23
- * Default-role picker for the no-arg case:
24
- * - First drone in cube + a human-seat role exists → that role
25
- * - Otherwise → the is_default role
26
- * - Neither available → undefined (caller errors out)
27
- */
28
- export function pickDefaultRole(roles, opts) {
29
- if (opts.isFirstDrone) {
30
- const humanSeat = roles.find((r) => r.is_human_seat);
31
- if (humanSeat)
32
- return humanSeat;
33
- }
34
- return roles.find((r) => r.is_default);
35
- }
36
- //# sourceMappingURL=role-resolver.js.map
1
+ function a(e){return e.toLowerCase().replace(/[_\s]+/g,"-").replace(/[^a-z0-9-]/g,"")}function i(e,t){const n=a(t);return e.find(r=>a(r.name)===n)}function o(e,t){if(t.isFirstDrone){const n=e.find(r=>r.is_human_seat);if(n)return n}return e.find(n=>n.is_default)}export{i as matchRoleByName,o as pickDefaultRole,a as roleSlug};
@@ -1,208 +1,8 @@
1
- // gh#496-A(b) + gh#513 — SINGLE canonical role-section parser, shared by BOTH
2
- // the client and the worker (the prior two-copy drift is eliminated). The
3
- // client render compression (`regen-format.ts`) splits role text with this
4
- // parser to decide which `… rationale:` sections to stub; the worker's
5
- // `getRoleRationale` (cubes.ts) resolves those stub headings and `sync-plan.ts`
6
- // diffs role sections — all import THIS module, so heading derivation
7
- // (sans-colon, isLabelLine rules) is identical by construction (no parity
8
- // test needed; agreement is structural).
9
- //
10
- // The worker imports this via `../client/src/role-section` (mirrors the
11
- // existing `workers/mcp-server.ts → ../client/src/templates` precedent). KEEP
12
- // THIS MODULE SELF-CONTAINED (no runtime imports) so the worker bundle
13
- // resolves it cleanly; the client tsconfig `rootDir: ./src` also requires the
14
- // shared canonical to live under `client/src/`.
15
- /**
16
- * Test whether a single line is a plain-label section heading.
17
- *
18
- * Rules (deliberately narrow to avoid false positives against prose
19
- * that merely contains a colon):
20
- * - no leading whitespace (label lines start at column 0);
21
- * - the line ends with exactly one `:`;
22
- * - the text before the colon is non-empty, contains no other `:`,
23
- * and is "short" (≤ 60 chars) — long colon-terminated prose lines
24
- * are not labels;
25
- * - the label is not a markdown emphasis run (does not start with
26
- * `*`, `-`, `#`, `>` or backtick) — those are list/markdown lines,
27
- * not plain labels (the woven `**Dense communication discipline:**`
28
- * constant headings must NOT be treated as section boundaries).
29
- */
30
- export function isLabelLine(line) {
31
- // Leading whitespace disqualifies (label lines are flush-left).
32
- if (/^\s/.test(line))
33
- return false;
34
- if (!line.endsWith(':'))
35
- return false;
36
- const label = line.slice(0, -1);
37
- if (label.length === 0)
38
- return false;
39
- if (label.length > 60)
40
- return false;
41
- // Exactly one colon total (the trailing one).
42
- if (label.includes(':'))
43
- return false;
44
- // Markdown/list/quote/code lead-ins are not plain labels.
45
- if (/^[*\-#>`]/.test(label))
46
- return false;
47
- return true;
48
- }
49
- /**
50
- * Parse role `detailed_description` text into ordered sections.
51
- *
52
- * The result always round-trips byte-identical through
53
- * `serializeSections`. An empty string yields a single empty preamble
54
- * section so callers always have a stable shape to address.
55
- */
56
- export function parseRoleSections(text) {
57
- const sections = [];
58
- const lines = text.split('\n');
59
- // Reconstruct each line WITH its terminating newline so that the
60
- // concatenation is lossless. `split('\n')` drops the separators; we
61
- // re-append '\n' to every line except the last (which had no trailing
62
- // newline in the source unless the source ended with one — in which
63
- // case the final split element is '' and contributes nothing).
64
- const lineWithSep = (idx) => idx < lines.length - 1 ? lines[idx] + '\n' : lines[idx];
65
- let currentHeading = null;
66
- let currentKind = 'preamble';
67
- let currentBody = '';
68
- let started = false;
69
- const flush = () => {
70
- sections.push({ heading: currentHeading, kind: currentKind, body: currentBody });
71
- };
72
- for (let i = 0; i < lines.length; i++) {
73
- const raw = lines[i];
74
- if (isLabelLine(raw)) {
75
- // Close the in-progress section (preamble or previous label) and
76
- // open a new label section headed by this line.
77
- flush();
78
- currentHeading = raw.slice(0, -1).trim();
79
- currentKind = 'label';
80
- currentBody = lineWithSep(i);
81
- started = true;
82
- }
83
- else {
84
- currentBody += lineWithSep(i);
85
- started = true;
86
- }
87
- }
88
- // Always emit the final in-progress section. For empty input this is a
89
- // single empty preamble (started === false, currentBody === '').
90
- if (started || sections.length === 0) {
91
- flush();
92
- }
93
- return sections;
94
- }
95
- /** Concatenate sections back into the original field text. */
96
- export function serializeSections(sections) {
97
- return sections.map((s) => s.body).join('');
98
- }
99
- function normalizeHeading(value) {
100
- return value.trim().toLowerCase();
101
- }
102
- /**
103
- * Build the body for a freshly inserted/replaced label section from a
104
- * heading + caller-supplied body text.
105
- *
106
- * The heading line is rendered as `<heading>:` on its own line. The
107
- * caller's `body` is the text BELOW the heading. We guarantee the
108
- * section body ends with a single trailing newline so adjacent sections
109
- * stay separated when re-serialized (matching how real role text
110
- * separates label sections with blank lines is the caller's choice via
111
- * their body text; we only ensure the structural newline).
112
- */
113
- function renderLabelSection(heading, body) {
114
- const headingLine = `${heading.trim()}:\n`;
115
- if (body === '')
116
- return headingLine;
117
- // Ensure the body ends with a newline so the next section's heading
118
- // starts on its own line.
119
- const normalizedBody = body.endsWith('\n') ? body : body + '\n';
120
- return headingLine + normalizedBody;
121
- }
122
- /**
123
- * Guarantee the section at `idx` ends in a newline before a new section
124
- * heading is glued on after it.
125
- *
126
- * Real role `detailed_description` text (every template role) ends WITHOUT
127
- * a trailing newline — it closes on the woven `${...DISCIPLINE}` constant.
128
- * Appending a `<heading>:` line directly onto such text would fuse the
129
- * heading onto the prior section's last line, so the heading is no longer
130
- * a flush-left label and the inserted section is LOST on re-parse. Adding
131
- * the structural newline here keeps the inserted heading on its own line.
132
- *
133
- * No-ops when the preceding body is empty (an empty preamble needs no
134
- * separator) or already ends in '\n' (preserves the already-correct case
135
- * byte-for-byte).
136
- */
137
- function ensureTrailingNewline(sections, idx) {
138
- if (idx < 0 || idx >= sections.length)
139
- return;
140
- const prev = sections[idx];
141
- if (prev.body !== '' && !prev.body.endsWith('\n')) {
142
- sections[idx] = { ...prev, body: prev.body + '\n' };
143
- }
144
- }
145
- /**
146
- * Apply a single section-level patch to role text, returning the new
147
- * full field text. Every other byte is preserved.
148
- *
149
- * - replace: overwrite the matched section's body with a re-rendered
150
- * `<heading>:\n<body>` block. The heading match is case-insensitive
151
- * on the label; the re-rendered heading uses the supplied casing.
152
- * - insert: add a new label section. Placed after the section named by
153
- * `after` (case-insensitive), or appended at the end when `after` is
154
- * omitted/null. Rejects if a section with the same heading already
155
- * exists (use replace instead).
156
- * - delete: drop the matched section entirely.
157
- *
158
- * Throws when the target section is missing (replace/delete) or already
159
- * present (insert), or when `after` does not resolve.
160
- */
161
- export function patchRoleSectionText(text, op) {
162
- const sections = parseRoleSections(text);
163
- const targetKey = normalizeHeading(op.heading);
164
- if (op.action === 'replace') {
165
- const idx = sections.findIndex((s) => s.kind === 'label' && s.heading != null && normalizeHeading(s.heading) === targetKey);
166
- if (idx === -1) {
167
- throw new Error(`Role section "${op.heading}" not found. Use action="insert" to add it.`);
168
- }
169
- sections[idx] = {
170
- heading: op.heading.trim(),
171
- kind: 'label',
172
- body: renderLabelSection(op.heading, op.body),
173
- };
174
- return serializeSections(sections);
175
- }
176
- if (op.action === 'delete') {
177
- const idx = sections.findIndex((s) => s.kind === 'label' && s.heading != null && normalizeHeading(s.heading) === targetKey);
178
- if (idx === -1) {
179
- throw new Error(`Role section "${op.heading}" not found.`);
180
- }
181
- sections.splice(idx, 1);
182
- return serializeSections(sections);
183
- }
184
- // insert
185
- const exists = sections.some((s) => s.kind === 'label' && s.heading != null && normalizeHeading(s.heading) === targetKey);
186
- if (exists) {
187
- throw new Error(`Role section "${op.heading}" already exists. Use action="replace" to overwrite it.`);
188
- }
189
- const newSection = {
190
- heading: op.heading.trim(),
191
- kind: 'label',
192
- body: renderLabelSection(op.heading, op.body),
193
- };
194
- if (op.after == null) {
195
- ensureTrailingNewline(sections, sections.length - 1);
196
- sections.push(newSection);
197
- return serializeSections(sections);
198
- }
199
- const afterKey = normalizeHeading(op.after);
200
- const afterIdx = sections.findIndex((s) => s.kind === 'label' && s.heading != null && normalizeHeading(s.heading) === afterKey);
201
- if (afterIdx === -1) {
202
- throw new Error(`Cannot insert after section "${op.after}" — it does not exist.`);
203
- }
204
- ensureTrailingNewline(sections, afterIdx);
205
- sections.splice(afterIdx + 1, 0, newSection);
206
- return serializeSections(sections);
207
- }
208
- //# sourceMappingURL=role-section.js.map
1
+ function b(t){if(/^\s/.test(t)||!t.endsWith(":"))return!1;const e=t.slice(0,-1);return!(e.length===0||e.length>60||e.includes(":")||/^[*\-#>`]/.test(e))}function w(t){const e=[],n=t.split(`
2
+ `),l=i=>i<n.length-1?n[i]+`
3
+ `:n[i];let f=null,s="preamble",d="",a=!1;const r=()=>{e.push({heading:f,kind:s,body:d})};for(let i=0;i<n.length;i++){const u=n[i];b(u)?(r(),f=u.slice(0,-1).trim(),s="label",d=l(i),a=!0):(d+=l(i),a=!0)}return(a||e.length===0)&&r(),e}function c(t){return t.map(e=>e.body).join("")}function o(t){return t.trim().toLowerCase()}function h(t,e){const n=`${t.trim()}:
4
+ `;if(e==="")return n;const l=e.endsWith(`
5
+ `)?e:e+`
6
+ `;return n+l}function g(t,e){if(e<0||e>=t.length)return;const n=t[e];n.body!==""&&!n.body.endsWith(`
7
+ `)&&(t[e]={...n,body:n.body+`
8
+ `})}function y(t,e){const n=w(t),l=o(e.heading);if(e.action==="replace"){const r=n.findIndex(i=>i.kind==="label"&&i.heading!=null&&o(i.heading)===l);if(r===-1)throw new Error(`Role section "${e.heading}" not found. Use action="insert" to add it.`);return n[r]={heading:e.heading.trim(),kind:"label",body:h(e.heading,e.body)},c(n)}if(e.action==="delete"){const r=n.findIndex(i=>i.kind==="label"&&i.heading!=null&&o(i.heading)===l);if(r===-1)throw new Error(`Role section "${e.heading}" not found.`);return n.splice(r,1),c(n)}if(n.some(r=>r.kind==="label"&&r.heading!=null&&o(r.heading)===l))throw new Error(`Role section "${e.heading}" already exists. Use action="replace" to overwrite it.`);const s={heading:e.heading.trim(),kind:"label",body:h(e.heading,e.body)};if(e.after==null)return g(n,n.length-1),n.push(s),c(n);const d=o(e.after),a=n.findIndex(r=>r.kind==="label"&&r.heading!=null&&o(r.heading)===d);if(a===-1)throw new Error(`Cannot insert after section "${e.after}" \u2014 it does not exist.`);return g(n,a),n.splice(a+1,0,s),c(n)}export{b as isLabelLine,w as parseRoleSections,y as patchRoleSectionText,c as serializeSections};
@@ -1,96 +1,3 @@
1
- /**
2
- * Pure renderer for the `borg:roster` MCP tool output.
3
- *
4
- * Split out from `index.ts` so the T2.1 sender-side liveness probe
5
- * (the `awake`/`stale-since-X` column rendering when a `since` arg is
6
- * passed) can be unit-tested without spinning up the MCP server. Same
7
- * pure-function + injected-`humanAgo` pattern as `stream-status.ts`.
8
- *
9
- * Two modes:
10
- * - No `since` provided: classic roster — one line per drone with
11
- * label, role, and `last seen` relative time.
12
- * - `since` provided: each line additionally carries an `awake`
13
- * marker if the drone's `last_seen` is after the resolved
14
- * timestamp, otherwise a `stale-since-<relative>` marker derived
15
- * from the resolved timestamp the server echoed back.
16
- */
17
- /**
18
- * gh#503 — render-layer display labels for the wake-path wire enum.
19
- *
20
- * The gh#406 SLI MEASURES a streak of unanswered directed challenges; it
21
- * cannot confirm a genuinely broken wake path ("deaf") — a seat that is
22
- * alive but idle-and-text-only looks identical past the SLA. So the
23
- * roster shows the measured-confidence label, not the unconfirmed verdict:
24
- * degraded (1 miss) → `unverified` (signal seen, not yet corroborated)
25
- * deaf (2+ miss) → `unresponsive` (no response past the SLA)
26
- *
27
- * The wire enum VALUES (`live`/`degraded`/`deaf`), computed server-side by
28
- * `wakePathFromStreak`, are intentionally UNCHANGED — this is a display-only
29
- * relabel within gh#503's copy-only scope (renaming the enum would be a
30
- * wire + schema change). An operator who wants a confirmed read cross-checks
31
- * `borg:stream-status` on the affected seat. Unknown wire values fall back
32
- * to the raw value so a newer server can't break an older client's roster.
33
- */
34
- const WAKE_PATH_DISPLAY = {
35
- degraded: 'unverified',
36
- deaf: 'unresponsive',
37
- };
38
- export function formatRoleAgentLabel(roleName, agentKind) {
39
- return agentKind ? `${roleName}, ${agentKind}` : roleName;
40
- }
41
- export function renderRoster(inputs) {
42
- const { cubeName, drones, roles, resolvedSince, humanAgo } = inputs;
43
- const roleById = new Map();
44
- for (const r of roles)
45
- roleById.set(r.id, r);
46
- const lines = [];
47
- lines.push(`# Drones in cube: ${cubeName}`);
48
- lines.push('');
49
- if (resolvedSince) {
50
- // Surface the liveness-probe context so the reader knows what the
51
- // `awake`/`stale-since-X` column is measured against.
52
- lines.push(`_Liveness probe since ${resolvedSince} (${humanAgo(resolvedSince)}). \`awake\` = drone posted to the cube log after that point._`);
53
- lines.push('');
54
- }
55
- if (!drones.length) {
56
- lines.push('_(no drones connected)_');
57
- return lines.join('\n');
58
- }
59
- for (const d of drones) {
60
- const role = roleById.get(d.role_id);
61
- const roleName = role?.name ?? 'unknown';
62
- const roleLabel = formatRoleAgentLabel(roleName, d.agent_kind);
63
- const lastSeen = humanAgo(d.last_seen);
64
- const wakePathMarker = d.wake_path && d.wake_path !== 'live'
65
- ? ` · \`wake-path:${WAKE_PATH_DISPLAY[d.wake_path] ?? d.wake_path}\``
66
- : '';
67
- const wakePathClassMarker = d.wake_path_alert_class && d.wake_path_alert_class !== 'independent'
68
- ? ` · \`wake-path-class:${d.wake_path_alert_class}\``
69
- : '';
70
- const regenCountMarker = typeof d.regen_count === 'number' ? ` · \`regen-count:${d.regen_count}\`` : '';
71
- if (resolvedSince) {
72
- // T2.1 awake/stale column. `seen_since === true` → drone called a
73
- // tool after the resolved timestamp; treat as awake. False or
74
- // missing → stale. Missing should not happen when the server
75
- // echoed a since, but defending against a shape mismatch is
76
- // cheap and matches the renderer's no-server-assumptions
77
- // discipline established in stream-status.ts.
78
- //
79
- // Per drone-4's polish NIT on the original T2.1 ship: the marker
80
- // is just `stale` (not `stale-since-<relative>`) because the
81
- // header already anchors the reference point and per-row "since
82
- // when" would be identical across all stale rows in a single
83
- // probe call — pure redundancy. The per-row `last seen X ago`
84
- // field carries the diagnostic detail for "how stale is this
85
- // particular drone."
86
- const isAwake = d.seen_since === true;
87
- const marker = isAwake ? '`awake`' : '`stale`';
88
- lines.push(`- **${d.label}** (${roleLabel}) — last seen ${lastSeen} · ${marker}${regenCountMarker}${wakePathMarker}${wakePathClassMarker}`);
89
- }
90
- else {
91
- lines.push(`- **${d.label}** (${roleLabel}) — last seen ${lastSeen}${regenCountMarker}${wakePathMarker}${wakePathClassMarker}`);
92
- }
93
- }
94
- return lines.join('\n');
95
- }
96
- //# sourceMappingURL=roster-render.js.map
1
+ const d={degraded:"unverified",deaf:"unresponsive"};function w(t,s){return s?`${t}, ${s}`:t}function b(t){const{cubeName:s,drones:o,roles:h,resolvedSince:a,humanAgo:r}=t,c=new Map;for(const e of h)c.set(e.id,e);const n=[];if(n.push(`# Drones in cube: ${s}`),n.push(""),a&&(n.push(`_Liveness probe since ${a} (${r(a)}). \`awake\` = drone posted to the cube log after that point._`),n.push("")),!o.length)return n.push("_(no drones connected)_"),n.join(`
2
+ `);for(const e of o){const k=c.get(e.role_id)?.name??"unknown",l=w(k,e.agent_kind),p=r(e.last_seen),u=e.wake_path&&e.wake_path!=="live"?` \xB7 \`wake-path:${d[e.wake_path]??e.wake_path}\``:"",_=e.wake_path_alert_class&&e.wake_path_alert_class!=="independent"?` \xB7 \`wake-path-class:${e.wake_path_alert_class}\``:"",i=typeof e.regen_count=="number"?` \xB7 \`regen-count:${e.regen_count}\``:"";if(a){const $=e.seen_since===!0?"`awake`":"`stale`";n.push(`- **${e.label}** (${l}) \u2014 last seen ${p} \xB7 ${$}${i}${u}${_}`)}else n.push(`- **${e.label}** (${l}) \u2014 last seen ${p}${i}${u}${_}`)}return n.join(`
3
+ `)}export{w as formatRoleAgentLabel,b as renderRoster};
package/dist/setup.js CHANGED
@@ -1,252 +1,37 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Borg MCP Setup Wizard
4
- *
5
- * Interactive setup flow:
6
- * 1. Configure Claude Code MCP settings
7
- * 2. Google OAuth authentication
8
- * 3. Subscription setup (web dashboard or Stripe)
9
- */
10
- import prompts from 'prompts';
11
- import chalk from 'chalk';
12
- import open from 'open';
13
- import which from 'which';
14
- import { authenticateWithGoogle } from './auth.js';
15
- import { checkSubscriptionStatus, createSubscription } from './remote-client.js';
16
- import { retrySubscriptionCheck } from './subscription-retry.js';
17
- import { addMcpServer, addSessionStartHook, addUserPromptSubmitHook, addCodexMcpServer, addCodexSessionStartHook, addCodexUserPromptSubmitHook, isMcpServerConfigured, isCodexMcpServerConfigured, } from './config-utils.js';
18
- import { isAuthenticated } from './config.js';
19
- import { handleVersionFlag } from './version.js';
20
- import { initDebugFromArgv } from './debug.js';
21
- /**
22
- * Main setup wizard
23
- */
24
- async function main() {
25
- // `--debug` / BORG_DEBUG observability. Wired here as well as in the
26
- // top-level dispatcher because `borg-setup` is its own bin (dist/setup.js)
27
- // and can be invoked directly, not only via `borg setup`. Idempotent.
28
- initDebugFromArgv(process.argv);
29
- handleVersionFlag();
30
- console.log(chalk.blue.bold('\n◼ Borg MCP Setup Wizard ◼'));
31
- // gh#557: `--no-browser` (alias `--device`) forces the device-code OAuth
32
- // flow for SSH / headless / container terminals. Scanned from argv so it
33
- // works both as `borg setup --no-browser` and `borg-setup --no-browser`.
34
- // (SSH/headless are auto-detected too; the flag is the explicit override.)
35
- const noBrowser = process.argv.includes('--no-browser') || process.argv.includes('--device');
36
- // Step 0: Check which agent CLIs exist
37
- let claudeCliPath = null;
38
- let codexCliPath = null;
39
- try {
40
- claudeCliPath = which.sync('claude');
41
- }
42
- catch (error) {
43
- // Optional: Borg can also run with Codex.
44
- }
45
- try {
46
- codexCliPath = which.sync('codex');
47
- }
48
- catch (error) {
49
- // Optional: Borg can also run with Claude Code.
50
- }
51
- if (claudeCliPath)
52
- console.log(chalk.gray(`Found Claude CLI: ${claudeCliPath}`));
53
- if (codexCliPath)
54
- console.log(chalk.gray(`Found Codex CLI: ${codexCliPath}`));
55
- if (claudeCliPath || codexCliPath)
56
- console.log('');
57
- if (!claudeCliPath && !codexCliPath) {
58
- console.error(chalk.red('◼ No supported agent CLI found\n'));
59
- console.error(chalk.yellow('Please install Claude Code or Codex first:'));
60
- console.error(chalk.gray(' Claude Code: https://claude.ai/download'));
61
- console.error(chalk.gray(' Codex: https://developers.openai.com/codex\n'));
62
- process.exit(1);
63
- }
64
- // Step 1: Configure every detected agent CLI. Idempotent; re-running
65
- // setup is the normal path for OAuth refresh and CLI self-healing.
66
- console.log(chalk.blue('◼ Agent CLI Integration'));
67
- if (claudeCliPath) {
68
- try {
69
- if (!isMcpServerConfigured())
70
- addMcpServer();
71
- addSessionStartHook();
72
- addUserPromptSubmitHook();
73
- console.log(chalk.green('◼ borg configured for Claude Code'));
74
- }
75
- catch (error) {
76
- console.error(chalk.red(`\n◼ Failed to configure Claude Code: ${error.message}\n`));
77
- process.exit(1);
78
- }
79
- }
80
- if (codexCliPath) {
81
- try {
82
- if (!isCodexMcpServerConfigured())
83
- addCodexMcpServer();
84
- addCodexSessionStartHook();
85
- addCodexUserPromptSubmitHook();
86
- console.log(chalk.green('◼ borg configured for Codex'));
87
- }
88
- catch (error) {
89
- console.error(chalk.red(`\n◼ Failed to configure Codex: ${error.message}\n`));
90
- process.exit(1);
91
- }
92
- }
93
- console.log('');
94
- // Step 2: Authentication
95
- console.log(chalk.blue('◼ Google Authentication'));
96
- const authed = await isAuthenticated();
97
- if (!authed) {
98
- try {
99
- await authenticateWithGoogle(noBrowser ? { noBrowser: true } : undefined);
100
- }
101
- catch (error) {
102
- console.error(chalk.red(`\n◼ Authentication failed: ${error.message}\n`));
103
- // gh#557 NOTE-2: device-flow errors (access_denied / expired_token) and
104
- // any other auth failure exit here — give the remote user a recovery
105
- // path instead of a bare exit.
106
- console.error(chalk.yellow('Re-run `borg setup` to try again.\n'));
107
- process.exit(1);
108
- }
109
- }
110
- else {
111
- console.log(chalk.green('◼ Already authenticated\n'));
112
- }
113
- // Step 3: Subscription
114
- console.log(chalk.blue('◼ Subscription Check'));
115
- let status;
116
- try {
117
- status = await checkSubscriptionStatus();
118
- }
119
- catch (error) {
120
- console.error(chalk.red(`\n◼ Failed to check subscription: ${error.message}\n`));
121
- process.exit(1);
122
- }
123
- // gh#521: a user who just subscribed via web hits propagation lag — retry a
124
- // few times (non-alarmingly) before declaring no subscription, instead of
125
- // flashing a scary "not found" immediately after payment.
126
- status = await retrySubscriptionCheck(status, {
127
- check: checkSubscriptionStatus,
128
- sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
129
- onRetry: (attempt, total) => console.log(chalk.gray(`◼ Checking subscription... (attempt ${attempt}/${total})`)),
130
- });
131
- if (!status.hasAccess) {
132
- // gh#687: a fresh user has no subscription BY DESIGN — the Free tier is the
133
- // permanent entry point (no trial). Lead with that as a WIN, not a
134
- // "not found" failure, and present upgrading as an OFFER (Continue on Free
135
- // is the default). The gh#521 just-subscribed propagation-lag retry already
136
- // ran above; the "I already subscribed — re-check" choice covers the tail.
137
- console.log(chalk.green("◼ You're on the Free tier — permanent, no card needed: 1 cube + 3 drones + 100 req/hr."));
138
- console.log(chalk.gray('◼ Start using borgmcp right now. Upgrade any time for unlimited cubes + drones ($1/cube/month).\n'));
139
- const { subscribeMethod } = await prompts({
140
- type: 'select',
141
- name: 'subscribeMethod',
142
- message: "You're ready on the Free tier. Want to do more?",
143
- choices: [
144
- {
145
- title: '◼ Continue on the Free tier (recommended)',
146
- value: 'skip',
147
- description: 'Start now — 1 cube, 3 drones, 100 req/hr. No payment required.'
148
- },
149
- {
150
- title: '◼ Upgrade to Cube tier — $1/cube/month',
151
- value: 'web',
152
- description: 'Unlimited cubes + drones + 1000 req/hr. Opens the subscribe page in your browser.'
153
- },
154
- {
155
- title: '◼ Quick Stripe checkout',
156
- value: 'stripe',
157
- description: 'Fast upgrade checkout in the browser'
158
- },
159
- {
160
- title: '◼ I already subscribed — re-check',
161
- value: 'recheck',
162
- description: 'Re-check now — a just-completed subscription can take a moment to activate'
163
- }
164
- ]
165
- });
166
- switch (subscribeMethod) {
167
- case 'web':
168
- console.log(chalk.blue('\n◼ Opening: https://borgmcp.ai/subscribe'));
169
- try {
170
- await open('https://borgmcp.ai/subscribe');
171
- console.log(chalk.gray('◼ Waiting for subscription (checking every 5s for 2 min)...\n'));
172
- await pollForSubscription();
173
- }
174
- catch (error) {
175
- console.error(chalk.yellow(`\n◼ ${error.message}`));
176
- }
177
- break;
178
- case 'stripe':
179
- try {
180
- const checkoutUrl = await createSubscription();
181
- console.log(chalk.blue(`\n◼ Opening Stripe: ${checkoutUrl}`));
182
- await open(checkoutUrl);
183
- console.log(chalk.gray('◼ Waiting for subscription...\n'));
184
- await pollForSubscription();
185
- }
186
- catch (error) {
187
- console.error(chalk.red(`\n◼ Failed to create checkout: ${error.message}\n`));
188
- }
189
- break;
190
- case 'recheck':
191
- try {
192
- const recheckStatus = await checkSubscriptionStatus();
193
- if (recheckStatus.hasAccess) {
194
- console.log(chalk.green('\n◼ Subscription found!\n'));
195
- }
196
- else {
197
- console.log(chalk.yellow('\n◼ No subscription found\n'));
198
- }
199
- }
200
- catch (error) {
201
- console.error(chalk.red(`\n◼ Failed to recheck: ${error.message}\n`));
202
- }
203
- break;
204
- case 'skip':
205
- console.log(chalk.green("\n◼ You're all set on the Free tier: 1 cube, 3 drones, 100 req/hr.\n"));
206
- break;
207
- }
208
- }
209
- else {
210
- console.log(chalk.green('◼ Active subscription found'));
211
- if (status.expiresAt) {
212
- const expiresAt = new Date(status.expiresAt);
213
- console.log(chalk.gray(` Expires: ${expiresAt.toLocaleDateString()}\n`));
214
- }
215
- else {
216
- console.log('');
217
- }
218
- }
219
- // Success message
220
- console.log(chalk.green.bold('Setup complete!\n'));
221
- console.log(chalk.yellow('🔄 Restart Claude Code/Codex (or open a new session) for the changes to take effect.\n'));
222
- console.log(chalk.gray('◼ Next steps:'));
223
- console.log(chalk.gray('1. Run "borg" to start Claude Code or Codex with your cube'));
224
- console.log(chalk.gray('2. Manage cubes and subscription at https://borgmcp.ai/dashboard\n'));
225
- }
226
- /**
227
- * Poll for subscription activation
228
- * Checks every 5 seconds for 2 minutes (24 attempts)
229
- */
230
- async function pollForSubscription() {
231
- const maxAttempts = 24;
232
- for (let i = 0; i < maxAttempts; i++) {
233
- await new Promise(resolve => setTimeout(resolve, 5000));
234
- try {
235
- const status = await checkSubscriptionStatus();
236
- if (status.hasAccess) {
237
- console.log(chalk.green('◼ Subscription activated!\n'));
238
- return;
239
- }
240
- }
241
- catch (error) {
242
- // Continue polling even on errors
243
- }
244
- }
245
- throw new Error('Timeout - Run setup again after subscribing');
246
- }
247
- // Run wizard
248
- main().catch((error) => {
249
- console.error(chalk.red(`\n◼ Setup failed: ${error.message}\n`));
250
- process.exit(1);
251
- });
252
- //# sourceMappingURL=setup.js.map
2
+ import d from"prompts";import e from"chalk";import a from"open";import l from"which";import{authenticateWithGoogle as g}from"./auth.js";import{checkSubscriptionStatus as i,createSubscription as p}from"./remote-client.js";import{retrySubscriptionCheck as h}from"./subscription-retry.js";import{addMcpServer as b,addSessionStartHook as m,addUserPromptSubmitHook as f,addCodexMcpServer as y,addCodexSessionStartHook as w,addCodexUserPromptSubmitHook as C,isMcpServerConfigured as k,isCodexMcpServerConfigured as S}from"./config-utils.js";import{isAuthenticated as x}from"./config.js";import{handleVersionFlag as v}from"./version.js";import{initDebugFromArgv as $}from"./debug.js";async function A(){$(process.argv),v(),console.log(e.blue.bold(`
3
+ \u25FC Borg MCP Setup Wizard \u25FC`));const c=process.argv.includes("--no-browser")||process.argv.includes("--device");let s=null,t=null;try{s=l.sync("claude")}catch{}try{t=l.sync("codex")}catch{}if(s&&console.log(e.gray(`Found Claude CLI: ${s}`)),t&&console.log(e.gray(`Found Codex CLI: ${t}`)),(s||t)&&console.log(""),!s&&!t&&(console.error(e.red(`\u25FC No supported agent CLI found
4
+ `)),console.error(e.yellow("Please install Claude Code or Codex first:")),console.error(e.gray(" Claude Code: https://claude.ai/download")),console.error(e.gray(` Codex: https://developers.openai.com/codex
5
+ `)),process.exit(1)),console.log(e.blue("\u25FC Agent CLI Integration")),s)try{k()||b(),m(),f(),console.log(e.green("\u25FC borg configured for Claude Code"))}catch(o){console.error(e.red(`
6
+ \u25FC Failed to configure Claude Code: ${o.message}
7
+ `)),process.exit(1)}if(t)try{S()||y(),w(),C(),console.log(e.green("\u25FC borg configured for Codex"))}catch(o){console.error(e.red(`
8
+ \u25FC Failed to configure Codex: ${o.message}
9
+ `)),process.exit(1)}if(console.log(""),console.log(e.blue("\u25FC Google Authentication")),await x())console.log(e.green(`\u25FC Already authenticated
10
+ `));else try{await g(c?{noBrowser:!0}:void 0)}catch(o){console.error(e.red(`
11
+ \u25FC Authentication failed: ${o.message}
12
+ `)),console.error(e.yellow("Re-run `borg setup` to try again.\n")),process.exit(1)}console.log(e.blue("\u25FC Subscription Check"));let n;try{n=await i()}catch(o){console.error(e.red(`
13
+ \u25FC Failed to check subscription: ${o.message}
14
+ `)),process.exit(1)}if(n=await h(n,{check:i,sleep:o=>new Promise(r=>setTimeout(r,o)),onRetry:(o,r)=>console.log(e.gray(`\u25FC Checking subscription... (attempt ${o}/${r})`))}),n.hasAccess)if(console.log(e.green("\u25FC Active subscription found")),n.expiresAt){const o=new Date(n.expiresAt);console.log(e.gray(` Expires: ${o.toLocaleDateString()}
15
+ `))}else console.log("");else{console.log(e.green("\u25FC You're on the Free tier \u2014 permanent, no card needed: 1 cube + 3 drones + 100 req/hr.")),console.log(e.gray(`\u25FC Start using borgmcp right now. Upgrade any time for unlimited cubes + drones ($1/cube/month).
16
+ `));const{subscribeMethod:o}=await d({type:"select",name:"subscribeMethod",message:"You're ready on the Free tier. Want to do more?",choices:[{title:"\u25FC Continue on the Free tier (recommended)",value:"skip",description:"Start now \u2014 1 cube, 3 drones, 100 req/hr. No payment required."},{title:"\u25FC Upgrade to Cube tier \u2014 $1/cube/month",value:"web",description:"Unlimited cubes + drones + 1000 req/hr. Opens the subscribe page in your browser."},{title:"\u25FC Quick Stripe checkout",value:"stripe",description:"Fast upgrade checkout in the browser"},{title:"\u25FC I already subscribed \u2014 re-check",value:"recheck",description:"Re-check now \u2014 a just-completed subscription can take a moment to activate"}]});switch(o){case"web":console.log(e.blue(`
17
+ \u25FC Opening: https://borgmcp.ai/subscribe`));try{await a("https://borgmcp.ai/subscribe"),console.log(e.gray(`\u25FC Waiting for subscription (checking every 5s for 2 min)...
18
+ `)),await u()}catch(r){console.error(e.yellow(`
19
+ \u25FC ${r.message}`))}break;case"stripe":try{const r=await p();console.log(e.blue(`
20
+ \u25FC Opening Stripe: ${r}`)),await a(r),console.log(e.gray(`\u25FC Waiting for subscription...
21
+ `)),await u()}catch(r){console.error(e.red(`
22
+ \u25FC Failed to create checkout: ${r.message}
23
+ `))}break;case"recheck":try{(await i()).hasAccess?console.log(e.green(`
24
+ \u25FC Subscription found!
25
+ `)):console.log(e.yellow(`
26
+ \u25FC No subscription found
27
+ `))}catch(r){console.error(e.red(`
28
+ \u25FC Failed to recheck: ${r.message}
29
+ `))}break;case"skip":console.log(e.green(`
30
+ \u25FC You're all set on the Free tier: 1 cube, 3 drones, 100 req/hr.
31
+ `));break}}console.log(e.green.bold(`Setup complete!
32
+ `)),console.log(e.yellow(`\u{1F504} Restart Claude Code/Codex (or open a new session) for the changes to take effect.
33
+ `)),console.log(e.gray("\u25FC Next steps:")),console.log(e.gray('1. Run "borg" to start Claude Code or Codex with your cube')),console.log(e.gray(`2. Manage cubes and subscription at https://borgmcp.ai/dashboard
34
+ `))}async function u(){for(let s=0;s<24;s++){await new Promise(t=>setTimeout(t,5e3));try{if((await i()).hasAccess){console.log(e.green(`\u25FC Subscription activated!
35
+ `));return}}catch{}}throw new Error("Timeout - Run setup again after subscribing")}A().catch(c=>{console.error(e.red(`
36
+ \u25FC Setup failed: ${c.message}
37
+ `)),process.exit(1)});