borgmcp 0.2.0-beta.9 → 0.4.0
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/README.md +2 -2
- package/dist/auth.js +5 -5
- package/dist/auth.js.map +1 -1
- package/dist/claude.d.ts +9 -4
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +62 -26
- package/dist/claude.js.map +1 -1
- package/dist/config-utils.d.ts +32 -0
- package/dist/config-utils.d.ts.map +1 -1
- package/dist/config-utils.js +159 -0
- package/dist/config-utils.js.map +1 -1
- package/dist/cubes.d.ts +51 -0
- package/dist/cubes.d.ts.map +1 -0
- package/dist/cubes.js +161 -0
- package/dist/cubes.js.map +1 -0
- package/dist/inbox.d.ts +33 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +125 -0
- package/dist/inbox.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +701 -25
- package/dist/index.js.map +1 -1
- package/dist/log-audit.d.ts +27 -0
- package/dist/log-audit.d.ts.map +1 -0
- package/dist/log-audit.js +161 -0
- package/dist/log-audit.js.map +1 -0
- package/dist/postinstall.d.ts +1 -1
- package/dist/postinstall.d.ts.map +1 -1
- package/dist/postinstall.js +4 -4
- package/dist/postinstall.js.map +1 -1
- package/dist/regen-format.d.ts +59 -0
- package/dist/regen-format.d.ts.map +1 -0
- package/dist/regen-format.js +172 -0
- package/dist/regen-format.js.map +1 -0
- package/dist/regen.d.ts +19 -0
- package/dist/regen.d.ts.map +1 -0
- package/dist/regen.js +36 -0
- package/dist/regen.js.map +1 -0
- package/dist/remote-client.d.ts +186 -2
- package/dist/remote-client.d.ts.map +1 -1
- package/dist/remote-client.js +232 -11
- package/dist/remote-client.js.map +1 -1
- package/dist/setup.js +40 -33
- package/dist/setup.js.map +1 -1
- package/dist/sync.js +1 -1
- package/dist/sync.js.map +1 -1
- package/dist/templates.d.ts +33 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +96 -0
- package/dist/templates.js.map +1 -0
- package/package.json +11 -10
package/dist/cubes.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-project active-cube persistence for Borg MCP client
|
|
3
|
+
*
|
|
4
|
+
* Stores the currently-assimilated cube (name + drone session token + drone
|
|
5
|
+
* label + apiUrl) PER PROJECT in ~/.config/borgmcp/cubes.json. The "project"
|
|
6
|
+
* is identified by walking up from cwd to find a .git directory; if none is
|
|
7
|
+
* found, cwd itself is used as the project key.
|
|
8
|
+
*
|
|
9
|
+
* The session token is a bearer credential for drone-scoped REST endpoints,
|
|
10
|
+
* but it is not as sensitive as the OAuth tokens (which still live in the
|
|
11
|
+
* OS keychain via config.ts) — it only authorizes access within the cube
|
|
12
|
+
* the user has already been admitted to.
|
|
13
|
+
*
|
|
14
|
+
* apiUrl is captured at assimilate time so subprocess invocations (e.g. the
|
|
15
|
+
* SessionStart hook firing borg-regen) don't need BORG_API_URL in their env
|
|
16
|
+
* to know which worker to talk to.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync } from 'node:fs';
|
|
19
|
+
import { mkdir, readFile, writeFile, unlink } from 'node:fs/promises';
|
|
20
|
+
import { homedir } from 'node:os';
|
|
21
|
+
import { dirname, join, resolve } from 'node:path';
|
|
22
|
+
const CUBES_DIR = join(homedir(), '.config', 'borgmcp');
|
|
23
|
+
const CUBES_FILE = join(CUBES_DIR, 'cubes.json');
|
|
24
|
+
const INBOX_DIR = join(CUBES_DIR, 'inboxes');
|
|
25
|
+
/**
|
|
26
|
+
* Walk up from cwd looking for a .git directory. If found, return that
|
|
27
|
+
* directory. If not found by filesystem root, return the original cwd.
|
|
28
|
+
* The returned absolute path is the "project key" used to scope cube state.
|
|
29
|
+
*/
|
|
30
|
+
export function findProjectRoot(cwd = process.cwd()) {
|
|
31
|
+
let dir = resolve(cwd);
|
|
32
|
+
while (true) {
|
|
33
|
+
if (existsSync(join(dir, '.git')))
|
|
34
|
+
return dir;
|
|
35
|
+
const parent = dirname(dir);
|
|
36
|
+
if (parent === dir)
|
|
37
|
+
return resolve(cwd); // hit root, fall back to cwd
|
|
38
|
+
dir = parent;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Per-(cube, drone) inbox file path. Each drone gets its own file so that
|
|
43
|
+
* multiple drones in the same cube don't trample each other's writes when
|
|
44
|
+
* they each receive the same long-poll batch. The file lives under a
|
|
45
|
+
* per-cube subdir keyed by cube ID, then by drone ID (a UUID, globally
|
|
46
|
+
* unique).
|
|
47
|
+
*
|
|
48
|
+
* Validates cubeId/droneId as UUIDs before using them in a filesystem
|
|
49
|
+
* path. The values come from cubes.json (populated from server response),
|
|
50
|
+
* so the input is trusted in normal operation — but a regex guard is
|
|
51
|
+
* cheap defense against a corrupted file or future bug that would
|
|
52
|
+
* otherwise let `../` slip through into the inbox path.
|
|
53
|
+
*/
|
|
54
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
55
|
+
export function inboxPathForDrone(cubeId, droneId) {
|
|
56
|
+
if (!UUID_RE.test(cubeId))
|
|
57
|
+
throw new Error(`Invalid cubeId: ${cubeId}`);
|
|
58
|
+
if (!UUID_RE.test(droneId))
|
|
59
|
+
throw new Error(`Invalid droneId: ${droneId}`);
|
|
60
|
+
return join(INBOX_DIR, cubeId, `${droneId}.log`);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Type guard: true iff the parsed JSON looks like the new schema. Anything
|
|
64
|
+
* else (old single-cube schema, malformed, missing) is treated as "no state".
|
|
65
|
+
*/
|
|
66
|
+
function isCubesFile(data) {
|
|
67
|
+
return (data !== null &&
|
|
68
|
+
typeof data === 'object' &&
|
|
69
|
+
typeof data.projects === 'object' &&
|
|
70
|
+
data.projects !== null &&
|
|
71
|
+
!Array.isArray(data.projects));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Read the cubes.json file. Returns null if the file does not exist, is
|
|
75
|
+
* unparseable, or is in the old single-cube schema (per the project's no-
|
|
76
|
+
* backward-compat stance, the old shape is treated as absent — it will be
|
|
77
|
+
* overwritten the next time setActiveCube() runs).
|
|
78
|
+
*/
|
|
79
|
+
async function readCubesFile() {
|
|
80
|
+
let raw;
|
|
81
|
+
try {
|
|
82
|
+
raw = await readFile(CUBES_FILE, 'utf8');
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error?.code === 'ENOENT')
|
|
86
|
+
return null;
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(raw);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
if (!isCubesFile(parsed))
|
|
97
|
+
return null;
|
|
98
|
+
return parsed;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Write the cubes.json file, ensuring the parent directory exists.
|
|
102
|
+
*/
|
|
103
|
+
async function writeCubesFile(data) {
|
|
104
|
+
await mkdir(dirname(CUBES_FILE), { recursive: true });
|
|
105
|
+
await writeFile(CUBES_FILE, JSON.stringify(data, null, 2) + '\n', {
|
|
106
|
+
mode: 0o600,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get the currently-active cube for the current project, or null if not
|
|
111
|
+
* assimilated in this project. Entries written by older client versions
|
|
112
|
+
* that lack the `cubeId` field are treated as absent — re-assimilate to
|
|
113
|
+
* refresh.
|
|
114
|
+
*/
|
|
115
|
+
export async function getActiveCube() {
|
|
116
|
+
const data = await readCubesFile();
|
|
117
|
+
if (!data)
|
|
118
|
+
return null;
|
|
119
|
+
const key = findProjectRoot();
|
|
120
|
+
const entry = data.projects[key];
|
|
121
|
+
if (!entry || typeof entry.cubeId !== 'string' || !entry.cubeId)
|
|
122
|
+
return null;
|
|
123
|
+
if (typeof entry.droneId !== 'string' || !entry.droneId)
|
|
124
|
+
return null;
|
|
125
|
+
return entry;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Set the active cube for the current project. Preserves entries for all
|
|
129
|
+
* other projects.
|
|
130
|
+
*/
|
|
131
|
+
export async function setActiveCube(active) {
|
|
132
|
+
const existing = (await readCubesFile()) ?? { projects: {} };
|
|
133
|
+
existing.projects[findProjectRoot()] = active;
|
|
134
|
+
await writeCubesFile(existing);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Clear the active cube for the current project. If the projects map
|
|
138
|
+
* becomes empty as a result, remove the file entirely rather than leave
|
|
139
|
+
* an empty {projects:{}} skeleton.
|
|
140
|
+
*/
|
|
141
|
+
export async function clearActiveCube() {
|
|
142
|
+
const existing = await readCubesFile();
|
|
143
|
+
if (!existing)
|
|
144
|
+
return;
|
|
145
|
+
const key = findProjectRoot();
|
|
146
|
+
if (!(key in existing.projects))
|
|
147
|
+
return;
|
|
148
|
+
delete existing.projects[key];
|
|
149
|
+
if (Object.keys(existing.projects).length === 0) {
|
|
150
|
+
try {
|
|
151
|
+
await unlink(CUBES_FILE);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (error?.code !== 'ENOENT')
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
await writeCubesFile(existing);
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=cubes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cubes.js","sourceRoot":"","sources":["../src/cubes.ts"],"names":[],"mappings":"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"}
|
package/dist/inbox.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real-time drone wakeup via long-poll + inbox file.
|
|
3
|
+
*
|
|
4
|
+
* The MCP client spawns a background poller that long-polls the worker's
|
|
5
|
+
* /api/drone/wait endpoint. When new activity from OTHER drones arrives
|
|
6
|
+
* in the cube, we append a one-line summary per entry to a per-(cube,
|
|
7
|
+
* drone) inbox file (see inboxPathForDrone in cubes.ts). One file per
|
|
8
|
+
* drone session avoids the duplicate-write problem that would happen
|
|
9
|
+
* if multiple drones in the same cube shared a file.
|
|
10
|
+
*
|
|
11
|
+
* Own-drone entries are filtered out — there's no point waking a drone
|
|
12
|
+
* on its own post.
|
|
13
|
+
*
|
|
14
|
+
* The launcher kickoff (see claude.ts) arms a Monitor on the same path
|
|
15
|
+
* so Claude wakes the moment a line is appended — no waiting for the
|
|
16
|
+
* next /loop heartbeat.
|
|
17
|
+
*
|
|
18
|
+
* Dedup by entry id is layered in as defense against any precision /
|
|
19
|
+
* timestamp-cursor edge cases on the server side. Without it, a single
|
|
20
|
+
* sub-millisecond gap between the DB column and our cursor string would
|
|
21
|
+
* have the worker return the same row forever.
|
|
22
|
+
*
|
|
23
|
+
* The poller is fire-and-forget. If it dies, the launcher's fallback
|
|
24
|
+
* heartbeat (ScheduleWakeup, ~30 minutes) still keeps the drone in sync.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Spawn the background long-poll loop. Idempotent: if no active cube
|
|
28
|
+
* is configured for this project, the loop sleeps and re-checks. The
|
|
29
|
+
* loop runs until process exit. Errors are logged to stderr (so they
|
|
30
|
+
* don't pollute the MCP stdio channel) and the loop continues.
|
|
31
|
+
*/
|
|
32
|
+
export declare function startInboxPoller(): void;
|
|
33
|
+
//# sourceMappingURL=inbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox.d.ts","sourceRoot":"","sources":["../src/inbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAOH;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAMvC"}
|
package/dist/inbox.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real-time drone wakeup via long-poll + inbox file.
|
|
3
|
+
*
|
|
4
|
+
* The MCP client spawns a background poller that long-polls the worker's
|
|
5
|
+
* /api/drone/wait endpoint. When new activity from OTHER drones arrives
|
|
6
|
+
* in the cube, we append a one-line summary per entry to a per-(cube,
|
|
7
|
+
* drone) inbox file (see inboxPathForDrone in cubes.ts). One file per
|
|
8
|
+
* drone session avoids the duplicate-write problem that would happen
|
|
9
|
+
* if multiple drones in the same cube shared a file.
|
|
10
|
+
*
|
|
11
|
+
* Own-drone entries are filtered out — there's no point waking a drone
|
|
12
|
+
* on its own post.
|
|
13
|
+
*
|
|
14
|
+
* The launcher kickoff (see claude.ts) arms a Monitor on the same path
|
|
15
|
+
* so Claude wakes the moment a line is appended — no waiting for the
|
|
16
|
+
* next /loop heartbeat.
|
|
17
|
+
*
|
|
18
|
+
* Dedup by entry id is layered in as defense against any precision /
|
|
19
|
+
* timestamp-cursor edge cases on the server side. Without it, a single
|
|
20
|
+
* sub-millisecond gap between the DB column and our cursor string would
|
|
21
|
+
* have the worker return the same row forever.
|
|
22
|
+
*
|
|
23
|
+
* The poller is fire-and-forget. If it dies, the launcher's fallback
|
|
24
|
+
* heartbeat (ScheduleWakeup, ~30 minutes) still keeps the drone in sync.
|
|
25
|
+
*/
|
|
26
|
+
import { promises as fs } from 'node:fs';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
import { waitForActivity } from './remote-client.js';
|
|
29
|
+
import { getActiveCube, inboxPathForDrone } from './cubes.js';
|
|
30
|
+
/**
|
|
31
|
+
* Spawn the background long-poll loop. Idempotent: if no active cube
|
|
32
|
+
* is configured for this project, the loop sleeps and re-checks. The
|
|
33
|
+
* loop runs until process exit. Errors are logged to stderr (so they
|
|
34
|
+
* don't pollute the MCP stdio channel) and the loop continues.
|
|
35
|
+
*/
|
|
36
|
+
export function startInboxPoller() {
|
|
37
|
+
void runLoop().catch((err) => {
|
|
38
|
+
process.stderr.write(`[borg-mcp inbox poller] fatal: ${err?.message ?? err}\n`);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async function runLoop() {
|
|
42
|
+
// Initialize lastSeen to "now" so we don't replay history that was
|
|
43
|
+
// already in the regen() snapshot included in the session-start
|
|
44
|
+
// orientation. We only want NEW activity from here forward.
|
|
45
|
+
let lastSeen = new Date().toISOString();
|
|
46
|
+
let currentCubeId = null;
|
|
47
|
+
let ownDroneId = null;
|
|
48
|
+
// Ring buffer of recently-written entry ids — protects against any
|
|
49
|
+
// server-side cursor drift that would otherwise re-emit the same row.
|
|
50
|
+
const recentIds = [];
|
|
51
|
+
const RECENT_CAP = 500;
|
|
52
|
+
while (true) {
|
|
53
|
+
const active = await getActiveCube();
|
|
54
|
+
if (!active) {
|
|
55
|
+
// No active cube — sleep and re-check. The user might assimilate
|
|
56
|
+
// mid-session.
|
|
57
|
+
await sleep(5000);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
// Reset the cursor and dedup state when the active cube changes so
|
|
61
|
+
// we don't try to pull "since lastSeen" entries from the wrong
|
|
62
|
+
// timeline.
|
|
63
|
+
if (active.cubeId !== currentCubeId) {
|
|
64
|
+
currentCubeId = active.cubeId;
|
|
65
|
+
ownDroneId = active.droneId;
|
|
66
|
+
lastSeen = new Date().toISOString();
|
|
67
|
+
recentIds.length = 0;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const { entries, drones, roles } = await waitForActivity(active.sessionToken, active.apiUrl, lastSeen);
|
|
71
|
+
if (entries.length > 0) {
|
|
72
|
+
const seenSet = new Set(recentIds);
|
|
73
|
+
const fresh = entries.filter((e) => {
|
|
74
|
+
if (seenSet.has(e.id))
|
|
75
|
+
return false;
|
|
76
|
+
// Filter out our own posts — we already know what we wrote.
|
|
77
|
+
if (e.drone_id === ownDroneId)
|
|
78
|
+
return false;
|
|
79
|
+
return true;
|
|
80
|
+
});
|
|
81
|
+
if (fresh.length > 0) {
|
|
82
|
+
await appendToInbox(active.cubeId, active.droneId, fresh, drones, roles);
|
|
83
|
+
for (const e of fresh)
|
|
84
|
+
recentIds.push(e.id);
|
|
85
|
+
while (recentIds.length > RECENT_CAP)
|
|
86
|
+
recentIds.shift();
|
|
87
|
+
}
|
|
88
|
+
// Advance the cursor against ALL returned entries (including
|
|
89
|
+
// own / already-seen). Otherwise we'd long-poll forever for
|
|
90
|
+
// entries we've intentionally skipped.
|
|
91
|
+
const newest = entries.reduce((a, b) => new Date(a.created_at) > new Date(b.created_at) ? a : b);
|
|
92
|
+
lastSeen = typeof newest.created_at === 'string'
|
|
93
|
+
? newest.created_at
|
|
94
|
+
: new Date(newest.created_at).toISOString();
|
|
95
|
+
}
|
|
96
|
+
// Whether empty or not, immediately re-poll. The server already
|
|
97
|
+
// held the request for up to ~25s; no client-side sleep needed.
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// Network blip, auth refresh, etc — back off briefly so we don't
|
|
101
|
+
// hot-loop on a persistent failure.
|
|
102
|
+
process.stderr.write(`[borg-mcp inbox poller] ${err?.message ?? err}\n`);
|
|
103
|
+
await sleep(5000);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function appendToInbox(cubeId, droneId, entries, drones, roles) {
|
|
108
|
+
const droneById = new Map(drones.map((d) => [d.id, d]));
|
|
109
|
+
const roleById = new Map(roles.map((r) => [r.id, r]));
|
|
110
|
+
const lines = entries.map((e) => {
|
|
111
|
+
const d = droneById.get(e.drone_id);
|
|
112
|
+
const r = d ? roleById.get(d.role_id) : null;
|
|
113
|
+
const ts = typeof e.created_at === 'string'
|
|
114
|
+
? new Date(e.created_at).toISOString()
|
|
115
|
+
: new Date(e.created_at).toISOString();
|
|
116
|
+
return `${ts} ${d?.label ?? '?'} (${r?.name ?? '?'}): ${e.message}`;
|
|
117
|
+
});
|
|
118
|
+
const p = inboxPathForDrone(cubeId, droneId);
|
|
119
|
+
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
120
|
+
await fs.appendFile(p, lines.join('\n') + '\n', 'utf-8');
|
|
121
|
+
}
|
|
122
|
+
function sleep(ms) {
|
|
123
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=inbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox.js","sourceRoot":"","sources":["../src/inbox.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* 1. Connects to Claude Code via stdio transport
|
|
7
7
|
* 2. Authenticates via Google OAuth device flow
|
|
8
8
|
* 3. Proxies MCP tools to remote server at api.borgmcp.ai
|
|
9
|
-
* 4. Provides borg:
|
|
9
|
+
* 4. Provides the borg: cube tool surface (assimilate / cube / role /
|
|
10
|
+
* roster / read-log) so Claude can act as a Drone in a hive of
|
|
11
|
+
* collaborating sessions.
|
|
10
12
|
*/
|
|
11
13
|
export {};
|
|
12
14
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}
|