@nexrall/code-core 1.4.67 → 1.4.68
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/agent/agentRegistry.d.ts +27 -0
- package/dist/agent/agentRegistry.d.ts.map +1 -1
- package/dist/agent/agentRegistry.js +45 -0
- package/dist/agent/hooks.d.ts +7 -0
- package/dist/agent/hooks.d.ts.map +1 -1
- package/dist/agent/hooks.js +14 -3
- package/dist/agent/loop.d.ts +28 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +93 -10
- package/dist/agent/subTask.d.ts +10 -1
- package/dist/agent/subTask.d.ts.map +1 -1
- package/dist/agent/subTask.js +23 -6
- package/dist/agent/subTaskSupport.d.ts +11 -0
- package/dist/agent/subTaskSupport.d.ts.map +1 -1
- package/dist/agent/subTaskSupport.js +17 -1
- package/dist/api/client.d.ts +41 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +156 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/mcp/server.d.ts +39 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +189 -0
- package/dist/plugins/data.d.ts +23 -0
- package/dist/plugins/data.d.ts.map +1 -0
- package/dist/plugins/data.js +141 -0
- package/dist/plugins/index.d.ts +14 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +45 -4
- package/dist/plugins/installer.d.ts +83 -0
- package/dist/plugins/installer.d.ts.map +1 -1
- package/dist/plugins/installer.js +207 -1
- package/package.json +1 -1
package/dist/api/client.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.API_BASE = void 0;
|
|
6
|
+
exports.SCHEDULE_MAX_MINUTES = exports.SCHEDULE_MIN_MINUTES = exports.API_BASE = void 0;
|
|
7
7
|
exports.chooseFinalContent = chooseFinalContent;
|
|
8
8
|
exports.earlyToolResolveEnabled = earlyToolResolveEnabled;
|
|
9
9
|
exports.gzipRequestsEnabled = gzipRequestsEnabled;
|
|
@@ -20,6 +20,12 @@ exports.getUsageDaily = getUsageDaily;
|
|
|
20
20
|
exports.exchangeVscodeCode = exchangeVscodeCode;
|
|
21
21
|
exports.login = login;
|
|
22
22
|
exports.getUserProfile = getUserProfile;
|
|
23
|
+
exports.clampScheduleMinutes = clampScheduleMinutes;
|
|
24
|
+
exports.listCloudSchedules = listCloudSchedules;
|
|
25
|
+
exports.createCloudSchedule = createCloudSchedule;
|
|
26
|
+
exports.updateCloudSchedule = updateCloudSchedule;
|
|
27
|
+
exports.deleteCloudSchedule = deleteCloudSchedule;
|
|
28
|
+
exports.getCloudScheduleRuns = getCloudScheduleRuns;
|
|
23
29
|
const eventsource_parser_1 = require("eventsource-parser");
|
|
24
30
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
25
31
|
const crypto_1 = require("crypto");
|
|
@@ -1982,4 +1988,153 @@ async function getUserProfile() {
|
|
|
1982
1988
|
}
|
|
1983
1989
|
return (await response.json());
|
|
1984
1990
|
}
|
|
1991
|
+
// ─── Cloud schedules ────────────────────────────────────────────────────────
|
|
1992
|
+
//
|
|
1993
|
+
// Recurring agent tasks that run on NEXRALL'S servers (routes/browserAgent.js
|
|
1994
|
+
// `/api/browser-agent/schedules`, picked up every 60s by services/agentTaskCron),
|
|
1995
|
+
// so the user can close the laptop and the schedule still fires — NOT the same
|
|
1996
|
+
// thing as the local `/loop` + cron tools, which live and die with the session.
|
|
1997
|
+
//
|
|
1998
|
+
// Wire-format notes that decide the code below:
|
|
1999
|
+
// • ids are sharded BIGINTs (migrations 048/080) that exceed
|
|
2000
|
+
// Number.MAX_SAFE_INTEGER. pg returns them as strings, the server's own
|
|
2001
|
+
// routes validate /^\d+$/ and pass the string straight back to pg — so an
|
|
2002
|
+
// id is a digit STRING from end to end here. A Number()/parseInt anywhere on
|
|
2003
|
+
// this path would round to the wrong row (the exact bug class the server
|
|
2004
|
+
// route comments about).
|
|
2005
|
+
// • the server clamps intervals to 15 minutes – 7 days (clampInterval in
|
|
2006
|
+
// routes/browserAgent.js); we validate client-side too, so callers can show
|
|
2007
|
+
// the real rule instead of relaying a 400.
|
|
2008
|
+
// • rows come back snake_case (raw pg). We expose camelCase, one stable shape
|
|
2009
|
+
// for every caller, so no caller has to know column names.
|
|
2010
|
+
exports.SCHEDULE_MIN_MINUTES = 15;
|
|
2011
|
+
exports.SCHEDULE_MAX_MINUTES = 7 * 24 * 60;
|
|
2012
|
+
/** Enforce the server's own bounds with a message that names them. */
|
|
2013
|
+
function clampScheduleMinutes(v) {
|
|
2014
|
+
const n = Math.round(Number(v));
|
|
2015
|
+
if (!Number.isFinite(n) || n < exports.SCHEDULE_MIN_MINUTES || n > exports.SCHEDULE_MAX_MINUTES) {
|
|
2016
|
+
throw new Error(`Interval must be between ${exports.SCHEDULE_MIN_MINUTES} minutes and ${exports.SCHEDULE_MAX_MINUTES / (24 * 60)} days.`);
|
|
2017
|
+
}
|
|
2018
|
+
return n;
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* A schedule id, still a digit string. Validated here because a malformed id
|
|
2022
|
+
* would otherwise be interpolated into the request path.
|
|
2023
|
+
*/
|
|
2024
|
+
function scheduleId(id) {
|
|
2025
|
+
const s = String(id ?? '').trim();
|
|
2026
|
+
if (!/^\d+$/.test(s)) {
|
|
2027
|
+
throw new Error(`Invalid schedule id "${id}" — expected the digit string shown in the schedule list.`);
|
|
2028
|
+
}
|
|
2029
|
+
return s;
|
|
2030
|
+
}
|
|
2031
|
+
function isoOrNull(v) {
|
|
2032
|
+
return typeof v === 'string' && v ? v : null;
|
|
2033
|
+
}
|
|
2034
|
+
/** One shape for a list row, a create response or a partial patch response. */
|
|
2035
|
+
function scheduleRow(row) {
|
|
2036
|
+
return {
|
|
2037
|
+
// String(), never Number() — see the header note on sharded ids.
|
|
2038
|
+
id: row.id == null ? '' : String(row.id),
|
|
2039
|
+
task: typeof row.task === 'string' ? row.task : '',
|
|
2040
|
+
startUrl: isoOrNull(row.start_url ?? row.startUrl),
|
|
2041
|
+
domains: Array.isArray(row.domains) ? row.domains.map((d) => String(d)) : null,
|
|
2042
|
+
intervalMinutes: Number(row.interval_minutes ?? row.intervalMinutes ?? 0) || 0,
|
|
2043
|
+
enabled: row.enabled === true,
|
|
2044
|
+
nextRunAt: isoOrNull(row.next_run_at ?? row.nextRunAt),
|
|
2045
|
+
lastRunAt: isoOrNull(row.last_run_at ?? row.lastRunAt),
|
|
2046
|
+
lastStatus: isoOrNull(row.last_status ?? row.lastStatus),
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
function scheduleRunRow(row) {
|
|
2050
|
+
return {
|
|
2051
|
+
id: row.id == null ? '' : String(row.id),
|
|
2052
|
+
status: typeof row.status === 'string' ? row.status : '',
|
|
2053
|
+
summary: typeof row.summary === 'string' && row.summary ? row.summary : null,
|
|
2054
|
+
steps: Number(row.steps ?? 0) || 0,
|
|
2055
|
+
createdAt: isoOrNull(row.created_at ?? row.createdAt),
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
/** The server answers errors as {error: '<sentence>'}; prefer that sentence. */
|
|
2059
|
+
function apiErrorMessage(status, text) {
|
|
2060
|
+
try {
|
|
2061
|
+
const j = JSON.parse(text);
|
|
2062
|
+
if (typeof j.error === 'string' && j.error)
|
|
2063
|
+
return j.error;
|
|
2064
|
+
}
|
|
2065
|
+
catch { /* not JSON — fall through to the raw text */ }
|
|
2066
|
+
return `API error ${status}${text ? `: ${text}` : ''}`;
|
|
2067
|
+
}
|
|
2068
|
+
async function scheduleRequest(method, path, body) {
|
|
2069
|
+
const fetchOnce = () => (0, node_fetch_1.default)(`${exports.API_BASE}/api/browser-agent${path}`, {
|
|
2070
|
+
method,
|
|
2071
|
+
headers: authHeaders(),
|
|
2072
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
2073
|
+
});
|
|
2074
|
+
let response = await fetchOnce();
|
|
2075
|
+
// Same silent-refresh pattern as getBalance/getOrgs — an expired token on the
|
|
2076
|
+
// first call of a session is the common case, not an error to surface.
|
|
2077
|
+
if (response.status === 401 || response.status === 403) {
|
|
2078
|
+
const text = await response.text().catch(() => '');
|
|
2079
|
+
if (isExpiredTokenResponse(response.status, text) && await refreshAccessToken()) {
|
|
2080
|
+
response = await fetchOnce();
|
|
2081
|
+
}
|
|
2082
|
+
else {
|
|
2083
|
+
throw new Error(apiErrorMessage(response.status, text));
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
if (!response.ok) {
|
|
2087
|
+
throw new Error(apiErrorMessage(response.status, await response.text().catch(() => '')));
|
|
2088
|
+
}
|
|
2089
|
+
return response.json().catch(() => null);
|
|
2090
|
+
}
|
|
2091
|
+
async function listCloudSchedules() {
|
|
2092
|
+
const json = (await scheduleRequest('GET', '/schedules'));
|
|
2093
|
+
return Array.isArray(json?.schedules)
|
|
2094
|
+
? json.schedules.map((r) => scheduleRow(r))
|
|
2095
|
+
: [];
|
|
2096
|
+
}
|
|
2097
|
+
async function createCloudSchedule(input) {
|
|
2098
|
+
const task = String(input.task ?? '').trim();
|
|
2099
|
+
if (!task)
|
|
2100
|
+
throw new Error('A schedule needs a task.');
|
|
2101
|
+
const json = (await scheduleRequest('POST', '/schedules', {
|
|
2102
|
+
task,
|
|
2103
|
+
intervalMinutes: clampScheduleMinutes(input.intervalMinutes),
|
|
2104
|
+
...(input.startUrl != null && input.startUrl !== '' ? { startUrl: input.startUrl } : {}),
|
|
2105
|
+
...(Array.isArray(input.domains) && input.domains.length ? { domains: input.domains } : {}),
|
|
2106
|
+
...(input.enabled === false ? { enabled: false } : {}),
|
|
2107
|
+
}));
|
|
2108
|
+
if (!json?.schedule)
|
|
2109
|
+
throw new Error('The server accepted the schedule but returned no record.');
|
|
2110
|
+
return scheduleRow(json.schedule);
|
|
2111
|
+
}
|
|
2112
|
+
async function updateCloudSchedule(id, patch) {
|
|
2113
|
+
const body = {};
|
|
2114
|
+
if (typeof patch.task === 'string' && patch.task.trim())
|
|
2115
|
+
body.task = patch.task.trim();
|
|
2116
|
+
if (patch.intervalMinutes != null)
|
|
2117
|
+
body.intervalMinutes = clampScheduleMinutes(patch.intervalMinutes);
|
|
2118
|
+
if (typeof patch.enabled === 'boolean')
|
|
2119
|
+
body.enabled = patch.enabled;
|
|
2120
|
+
if (typeof patch.startUrl === 'string')
|
|
2121
|
+
body.startUrl = patch.startUrl;
|
|
2122
|
+
if (Array.isArray(patch.domains))
|
|
2123
|
+
body.domains = patch.domains;
|
|
2124
|
+
if (!Object.keys(body).length)
|
|
2125
|
+
throw new Error('Nothing to update.');
|
|
2126
|
+
const json = (await scheduleRequest('PATCH', `/schedules/${scheduleId(id)}`, body));
|
|
2127
|
+
if (!json?.schedule)
|
|
2128
|
+
throw new Error('The server accepted the update but returned no record.');
|
|
2129
|
+
return scheduleRow(json.schedule);
|
|
2130
|
+
}
|
|
2131
|
+
async function deleteCloudSchedule(id) {
|
|
2132
|
+
await scheduleRequest('DELETE', `/schedules/${scheduleId(id)}`);
|
|
2133
|
+
}
|
|
2134
|
+
async function getCloudScheduleRuns(id) {
|
|
2135
|
+
const json = (await scheduleRequest('GET', `/schedules/${scheduleId(id)}/runs`));
|
|
2136
|
+
return Array.isArray(json?.runs)
|
|
2137
|
+
? json.runs.map((r) => scheduleRunRow(r))
|
|
2138
|
+
: [];
|
|
2139
|
+
}
|
|
1985
2140
|
//# sourceMappingURL=client.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export * from './agent/skills';
|
|
|
18
18
|
export * from './mcp/client';
|
|
19
19
|
export * from './mcp/httpClient';
|
|
20
20
|
export * from './mcp/manager';
|
|
21
|
+
export * from './mcp/server';
|
|
21
22
|
export * from './mcp/stats';
|
|
22
23
|
export * from './checkpoint/manager';
|
|
23
24
|
export * from './commands/loader';
|
|
@@ -32,6 +33,7 @@ export * from './permissions/destructive';
|
|
|
32
33
|
export * from './plugins/index';
|
|
33
34
|
export * from './plugins/installer';
|
|
34
35
|
export * from './plugins/sources';
|
|
36
|
+
export * from './plugins/data';
|
|
35
37
|
export * from './agent/trust';
|
|
36
38
|
export * from './agent/crossProcessLock';
|
|
37
39
|
export * from './agent/worktree';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ __exportStar(require("./agent/skills"), exports);
|
|
|
34
34
|
__exportStar(require("./mcp/client"), exports);
|
|
35
35
|
__exportStar(require("./mcp/httpClient"), exports);
|
|
36
36
|
__exportStar(require("./mcp/manager"), exports);
|
|
37
|
+
__exportStar(require("./mcp/server"), exports);
|
|
37
38
|
__exportStar(require("./mcp/stats"), exports);
|
|
38
39
|
__exportStar(require("./checkpoint/manager"), exports);
|
|
39
40
|
__exportStar(require("./commands/loader"), exports);
|
|
@@ -48,6 +49,7 @@ __exportStar(require("./permissions/destructive"), exports);
|
|
|
48
49
|
__exportStar(require("./plugins/index"), exports);
|
|
49
50
|
__exportStar(require("./plugins/installer"), exports);
|
|
50
51
|
__exportStar(require("./plugins/sources"), exports);
|
|
52
|
+
__exportStar(require("./plugins/data"), exports);
|
|
51
53
|
__exportStar(require("./agent/trust"), exports);
|
|
52
54
|
__exportStar(require("./agent/crossProcessLock"), exports);
|
|
53
55
|
__exportStar(require("./agent/worktree"), exports);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Readable, Writable } from 'stream';
|
|
2
|
+
import type { ToolResult } from '../types';
|
|
3
|
+
export interface McpServerTool {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: 'object';
|
|
8
|
+
properties?: Record<string, unknown>;
|
|
9
|
+
required?: string[];
|
|
10
|
+
};
|
|
11
|
+
run(input: Record<string, unknown>, ctx: {
|
|
12
|
+
workDir: string;
|
|
13
|
+
}): Promise<ToolResult>;
|
|
14
|
+
}
|
|
15
|
+
export interface ServeOptions {
|
|
16
|
+
/** Workspace root relative paths resolve against (the client's project). */
|
|
17
|
+
workDir: string;
|
|
18
|
+
/** Reported in `serverInfo` during initialize. */
|
|
19
|
+
version?: string;
|
|
20
|
+
/** Defaults to `readOnlyMcpTools()`. */
|
|
21
|
+
tools?: McpServerTool[];
|
|
22
|
+
/** Injectable for tests; default process.stdin/stdout. */
|
|
23
|
+
input?: Readable;
|
|
24
|
+
output?: Writable;
|
|
25
|
+
/** Diagnostics — stderr by convention. NEVER stdout. */
|
|
26
|
+
log?: (message: string) => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The read-only surface `nex mcp serve` offers. Kept small on purpose: every tool here is one
|
|
30
|
+
* an outside agent can call with no human in the loop, so nothing that writes, deletes or
|
|
31
|
+
* executes may join without a permission story first.
|
|
32
|
+
*/
|
|
33
|
+
export declare function readOnlyMcpTools(): McpServerTool[];
|
|
34
|
+
/**
|
|
35
|
+
* Serve MCP on stdio until the client closes the pipe. Resolves (never rejects) at EOF — the
|
|
36
|
+
* standard way an MCP client says "stop" is to close stdin, and the exit code is then 0.
|
|
37
|
+
*/
|
|
38
|
+
export declare function serveMcp(opts: ServeOptions): Promise<void>;
|
|
39
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAEjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAI3C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACpF;AAED,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,KAAK,CAAC,EAAE,aAAa,EAAE,CAAC;IACxB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,wDAAwD;IACxD,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAaD;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,EAAE,CA+BlD;AASD;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAuHhE"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// MCP server — the other direction of `mcp/client.ts`.
|
|
3
|
+
//
|
|
4
|
+
// Point any MCP client at `nex mcp serve` and it can read this machine's files with the same
|
|
5
|
+
// tools the CLI uses. Deliberately READ-ONLY for now: the client is usually another agent with
|
|
6
|
+
// its own permission model, and a server that silently edits files has no one to ask — a write
|
|
7
|
+
// toolset only belongs here once there is a person (or a policy file) behind the approval.
|
|
8
|
+
//
|
|
9
|
+
// Framing is the same newline-delimited JSON-RPC 2.0 the client parses, so our own client is
|
|
10
|
+
// the natural test driver. Two hard rules, both enforced by tests:
|
|
11
|
+
// * stdout carries protocol frames and NOTHING else — one stray console.log corrupts the
|
|
12
|
+
// channel; every diagnostic goes to stderr;
|
|
13
|
+
// * a bad line, an unknown method or a throwing tool must never end the loop.
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.readOnlyMcpTools = readOnlyMcpTools;
|
|
16
|
+
exports.serveMcp = serveMcp;
|
|
17
|
+
const executor_1 = require("../tools/executor");
|
|
18
|
+
// ─── The toolset ──────────────────────────────────────────────────────────────
|
|
19
|
+
/** Wrap one executor tool. The executor is the single source of behaviour — same caps, same
|
|
20
|
+
* error messages, same path resolution as the CLI's own turns. */
|
|
21
|
+
const fromExecutor = (name, description, inputSchema) => ({
|
|
22
|
+
name,
|
|
23
|
+
description,
|
|
24
|
+
inputSchema,
|
|
25
|
+
run: (input, ctx) => (0, executor_1.executeTool)(name, input, undefined, undefined, ctx.workDir),
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* The read-only surface `nex mcp serve` offers. Kept small on purpose: every tool here is one
|
|
29
|
+
* an outside agent can call with no human in the loop, so nothing that writes, deletes or
|
|
30
|
+
* executes may join without a permission story first.
|
|
31
|
+
*/
|
|
32
|
+
function readOnlyMcpTools() {
|
|
33
|
+
return [
|
|
34
|
+
fromExecutor('read_file', 'Read a text file. Returns numbered lines so later edits can cite them. Binary files are refused.', { type: 'object', properties: {
|
|
35
|
+
path: { type: 'string', description: 'File path, absolute or relative to the workspace root' },
|
|
36
|
+
offset: { type: 'number', description: '0-based line to start from (default 0)' },
|
|
37
|
+
limit: { type: 'number', description: 'How many lines to return' },
|
|
38
|
+
}, required: ['path'] }),
|
|
39
|
+
fromExecutor('list_directory', 'List the entries of a directory, each marked as file or directory.', { type: 'object', properties: {
|
|
40
|
+
path: { type: 'string', description: 'Directory path (default: the workspace root)' },
|
|
41
|
+
} }),
|
|
42
|
+
fromExecutor('glob', 'Find files by glob pattern, e.g. "**/*.ts" or "src/**/*.test.js".', { type: 'object', properties: {
|
|
43
|
+
pattern: { type: 'string', description: 'Glob pattern' },
|
|
44
|
+
path: { type: 'string', description: 'Directory to search from (default: the workspace root)' },
|
|
45
|
+
}, required: ['pattern'] }),
|
|
46
|
+
fromExecutor('search_files', 'Search file contents with a regular expression. Returns matching file paths by default; ask for context lines to get the matches themselves.', { type: 'object', properties: {
|
|
47
|
+
pattern: { type: 'string', description: 'Regular expression' },
|
|
48
|
+
path: { type: 'string', description: 'Directory to search (default: the workspace root)' },
|
|
49
|
+
type: { type: 'string', description: '"files" (paths only) or "content" (matching lines)' },
|
|
50
|
+
include: { type: 'string', description: 'Only files matching this glob, e.g. "*.ts"' },
|
|
51
|
+
case_insensitive: { type: 'boolean', description: 'Case-insensitive match' },
|
|
52
|
+
context_lines: { type: 'number', description: 'Lines of context around each match (implies type "content")' },
|
|
53
|
+
}, required: ['pattern'] }),
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
// ─── Protocol ─────────────────────────────────────────────────────────────────
|
|
57
|
+
/** Versions whose framing we actually implement (they are all the same for our surface). */
|
|
58
|
+
const SUPPORTED_PROTOCOLS = ['2024-11-05', '2025-03-26', '2025-06-18'];
|
|
59
|
+
const DEFAULT_PROTOCOL = '2024-11-05';
|
|
60
|
+
const SERVER_NAME = 'nexrall';
|
|
61
|
+
/**
|
|
62
|
+
* Serve MCP on stdio until the client closes the pipe. Resolves (never rejects) at EOF — the
|
|
63
|
+
* standard way an MCP client says "stop" is to close stdin, and the exit code is then 0.
|
|
64
|
+
*/
|
|
65
|
+
async function serveMcp(opts) {
|
|
66
|
+
const input = opts.input ?? process.stdin;
|
|
67
|
+
const output = opts.output ?? process.stdout;
|
|
68
|
+
const log = opts.log ?? ((m) => process.stderr.write(`[nexrall mcp] ${m}\n`));
|
|
69
|
+
const tools = opts.tools ?? readOnlyMcpTools();
|
|
70
|
+
const version = opts.version ?? '0.0.0';
|
|
71
|
+
const byName = new Map(tools.map((t) => [t.name, t]));
|
|
72
|
+
const send = (msg) => { output.write(JSON.stringify(msg) + '\n'); };
|
|
73
|
+
const reply = (id, result) => send({ jsonrpc: '2.0', id, result });
|
|
74
|
+
const replyError = (id, code, message) => send({ jsonrpc: '2.0', id, error: { code, message } });
|
|
75
|
+
async function handle(message) {
|
|
76
|
+
const id = message.id;
|
|
77
|
+
const isRequest = id !== undefined && id !== null;
|
|
78
|
+
const method = message.method;
|
|
79
|
+
const params = (message.params ?? {});
|
|
80
|
+
switch (method) {
|
|
81
|
+
case 'initialize': {
|
|
82
|
+
// Echo the client's version when we implement it, else our default — that is the
|
|
83
|
+
// spec's instruction and the number is inert for this surface either way.
|
|
84
|
+
const asked = typeof params.protocolVersion === 'string' ? params.protocolVersion : '';
|
|
85
|
+
reply(id, {
|
|
86
|
+
protocolVersion: SUPPORTED_PROTOCOLS.includes(asked) ? asked : DEFAULT_PROTOCOL,
|
|
87
|
+
capabilities: { tools: {} },
|
|
88
|
+
serverInfo: { name: SERVER_NAME, version },
|
|
89
|
+
instructions: `Read-only file access to ${opts.workDir}. ` +
|
|
90
|
+
`Tools: ${tools.map((t) => t.name).join(', ')}. Nothing here writes, deletes or runs commands.`,
|
|
91
|
+
});
|
|
92
|
+
log(`initialize from ${typeof params.clientInfo === 'object' && params.clientInfo ? JSON.stringify(params.clientInfo.name ?? 'client') : 'client'}`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
case 'notifications/initialized':
|
|
96
|
+
case 'notifications/cancelled':
|
|
97
|
+
return; // notifications take no reply
|
|
98
|
+
case 'ping':
|
|
99
|
+
reply(id, {});
|
|
100
|
+
return;
|
|
101
|
+
case 'tools/list':
|
|
102
|
+
reply(id, { tools: tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })) });
|
|
103
|
+
return;
|
|
104
|
+
case 'tools/call': {
|
|
105
|
+
const name = typeof params.name === 'string' ? params.name : '';
|
|
106
|
+
const args = (params.arguments ?? {});
|
|
107
|
+
if (!name)
|
|
108
|
+
return replyError(id, -32602, 'Invalid params: name is required');
|
|
109
|
+
const tool = byName.get(name);
|
|
110
|
+
if (!tool) {
|
|
111
|
+
// A RESULT, not a protocol error: the calling model should read "not offered" as tool
|
|
112
|
+
// output and pick another route, not see the whole call fail.
|
|
113
|
+
reply(id, { content: [{ type: 'text', text: `Tool "${name}" is not offered by this server (read-only). Available: ${[...byName.keys()].join(', ')}.` }], isError: true });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const res = await tool.run(args, { workDir: opts.workDir });
|
|
118
|
+
if (res.error)
|
|
119
|
+
reply(id, { content: [{ type: 'text', text: res.error }], isError: true });
|
|
120
|
+
else
|
|
121
|
+
reply(id, { content: [{ type: 'text', text: res.output ?? '' }] });
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
// A throwing tool is the tool's problem, not the session's — answer and keep serving.
|
|
125
|
+
reply(id, { content: [{ type: 'text', text: err instanceof Error ? err.message : String(err) }], isError: true });
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
default:
|
|
130
|
+
if (isRequest)
|
|
131
|
+
replyError(id, -32601, `Method not found: ${method}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
input.setEncoding('utf8');
|
|
136
|
+
let buffer = '';
|
|
137
|
+
let queue = Promise.resolve();
|
|
138
|
+
const feed = (line) => {
|
|
139
|
+
// Chain instead of racing: responses stay in request order and writes never interleave.
|
|
140
|
+
queue = queue.then(async () => {
|
|
141
|
+
const trimmed = line.trim();
|
|
142
|
+
if (!trimmed)
|
|
143
|
+
return;
|
|
144
|
+
let message;
|
|
145
|
+
try {
|
|
146
|
+
message = JSON.parse(trimmed);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
log(`ignoring a line that is not JSON (${trimmed.slice(0, 80)})`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!message || message.jsonrpc !== '2.0' || typeof message.method !== 'string') {
|
|
153
|
+
log('ignoring a message without jsonrpc 2.0 + method');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
await handle(message);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
// handle() is written not to throw, so reaching here is a bug in the server — report
|
|
161
|
+
// it as a JSON-RPC error rather than dropping the connection.
|
|
162
|
+
if (message.id !== undefined)
|
|
163
|
+
replyError(message.id, -32603, err instanceof Error ? err.message : String(err));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
await new Promise((resolve) => {
|
|
168
|
+
input.on('data', (chunk) => {
|
|
169
|
+
buffer += chunk.toString();
|
|
170
|
+
let nl;
|
|
171
|
+
while ((nl = buffer.indexOf('\n')) >= 0) {
|
|
172
|
+
feed(buffer.slice(0, nl));
|
|
173
|
+
buffer = buffer.slice(nl + 1);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
const done = () => {
|
|
177
|
+
if (buffer.trim()) {
|
|
178
|
+
feed(buffer);
|
|
179
|
+
buffer = '';
|
|
180
|
+
}
|
|
181
|
+
resolve();
|
|
182
|
+
};
|
|
183
|
+
input.on('end', done);
|
|
184
|
+
input.on('close', done);
|
|
185
|
+
});
|
|
186
|
+
await queue; // let in-flight calls finish before the process exits
|
|
187
|
+
log('client closed stdin — server exiting');
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Claude Code's rule: letters, digits, `_`, `-` survive; everything else becomes `-`. */
|
|
2
|
+
export declare function sanitizePluginId(name: string): string;
|
|
3
|
+
/** Where this plugin's persistent data lives. Pure — does not touch the disk. */
|
|
4
|
+
export declare function pluginDataDir(name: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* `pluginDataDir`, created if missing. Returns the path either way.
|
|
7
|
+
*
|
|
8
|
+
* Called right before a plugin's process is spawned with NEXRALL_PLUGIN_DATA in
|
|
9
|
+
* its environment, so the promise "the variable points at a directory you may
|
|
10
|
+
* write to" holds the moment the plugin can read it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ensurePluginDataDir(name: string): string;
|
|
13
|
+
/** True when the plugin has data on disk from earlier runs. */
|
|
14
|
+
export declare function pluginHasData(name: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Raw bytes under a plugin's data directory (0 when missing). Bounded walk: a
|
|
17
|
+
* plugin can park a large tree here, and reporting the size must not be the
|
|
18
|
+
* thing that makes `nex plugin prune` slow, so entries are counted up to a cap.
|
|
19
|
+
*/
|
|
20
|
+
export declare function pluginDataSize(name: string, maxEntries?: number): number;
|
|
21
|
+
/** Remove a plugin's data directory. Returns true when something was removed. */
|
|
22
|
+
export declare function removePluginData(name: string): boolean;
|
|
23
|
+
//# sourceMappingURL=data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/plugins/data.ts"],"names":[],"mappings":"AAwBA,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMnD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,SAAO,GAAG,MAAM,CAiBtE;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAStD"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.sanitizePluginId = sanitizePluginId;
|
|
37
|
+
exports.pluginDataDir = pluginDataDir;
|
|
38
|
+
exports.ensurePluginDataDir = ensurePluginDataDir;
|
|
39
|
+
exports.pluginHasData = pluginHasData;
|
|
40
|
+
exports.pluginDataSize = pluginDataSize;
|
|
41
|
+
exports.removePluginData = removePluginData;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
// ─── Plugin data directories ──────────────────────────────────────────────────
|
|
46
|
+
//
|
|
47
|
+
// A plugin's own files are replaced on every update, so state a plugin wants to
|
|
48
|
+
// keep — caches, node_modules, a downloaded model, a database — cannot live
|
|
49
|
+
// inside its install directory. It gets a per-plugin data directory instead:
|
|
50
|
+
//
|
|
51
|
+
// ~/.nexrall/plugin-data/<id>/
|
|
52
|
+
//
|
|
53
|
+
// which survives updates and is exposed to the plugin's own processes (its hook
|
|
54
|
+
// commands and its MCP servers) as NEXRALL_PLUGIN_DATA. Same idea as Claude
|
|
55
|
+
// Code's ${CLAUDE_PLUGIN_DATA}, same id rule: everything outside [A-Za-z0-9_-]
|
|
56
|
+
// becomes "-", so a name can never escape the parent directory or collide with
|
|
57
|
+
// path syntax ("my.plugin" → "my-plugin"). Two names that differ only in those
|
|
58
|
+
// characters share a directory — which is why the id is derived, never taken
|
|
59
|
+
// verbatim.
|
|
60
|
+
//
|
|
61
|
+
// The directory is created on FIRST REFERENCE, not at install: a plugin that
|
|
62
|
+
// never touches its data dir leaves nothing behind, and `nex plugin remove`
|
|
63
|
+
// does not have to guess whether a directory it finds is in use.
|
|
64
|
+
/** Claude Code's rule: letters, digits, `_`, `-` survive; everything else becomes `-`. */
|
|
65
|
+
function sanitizePluginId(name) {
|
|
66
|
+
return name.replace(/[^\w-]/g, '-');
|
|
67
|
+
}
|
|
68
|
+
/** Where this plugin's persistent data lives. Pure — does not touch the disk. */
|
|
69
|
+
function pluginDataDir(name) {
|
|
70
|
+
return path.join(os.homedir(), '.nexrall', 'plugin-data', sanitizePluginId(name));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* `pluginDataDir`, created if missing. Returns the path either way.
|
|
74
|
+
*
|
|
75
|
+
* Called right before a plugin's process is spawned with NEXRALL_PLUGIN_DATA in
|
|
76
|
+
* its environment, so the promise "the variable points at a directory you may
|
|
77
|
+
* write to" holds the moment the plugin can read it.
|
|
78
|
+
*/
|
|
79
|
+
function ensurePluginDataDir(name) {
|
|
80
|
+
const dir = pluginDataDir(name);
|
|
81
|
+
try {
|
|
82
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
catch { /* unwritable home: the plugin will see the path and fail its own write */ }
|
|
85
|
+
return dir;
|
|
86
|
+
}
|
|
87
|
+
/** True when the plugin has data on disk from earlier runs. */
|
|
88
|
+
function pluginHasData(name) {
|
|
89
|
+
try {
|
|
90
|
+
return fs.readdirSync(pluginDataDir(name)).length > 0;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Raw bytes under a plugin's data directory (0 when missing). Bounded walk: a
|
|
98
|
+
* plugin can park a large tree here, and reporting the size must not be the
|
|
99
|
+
* thing that makes `nex plugin prune` slow, so entries are counted up to a cap.
|
|
100
|
+
*/
|
|
101
|
+
function pluginDataSize(name, maxEntries = 5000) {
|
|
102
|
+
let total = 0;
|
|
103
|
+
let seen = 0;
|
|
104
|
+
const walk = (dir) => {
|
|
105
|
+
let entries;
|
|
106
|
+
try {
|
|
107
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
for (const e of entries) {
|
|
113
|
+
if (seen++ >= maxEntries)
|
|
114
|
+
return;
|
|
115
|
+
const full = path.join(dir, e.name);
|
|
116
|
+
try {
|
|
117
|
+
if (e.isDirectory())
|
|
118
|
+
walk(full);
|
|
119
|
+
else
|
|
120
|
+
total += fs.statSync(full).size;
|
|
121
|
+
}
|
|
122
|
+
catch { /* unreadable entry — skip it, size is an estimate */ }
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
walk(pluginDataDir(name));
|
|
126
|
+
return total;
|
|
127
|
+
}
|
|
128
|
+
/** Remove a plugin's data directory. Returns true when something was removed. */
|
|
129
|
+
function removePluginData(name) {
|
|
130
|
+
const dir = pluginDataDir(name);
|
|
131
|
+
try {
|
|
132
|
+
if (!fs.existsSync(dir))
|
|
133
|
+
return false;
|
|
134
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=data.js.map
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -58,8 +58,20 @@ export declare function loadPlugins(workDir: string): PluginInfo[];
|
|
|
58
58
|
export declare function findPlugin(workDir: string, name: string, scope: 'project' | 'global'): PluginInfo | undefined;
|
|
59
59
|
/** Subdirectories of every ENABLED installed plugin that hold `kind` assets (existing only). */
|
|
60
60
|
export declare function pluginAssetDirs(workDir: string, kind: 'commands' | 'agents' | 'skills'): string[];
|
|
61
|
-
/** Merge hooks.json from every ENABLED plugin into a single hooks config (arrays concatenated).
|
|
61
|
+
/** Merge hooks.json from every ENABLED plugin into a single hooks config (arrays concatenated).
|
|
62
|
+
*
|
|
63
|
+
* Every handler is stamped with `plugin: <name>`. Provenance is the one thing the merge
|
|
64
|
+
* would otherwise destroy — and it is what lets the hook runner hand the plugin's own
|
|
65
|
+
* processes their NEXRALL_PLUGIN_DATA (see runHookHandler). Both entry forms are stamped:
|
|
66
|
+
* the flat one (the entry IS the handler) and the nested one (each item under `hooks`).
|
|
67
|
+
*/
|
|
62
68
|
export declare function pluginHooks(workDir: string): Record<string, unknown[]>;
|
|
63
|
-
/** MCP server maps declared by ENABLED plugins: { serverName → config } (first plugin wins on clash).
|
|
69
|
+
/** MCP server maps declared by ENABLED plugins: { serverName → config } (first plugin wins on clash).
|
|
70
|
+
*
|
|
71
|
+
* A stdio server from a plugin gets NEXRALL_PLUGIN_DATA in its environment, pointing at the
|
|
72
|
+
* plugin's persistent data directory — the install directory is replaced on every update, so
|
|
73
|
+
* this is the only place a server can keep state. A plugin that set the variable itself keeps
|
|
74
|
+
* its own value: an explicit config value is never overridden here.
|
|
75
|
+
*/
|
|
64
76
|
export declare function pluginMcpServers(workDir: string): Record<string, unknown>;
|
|
65
77
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugins/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugins/index.ts"],"names":[],"mappings":"AAuCA,MAAM,WAAW,UAAU;IACzB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,6EAA6E;IAC7E,QAAQ,EAAE,OAAO,CAAC;CACnB;AAYD,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAMrD;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAWtE;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe;;;;CAIlB,CAAC;AAEX,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,eAAe,GAAG,MAAM,GAAG,IAAI,CAQhG;AAwCD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAKzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAK7G;AAED,gGAAgG;AAChG,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,EAAE,CAOjG;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAkBtE;AAeD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBzE"}
|