@rewind-ai/cli 0.6.3 → 0.6.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/dist/src/clients/mcp-json.d.ts +59 -0
- package/dist/src/clients/mcp-json.js +162 -0
- package/dist/src/clients/mcp-json.js.map +1 -0
- package/dist/src/clients/project-registry.d.ts +36 -0
- package/dist/src/clients/project-registry.js +136 -0
- package/dist/src/clients/project-registry.js.map +1 -0
- package/dist/src/clients/types.d.ts +40 -19
- package/dist/src/clients/types.js +298 -72
- package/dist/src/clients/types.js.map +1 -1
- package/dist/src/product/setup.d.ts +2 -2
- package/dist/src/product/setup.js +151 -38
- package/dist/src/product/setup.js.map +1 -1
- package/docs/MCP.md +13 -9
- package/docs/PACKAGING.md +11 -7
- package/package.json +1 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe Cursor MCP JSON editing.
|
|
3
|
+
* Preserves unrelated servers; never overwrites malformed config.
|
|
4
|
+
*/
|
|
5
|
+
export interface McpServerEntry {
|
|
6
|
+
command?: string;
|
|
7
|
+
args?: string[];
|
|
8
|
+
env?: Record<string, string>;
|
|
9
|
+
url?: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
export interface McpConfig {
|
|
13
|
+
mcpServers?: Record<string, McpServerEntry>;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
export declare class McpConfigError extends Error {
|
|
17
|
+
readonly name = "McpConfigError";
|
|
18
|
+
constructor(message: string);
|
|
19
|
+
}
|
|
20
|
+
/** Cursor user config directory (~/.cursor), overridable for tests. */
|
|
21
|
+
export declare function getCursorUserDir(override?: string | undefined): string;
|
|
22
|
+
export declare function getUserMcpConfigPath(cursorDir?: string): string;
|
|
23
|
+
export declare function projectMcpConfigPath(projectRoot: string): string;
|
|
24
|
+
export declare function writeJsonAtomic(file: string, data: unknown): Promise<void>;
|
|
25
|
+
export declare function backupFile(file: string): Promise<string | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Read MCP config. Missing file → empty config.
|
|
28
|
+
* Malformed JSON → throws McpConfigError (caller must not overwrite).
|
|
29
|
+
*/
|
|
30
|
+
export declare function readMcpConfig(file: string): Promise<{
|
|
31
|
+
config: McpConfig;
|
|
32
|
+
existed: boolean;
|
|
33
|
+
raw: string | null;
|
|
34
|
+
}>;
|
|
35
|
+
export interface MutateMcpResult {
|
|
36
|
+
path: string;
|
|
37
|
+
changed: boolean;
|
|
38
|
+
backupPath: string | null;
|
|
39
|
+
removedKeys: string[];
|
|
40
|
+
upsertedKeys: string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Atomically mutate mcpServers. Backs up before write.
|
|
44
|
+
* Never truncates on failure; malformed input throws before any write.
|
|
45
|
+
*/
|
|
46
|
+
export declare function mutateMcpConfig(file: string, mutate: (servers: Record<string, McpServerEntry>) => {
|
|
47
|
+
changed: boolean;
|
|
48
|
+
removedKeys?: string[];
|
|
49
|
+
upsertedKeys?: string[];
|
|
50
|
+
}): Promise<MutateMcpResult>;
|
|
51
|
+
/** True if MCP entry still points at source-repo-only tooling. */
|
|
52
|
+
export declare function isLegacySourceMcpEntry(entry: {
|
|
53
|
+
command?: string;
|
|
54
|
+
args?: string[];
|
|
55
|
+
}): boolean;
|
|
56
|
+
/** Detect REWIND-managed MCP server keys. */
|
|
57
|
+
export declare function isRewindServerKey(key: string): boolean;
|
|
58
|
+
export declare function entryProjectRoot(entry: McpServerEntry | undefined): string | null;
|
|
59
|
+
export declare function looksLikeRewindEntry(key: string, entry: McpServerEntry): boolean;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe Cursor MCP JSON editing.
|
|
3
|
+
* Preserves unrelated servers; never overwrites malformed config.
|
|
4
|
+
*/
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import { access, mkdir, readFile, writeFile, rename, copyFile, } from "node:fs/promises";
|
|
8
|
+
import { constants as fsConstants } from "node:fs";
|
|
9
|
+
export class McpConfigError extends Error {
|
|
10
|
+
name = "McpConfigError";
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function exists(p) {
|
|
16
|
+
try {
|
|
17
|
+
await access(p, fsConstants.F_OK);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Cursor user config directory (~/.cursor), overridable for tests. */
|
|
25
|
+
export function getCursorUserDir(override = process.env.REWIND_CURSOR_DIR) {
|
|
26
|
+
if (override)
|
|
27
|
+
return path.resolve(override);
|
|
28
|
+
return path.join(os.homedir(), ".cursor");
|
|
29
|
+
}
|
|
30
|
+
export function getUserMcpConfigPath(cursorDir = getCursorUserDir()) {
|
|
31
|
+
return path.join(cursorDir, "mcp.json");
|
|
32
|
+
}
|
|
33
|
+
export function projectMcpConfigPath(projectRoot) {
|
|
34
|
+
return path.join(projectRoot, ".cursor", "mcp.json");
|
|
35
|
+
}
|
|
36
|
+
export async function writeJsonAtomic(file, data) {
|
|
37
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
38
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
39
|
+
const body = JSON.stringify(data, null, 2) + "\n";
|
|
40
|
+
await writeFile(tmp, body, "utf8");
|
|
41
|
+
// Validate temp before rename
|
|
42
|
+
JSON.parse(await readFile(tmp, "utf8"));
|
|
43
|
+
await rename(tmp, file);
|
|
44
|
+
}
|
|
45
|
+
export async function backupFile(file) {
|
|
46
|
+
if (!(await exists(file)))
|
|
47
|
+
return null;
|
|
48
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
49
|
+
const backup = `${file}.rewind-backup-${stamp}`;
|
|
50
|
+
await copyFile(file, backup);
|
|
51
|
+
// Also keep a stable latest pointer for recovery docs
|
|
52
|
+
await copyFile(file, `${file}.rewind-backup`);
|
|
53
|
+
return backup;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read MCP config. Missing file → empty config.
|
|
57
|
+
* Malformed JSON → throws McpConfigError (caller must not overwrite).
|
|
58
|
+
*/
|
|
59
|
+
export async function readMcpConfig(file) {
|
|
60
|
+
if (!(await exists(file))) {
|
|
61
|
+
return { config: { mcpServers: {} }, existed: false, raw: null };
|
|
62
|
+
}
|
|
63
|
+
const raw = await readFile(file, "utf8");
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(raw);
|
|
66
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
67
|
+
throw new McpConfigError(`MCP config is not a JSON object: ${file}. Fix or remove it, then re-run rewind setup.`);
|
|
68
|
+
}
|
|
69
|
+
const config = parsed;
|
|
70
|
+
if (config.mcpServers != null && typeof config.mcpServers !== "object") {
|
|
71
|
+
throw new McpConfigError(`MCP config mcpServers is not an object: ${file}. Fix or remove it, then re-run rewind setup.`);
|
|
72
|
+
}
|
|
73
|
+
if (!config.mcpServers)
|
|
74
|
+
config.mcpServers = {};
|
|
75
|
+
return { config, existed: true, raw };
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
if (err instanceof McpConfigError)
|
|
79
|
+
throw err;
|
|
80
|
+
throw new McpConfigError(`MCP config is malformed JSON at ${file}. ` +
|
|
81
|
+
`REWIND will not overwrite it. Fix the file manually, then re-run rewind setup. ` +
|
|
82
|
+
`(${err instanceof Error ? err.message : String(err)})`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Atomically mutate mcpServers. Backs up before write.
|
|
87
|
+
* Never truncates on failure; malformed input throws before any write.
|
|
88
|
+
*/
|
|
89
|
+
export async function mutateMcpConfig(file, mutate) {
|
|
90
|
+
const { config, existed } = await readMcpConfig(file);
|
|
91
|
+
const servers = {
|
|
92
|
+
...(config.mcpServers ?? {}),
|
|
93
|
+
};
|
|
94
|
+
const result = mutate(servers);
|
|
95
|
+
if (!result.changed) {
|
|
96
|
+
return {
|
|
97
|
+
path: file,
|
|
98
|
+
changed: false,
|
|
99
|
+
backupPath: null,
|
|
100
|
+
removedKeys: [],
|
|
101
|
+
upsertedKeys: [],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
let backupPath = null;
|
|
105
|
+
if (existed) {
|
|
106
|
+
backupPath = await backupFile(file);
|
|
107
|
+
}
|
|
108
|
+
const next = { ...config, mcpServers: servers };
|
|
109
|
+
await writeJsonAtomic(file, next);
|
|
110
|
+
// Post-write validation
|
|
111
|
+
const verify = await readMcpConfig(file);
|
|
112
|
+
if (!verify.config.mcpServers) {
|
|
113
|
+
throw new McpConfigError(`Post-write validation failed for ${file}`);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
path: file,
|
|
117
|
+
changed: true,
|
|
118
|
+
backupPath,
|
|
119
|
+
removedKeys: result.removedKeys ?? [],
|
|
120
|
+
upsertedKeys: result.upsertedKeys ?? [],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** True if MCP entry still points at source-repo-only tooling. */
|
|
124
|
+
export function isLegacySourceMcpEntry(entry) {
|
|
125
|
+
const cmd = entry.command ?? "";
|
|
126
|
+
const joined = [cmd, ...(entry.args ?? [])].join(" ");
|
|
127
|
+
if (/tsx/i.test(joined) && /src[/\\]cli\.ts/i.test(joined))
|
|
128
|
+
return true;
|
|
129
|
+
if (/node_modules[/\\]\.bin[/\\]tsx/i.test(cmd))
|
|
130
|
+
return true;
|
|
131
|
+
if (/src[/\\]cli\.ts/i.test(joined) &&
|
|
132
|
+
!/dist[/\\]src[/\\]cli\.js/i.test(joined)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
/** Detect REWIND-managed MCP server keys. */
|
|
138
|
+
export function isRewindServerKey(key) {
|
|
139
|
+
return key === "rewind" || key.startsWith("rewind-");
|
|
140
|
+
}
|
|
141
|
+
export function entryProjectRoot(entry) {
|
|
142
|
+
const root = entry?.env?.REWIND_PROJECT_ROOT;
|
|
143
|
+
if (root == null || String(root).trim() === "")
|
|
144
|
+
return null;
|
|
145
|
+
return path.resolve(String(root).trim());
|
|
146
|
+
}
|
|
147
|
+
export function looksLikeRewindEntry(key, entry) {
|
|
148
|
+
if (isRewindServerKey(key))
|
|
149
|
+
return true;
|
|
150
|
+
if (entry.env?.REWIND_PROJECT_ROOT)
|
|
151
|
+
return true;
|
|
152
|
+
const joined = [entry.command ?? "", ...(entry.args ?? [])].join(" ");
|
|
153
|
+
if (/@rewind-ai[/\\]cli/i.test(joined))
|
|
154
|
+
return true;
|
|
155
|
+
if (/[/\\]cli\.(js|ts)\b/i.test(joined) && /\bmcp\b/.test(joined)) {
|
|
156
|
+
// Heuristic only when clearly REWIND-ish
|
|
157
|
+
if (/rewind/i.test(joined))
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=mcp-json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-json.js","sourceRoot":"","sources":["../../../src/clients/mcp-json.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global REWIND project registry (~/.rewind/projects.json).
|
|
3
|
+
* Installation / routing / discovery only — not the transaction ledger.
|
|
4
|
+
*/
|
|
5
|
+
export type CursorIntegrationState = "configured" | "needs_reload" | "connected" | "removed" | "failed";
|
|
6
|
+
export interface RegisteredProject {
|
|
7
|
+
id: string;
|
|
8
|
+
path: string;
|
|
9
|
+
displayName: string;
|
|
10
|
+
serverName: string;
|
|
11
|
+
createdAt: string;
|
|
12
|
+
updatedAt: string;
|
|
13
|
+
cursorIntegration: CursorIntegrationState;
|
|
14
|
+
}
|
|
15
|
+
export interface ProjectRegistry {
|
|
16
|
+
version: 1;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
projects: RegisteredProject[];
|
|
19
|
+
}
|
|
20
|
+
export declare function getProjectsRegistryPath(userDir?: string): string;
|
|
21
|
+
export declare function slugifyProjectName(name: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Stable readable MCP server name for a project.
|
|
24
|
+
* Collisions with a different path get a short path suffix.
|
|
25
|
+
*/
|
|
26
|
+
export declare function allocateServerName(projectRoot: string, existing: RegisteredProject[]): string;
|
|
27
|
+
export declare function loadProjectRegistry(userDir?: string): Promise<ProjectRegistry>;
|
|
28
|
+
export declare function saveProjectRegistry(registry: ProjectRegistry, userDir?: string): Promise<void>;
|
|
29
|
+
export declare function upsertRegisteredProject(projectRoot: string, opts?: {
|
|
30
|
+
serverName?: string;
|
|
31
|
+
cursorIntegration?: CursorIntegrationState;
|
|
32
|
+
displayName?: string;
|
|
33
|
+
}, userDir?: string): Promise<RegisteredProject>;
|
|
34
|
+
export declare function findRegisteredProject(projectRoot: string, userDir?: string): Promise<RegisteredProject | null>;
|
|
35
|
+
export declare function removeRegisteredProject(projectRoot: string, userDir?: string): Promise<RegisteredProject | null>;
|
|
36
|
+
export declare function markProjectIntegration(projectRoot: string, state: CursorIntegrationState, userDir?: string): Promise<void>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global REWIND project registry (~/.rewind/projects.json).
|
|
3
|
+
* Installation / routing / discovery only — not the transaction ledger.
|
|
4
|
+
*/
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { mkdir, readFile, writeFile, rename, access } from "node:fs/promises";
|
|
8
|
+
import { constants as fsConstants } from "node:fs";
|
|
9
|
+
import { getUserRewindDir } from "../paths.js";
|
|
10
|
+
async function exists(p) {
|
|
11
|
+
try {
|
|
12
|
+
await access(p, fsConstants.F_OK);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function getProjectsRegistryPath(userDir = getUserRewindDir()) {
|
|
20
|
+
return path.join(userDir, "projects.json");
|
|
21
|
+
}
|
|
22
|
+
function stableProjectId(projectRoot) {
|
|
23
|
+
const abs = path.resolve(projectRoot);
|
|
24
|
+
const hash = createHash("sha256").update(abs).digest("hex").slice(0, 12);
|
|
25
|
+
return `proj_${hash}`;
|
|
26
|
+
}
|
|
27
|
+
export function slugifyProjectName(name) {
|
|
28
|
+
const slug = name
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
31
|
+
.replace(/^-+|-+$/g, "")
|
|
32
|
+
.slice(0, 40);
|
|
33
|
+
return slug || "project";
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Stable readable MCP server name for a project.
|
|
37
|
+
* Collisions with a different path get a short path suffix.
|
|
38
|
+
*/
|
|
39
|
+
export function allocateServerName(projectRoot, existing) {
|
|
40
|
+
const abs = path.resolve(projectRoot);
|
|
41
|
+
const prior = existing.find((p) => path.resolve(p.path) === abs);
|
|
42
|
+
if (prior)
|
|
43
|
+
return prior.serverName;
|
|
44
|
+
const base = `rewind-${slugifyProjectName(path.basename(abs))}`;
|
|
45
|
+
const taken = new Set(existing
|
|
46
|
+
.filter((p) => path.resolve(p.path) !== abs)
|
|
47
|
+
.map((p) => p.serverName));
|
|
48
|
+
if (!taken.has(base) && base !== "rewind")
|
|
49
|
+
return base;
|
|
50
|
+
const suffix = createHash("sha256").update(abs).digest("hex").slice(0, 6);
|
|
51
|
+
let candidate = `${base}-${suffix}`;
|
|
52
|
+
let n = 2;
|
|
53
|
+
while (taken.has(candidate)) {
|
|
54
|
+
candidate = `${base}-${suffix}-${n}`;
|
|
55
|
+
n += 1;
|
|
56
|
+
}
|
|
57
|
+
return candidate;
|
|
58
|
+
}
|
|
59
|
+
async function writeRegistryAtomic(file, data) {
|
|
60
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
61
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
62
|
+
await writeFile(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
63
|
+
await rename(tmp, file);
|
|
64
|
+
}
|
|
65
|
+
export async function loadProjectRegistry(userDir = getUserRewindDir()) {
|
|
66
|
+
const file = getProjectsRegistryPath(userDir);
|
|
67
|
+
if (!(await exists(file))) {
|
|
68
|
+
return { version: 1, updatedAt: new Date().toISOString(), projects: [] };
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const raw = JSON.parse(await readFile(file, "utf8"));
|
|
72
|
+
if (!raw || !Array.isArray(raw.projects)) {
|
|
73
|
+
return { version: 1, updatedAt: new Date().toISOString(), projects: [] };
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
version: 1,
|
|
77
|
+
updatedAt: raw.updatedAt ?? new Date().toISOString(),
|
|
78
|
+
projects: raw.projects,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { version: 1, updatedAt: new Date().toISOString(), projects: [] };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export async function saveProjectRegistry(registry, userDir = getUserRewindDir()) {
|
|
86
|
+
registry.updatedAt = new Date().toISOString();
|
|
87
|
+
await writeRegistryAtomic(getProjectsRegistryPath(userDir), registry);
|
|
88
|
+
}
|
|
89
|
+
export async function upsertRegisteredProject(projectRoot, opts = {}, userDir = getUserRewindDir()) {
|
|
90
|
+
const abs = path.resolve(projectRoot);
|
|
91
|
+
const registry = await loadProjectRegistry(userDir);
|
|
92
|
+
const now = new Date().toISOString();
|
|
93
|
+
const idx = registry.projects.findIndex((p) => path.resolve(p.path) === abs);
|
|
94
|
+
const serverName = opts.serverName ??
|
|
95
|
+
(idx >= 0
|
|
96
|
+
? registry.projects[idx].serverName
|
|
97
|
+
: allocateServerName(abs, registry.projects));
|
|
98
|
+
const entry = {
|
|
99
|
+
id: idx >= 0 ? registry.projects[idx].id : stableProjectId(abs),
|
|
100
|
+
path: abs,
|
|
101
|
+
displayName: opts.displayName ??
|
|
102
|
+
(idx >= 0
|
|
103
|
+
? registry.projects[idx].displayName
|
|
104
|
+
: path.basename(abs)),
|
|
105
|
+
serverName,
|
|
106
|
+
createdAt: idx >= 0 ? registry.projects[idx].createdAt : now,
|
|
107
|
+
updatedAt: now,
|
|
108
|
+
cursorIntegration: opts.cursorIntegration ??
|
|
109
|
+
(idx >= 0 ? registry.projects[idx].cursorIntegration : "configured"),
|
|
110
|
+
};
|
|
111
|
+
if (idx >= 0)
|
|
112
|
+
registry.projects[idx] = entry;
|
|
113
|
+
else
|
|
114
|
+
registry.projects.push(entry);
|
|
115
|
+
await saveProjectRegistry(registry, userDir);
|
|
116
|
+
return entry;
|
|
117
|
+
}
|
|
118
|
+
export async function findRegisteredProject(projectRoot, userDir = getUserRewindDir()) {
|
|
119
|
+
const abs = path.resolve(projectRoot);
|
|
120
|
+
const registry = await loadProjectRegistry(userDir);
|
|
121
|
+
return (registry.projects.find((p) => path.resolve(p.path) === abs) ?? null);
|
|
122
|
+
}
|
|
123
|
+
export async function removeRegisteredProject(projectRoot, userDir = getUserRewindDir()) {
|
|
124
|
+
const abs = path.resolve(projectRoot);
|
|
125
|
+
const registry = await loadProjectRegistry(userDir);
|
|
126
|
+
const idx = registry.projects.findIndex((p) => path.resolve(p.path) === abs);
|
|
127
|
+
if (idx < 0)
|
|
128
|
+
return null;
|
|
129
|
+
const [removed] = registry.projects.splice(idx, 1);
|
|
130
|
+
await saveProjectRegistry(registry, userDir);
|
|
131
|
+
return removed ?? null;
|
|
132
|
+
}
|
|
133
|
+
export async function markProjectIntegration(projectRoot, state, userDir = getUserRewindDir()) {
|
|
134
|
+
await upsertRegisteredProject(projectRoot, { cursorIntegration: state }, userDir);
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=project-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-registry.js","sourceRoot":"","sources":["../../../src/clients/project-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent client integration layer.
|
|
3
3
|
* Cursor is the first proven integration. Do not advertise untested clients.
|
|
4
|
+
*
|
|
5
|
+
* Strategy (observed Cursor behaviour):
|
|
6
|
+
* Project-level `.cursor/mcp.json` may be discovered but remain disconnected.
|
|
7
|
+
* User-level `~/.cursor/mcp.json` connects reliably.
|
|
8
|
+
* Therefore: one user-level MCP server entry per REWIND project
|
|
9
|
+
* (`rewind-<slug>`) with absolute REWIND_PROJECT_ROOT — never overwrite
|
|
10
|
+
* a single global entry when setting up another project.
|
|
4
11
|
*/
|
|
5
|
-
|
|
12
|
+
import { type CursorIntegrationState } from "./project-registry.js";
|
|
13
|
+
import { isLegacySourceMcpEntry, type McpServerEntry } from "./mcp-json.js";
|
|
14
|
+
export type ClientConnectionStatus = "not_detected" | "detected" | "configured" | "connected" | "needs_reload" | "invalid";
|
|
6
15
|
export interface ClientStatus {
|
|
7
16
|
id: string;
|
|
8
17
|
displayName: string;
|
|
@@ -11,6 +20,9 @@ export interface ClientStatus {
|
|
|
11
20
|
configPath: string | null;
|
|
12
21
|
message: string;
|
|
13
22
|
supported: boolean;
|
|
23
|
+
/** Honest integration state for setup/doctor */
|
|
24
|
+
integrationState?: CursorIntegrationState | "not_configured";
|
|
25
|
+
serverName?: string | null;
|
|
14
26
|
}
|
|
15
27
|
export interface AgentClientIntegration {
|
|
16
28
|
readonly id: string;
|
|
@@ -19,10 +31,7 @@ export interface AgentClientIntegration {
|
|
|
19
31
|
detect(): Promise<boolean>;
|
|
20
32
|
getConfigPath(): Promise<string | null>;
|
|
21
33
|
readConfig(): Promise<unknown | null>;
|
|
22
|
-
install(projectRoot: string): Promise<
|
|
23
|
-
path: string;
|
|
24
|
-
changed: boolean;
|
|
25
|
-
}>;
|
|
34
|
+
install(projectRoot: string): Promise<CursorInstallResult>;
|
|
26
35
|
validate(projectRoot: string): Promise<{
|
|
27
36
|
ok: boolean;
|
|
28
37
|
message: string;
|
|
@@ -33,32 +42,47 @@ export interface AgentClientIntegration {
|
|
|
33
42
|
}>;
|
|
34
43
|
status(projectRoot: string): Promise<ClientStatus>;
|
|
35
44
|
}
|
|
45
|
+
export interface CursorInstallResult {
|
|
46
|
+
path: string;
|
|
47
|
+
changed: boolean;
|
|
48
|
+
serverName: string;
|
|
49
|
+
backupPath: string | null;
|
|
50
|
+
removedKeys: string[];
|
|
51
|
+
legacyCleaned: string[];
|
|
52
|
+
projectMcpCleaned: boolean;
|
|
53
|
+
state: CursorIntegrationState;
|
|
54
|
+
notes: string[];
|
|
55
|
+
}
|
|
36
56
|
/** Portable MCP entry — installed CLI only (never tsx + src/cli.ts). */
|
|
37
|
-
export declare function rewindMcpEntry(projectRoot: string):
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
export declare function rewindMcpEntry(projectRoot: string): McpServerEntry;
|
|
58
|
+
export { isLegacySourceMcpEntry };
|
|
59
|
+
/**
|
|
60
|
+
* Evidence that Cursor has actually loaded/used REWIND MCP for this project.
|
|
61
|
+
* Config presence alone is NOT connection.
|
|
62
|
+
*/
|
|
63
|
+
export declare function observeCursorMcpConnection(projectRoot: string): Promise<{
|
|
64
|
+
connected: boolean;
|
|
65
|
+
detail: string;
|
|
66
|
+
}>;
|
|
43
67
|
export declare class CursorIntegration implements AgentClientIntegration {
|
|
44
68
|
readonly id = "cursor";
|
|
45
69
|
readonly displayName = "Cursor";
|
|
46
70
|
readonly supported = true;
|
|
47
71
|
detect(): Promise<boolean>;
|
|
48
72
|
getConfigPath(): Promise<string | null>;
|
|
73
|
+
/** @deprecated Prefer user-level config; kept for cleanup of old installs */
|
|
49
74
|
projectMcpPath(projectRoot: string): string;
|
|
50
75
|
readConfig(): Promise<unknown | null>;
|
|
51
|
-
install(projectRoot: string): Promise<
|
|
52
|
-
path: string;
|
|
53
|
-
changed: boolean;
|
|
54
|
-
}>;
|
|
76
|
+
install(projectRoot: string): Promise<CursorInstallResult>;
|
|
55
77
|
validate(projectRoot: string): Promise<{
|
|
56
78
|
ok: boolean;
|
|
57
79
|
message: string;
|
|
58
80
|
}>;
|
|
81
|
+
private findServerNameForProject;
|
|
59
82
|
remove(projectRoot: string): Promise<{
|
|
60
83
|
removed: boolean;
|
|
61
84
|
path: string | null;
|
|
85
|
+
serverName?: string;
|
|
62
86
|
}>;
|
|
63
87
|
status(projectRoot: string): Promise<ClientStatus>;
|
|
64
88
|
}
|
|
@@ -71,10 +95,7 @@ export declare class UnsupportedClientStub implements AgentClientIntegration {
|
|
|
71
95
|
detect(): Promise<boolean>;
|
|
72
96
|
getConfigPath(): Promise<string | null>;
|
|
73
97
|
readConfig(): Promise<unknown | null>;
|
|
74
|
-
install(): Promise<
|
|
75
|
-
path: string;
|
|
76
|
-
changed: boolean;
|
|
77
|
-
}>;
|
|
98
|
+
install(): Promise<CursorInstallResult>;
|
|
78
99
|
validate(): Promise<{
|
|
79
100
|
ok: boolean;
|
|
80
101
|
message: string;
|