recallx-headless 1.0.2 → 1.0.4
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 +14 -2
- package/app/cli/src/cli.js +17 -1
- package/app/cli/src/format.js +30 -0
- package/app/cli/src/update.js +118 -0
- package/app/server/app.js +3 -4
- package/app/shared/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
- This package is the npm-distributed headless runtime for RecallX.
|
|
6
6
|
- It provides the `recallx` and `recallx-mcp` commands.
|
|
7
7
|
- It can start the local RecallX API directly through `recallx serve`.
|
|
8
|
-
- It does not include the renderer
|
|
8
|
+
- It does not include the renderer.
|
|
9
9
|
|
|
10
10
|
It defers behavior to the local RecallX API contract in [`docs/api.md`](../../docs/api.md).
|
|
11
11
|
|
|
@@ -28,6 +28,7 @@ In another shell:
|
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
recallx health
|
|
31
|
+
recallx update
|
|
31
32
|
recallx-mcp --help
|
|
32
33
|
recallx mcp install
|
|
33
34
|
```
|
|
@@ -55,7 +56,16 @@ recallx serve --workspace-name "Personal Workspace"
|
|
|
55
56
|
recallx serve --api-token secret-token
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
To update an npm-installed RecallX runtime from the CLI:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
recallx update
|
|
63
|
+
recallx update --apply
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`recallx update` currently supports npm global installs of `recallx` and `recallx-headless`. Source checkouts should keep using their package manager directly.
|
|
67
|
+
|
|
68
|
+
The headless package does not ship renderer pages. At `/`, it returns a runtime notice instead of the renderer app.
|
|
59
69
|
|
|
60
70
|
You can also print the direct MCP command or a config snippet:
|
|
61
71
|
|
|
@@ -71,6 +81,7 @@ Quick health and workspace checks:
|
|
|
71
81
|
|
|
72
82
|
```bash
|
|
73
83
|
recallx health
|
|
84
|
+
recallx update
|
|
74
85
|
recallx workspace current
|
|
75
86
|
recallx workspace list
|
|
76
87
|
```
|
|
@@ -106,6 +117,7 @@ recallx mcp config
|
|
|
106
117
|
|
|
107
118
|
```bash
|
|
108
119
|
recallx health
|
|
120
|
+
recallx update [--apply]
|
|
109
121
|
recallx mcp config
|
|
110
122
|
recallx mcp install
|
|
111
123
|
recallx mcp path
|
package/app/cli/src/cli.js
CHANGED
|
@@ -5,7 +5,8 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { getApiBase, getAuthToken, requestJson } from "./http.js";
|
|
7
7
|
import { RECALLX_VERSION } from "../../shared/version.js";
|
|
8
|
-
import {
|
|
8
|
+
import { applyCliUpdate, getCliUpdatePlan } from "./update.js";
|
|
9
|
+
import { renderActivitySearchResults, renderBundleMarkdown, renderGovernanceIssues, renderJson, renderNode, renderRelated, renderSearchResults, renderTelemetryErrors, renderTelemetrySummary, renderText, renderUpdateResult, renderWorkspaceSearchResults, renderWorkspaces, } from "./format.js";
|
|
9
10
|
const DEFAULT_SOURCE = {
|
|
10
11
|
actorType: "human",
|
|
11
12
|
actorLabel: "recallx-cli",
|
|
@@ -27,6 +28,8 @@ export async function runCli(argv) {
|
|
|
27
28
|
return runHealth(apiBase, token, format);
|
|
28
29
|
case "serve":
|
|
29
30
|
return runServe(args);
|
|
31
|
+
case "update":
|
|
32
|
+
return runUpdate(format, args);
|
|
30
33
|
case "mcp":
|
|
31
34
|
return runMcp(apiBase, token, format, args, positionals);
|
|
32
35
|
case "search":
|
|
@@ -85,6 +88,15 @@ async function runServe(args) {
|
|
|
85
88
|
}
|
|
86
89
|
await import(pathToFileURL(resolveServerEntryScript()).href);
|
|
87
90
|
}
|
|
91
|
+
export async function runUpdate(format, args, dependencies = {}) {
|
|
92
|
+
const plan = await (dependencies.getCliUpdatePlan ?? getCliUpdatePlan)();
|
|
93
|
+
if (parseBooleanFlag(args.apply, false)) {
|
|
94
|
+
const result = await (dependencies.applyCliUpdate ?? applyCliUpdate)(plan);
|
|
95
|
+
outputData(result, format, "update");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
outputData(plan, format, "update");
|
|
99
|
+
}
|
|
88
100
|
async function runMcp(apiBase, token, format, args, positionals) {
|
|
89
101
|
const action = positionals[0] || args.action || "config";
|
|
90
102
|
const launcherPath = args.path || args.launcher || DEFAULT_MCP_LAUNCHER_PATH;
|
|
@@ -582,6 +594,9 @@ function outputData(data, format, command) {
|
|
|
582
594
|
"",
|
|
583
595
|
].join("\n"));
|
|
584
596
|
return;
|
|
597
|
+
case "update":
|
|
598
|
+
writeStdout(renderUpdateResult(payload));
|
|
599
|
+
return;
|
|
585
600
|
default:
|
|
586
601
|
writeStdout(renderText(payload));
|
|
587
602
|
}
|
|
@@ -657,6 +672,7 @@ Usage:
|
|
|
657
672
|
recallx serve [--workspace-name Personal] [--api-token secret]
|
|
658
673
|
recallx serve [--renderer-dist /path/to/dist/renderer]
|
|
659
674
|
recallx health
|
|
675
|
+
recallx update [--apply]
|
|
660
676
|
recallx mcp config
|
|
661
677
|
recallx mcp install [--path ~/.recallx/bin/recallx-mcp]
|
|
662
678
|
recallx mcp path
|
package/app/cli/src/format.js
CHANGED
|
@@ -144,6 +144,36 @@ export function renderWorkspaces(data) {
|
|
|
144
144
|
.join("\n\n")}\n`;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
export function renderUpdateResult(data) {
|
|
148
|
+
const lines = [
|
|
149
|
+
`package: ${data?.packageName || ""}`,
|
|
150
|
+
`current: ${data?.currentVersion || ""}`,
|
|
151
|
+
`latest: ${data?.latestVersion || ""}`,
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
if (data?.status === "updated") {
|
|
155
|
+
lines.push("status: updated");
|
|
156
|
+
} else if (data?.status === "up_to_date") {
|
|
157
|
+
lines.push("status: up to date");
|
|
158
|
+
} else {
|
|
159
|
+
lines.push("status: update available");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (data?.installCommand) {
|
|
163
|
+
lines.push(`command: ${data.installCommand}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (data?.status === "update_available" && data?.applied !== true) {
|
|
167
|
+
lines.push("hint: re-run with `recallx update --apply` to install the latest npm package from this shell.");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (data?.packageRoot) {
|
|
171
|
+
lines.push(`packageRoot: ${data.packageRoot}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return `${lines.join("\n")}\n`;
|
|
175
|
+
}
|
|
176
|
+
|
|
147
177
|
export function renderBundleMarkdown(bundle) {
|
|
148
178
|
const lines = [];
|
|
149
179
|
lines.push(`# ${bundle.target?.title || bundle.target?.id || "Context bundle"}`);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const SUPPORTED_NPM_PACKAGES = new Set(["recallx", "recallx-headless"]);
|
|
8
|
+
export async function getCliUpdatePlan(options = {}) {
|
|
9
|
+
const moduleUrl = options.moduleUrl ?? import.meta.url;
|
|
10
|
+
const packageRoot = options.packageRoot ?? resolveCliPackageRoot(moduleUrl);
|
|
11
|
+
const packageJson = readPackageJson(packageRoot, options.readFileSyncFn ?? readFileSync);
|
|
12
|
+
const packageName = options.packageName ?? packageJson.name;
|
|
13
|
+
const currentVersion = options.currentVersion ?? packageJson.version;
|
|
14
|
+
const platform = options.platform ?? process.platform;
|
|
15
|
+
const npmCommand = options.npmCommand ?? resolveNpmCommand(platform);
|
|
16
|
+
const execAsync = options.execFileAsyncFn ?? execFileAsync;
|
|
17
|
+
if (!SUPPORTED_NPM_PACKAGES.has(packageName)) {
|
|
18
|
+
throw new Error("UPDATE_UNSUPPORTED: `recallx update` currently supports npm-installed `recallx` and `recallx-headless` runtimes only.");
|
|
19
|
+
}
|
|
20
|
+
const globalRoot = (await runCommand(execAsync, npmCommand, ["root", "-g"])).trim();
|
|
21
|
+
if (!globalRoot || !isPathInside(packageRoot, globalRoot)) {
|
|
22
|
+
throw new Error("UPDATE_UNSUPPORTED: `recallx update` only works for npm global installs. For source checkouts or other install methods, update the package with your package manager directly.");
|
|
23
|
+
}
|
|
24
|
+
const latestVersionRaw = await runCommand(execAsync, npmCommand, ["view", packageName, "version", "--json"]);
|
|
25
|
+
const latestVersion = normalizeVersionPayload(latestVersionRaw);
|
|
26
|
+
const installArgs = ["install", "-g", `${packageName}@latest`];
|
|
27
|
+
return {
|
|
28
|
+
packageName,
|
|
29
|
+
currentVersion,
|
|
30
|
+
latestVersion,
|
|
31
|
+
packageRoot,
|
|
32
|
+
globalRoot,
|
|
33
|
+
npmCommand,
|
|
34
|
+
installArgs,
|
|
35
|
+
installCommand: [npmCommand, ...installArgs].map(quoteShellArg).join(" "),
|
|
36
|
+
status: currentVersion === latestVersion ? "up_to_date" : "update_available",
|
|
37
|
+
applied: false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function applyCliUpdate(plan, options = {}) {
|
|
41
|
+
if (!plan || typeof plan !== "object") {
|
|
42
|
+
throw new Error("UPDATE_INVALID_PLAN: Missing update plan.");
|
|
43
|
+
}
|
|
44
|
+
if (plan.status === "up_to_date") {
|
|
45
|
+
return {
|
|
46
|
+
...plan,
|
|
47
|
+
applied: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const platform = options.platform ?? process.platform;
|
|
51
|
+
const npmCommand = options.npmCommand ?? plan.npmCommand ?? resolveNpmCommand(platform);
|
|
52
|
+
const execSync = options.execFileSyncFn ?? execFileSync;
|
|
53
|
+
execSync(npmCommand, plan.installArgs ?? ["install", "-g", `${plan.packageName}@latest`], {
|
|
54
|
+
stdio: "inherit",
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
...plan,
|
|
58
|
+
status: "updated",
|
|
59
|
+
applied: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function resolveCliPackageRoot(moduleUrl) {
|
|
63
|
+
const modulePath = fileURLToPath(moduleUrl);
|
|
64
|
+
return path.resolve(path.dirname(modulePath), "../../..");
|
|
65
|
+
}
|
|
66
|
+
function readPackageJson(packageRoot, readFileSyncFn) {
|
|
67
|
+
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
68
|
+
if (!existsSync(packageJsonPath)) {
|
|
69
|
+
throw new Error(`UPDATE_UNSUPPORTED: Could not find package metadata at ${packageJsonPath}.`);
|
|
70
|
+
}
|
|
71
|
+
return JSON.parse(readFileSyncFn(packageJsonPath, "utf8"));
|
|
72
|
+
}
|
|
73
|
+
async function runCommand(execAsync, command, args) {
|
|
74
|
+
try {
|
|
75
|
+
const result = await execAsync(command, args, { encoding: "utf8" });
|
|
76
|
+
return typeof result.stdout === "string" ? result.stdout.trim() : "";
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const stderr = error && typeof error === "object" && "stderr" in error && typeof error.stderr === "string"
|
|
80
|
+
? error.stderr.trim()
|
|
81
|
+
: "";
|
|
82
|
+
const detail = stderr || (error instanceof Error ? error.message : String(error));
|
|
83
|
+
throw new Error(`UPDATE_COMMAND_FAILED: ${detail}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function normalizeVersionPayload(value) {
|
|
87
|
+
if (!value) {
|
|
88
|
+
throw new Error("UPDATE_LOOKUP_FAILED: npm did not return a version.");
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(value);
|
|
92
|
+
if (Array.isArray(parsed)) {
|
|
93
|
+
const last = parsed.at(-1);
|
|
94
|
+
if (typeof last === "string" && last.trim()) {
|
|
95
|
+
return last;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (typeof parsed === "string" && parsed.trim()) {
|
|
99
|
+
return parsed;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
if (typeof value === "string" && value.trim()) {
|
|
104
|
+
return value.trim();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
throw new Error("UPDATE_LOOKUP_FAILED: npm returned an invalid version payload.");
|
|
108
|
+
}
|
|
109
|
+
function isPathInside(candidatePath, parentPath) {
|
|
110
|
+
const relative = path.relative(parentPath, candidatePath);
|
|
111
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
112
|
+
}
|
|
113
|
+
function quoteShellArg(value) {
|
|
114
|
+
return `"${String(value).replace(/"/g, '\\"')}"`;
|
|
115
|
+
}
|
|
116
|
+
function resolveNpmCommand(platform) {
|
|
117
|
+
return platform === "win32" ? "npm.cmd" : "npm";
|
|
118
|
+
}
|
package/app/server/app.js
CHANGED
|
@@ -14,7 +14,6 @@ import { buildProjectGraph } from "./project-graph.js";
|
|
|
14
14
|
import { createId, isPathWithinRoot } from "./utils.js";
|
|
15
15
|
const relationTypeSet = new Set(relationTypes);
|
|
16
16
|
const allowedLoopbackHostnames = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
17
|
-
const isDesktopManagedApi = process.env.ELECTRON_RUN_AS_NODE === "1";
|
|
18
17
|
const updateNodeRequestSchema = updateNodeSchema.extend({
|
|
19
18
|
source: sourceSchema
|
|
20
19
|
});
|
|
@@ -453,7 +452,7 @@ function buildServiceIndex(workspaceInfo) {
|
|
|
453
452
|
{
|
|
454
453
|
method: "GET",
|
|
455
454
|
path: "/api/v1/observability/errors?since=24h&surface=mcp",
|
|
456
|
-
purpose: "Inspect recent telemetry errors for the API
|
|
455
|
+
purpose: "Inspect recent telemetry errors for the API or MCP bridge."
|
|
457
456
|
},
|
|
458
457
|
{
|
|
459
458
|
method: "POST",
|
|
@@ -543,7 +542,7 @@ export function createRecallXApp(params) {
|
|
|
543
542
|
"observability.capturePayloadShape"
|
|
544
543
|
]);
|
|
545
544
|
return {
|
|
546
|
-
enabled:
|
|
545
|
+
enabled: true,
|
|
547
546
|
workspaceRoot: currentWorkspaceRoot(),
|
|
548
547
|
workspaceName: currentWorkspaceInfo().workspaceName,
|
|
549
548
|
retentionDays: Math.max(1, parseNumberSetting(settings["observability.retentionDays"], 14)),
|
|
@@ -1391,7 +1390,7 @@ export function createRecallXApp(params) {
|
|
|
1391
1390
|
}));
|
|
1392
1391
|
app.get("/api/v1/observability/errors", handleAsyncRoute(async (request, response) => {
|
|
1393
1392
|
const surface = readRequestParam(request.query.surface);
|
|
1394
|
-
const normalizedSurface = surface === "api" || surface === "mcp"
|
|
1393
|
+
const normalizedSurface = surface === "api" || surface === "mcp" ? surface : "all";
|
|
1395
1394
|
const errors = await observability.listErrors({
|
|
1396
1395
|
since: readRequestParam(request.query.since),
|
|
1397
1396
|
surface: normalizedSurface,
|
package/app/shared/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RECALLX_VERSION = "1.0.
|
|
1
|
+
export const RECALLX_VERSION = "1.0.4";
|