@lore-co/cli 0.2.22 → 0.2.23
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 +59 -11
- package/dist/approach-guidance.js +2 -2
- package/dist/approach-guidance.js.map +1 -1
- package/dist/approach.d.ts +1 -1
- package/dist/approach.d.ts.map +1 -1
- package/dist/approach.js +7 -0
- package/dist/approach.js.map +1 -1
- package/dist/cli.d.ts +24 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +227 -65
- package/dist/cli.js.map +1 -1
- package/dist/command-help.d.ts +2 -1
- package/dist/command-help.d.ts.map +1 -1
- package/dist/command-help.js +4 -0
- package/dist/command-help.js.map +1 -1
- package/dist/generated-assets.d.ts +6 -6
- package/dist/generated-assets.d.ts.map +1 -1
- package/dist/generated-assets.js +6 -6
- package/dist/generated-assets.js.map +1 -1
- package/dist/learnings.d.ts +20 -0
- package/dist/learnings.d.ts.map +1 -1
- package/dist/learnings.js +25 -16
- package/dist/learnings.js.map +1 -1
- package/dist/mcp-config.d.ts +29 -0
- package/dist/mcp-config.d.ts.map +1 -0
- package/dist/mcp-config.js +136 -0
- package/dist/mcp-config.js.map +1 -0
- package/dist/review.d.ts +30 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +276 -0
- package/dist/review.js.map +1 -0
- package/dist/self-host.d.ts +1 -1
- package/dist/self-host.js +2 -2
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +36 -4
- package/dist/update.js.map +1 -1
- package/package.json +5 -3
package/dist/cli.js
CHANGED
|
@@ -12,11 +12,12 @@ import { withDirectoryLock } from "./reliability-store.js";
|
|
|
12
12
|
import { runConfigCommand } from "./config-command.js";
|
|
13
13
|
import { codexApprovalDetail, inspectCodexHookApproval } from "./codex-hook-approval.js";
|
|
14
14
|
import { claudeOnlyCommand, disableDevinCli, runDevinCliHook } from "./devin-cli.js";
|
|
15
|
+
import { LORE_MCP_SERVER_NAME, MCP_HOST_AGENTS, inspectCodexMcp, isLoreMcpServer, loreMcpServerEntry, mergeLoreCodexMcp, mergeLoreMcpServers, removeLoreCodexMcp, removeLoreMcpServers, } from "./mcp-config.js";
|
|
15
16
|
import { access, chmod, copyFile, mkdir, open, readFile, readdir, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
16
17
|
import { constants as fsConstants, realpathSync } from "node:fs";
|
|
17
18
|
import { spawn, isWindows, windowsCommand, commandText, findExecutable, openBrowser } from "./platform.js";
|
|
18
19
|
import { createHash, randomUUID } from "node:crypto";
|
|
19
|
-
import { dirname, resolve } from "node:path";
|
|
20
|
+
import { basename, dirname, resolve } from "node:path";
|
|
20
21
|
import { homedir, hostname, platform } from "node:os";
|
|
21
22
|
import { setTimeout as delay } from "node:timers/promises";
|
|
22
23
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -25,6 +26,7 @@ import { runHook, } from "./runtime.js";
|
|
|
25
26
|
import { readInvocationHealthWriteInput, runInvocationHealthWrite, } from "./invocation-health-writer.js";
|
|
26
27
|
import { runAskCommand } from "./ask.js";
|
|
27
28
|
import { runLearningsCommand } from "./learnings.js";
|
|
29
|
+
import { runReviewCommand } from "./review.js";
|
|
28
30
|
import { runEmbeddingsCommand } from "./embeddings.js";
|
|
29
31
|
import { runRememberCommand } from "./remember.js";
|
|
30
32
|
import { installLoreSkill, removeLoreSkill } from "./skills.js";
|
|
@@ -398,6 +400,34 @@ export function removeLoreCursorMcp(input) {
|
|
|
398
400
|
const { [CURSOR_MCP_NAME]: _lore, ...rest } = config.mcpServers;
|
|
399
401
|
return { ...config, mcpServers: rest };
|
|
400
402
|
}
|
|
403
|
+
function isMcpHostAgent(agent) {
|
|
404
|
+
return MCP_HOST_AGENTS.includes(agent);
|
|
405
|
+
}
|
|
406
|
+
/** The full Lore MCP server, run by this CLI so it shares the hooks' credentials and version. */
|
|
407
|
+
export function loreMcpServer(paths) {
|
|
408
|
+
return loreMcpServerEntry([process.execPath, ...(IS_STANDALONE_BINARY ? [] : [fileURLToPath(import.meta.url)])], paths.home);
|
|
409
|
+
}
|
|
410
|
+
/** Where each agent lists MCP servers, located from the hooks file Lore already manages. */
|
|
411
|
+
export function mcpConfigPath(agent, hooksPath) {
|
|
412
|
+
const directory = dirname(hooksPath);
|
|
413
|
+
switch (agent) {
|
|
414
|
+
case "claude":
|
|
415
|
+
// ~/.claude/settings.json pairs with ~/.claude.json; a CLAUDE_CONFIG_DIR keeps both inside it.
|
|
416
|
+
return basename(directory) === ".claude"
|
|
417
|
+
? resolve(directory, "..", ".claude.json")
|
|
418
|
+
: resolve(directory, ".claude.json");
|
|
419
|
+
case "codex":
|
|
420
|
+
return resolve(directory, "config.toml");
|
|
421
|
+
case "cursor":
|
|
422
|
+
return resolve(directory, "mcp.json");
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
export function mergeLoreMcp(input, agent, paths) {
|
|
426
|
+
return mergeLoreMcpServers(objectConfiguration(input, agent), loreMcpServer(paths), agent);
|
|
427
|
+
}
|
|
428
|
+
export function removeLoreMcp(input, agent) {
|
|
429
|
+
return removeLoreMcpServers(objectConfiguration(input, agent));
|
|
430
|
+
}
|
|
401
431
|
function stripLoreFromCursorEvent(value) {
|
|
402
432
|
return Array.isArray(value) ? value.filter((hook) => !isLoreHook(hook)) : [];
|
|
403
433
|
}
|
|
@@ -920,6 +950,39 @@ async function writeMergedJson(path, document, value, now) {
|
|
|
920
950
|
...(backup === undefined ? {} : { backup }),
|
|
921
951
|
};
|
|
922
952
|
}
|
|
953
|
+
async function readTextDocument(path) {
|
|
954
|
+
try {
|
|
955
|
+
const [value, metadata] = await Promise.all([
|
|
956
|
+
readFile(path, "utf8"),
|
|
957
|
+
stat(path),
|
|
958
|
+
]);
|
|
959
|
+
return { exists: true, value, mode: metadata.mode & 0o777 };
|
|
960
|
+
}
|
|
961
|
+
catch (error) {
|
|
962
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
963
|
+
? error.code
|
|
964
|
+
: undefined;
|
|
965
|
+
if (code === "ENOENT") {
|
|
966
|
+
return { exists: false, value: "", mode: 0o600 };
|
|
967
|
+
}
|
|
968
|
+
throw error;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
async function writeMergedText(path, document, value, now) {
|
|
972
|
+
if (document.exists && document.value === value) {
|
|
973
|
+
return { changed: false };
|
|
974
|
+
}
|
|
975
|
+
let backup;
|
|
976
|
+
if (document.exists) {
|
|
977
|
+
backup = `${path}.lore-backup-${backupTimestamp(now)}-${randomUUID().slice(0, 8)}`;
|
|
978
|
+
await copyFile(path, backup, fsConstants.COPYFILE_EXCL);
|
|
979
|
+
}
|
|
980
|
+
await atomicWrite(path, value, document.mode);
|
|
981
|
+
return {
|
|
982
|
+
changed: true,
|
|
983
|
+
...(backup === undefined ? {} : { backup }),
|
|
984
|
+
};
|
|
985
|
+
}
|
|
923
986
|
function parseConnectorConfig(value) {
|
|
924
987
|
if (!isObject(value) ||
|
|
925
988
|
(value.version !== 1 && value.version !== 2) ||
|
|
@@ -1145,7 +1208,8 @@ function objectConfiguration(input, agent) {
|
|
|
1145
1208
|
const AGENT_TARGETS = {
|
|
1146
1209
|
claude: {
|
|
1147
1210
|
integration: "hooks",
|
|
1148
|
-
|
|
1211
|
+
// Native hooks plus the Lore MCP server entry.
|
|
1212
|
+
expected: HOOK_EVENTS.length + 1,
|
|
1149
1213
|
runtimeRequired: true,
|
|
1150
1214
|
emptyValue: () => ({}),
|
|
1151
1215
|
configPath: (paths) => paths.claudeSettings,
|
|
@@ -1157,7 +1221,7 @@ const AGENT_TARGETS = {
|
|
|
1157
1221
|
},
|
|
1158
1222
|
codex: {
|
|
1159
1223
|
integration: "hooks",
|
|
1160
|
-
expected: HOOK_EVENTS.length,
|
|
1224
|
+
expected: HOOK_EVENTS.length + 1,
|
|
1161
1225
|
runtimeRequired: true,
|
|
1162
1226
|
emptyValue: () => ({}),
|
|
1163
1227
|
configPath: (paths) => paths.codexHooks,
|
|
@@ -1193,7 +1257,8 @@ const AGENT_TARGETS = {
|
|
|
1193
1257
|
},
|
|
1194
1258
|
cursor: {
|
|
1195
1259
|
integration: "hooks",
|
|
1196
|
-
|
|
1260
|
+
// Hooks, the capture bridge, and the Lore MCP server entry.
|
|
1261
|
+
expected: CURSOR_HOOK_EVENTS.length + 2,
|
|
1197
1262
|
runtimeRequired: true,
|
|
1198
1263
|
emptyValue: () => ({}),
|
|
1199
1264
|
configPath: (paths) => paths.cursorHooks,
|
|
@@ -1548,6 +1613,99 @@ function mergeAgentConfig(input, agent, paths) {
|
|
|
1548
1613
|
function removeAgentConfig(input, agent) {
|
|
1549
1614
|
return targetFor(agent).remove(input);
|
|
1550
1615
|
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Read each installation's hooks file and, for MCP hosts, its MCP config, and
|
|
1618
|
+
* merge the Lore entries this CLI version writes. Nothing touches disk yet.
|
|
1619
|
+
*/
|
|
1620
|
+
async function prepareAgentEntries(installations, paths) {
|
|
1621
|
+
const documents = new Map();
|
|
1622
|
+
const mergedDocuments = new Map();
|
|
1623
|
+
const mcpDocuments = new Map();
|
|
1624
|
+
for (const installation of installations) {
|
|
1625
|
+
const key = installationKey(installation);
|
|
1626
|
+
const document = await readJsonDocument(installation.configPath);
|
|
1627
|
+
documents.set(key, document);
|
|
1628
|
+
mergedDocuments.set(key, mergeAgentConfig(document.value, installation.agent, paths));
|
|
1629
|
+
if (isMcpHostAgent(installation.agent)) {
|
|
1630
|
+
const mcpPath = mcpConfigPath(installation.agent, installation.configPath);
|
|
1631
|
+
if (installation.agent === "codex") {
|
|
1632
|
+
const text = await readTextDocument(mcpPath);
|
|
1633
|
+
mcpDocuments.set(mcpPath, { kind: "text", document: text, merged: mergeLoreCodexMcp(text.value, loreMcpServer(paths)) });
|
|
1634
|
+
}
|
|
1635
|
+
else {
|
|
1636
|
+
const mcp = await readJsonDocument(mcpPath);
|
|
1637
|
+
const base = mcp.exists ? mcp.value : {};
|
|
1638
|
+
// Cursor keeps its capture bridge next to the shared tool server.
|
|
1639
|
+
const withBridge = installation.agent === "cursor" ? mergeLoreCursorMcp(base, paths) : base;
|
|
1640
|
+
mcpDocuments.set(mcpPath, { kind: "json", document: mcp, merged: mergeLoreMcp(withBridge, installation.agent, paths) });
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
return { installations: [...installations], documents, mergedDocuments, mcpDocuments };
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Install the hook runtime when an installation needs it, then write the
|
|
1648
|
+
* merged files. Unchanged files are left alone; changed ones get a backup.
|
|
1649
|
+
*/
|
|
1650
|
+
async function writeAgentEntries(entries, paths, now) {
|
|
1651
|
+
if (entries.installations.some((installation) => targetFor(installation.agent).runtimeRequired)) {
|
|
1652
|
+
await installRuntime(paths);
|
|
1653
|
+
}
|
|
1654
|
+
const changedFiles = [];
|
|
1655
|
+
const backups = [];
|
|
1656
|
+
for (const [path, entry] of entries.mcpDocuments) {
|
|
1657
|
+
const result = entry.kind === "json"
|
|
1658
|
+
? await writeMergedJson(path, entry.document, entry.merged, now)
|
|
1659
|
+
: await writeMergedText(path, entry.document, entry.merged, now);
|
|
1660
|
+
if (result.changed)
|
|
1661
|
+
changedFiles.push(path);
|
|
1662
|
+
if (result.backup)
|
|
1663
|
+
backups.push(result.backup);
|
|
1664
|
+
}
|
|
1665
|
+
for (const installation of entries.installations) {
|
|
1666
|
+
const key = installationKey(installation);
|
|
1667
|
+
const document = entries.documents.get(key);
|
|
1668
|
+
const merged = entries.mergedDocuments.get(key);
|
|
1669
|
+
if (document === undefined || merged === undefined) {
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
const result = await writeMergedJson(installation.configPath, document, merged, now);
|
|
1673
|
+
if (result.changed) {
|
|
1674
|
+
changedFiles.push(installation.configPath);
|
|
1675
|
+
}
|
|
1676
|
+
if (result.backup !== undefined) {
|
|
1677
|
+
backups.push(result.backup);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
return { changedFiles, backups };
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Re-apply the Lore-owned hook and MCP entries and the `lore` skill for the
|
|
1684
|
+
* agents recorded in the stored connection, for example after `lore update`
|
|
1685
|
+
* replaced the binary. Nothing is authorized again and folder rules are
|
|
1686
|
+
* untouched. Resolves to null when no connection is stored.
|
|
1687
|
+
*/
|
|
1688
|
+
export async function refreshAgentEntries(paths = getLorePaths()) {
|
|
1689
|
+
const config = await readConnectorConfig(paths);
|
|
1690
|
+
if (config === null)
|
|
1691
|
+
return null;
|
|
1692
|
+
const installations = uniqueInstallations(configuredInstallations(config, paths));
|
|
1693
|
+
return withDirectoryLock(resolve(paths.loreDirectory, "connections.lock"), async () => {
|
|
1694
|
+
const entries = await prepareAgentEntries(installations, paths);
|
|
1695
|
+
const written = await writeAgentEntries(entries, paths, new Date());
|
|
1696
|
+
const skills = await installLoreSkill(paths, installations);
|
|
1697
|
+
return {
|
|
1698
|
+
// Same order the files are written in, so the two update lines match.
|
|
1699
|
+
configFiles: [
|
|
1700
|
+
...entries.mcpDocuments.keys(),
|
|
1701
|
+
...installations.map((installation) => installation.configPath),
|
|
1702
|
+
],
|
|
1703
|
+
...written,
|
|
1704
|
+
skillFiles: skills.files,
|
|
1705
|
+
warnings: skills.warnings,
|
|
1706
|
+
};
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1551
1709
|
function countAgentIntegrations(input, agent) {
|
|
1552
1710
|
const target = targetFor(agent);
|
|
1553
1711
|
return target.count(input === undefined ? target.emptyValue() : input);
|
|
@@ -1773,20 +1931,9 @@ async function connectWithCredentialLocked(options, identity) {
|
|
|
1773
1931
|
throw new Error("No Claude, Codex, Copilot CLI, Cursor, OpenCode, or Polytoken installation detected. Use --agent <name>; T3 Code users can use --agent t3code, and Devin CLI users --agent devin.");
|
|
1774
1932
|
}
|
|
1775
1933
|
const now = new Date();
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
const
|
|
1779
|
-
for (const installation of installations) {
|
|
1780
|
-
const key = installationKey(installation);
|
|
1781
|
-
const document = await readJsonDocument(installation.configPath);
|
|
1782
|
-
documents.set(key, document);
|
|
1783
|
-
mergedDocuments.set(key, mergeAgentConfig(document.value, installation.agent, paths));
|
|
1784
|
-
if (installation.agent === "cursor") {
|
|
1785
|
-
const mcpPath = resolve(dirname(installation.configPath), "mcp.json");
|
|
1786
|
-
const mcp = await readJsonDocument(mcpPath);
|
|
1787
|
-
cursorMcpDocuments.set(mcpPath, { document: mcp, merged: mergeLoreCursorMcp(mcp.exists ? mcp.value : {}, paths) });
|
|
1788
|
-
}
|
|
1789
|
-
}
|
|
1934
|
+
// Read and merge before anything is persisted so an unreadable agent file
|
|
1935
|
+
// stops the run without a half-written connection.
|
|
1936
|
+
const entries = await prepareAgentEntries(installations, paths);
|
|
1790
1937
|
const agentConfigPaths = {};
|
|
1791
1938
|
for (const agent of agents) {
|
|
1792
1939
|
agentConfigPaths[agent] = installations
|
|
@@ -1812,33 +1959,7 @@ async function connectWithCredentialLocked(options, identity) {
|
|
|
1812
1959
|
await reliabilityStore.initialize();
|
|
1813
1960
|
// Folder restrictions must be durable before any new hook can run.
|
|
1814
1961
|
await persistConnection(paths, config, connection.profiles);
|
|
1815
|
-
|
|
1816
|
-
await installRuntime(paths);
|
|
1817
|
-
}
|
|
1818
|
-
const changedHookFiles = [];
|
|
1819
|
-
const backups = [];
|
|
1820
|
-
for (const [path, { document, merged }] of cursorMcpDocuments) {
|
|
1821
|
-
const result = await writeMergedJson(path, document, merged, now);
|
|
1822
|
-
if (result.changed)
|
|
1823
|
-
changedHookFiles.push(path);
|
|
1824
|
-
if (result.backup)
|
|
1825
|
-
backups.push(result.backup);
|
|
1826
|
-
}
|
|
1827
|
-
for (const installation of installations) {
|
|
1828
|
-
const key = installationKey(installation);
|
|
1829
|
-
const document = documents.get(key);
|
|
1830
|
-
const merged = mergedDocuments.get(key);
|
|
1831
|
-
if (document === undefined || merged === undefined) {
|
|
1832
|
-
continue;
|
|
1833
|
-
}
|
|
1834
|
-
const result = await writeMergedJson(installation.configPath, document, merged, now);
|
|
1835
|
-
if (result.changed) {
|
|
1836
|
-
changedHookFiles.push(installation.configPath);
|
|
1837
|
-
}
|
|
1838
|
-
if (result.backup !== undefined) {
|
|
1839
|
-
backups.push(result.backup);
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1962
|
+
const { changedFiles: changedHookFiles, backups } = await writeAgentEntries(entries, paths, now);
|
|
1842
1963
|
if (existing && !existing.connectionId && existing.apiUrl === apiUrl && existing.token === token) {
|
|
1843
1964
|
await new ReliabilityStore(identity.workspaceId, { home: paths.home }).transferPendingTo(reliabilityStore);
|
|
1844
1965
|
await reliabilityStore.migrateLegacyQueue(paths.queue);
|
|
@@ -2024,11 +2145,11 @@ async function acknowledgeDeviceAuthorization(apiUrl, authorization) {
|
|
|
2024
2145
|
export function connectNextStep(agent) {
|
|
2025
2146
|
switch (agent) {
|
|
2026
2147
|
case "claude":
|
|
2027
|
-
return "Restart Claude Code.";
|
|
2148
|
+
return "Restart Claude Code and allow the lore MCP server when it asks.";
|
|
2028
2149
|
case "codex":
|
|
2029
2150
|
return "Restart Codex, open /hooks, approve Lore's hooks, then start a fresh session.";
|
|
2030
2151
|
case "cursor":
|
|
2031
|
-
return "Restart Cursor and enable the lore-native
|
|
2152
|
+
return "Restart Cursor and enable the lore-native and lore MCP servers.";
|
|
2032
2153
|
case "opencode":
|
|
2033
2154
|
return "Restart OpenCode.";
|
|
2034
2155
|
case "copilot":
|
|
@@ -2350,13 +2471,30 @@ async function getAgentStatus(agent, config, paths, runtime) {
|
|
|
2350
2471
|
units.push(...inspectInstallationUnits(document.value === undefined
|
|
2351
2472
|
? target.emptyValue()
|
|
2352
2473
|
: document.value, agent, paths, index, configPaths.length));
|
|
2353
|
-
if (agent
|
|
2354
|
-
const
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2474
|
+
if (isMcpHostAgent(agent)) {
|
|
2475
|
+
const mcpPath = mcpConfigPath(agent, path);
|
|
2476
|
+
if (agent === "codex") {
|
|
2477
|
+
const state = inspectCodexMcp((await readTextDocument(mcpPath)).value, loreMcpServer(paths));
|
|
2478
|
+
installed += state.owned;
|
|
2479
|
+
units.push({ id: installationUnitId("mcp-tools", index, configPaths.length),
|
|
2480
|
+
state: installationUnitState(state.owned, Number(state.exact)) });
|
|
2481
|
+
}
|
|
2482
|
+
else {
|
|
2483
|
+
const mcp = await readJsonDocument(mcpPath);
|
|
2484
|
+
const servers = isObject(mcp.value) && isObject(mcp.value.mcpServers) ? mcp.value.mcpServers : {};
|
|
2485
|
+
if (agent === "cursor") {
|
|
2486
|
+
const bridge = servers[CURSOR_MCP_NAME];
|
|
2487
|
+
const owned = Number(isLoreCursorMcp(bridge));
|
|
2488
|
+
installed += owned;
|
|
2489
|
+
units.push({ id: installationUnitId("mcp", index, configPaths.length),
|
|
2490
|
+
state: installationUnitState(owned, Number(exactConfiguration(bridge, cursorMcpServer(paths)))) });
|
|
2491
|
+
}
|
|
2492
|
+
const value = servers[LORE_MCP_SERVER_NAME];
|
|
2493
|
+
const owned = Number(isLoreMcpServer(value));
|
|
2494
|
+
installed += owned;
|
|
2495
|
+
units.push({ id: installationUnitId("mcp-tools", index, configPaths.length),
|
|
2496
|
+
state: installationUnitState(owned, Number(exactConfiguration(value, loreMcpServer(paths)))) });
|
|
2497
|
+
}
|
|
2360
2498
|
}
|
|
2361
2499
|
}
|
|
2362
2500
|
catch {
|
|
@@ -2518,15 +2656,29 @@ export async function disconnectInstallation(paths = getLorePaths(), preservePen
|
|
|
2518
2656
|
]);
|
|
2519
2657
|
for (const installation of installations) {
|
|
2520
2658
|
const path = installation.configPath;
|
|
2521
|
-
if (installation.agent
|
|
2522
|
-
const mcpPath =
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2659
|
+
if (isMcpHostAgent(installation.agent)) {
|
|
2660
|
+
const mcpPath = mcpConfigPath(installation.agent, path);
|
|
2661
|
+
if (installation.agent === "codex") {
|
|
2662
|
+
const text = await readTextDocument(mcpPath);
|
|
2663
|
+
if (text.exists) {
|
|
2664
|
+
const result = await writeMergedText(mcpPath, text, removeLoreCodexMcp(text.value), now);
|
|
2665
|
+
if (result.changed)
|
|
2666
|
+
changedHookFiles.push(mcpPath);
|
|
2667
|
+
if (result.backup)
|
|
2668
|
+
backups.push(result.backup);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
else {
|
|
2672
|
+
const mcp = await readJsonDocument(mcpPath);
|
|
2673
|
+
if (mcp.exists) {
|
|
2674
|
+
const withoutTools = removeLoreMcp(mcp.value, installation.agent);
|
|
2675
|
+
const cleaned = installation.agent === "cursor" ? removeLoreCursorMcp(withoutTools) : withoutTools;
|
|
2676
|
+
const result = await writeMergedJson(mcpPath, mcp, cleaned, now);
|
|
2677
|
+
if (result.changed)
|
|
2678
|
+
changedHookFiles.push(mcpPath);
|
|
2679
|
+
if (result.backup)
|
|
2680
|
+
backups.push(result.backup);
|
|
2681
|
+
}
|
|
2530
2682
|
}
|
|
2531
2683
|
}
|
|
2532
2684
|
const document = await readJsonDocument(path);
|
|
@@ -2910,6 +3062,9 @@ async function printCommandHelp(path) {
|
|
|
2910
3062
|
case "learnings":
|
|
2911
3063
|
await runLearningsCommand(args, null);
|
|
2912
3064
|
return;
|
|
3065
|
+
case "review":
|
|
3066
|
+
await runReviewCommand(args, null);
|
|
3067
|
+
return;
|
|
2913
3068
|
case "remember":
|
|
2914
3069
|
await runRememberCommand(args, null);
|
|
2915
3070
|
return;
|
|
@@ -2994,7 +3149,7 @@ async function executeCli(args = process.argv.slice(2)) {
|
|
|
2994
3149
|
const helpIndex = args.findIndex((value, index) => index > 0 && (separator < 0 || index < separator) && (value === "--help" || value === "-h"));
|
|
2995
3150
|
if (helpIndex > 0) {
|
|
2996
3151
|
const subcommand = args[1];
|
|
2997
|
-
const grouped = ["connect", "config", "embeddings", "guard", "recovery", "github", "devin", "host", "self-host", "scope"].includes(command ?? "");
|
|
3152
|
+
const grouped = ["connect", "config", "embeddings", "guard", "recovery", "github", "devin", "host", "self-host", "scope", "review"].includes(command ?? "");
|
|
2998
3153
|
await printCommandHelp([command, ...(grouped && subcommand && !subcommand.startsWith("-") ? [subcommand] : [])]);
|
|
2999
3154
|
return;
|
|
3000
3155
|
}
|
|
@@ -3007,7 +3162,7 @@ async function executeCli(args = process.argv.slice(2)) {
|
|
|
3007
3162
|
return;
|
|
3008
3163
|
}
|
|
3009
3164
|
const commandArgs = args.slice(1);
|
|
3010
|
-
if (["ask", "learnings", "remember", "share", "resume", "continue", "approach", "skill"].includes(command) && !(command === "skill" && commandArgs[0] === "discover")) {
|
|
3165
|
+
if (["ask", "learnings", "review", "remember", "share", "resume", "continue", "approach", "skill"].includes(command) && !(command === "skill" && commandArgs[0] === "discover")) {
|
|
3011
3166
|
const decision = await checkDirectory(process.cwd(), getLorePaths(["approach", "skill"].includes(command) ? process.env.LORE_HOME : undefined).home);
|
|
3012
3167
|
if (!decision.allowed)
|
|
3013
3168
|
throw new Error(`Lore is off in this folder. ${decision.reason}`);
|
|
@@ -3050,6 +3205,9 @@ async function executeCli(args = process.argv.slice(2)) {
|
|
|
3050
3205
|
case "learnings":
|
|
3051
3206
|
await runLearningsCommand(commandArgs, await readConnectorConfig(getLorePaths(), process.cwd()));
|
|
3052
3207
|
return;
|
|
3208
|
+
case "review":
|
|
3209
|
+
await runReviewCommand(commandArgs, await readConnectorConfig(getLorePaths(), process.cwd()));
|
|
3210
|
+
return;
|
|
3053
3211
|
case "remember":
|
|
3054
3212
|
await runRememberCommand(commandArgs, await readConnectorConfig(getLorePaths(), process.cwd()));
|
|
3055
3213
|
return;
|
|
@@ -3164,6 +3322,10 @@ async function executeCli(args = process.argv.slice(2)) {
|
|
|
3164
3322
|
case "cursor-mcp":
|
|
3165
3323
|
await (await import("./cursor-mcp.js")).runCursorMcpServer();
|
|
3166
3324
|
return;
|
|
3325
|
+
case "mcp":
|
|
3326
|
+
// The full Lore MCP server, registered by connect for hook-based agents.
|
|
3327
|
+
await (await import("@lore-co/mcp")).runStdioServer();
|
|
3328
|
+
return;
|
|
3167
3329
|
case "internal-health-write": {
|
|
3168
3330
|
const encoded = commandArgs[0] ?? (await readInvocationHealthWriteInput());
|
|
3169
3331
|
await runInvocationHealthWrite(encoded);
|