@love-moon/conductor-sdk 0.1.0 → 0.1.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/backend/client.d.ts +17 -0
- package/dist/backend/client.js +27 -0
- package/dist/mcp/server.d.ts +3 -1
- package/dist/mcp/server.js +52 -1
- package/dist/session/store.d.ts +6 -0
- package/dist/session/store.js +29 -0
- package/package.json +1 -1
package/dist/backend/client.d.ts
CHANGED
|
@@ -56,6 +56,23 @@ export declare class BackendApiClient {
|
|
|
56
56
|
content: string;
|
|
57
57
|
metadata?: Record<string, unknown>;
|
|
58
58
|
}): Promise<any>;
|
|
59
|
+
matchProjectByPath(params: {
|
|
60
|
+
hostname: string;
|
|
61
|
+
path: string;
|
|
62
|
+
}): Promise<{
|
|
63
|
+
project: ProjectSummary | null;
|
|
64
|
+
matchedPath: string | null;
|
|
65
|
+
}>;
|
|
66
|
+
getProject(projectId: string): Promise<{
|
|
67
|
+
id: string;
|
|
68
|
+
name?: string;
|
|
69
|
+
description?: string | null;
|
|
70
|
+
metadata?: Record<string, unknown>;
|
|
71
|
+
}>;
|
|
72
|
+
updateProject(projectId: string, params: {
|
|
73
|
+
name?: string;
|
|
74
|
+
metadata?: Record<string, unknown>;
|
|
75
|
+
}): Promise<ProjectSummary>;
|
|
59
76
|
private request;
|
|
60
77
|
private parseJson;
|
|
61
78
|
private safeJson;
|
package/dist/backend/client.js
CHANGED
|
@@ -139,6 +139,33 @@ export class BackendApiClient {
|
|
|
139
139
|
});
|
|
140
140
|
return this.parseJson(response);
|
|
141
141
|
}
|
|
142
|
+
async matchProjectByPath(params) {
|
|
143
|
+
const response = await this.request('POST', '/projects/match-path', {
|
|
144
|
+
body: JSON.stringify(params),
|
|
145
|
+
});
|
|
146
|
+
const payload = await this.parseJson(response);
|
|
147
|
+
return {
|
|
148
|
+
project: payload.project ? ProjectSummary.fromJSON(payload.project) : null,
|
|
149
|
+
matchedPath: payload.matched_path ?? null,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
async getProject(projectId) {
|
|
153
|
+
const response = await this.request('GET', `/projects/${projectId}`);
|
|
154
|
+
const payload = await this.parseJson(response);
|
|
155
|
+
return {
|
|
156
|
+
id: payload.id,
|
|
157
|
+
name: payload.name,
|
|
158
|
+
description: payload.description,
|
|
159
|
+
metadata: payload.metadata ?? undefined,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async updateProject(projectId, params) {
|
|
163
|
+
const response = await this.request('PATCH', `/projects/${projectId}`, {
|
|
164
|
+
body: JSON.stringify(params),
|
|
165
|
+
});
|
|
166
|
+
const payload = await this.parseJson(response);
|
|
167
|
+
return ProjectSummary.fromJSON(payload);
|
|
168
|
+
}
|
|
142
169
|
async request(method, pathname, opts = {}) {
|
|
143
170
|
const url = new URL(`${this.baseUrl}${pathname}`);
|
|
144
171
|
if (opts.query) {
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface MCPServerOptions {
|
|
|
8
8
|
sessionManager: SessionManager;
|
|
9
9
|
messageRouter: MessageRouter;
|
|
10
10
|
backendSender: BackendSender;
|
|
11
|
-
backendApi: Pick<BackendApiClient, 'listProjects' | 'listTasks' | 'createProject' | 'createTask'>;
|
|
11
|
+
backendApi: Pick<BackendApiClient, 'listProjects' | 'listTasks' | 'createProject' | 'createTask' | 'matchProjectByPath' | 'getProject' | 'updateProject'>;
|
|
12
12
|
sessionStore?: SessionDiskStore;
|
|
13
13
|
env?: Record<string, string | undefined>;
|
|
14
14
|
}
|
|
@@ -30,6 +30,8 @@ export declare class MCPServer {
|
|
|
30
30
|
private toolCreateProject;
|
|
31
31
|
private toolListTasks;
|
|
32
32
|
private toolGetLocalProjectId;
|
|
33
|
+
private toolMatchProjectByPath;
|
|
34
|
+
private toolBindProjectPath;
|
|
33
35
|
private resolveHostname;
|
|
34
36
|
private waitForTaskCreation;
|
|
35
37
|
private readIntEnv;
|
package/dist/mcp/server.js
CHANGED
|
@@ -10,7 +10,8 @@ export class MCPServer {
|
|
|
10
10
|
constructor(config, options) {
|
|
11
11
|
this.config = config;
|
|
12
12
|
this.options = options;
|
|
13
|
-
|
|
13
|
+
// Use backend URL to determine session file path (isolates different environments)
|
|
14
|
+
this.sessionStore = options.sessionStore ?? SessionDiskStore.forBackendUrl(config.backendUrl);
|
|
14
15
|
this.env = options.env ?? process.env;
|
|
15
16
|
this.tools = {
|
|
16
17
|
create_task_session: this.toolCreateTaskSession,
|
|
@@ -21,6 +22,8 @@ export class MCPServer {
|
|
|
21
22
|
create_project: this.toolCreateProject,
|
|
22
23
|
list_tasks: this.toolListTasks,
|
|
23
24
|
get_local_project_id: this.toolGetLocalProjectId,
|
|
25
|
+
match_project_by_path: this.toolMatchProjectByPath,
|
|
26
|
+
bind_project_path: this.toolBindProjectPath,
|
|
24
27
|
};
|
|
25
28
|
}
|
|
26
29
|
async handleRequest(toolName, payload) {
|
|
@@ -154,6 +157,54 @@ export class MCPServer {
|
|
|
154
157
|
hostname: record.hostname,
|
|
155
158
|
};
|
|
156
159
|
}
|
|
160
|
+
async toolMatchProjectByPath(payload) {
|
|
161
|
+
const hostname = typeof payload.hostname === 'string' ? payload.hostname : currentHostname();
|
|
162
|
+
const projectPath = typeof payload.project_path === 'string' && payload.project_path
|
|
163
|
+
? payload.project_path
|
|
164
|
+
: process.cwd();
|
|
165
|
+
console.error(`[mcp] match_project_by_path hostname=${hostname} path=${projectPath}`);
|
|
166
|
+
const result = await this.options.backendApi.matchProjectByPath({
|
|
167
|
+
hostname,
|
|
168
|
+
path: projectPath,
|
|
169
|
+
});
|
|
170
|
+
if (result.project) {
|
|
171
|
+
return {
|
|
172
|
+
project_id: result.project.id,
|
|
173
|
+
project_name: result.project.name,
|
|
174
|
+
matched_path: result.matchedPath,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
project_id: null,
|
|
179
|
+
project_name: null,
|
|
180
|
+
matched_path: null,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
async toolBindProjectPath(payload) {
|
|
184
|
+
const projectId = String(payload.project_id || '');
|
|
185
|
+
if (!projectId) {
|
|
186
|
+
throw new Error('project_id is required');
|
|
187
|
+
}
|
|
188
|
+
const hostname = typeof payload.hostname === 'string' ? payload.hostname : currentHostname();
|
|
189
|
+
const projectPath = typeof payload.project_path === 'string' && payload.project_path
|
|
190
|
+
? payload.project_path
|
|
191
|
+
: process.cwd();
|
|
192
|
+
console.error(`[mcp] bind_project_path project=${projectId} hostname=${hostname} path=${projectPath}`);
|
|
193
|
+
// Get current project metadata
|
|
194
|
+
const project = await this.options.backendApi.getProject(projectId);
|
|
195
|
+
const metadata = (project.metadata || {});
|
|
196
|
+
const localPaths = (metadata.localPaths || {});
|
|
197
|
+
// Update localPaths with new binding
|
|
198
|
+
localPaths[hostname] = projectPath;
|
|
199
|
+
metadata.localPaths = localPaths;
|
|
200
|
+
// Update project
|
|
201
|
+
await this.options.backendApi.updateProject(projectId, { metadata });
|
|
202
|
+
return {
|
|
203
|
+
success: true,
|
|
204
|
+
hostname,
|
|
205
|
+
path: projectPath,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
157
208
|
resolveHostname() {
|
|
158
209
|
const records = this.sessionStore.load();
|
|
159
210
|
for (const record of records) {
|
package/dist/session/store.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const DEFAULT_SESSION_DIR: string;
|
|
1
2
|
export declare const DEFAULT_SESSION_PATH: string;
|
|
2
3
|
export declare const DEFAULT_SESSION_ENV = "CODEX_SESSION_ID";
|
|
3
4
|
export declare const DEFAULT_SESSION_FALLBACK_ENV = "SESSION_ID";
|
|
@@ -21,6 +22,11 @@ export declare class SessionRecord {
|
|
|
21
22
|
export declare class SessionDiskStore {
|
|
22
23
|
private readonly filePath;
|
|
23
24
|
constructor(filePath?: string);
|
|
25
|
+
/**
|
|
26
|
+
* Create a SessionDiskStore for a specific backend URL.
|
|
27
|
+
* Sessions are stored in ~/.conductor/sessions/<host>.yaml
|
|
28
|
+
*/
|
|
29
|
+
static forBackendUrl(backendUrl: string): SessionDiskStore;
|
|
24
30
|
load(): SessionRecord[];
|
|
25
31
|
save(records: SessionRecord[]): void;
|
|
26
32
|
findByPath(projectPath: string): SessionRecord | undefined;
|
package/dist/session/store.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import yaml from 'yaml';
|
|
5
|
+
export const DEFAULT_SESSION_DIR = path.join(os.homedir(), '.conductor', 'sessions');
|
|
5
6
|
export const DEFAULT_SESSION_PATH = path.join(os.homedir(), '.conductor', 'session.yaml');
|
|
6
7
|
export const DEFAULT_SESSION_ENV = 'CODEX_SESSION_ID';
|
|
7
8
|
export const DEFAULT_SESSION_FALLBACK_ENV = 'SESSION_ID';
|
|
@@ -55,6 +56,15 @@ export class SessionDiskStore {
|
|
|
55
56
|
constructor(filePath = DEFAULT_SESSION_PATH) {
|
|
56
57
|
this.filePath = path.resolve(filePath);
|
|
57
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Create a SessionDiskStore for a specific backend URL.
|
|
61
|
+
* Sessions are stored in ~/.conductor/sessions/<host>.yaml
|
|
62
|
+
*/
|
|
63
|
+
static forBackendUrl(backendUrl) {
|
|
64
|
+
const host = extractHostKey(backendUrl);
|
|
65
|
+
const filePath = path.join(DEFAULT_SESSION_DIR, `${host}.yaml`);
|
|
66
|
+
return new SessionDiskStore(filePath);
|
|
67
|
+
}
|
|
58
68
|
load() {
|
|
59
69
|
if (!fs.existsSync(this.filePath)) {
|
|
60
70
|
return [];
|
|
@@ -145,3 +155,22 @@ export function currentHostname() {
|
|
|
145
155
|
return 'unknown';
|
|
146
156
|
}
|
|
147
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Extract a safe filename key from a backend URL.
|
|
160
|
+
* e.g., "http://localhost:6152" -> "localhost_6152"
|
|
161
|
+
* "https://conductor-ai.top" -> "conductor-ai.top"
|
|
162
|
+
*/
|
|
163
|
+
function extractHostKey(backendUrl) {
|
|
164
|
+
try {
|
|
165
|
+
const url = new URL(backendUrl);
|
|
166
|
+
const host = url.hostname;
|
|
167
|
+
const port = url.port;
|
|
168
|
+
// Replace unsafe characters for filenames
|
|
169
|
+
const safeHost = host.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
170
|
+
return port ? `${safeHost}_${port}` : safeHost;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Fallback: sanitize the entire URL
|
|
174
|
+
return backendUrl.replace(/[^a-zA-Z0-9.-]/g, '_').slice(0, 50);
|
|
175
|
+
}
|
|
176
|
+
}
|