recallx 1.0.2 → 1.0.5
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 +34 -12
- 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/dist/renderer/assets/{ProjectGraphCanvas-BVlOgk7l.js → ProjectGraphCanvas-Crp1MklO.js} +2 -2
- package/dist/renderer/assets/index-CSO0evPr.js +69 -0
- package/dist/renderer/index.html +1 -1
- package/package.json +1 -1
- package/dist/renderer/assets/index-JtUcLLhP.js +0 -76
package/README.md
CHANGED
|
@@ -35,13 +35,14 @@ RecallX is documented around three public ways to use it:
|
|
|
35
35
|
2. npm package `recallx` for the full local runtime
|
|
36
36
|
3. npm package `recallx-headless` for the headless runtime
|
|
37
37
|
|
|
38
|
+
RecallX does not currently ship separate OS-native installers or package formats such as `.dmg`, `.msi`, `.deb`, or `AppImage`.
|
|
39
|
+
|
|
38
40
|
## 1. Git Public Repo
|
|
39
41
|
|
|
40
42
|
Use the public repo when you want the full source-run surface:
|
|
41
43
|
|
|
42
44
|
- local API under `/api/v1`
|
|
43
45
|
- source-run renderer workflow through `npm run dev`
|
|
44
|
-
- source-run desktop workflow through `npm run dev:desktop`
|
|
45
46
|
- stdio MCP bridge through `npm run mcp`
|
|
46
47
|
- runtime workspace create/open switching without restarting the service
|
|
47
48
|
|
|
@@ -52,12 +53,6 @@ npm install
|
|
|
52
53
|
npm run dev
|
|
53
54
|
```
|
|
54
55
|
|
|
55
|
-
Desktop runtime from source:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
npm run dev:desktop
|
|
59
|
-
```
|
|
60
|
-
|
|
61
56
|
MCP from source:
|
|
62
57
|
|
|
63
58
|
```bash
|
|
@@ -74,11 +69,25 @@ npm start
|
|
|
74
69
|
Checks:
|
|
75
70
|
|
|
76
71
|
```bash
|
|
72
|
+
npm run branch:check
|
|
73
|
+
npm run version:check
|
|
77
74
|
npm run check
|
|
78
75
|
npm test
|
|
79
76
|
npm run build
|
|
80
77
|
```
|
|
81
78
|
|
|
79
|
+
Start unrelated work in a separate branch/worktree instead of stacking it on the current checkout:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run branch:new -- fix-short-name
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
When you are preparing a release, bump from the highest known version instead of whatever the current branch happens to say:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm run version:bump -- patch
|
|
89
|
+
```
|
|
90
|
+
|
|
82
91
|
If you want an installable runtime instead of source-run workflows, use one of the npm distribution paths below.
|
|
83
92
|
|
|
84
93
|
## 2. npm Full Runtime (`recallx`)
|
|
@@ -94,6 +103,7 @@ In another shell:
|
|
|
94
103
|
|
|
95
104
|
```bash
|
|
96
105
|
recallx health
|
|
106
|
+
recallx update
|
|
97
107
|
recallx mcp install
|
|
98
108
|
recallx-mcp --help
|
|
99
109
|
```
|
|
@@ -106,10 +116,6 @@ The full npm package includes:
|
|
|
106
116
|
- `recallx serve` and subcommands
|
|
107
117
|
- `recallx-mcp`
|
|
108
118
|
|
|
109
|
-
The full npm package does not include:
|
|
110
|
-
|
|
111
|
-
- desktop release artifacts
|
|
112
|
-
|
|
113
119
|
`recallx mcp install` writes a stable launcher to `~/.recallx/bin/recallx-mcp`, which is the recommended command path for Codex and other editor MCP configs.
|
|
114
120
|
|
|
115
121
|
If the API is running in bearer mode, set `RECALLX_API_TOKEN` in the MCP client environment. The launcher does not write tokens to disk.
|
|
@@ -128,6 +134,15 @@ recallx serve --workspace-root /Users/name/Documents/RecallX
|
|
|
128
134
|
recallx serve --api-token secret-token
|
|
129
135
|
```
|
|
130
136
|
|
|
137
|
+
To update an npm-installed full runtime:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
recallx update
|
|
141
|
+
recallx update --apply
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`recallx update` currently supports npm global installs of `recallx` and `recallx-headless`. Source checkouts should keep using their package manager directly.
|
|
145
|
+
|
|
131
146
|
## 3. npm Headless Runtime (`recallx-headless`)
|
|
132
147
|
|
|
133
148
|
Use the headless npm package when you want the local API, CLI, and MCP entrypoint without shipping the renderer bundle:
|
|
@@ -141,6 +156,7 @@ In another shell:
|
|
|
141
156
|
|
|
142
157
|
```bash
|
|
143
158
|
recallx health
|
|
159
|
+
recallx update
|
|
144
160
|
recallx-mcp --help
|
|
145
161
|
```
|
|
146
162
|
|
|
@@ -154,10 +170,16 @@ The headless npm package includes:
|
|
|
154
170
|
The headless npm package does not include:
|
|
155
171
|
|
|
156
172
|
- renderer pages
|
|
157
|
-
- desktop release artifacts
|
|
158
173
|
|
|
159
174
|
At `/`, the headless runtime returns a small runtime notice instead of the renderer.
|
|
160
175
|
|
|
176
|
+
To update an npm-installed headless runtime:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
recallx update
|
|
180
|
+
recallx update --apply
|
|
181
|
+
```
|
|
182
|
+
|
|
161
183
|
Node requirements:
|
|
162
184
|
|
|
163
185
|
- npm packages: Node 22.13+
|
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.5";
|