@parall/cli 1.12.0
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/commands/agents.d.ts +3 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +18 -0
- package/dist/commands/chats.d.ts +3 -0
- package/dist/commands/chats.d.ts.map +1 -0
- package/dist/commands/chats.js +105 -0
- package/dist/commands/dm.d.ts +3 -0
- package/dist/commands/dm.d.ts.map +1 -0
- package/dist/commands/dm.js +51 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +439 -0
- package/dist/commands/messages.d.ts +3 -0
- package/dist/commands/messages.d.ts.map +1 -0
- package/dist/commands/messages.js +102 -0
- package/dist/commands/projects.d.ts +3 -0
- package/dist/commands/projects.d.ts.map +1 -0
- package/dist/commands/projects.js +104 -0
- package/dist/commands/refs.d.ts +3 -0
- package/dist/commands/refs.d.ts.map +1 -0
- package/dist/commands/refs.js +50 -0
- package/dist/commands/tasks.d.ts +3 -0
- package/dist/commands/tasks.d.ts.map +1 -0
- package/dist/commands/tasks.js +240 -0
- package/dist/commands/users.d.ts +3 -0
- package/dist/commands/users.d.ts.map +1 -0
- package/dist/commands/users.js +49 -0
- package/dist/commands/wiki.d.ts +3 -0
- package/dist/commands/wiki.d.ts.map +1 -0
- package/dist/commands/wiki.js +644 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/lib/client.d.ts +24 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/client.js +47 -0
- package/dist/lib/output.d.ts +3 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +18 -0
- package/dist/lib/wiki.d.ts +269 -0
- package/dist/lib/wiki.d.ts.map +1 -0
- package/dist/lib/wiki.js +1800 -0
- package/package.json +43 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,QAerD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { resolveCredentials } from '../lib/client.js';
|
|
2
|
+
import { printJson, printError } from '../lib/output.js';
|
|
3
|
+
export function registerAgentCommands(program) {
|
|
4
|
+
const agents = program.command('agents').description('Manage agents');
|
|
5
|
+
agents
|
|
6
|
+
.command('list')
|
|
7
|
+
.description('List all agents in the organization')
|
|
8
|
+
.action(async () => {
|
|
9
|
+
try {
|
|
10
|
+
const { client, orgId } = resolveCredentials();
|
|
11
|
+
const result = await client.getAgents(orgId);
|
|
12
|
+
printJson(result);
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
printError(err);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chats.d.ts","sourceRoot":"","sources":["../../src/commands/chats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAsGpD"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { resolveCredentials } from '../lib/client.js';
|
|
2
|
+
import { printJson, printError } from '../lib/output.js';
|
|
3
|
+
export function registerChatCommands(program) {
|
|
4
|
+
const chats = program.command('chats').description('Manage chats');
|
|
5
|
+
chats
|
|
6
|
+
.command('list')
|
|
7
|
+
.description('List chats in the organization')
|
|
8
|
+
.option('--limit <n>', 'Maximum number of chats to return', '20')
|
|
9
|
+
.option('--cursor <cursor>', 'Pagination cursor')
|
|
10
|
+
.action(async (opts) => {
|
|
11
|
+
try {
|
|
12
|
+
const { client, orgId } = resolveCredentials();
|
|
13
|
+
const params = { limit: Number(opts.limit) };
|
|
14
|
+
if (opts.cursor !== undefined)
|
|
15
|
+
params.cursor = opts.cursor;
|
|
16
|
+
const result = await client.getChats(orgId, params);
|
|
17
|
+
printJson(result);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
printError(err);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
chats
|
|
24
|
+
.command('get')
|
|
25
|
+
.description('Get a chat by ID')
|
|
26
|
+
.argument('<chatId>', 'Chat ID')
|
|
27
|
+
.action(async (chatId) => {
|
|
28
|
+
try {
|
|
29
|
+
const { client, orgId } = resolveCredentials();
|
|
30
|
+
const result = await client.getChat(orgId, chatId);
|
|
31
|
+
printJson(result);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
printError(err);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
chats
|
|
38
|
+
.command('create')
|
|
39
|
+
.description('Create a new chat')
|
|
40
|
+
.requiredOption('--type <type>', 'Chat type: direct or group')
|
|
41
|
+
.requiredOption('--member-ids <ids>', 'Comma-separated member IDs')
|
|
42
|
+
.option('--name <name>', 'Chat name')
|
|
43
|
+
.option('--description <text>', 'Chat description')
|
|
44
|
+
.action(async (opts) => {
|
|
45
|
+
try {
|
|
46
|
+
const { client, orgId } = resolveCredentials();
|
|
47
|
+
const result = await client.createChat(orgId, {
|
|
48
|
+
type: opts.type,
|
|
49
|
+
member_ids: opts.memberIds.split(',').map((s) => s.trim()).filter(Boolean),
|
|
50
|
+
name: opts.name,
|
|
51
|
+
description: opts.description,
|
|
52
|
+
});
|
|
53
|
+
printJson(result);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
printError(err);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const members = chats.command('members').description('Manage chat members');
|
|
60
|
+
members
|
|
61
|
+
.command('list')
|
|
62
|
+
.description('List members of a chat')
|
|
63
|
+
.argument('<chatId>', 'Chat ID')
|
|
64
|
+
.action(async (chatId) => {
|
|
65
|
+
try {
|
|
66
|
+
const { client, orgId } = resolveCredentials();
|
|
67
|
+
const result = await client.getChatMembers(orgId, chatId);
|
|
68
|
+
printJson(result);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
printError(err);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
members
|
|
75
|
+
.command('add')
|
|
76
|
+
.description('Add a member to a chat')
|
|
77
|
+
.argument('<chatId>', 'Chat ID')
|
|
78
|
+
.requiredOption('--user-id <userId>', 'User ID to add')
|
|
79
|
+
.option('--role <role>', 'Member role: owner, admin, or member', 'member')
|
|
80
|
+
.action(async (chatId, opts) => {
|
|
81
|
+
try {
|
|
82
|
+
const { client, orgId } = resolveCredentials();
|
|
83
|
+
await client.addChatMember(orgId, chatId, opts.userId, opts.role);
|
|
84
|
+
printJson({ ok: true });
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
printError(err);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
members
|
|
91
|
+
.command('remove')
|
|
92
|
+
.description('Remove a member from a chat')
|
|
93
|
+
.argument('<chatId>', 'Chat ID')
|
|
94
|
+
.requiredOption('--user-id <userId>', 'User ID to remove')
|
|
95
|
+
.action(async (chatId, opts) => {
|
|
96
|
+
try {
|
|
97
|
+
const { client, orgId } = resolveCredentials();
|
|
98
|
+
await client.removeChatMember(orgId, chatId, opts.userId);
|
|
99
|
+
printJson({ ok: true });
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
printError(err);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dm.d.ts","sourceRoot":"","sources":["../../src/commands/dm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2BpC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,QA4BlD"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { resolveCredentials, resolveRuntimeContext } from '../lib/client.js';
|
|
2
|
+
import { printJson, printError } from '../lib/output.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a name-or-id argument to a user ID.
|
|
5
|
+
* If it starts with "usr_", treat as ID. Otherwise, search org members by display_name.
|
|
6
|
+
*/
|
|
7
|
+
async function resolveUserId(client, orgId, nameOrId) {
|
|
8
|
+
if (nameOrId.startsWith('usr_'))
|
|
9
|
+
return nameOrId;
|
|
10
|
+
const members = await client.getOrgMembers(orgId);
|
|
11
|
+
const matches = members.filter((m) => m.user?.display_name?.toLowerCase() === nameOrId.toLowerCase());
|
|
12
|
+
if (matches.length === 0) {
|
|
13
|
+
throw new Error(`No user found with display name "${nameOrId}"`);
|
|
14
|
+
}
|
|
15
|
+
if (matches.length > 1) {
|
|
16
|
+
const ids = matches.map((m) => `${m.user_id} (${m.user?.display_name})`).join(', ');
|
|
17
|
+
throw new Error(`Ambiguous name "${nameOrId}" matches multiple users: ${ids}`);
|
|
18
|
+
}
|
|
19
|
+
return matches[0].user_id;
|
|
20
|
+
}
|
|
21
|
+
export function registerDMCommands(program) {
|
|
22
|
+
program
|
|
23
|
+
.command('dm')
|
|
24
|
+
.description('Send a direct message to a user by name or ID (auto-creates chat if needed)')
|
|
25
|
+
.argument('<nameOrId>', 'Target user display name or ID (usr_...)')
|
|
26
|
+
.requiredOption('--text <text>', 'Message text')
|
|
27
|
+
.option('--no-reply', 'Hint that the recipient should not reply')
|
|
28
|
+
.action(async (nameOrId, opts) => {
|
|
29
|
+
try {
|
|
30
|
+
const { client, orgId } = resolveCredentials();
|
|
31
|
+
const ctx = resolveRuntimeContext();
|
|
32
|
+
const userId = await resolveUserId(client, orgId, nameOrId);
|
|
33
|
+
const req = {
|
|
34
|
+
user_id: userId,
|
|
35
|
+
message_type: 'text',
|
|
36
|
+
content: { text: opts.text },
|
|
37
|
+
};
|
|
38
|
+
// Commander parses --no-reply as opts.reply = false
|
|
39
|
+
if (opts.reply === false) {
|
|
40
|
+
req.hints = { no_reply: true };
|
|
41
|
+
}
|
|
42
|
+
if (ctx.stepId)
|
|
43
|
+
req.agent_step_id = ctx.stepId;
|
|
44
|
+
const result = await client.sendDirectMessage(orgId, req);
|
|
45
|
+
printJson(result);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
printError(err);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,QA6bnD"}
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import * as z from 'zod/v4';
|
|
4
|
+
import { resolveCredentials } from '../lib/client.js';
|
|
5
|
+
import { getWikiBlob, getWikiChangesetDiff, getWikiOutline, queryWiki, getWikiSection, getWikiTree, getWikiWorkspaceDiff, getWikiWorkspaceStatus, listWikis, proposeWikiChangeset, searchWiki, } from '../lib/wiki.js';
|
|
6
|
+
export function registerMcpCommands(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('mcp')
|
|
9
|
+
.description('Run a Parall MCP server over stdio')
|
|
10
|
+
.action(async () => {
|
|
11
|
+
const ctx = resolveCredentials();
|
|
12
|
+
const server = new McpServer({
|
|
13
|
+
name: 'parall-mcp',
|
|
14
|
+
version: '1.0.0',
|
|
15
|
+
});
|
|
16
|
+
server.registerTool('wiki_list', {
|
|
17
|
+
title: 'List Parall Wikis',
|
|
18
|
+
description: 'List Parall wiki repositories that the current credentials can access.',
|
|
19
|
+
annotations: {
|
|
20
|
+
readOnlyHint: true,
|
|
21
|
+
},
|
|
22
|
+
outputSchema: {
|
|
23
|
+
wikis: z.array(z.object({
|
|
24
|
+
id: z.string(),
|
|
25
|
+
slug: z.string(),
|
|
26
|
+
name: z.string(),
|
|
27
|
+
default_branch: z.string(),
|
|
28
|
+
})),
|
|
29
|
+
},
|
|
30
|
+
}, async () => {
|
|
31
|
+
const wikis = await listWikis(ctx);
|
|
32
|
+
const structuredContent = {
|
|
33
|
+
wikis: wikis.map((wiki) => ({
|
|
34
|
+
id: wiki.id,
|
|
35
|
+
slug: wiki.slug,
|
|
36
|
+
name: wiki.name,
|
|
37
|
+
default_branch: wiki.default_branch,
|
|
38
|
+
})),
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],
|
|
42
|
+
structuredContent,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
server.registerTool('wiki_tree', {
|
|
46
|
+
title: 'Browse Wiki Tree',
|
|
47
|
+
description: 'List files and folders under a Parall wiki path.',
|
|
48
|
+
annotations: {
|
|
49
|
+
readOnlyHint: true,
|
|
50
|
+
},
|
|
51
|
+
inputSchema: {
|
|
52
|
+
wiki: z.string().describe('Wiki slug or ID'),
|
|
53
|
+
path: z.string().optional().describe('Optional tree path prefix inside the wiki'),
|
|
54
|
+
},
|
|
55
|
+
outputSchema: {
|
|
56
|
+
wiki_id: z.string(),
|
|
57
|
+
wiki_slug: z.string(),
|
|
58
|
+
wiki_name: z.string(),
|
|
59
|
+
path: z.string(),
|
|
60
|
+
entries: z.array(z.object({
|
|
61
|
+
name: z.string(),
|
|
62
|
+
path: z.string(),
|
|
63
|
+
type: z.enum(['tree', 'blob']),
|
|
64
|
+
size: z.number().optional(),
|
|
65
|
+
})),
|
|
66
|
+
},
|
|
67
|
+
}, async ({ wiki, path }) => {
|
|
68
|
+
const result = await getWikiTree(ctx, wiki, { path });
|
|
69
|
+
const structuredContent = {
|
|
70
|
+
wiki_id: result.wiki.id,
|
|
71
|
+
wiki_slug: result.wiki.slug,
|
|
72
|
+
wiki_name: result.wiki.name,
|
|
73
|
+
path: result.path,
|
|
74
|
+
entries: result.entries,
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],
|
|
78
|
+
structuredContent,
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
server.registerTool('wiki_status', {
|
|
82
|
+
title: 'Inspect Wiki Workspace Status',
|
|
83
|
+
description: 'Inspect the current local mounted wiki workspace before proposing changes.',
|
|
84
|
+
annotations: {
|
|
85
|
+
readOnlyHint: true,
|
|
86
|
+
},
|
|
87
|
+
inputSchema: {
|
|
88
|
+
wiki: z.string().describe('Wiki slug or ID'),
|
|
89
|
+
},
|
|
90
|
+
outputSchema: {
|
|
91
|
+
wiki_id: z.string(),
|
|
92
|
+
wiki_slug: z.string(),
|
|
93
|
+
wiki_name: z.string(),
|
|
94
|
+
mount_path: z.string(),
|
|
95
|
+
mode: z.enum(['read_only', 'read_write']),
|
|
96
|
+
default_ref: z.string(),
|
|
97
|
+
changed_paths: z.array(z.string()),
|
|
98
|
+
diff_files: z.array(z.object({
|
|
99
|
+
path: z.string(),
|
|
100
|
+
additions: z.number(),
|
|
101
|
+
deletions: z.number(),
|
|
102
|
+
})),
|
|
103
|
+
dirty: z.boolean(),
|
|
104
|
+
source: z.literal('local_mount'),
|
|
105
|
+
},
|
|
106
|
+
}, async ({ wiki }) => {
|
|
107
|
+
const result = await getWikiWorkspaceStatus(ctx, wiki);
|
|
108
|
+
return {
|
|
109
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
110
|
+
structuredContent: result,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
server.registerTool('wiki_diff', {
|
|
114
|
+
title: 'Read Wiki Workspace Diff',
|
|
115
|
+
description: 'Read the current unified diff for a local mounted wiki workspace.',
|
|
116
|
+
annotations: {
|
|
117
|
+
readOnlyHint: true,
|
|
118
|
+
},
|
|
119
|
+
inputSchema: {
|
|
120
|
+
wiki: z.string().describe('Wiki slug or ID'),
|
|
121
|
+
},
|
|
122
|
+
outputSchema: {
|
|
123
|
+
wiki_id: z.string(),
|
|
124
|
+
wiki_slug: z.string(),
|
|
125
|
+
wiki_name: z.string(),
|
|
126
|
+
mount_path: z.string(),
|
|
127
|
+
changed_paths: z.array(z.string()),
|
|
128
|
+
diff_files: z.array(z.object({
|
|
129
|
+
path: z.string(),
|
|
130
|
+
additions: z.number(),
|
|
131
|
+
deletions: z.number(),
|
|
132
|
+
})),
|
|
133
|
+
patch: z.string(),
|
|
134
|
+
source: z.literal('local_mount'),
|
|
135
|
+
},
|
|
136
|
+
}, async ({ wiki }) => {
|
|
137
|
+
const result = await getWikiWorkspaceDiff(ctx, wiki);
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: 'text', text: result.patch || JSON.stringify(result, null, 2) }],
|
|
140
|
+
structuredContent: result,
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
server.registerTool('wiki_blob', {
|
|
144
|
+
title: 'Read Wiki File',
|
|
145
|
+
description: 'Read a file from a Parall wiki and return its text content.',
|
|
146
|
+
annotations: {
|
|
147
|
+
readOnlyHint: true,
|
|
148
|
+
},
|
|
149
|
+
inputSchema: {
|
|
150
|
+
wiki: z.string().describe('Wiki slug or ID'),
|
|
151
|
+
path: z.string().describe('File path inside the wiki'),
|
|
152
|
+
},
|
|
153
|
+
outputSchema: {
|
|
154
|
+
wiki_id: z.string(),
|
|
155
|
+
wiki_slug: z.string(),
|
|
156
|
+
wiki_name: z.string(),
|
|
157
|
+
content: z.string(),
|
|
158
|
+
sha256: z.string(),
|
|
159
|
+
size: z.number(),
|
|
160
|
+
},
|
|
161
|
+
}, async ({ wiki, path }) => {
|
|
162
|
+
const result = await getWikiBlob(ctx, wiki, { path });
|
|
163
|
+
const structuredContent = {
|
|
164
|
+
wiki_id: result.wiki.id,
|
|
165
|
+
wiki_slug: result.wiki.slug,
|
|
166
|
+
wiki_name: result.wiki.name,
|
|
167
|
+
content: result.content,
|
|
168
|
+
size: result.size,
|
|
169
|
+
};
|
|
170
|
+
return {
|
|
171
|
+
content: [{ type: 'text', text: result.content || JSON.stringify(structuredContent, null, 2) }],
|
|
172
|
+
structuredContent,
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
server.registerTool('wiki_search', {
|
|
176
|
+
title: 'Search Wiki Markdown',
|
|
177
|
+
description: 'Search Markdown headings and section bodies inside a Parall wiki. Prefers local mounts when no ref is supplied and agent env is available; otherwise falls back to the Parall API.',
|
|
178
|
+
annotations: {
|
|
179
|
+
readOnlyHint: true,
|
|
180
|
+
},
|
|
181
|
+
inputSchema: {
|
|
182
|
+
wiki: z.string().describe('Wiki slug or ID'),
|
|
183
|
+
query: z.string().describe('Search query'),
|
|
184
|
+
ref: z.string().optional().describe('Optional git ref for API-backed search'),
|
|
185
|
+
path_prefix: z.string().optional().describe('Restrict search to a file or directory path prefix'),
|
|
186
|
+
limit: z.number().int().positive().max(20).optional().describe('Maximum number of results; default 5'),
|
|
187
|
+
include_content: z.boolean().optional().describe('Include full matched section content in each result'),
|
|
188
|
+
},
|
|
189
|
+
outputSchema: {
|
|
190
|
+
results: z.array(z.object({
|
|
191
|
+
wiki_id: z.string(),
|
|
192
|
+
wiki_slug: z.string(),
|
|
193
|
+
wiki_name: z.string(),
|
|
194
|
+
node_id: z.string(),
|
|
195
|
+
path: z.string(),
|
|
196
|
+
title: z.string(),
|
|
197
|
+
heading_path: z.array(z.string()).optional(),
|
|
198
|
+
section_path: z.string(),
|
|
199
|
+
level: z.number(),
|
|
200
|
+
start_line: z.number(),
|
|
201
|
+
end_line: z.number(),
|
|
202
|
+
score: z.number(),
|
|
203
|
+
snippet: z.string(),
|
|
204
|
+
content: z.string().optional(),
|
|
205
|
+
source: z.enum(['local_mount', 'api']),
|
|
206
|
+
})),
|
|
207
|
+
},
|
|
208
|
+
}, async ({ wiki, query, ref, path_prefix: pathPrefix, limit, include_content: includeContent }) => {
|
|
209
|
+
const results = await searchWiki(ctx, wiki, query, {
|
|
210
|
+
ref,
|
|
211
|
+
pathPrefix,
|
|
212
|
+
limit,
|
|
213
|
+
includeContent,
|
|
214
|
+
});
|
|
215
|
+
const structuredContent = { results };
|
|
216
|
+
return {
|
|
217
|
+
content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],
|
|
218
|
+
structuredContent,
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
server.registerTool('wiki_query', {
|
|
222
|
+
title: 'Query Wiki Structurally',
|
|
223
|
+
description: 'Run a tree-aware wiki query that first narrows candidate documents and then selects the best sections.',
|
|
224
|
+
annotations: {
|
|
225
|
+
readOnlyHint: true,
|
|
226
|
+
},
|
|
227
|
+
inputSchema: {
|
|
228
|
+
wiki: z.string().describe('Wiki slug or ID'),
|
|
229
|
+
query: z.string().describe('Natural-language query'),
|
|
230
|
+
ref: z.string().optional().describe('Optional git ref for API-backed queries'),
|
|
231
|
+
path_prefix: z.string().optional().describe('Restrict the query to a file or directory path prefix'),
|
|
232
|
+
limit: z.number().int().positive().max(20).optional().describe('Maximum number of section results; default 5'),
|
|
233
|
+
include_content: z.boolean().optional().describe('Include full matched section content in each result'),
|
|
234
|
+
},
|
|
235
|
+
outputSchema: {
|
|
236
|
+
wiki_id: z.string(),
|
|
237
|
+
wiki_slug: z.string(),
|
|
238
|
+
wiki_name: z.string(),
|
|
239
|
+
query: z.string(),
|
|
240
|
+
ref: z.string().optional(),
|
|
241
|
+
path_prefix: z.string().optional(),
|
|
242
|
+
generated_at: z.string(),
|
|
243
|
+
file_count: z.number(),
|
|
244
|
+
source: z.enum(['local_mount', 'api']),
|
|
245
|
+
documents: z.array(z.object({
|
|
246
|
+
path: z.string(),
|
|
247
|
+
title: z.string(),
|
|
248
|
+
score: z.number(),
|
|
249
|
+
matched_headings: z.array(z.string()),
|
|
250
|
+
node_count: z.number(),
|
|
251
|
+
})),
|
|
252
|
+
sections: z.array(z.object({
|
|
253
|
+
wiki_id: z.string(),
|
|
254
|
+
wiki_slug: z.string(),
|
|
255
|
+
wiki_name: z.string(),
|
|
256
|
+
node_id: z.string(),
|
|
257
|
+
path: z.string(),
|
|
258
|
+
title: z.string(),
|
|
259
|
+
heading_path: z.array(z.string()).optional(),
|
|
260
|
+
section_path: z.string(),
|
|
261
|
+
level: z.number(),
|
|
262
|
+
start_line: z.number(),
|
|
263
|
+
end_line: z.number(),
|
|
264
|
+
score: z.number(),
|
|
265
|
+
document_score: z.number(),
|
|
266
|
+
snippet: z.string(),
|
|
267
|
+
content: z.string().optional(),
|
|
268
|
+
source: z.enum(['local_mount', 'api']),
|
|
269
|
+
reasoning: z.array(z.string()),
|
|
270
|
+
})),
|
|
271
|
+
},
|
|
272
|
+
}, async ({ wiki, query, ref, path_prefix: pathPrefix, limit, include_content: includeContent }) => {
|
|
273
|
+
const result = await queryWiki(ctx, wiki, query, {
|
|
274
|
+
ref,
|
|
275
|
+
pathPrefix,
|
|
276
|
+
limit,
|
|
277
|
+
includeContent,
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
281
|
+
structuredContent: result,
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
server.registerTool('wiki_outline', {
|
|
285
|
+
title: 'Read Wiki Outline',
|
|
286
|
+
description: 'Return the structural node outline for a Parall wiki or a path prefix within it.',
|
|
287
|
+
annotations: {
|
|
288
|
+
readOnlyHint: true,
|
|
289
|
+
},
|
|
290
|
+
inputSchema: {
|
|
291
|
+
wiki: z.string().describe('Wiki slug or ID'),
|
|
292
|
+
ref: z.string().optional().describe('Optional git ref for API-backed outline'),
|
|
293
|
+
path_prefix: z.string().optional().describe('Restrict outline to a file or directory path prefix'),
|
|
294
|
+
},
|
|
295
|
+
outputSchema: {
|
|
296
|
+
wiki_id: z.string(),
|
|
297
|
+
wiki_slug: z.string(),
|
|
298
|
+
wiki_name: z.string(),
|
|
299
|
+
ref: z.string().optional(),
|
|
300
|
+
path_prefix: z.string().optional(),
|
|
301
|
+
generated_at: z.string(),
|
|
302
|
+
file_count: z.number(),
|
|
303
|
+
source: z.enum(['local_mount', 'api']),
|
|
304
|
+
nodes: z.array(z.object({
|
|
305
|
+
wiki_id: z.string(),
|
|
306
|
+
wiki_slug: z.string(),
|
|
307
|
+
wiki_name: z.string(),
|
|
308
|
+
node_id: z.string(),
|
|
309
|
+
path: z.string(),
|
|
310
|
+
title: z.string(),
|
|
311
|
+
heading_path: z.array(z.string()).optional(),
|
|
312
|
+
section_path: z.string(),
|
|
313
|
+
level: z.number(),
|
|
314
|
+
start_line: z.number(),
|
|
315
|
+
end_line: z.number(),
|
|
316
|
+
})),
|
|
317
|
+
},
|
|
318
|
+
}, async ({ wiki, ref, path_prefix: pathPrefix }) => {
|
|
319
|
+
const outline = await getWikiOutline(ctx, wiki, { ref, pathPrefix });
|
|
320
|
+
return {
|
|
321
|
+
content: [{ type: 'text', text: JSON.stringify(outline, null, 2) }],
|
|
322
|
+
structuredContent: outline,
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
server.registerTool('wiki_section', {
|
|
326
|
+
title: 'Read Wiki Section',
|
|
327
|
+
description: 'Read a specific Markdown section from a Parall wiki by node ID. Node IDs come from `wiki_search` results.',
|
|
328
|
+
annotations: {
|
|
329
|
+
readOnlyHint: true,
|
|
330
|
+
},
|
|
331
|
+
inputSchema: {
|
|
332
|
+
wiki: z.string().describe('Wiki slug or ID'),
|
|
333
|
+
node_id: z.string().describe('Section node ID returned by wiki_search'),
|
|
334
|
+
ref: z.string().optional().describe('Optional git ref; when set, reads from the Parall API instead of a local mount'),
|
|
335
|
+
},
|
|
336
|
+
outputSchema: {
|
|
337
|
+
wiki_id: z.string(),
|
|
338
|
+
wiki_slug: z.string(),
|
|
339
|
+
wiki_name: z.string(),
|
|
340
|
+
node_id: z.string(),
|
|
341
|
+
path: z.string(),
|
|
342
|
+
title: z.string(),
|
|
343
|
+
heading_path: z.array(z.string()).optional(),
|
|
344
|
+
section_path: z.string(),
|
|
345
|
+
level: z.number(),
|
|
346
|
+
start_line: z.number(),
|
|
347
|
+
end_line: z.number(),
|
|
348
|
+
content: z.string(),
|
|
349
|
+
source: z.enum(['local_mount', 'api']),
|
|
350
|
+
},
|
|
351
|
+
}, async ({ wiki, node_id: nodeId, ref }) => {
|
|
352
|
+
const section = await getWikiSection(ctx, wiki, { nodeId, ref });
|
|
353
|
+
return {
|
|
354
|
+
content: [{ type: 'text', text: section.content || JSON.stringify(section, null, 2) }],
|
|
355
|
+
structuredContent: section,
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
server.registerTool('wiki_changeset_diff', {
|
|
359
|
+
title: 'Read Wiki Changeset Diff',
|
|
360
|
+
description: 'Fetch the unified diff and changed file summary for a Parall wiki changeset.',
|
|
361
|
+
annotations: {
|
|
362
|
+
readOnlyHint: true,
|
|
363
|
+
},
|
|
364
|
+
inputSchema: {
|
|
365
|
+
wiki: z.string().describe('Wiki slug or ID'),
|
|
366
|
+
changeset_id: z.string().describe('Wiki changeset ID'),
|
|
367
|
+
},
|
|
368
|
+
outputSchema: {
|
|
369
|
+
wiki_id: z.string(),
|
|
370
|
+
wiki_slug: z.string(),
|
|
371
|
+
wiki_name: z.string(),
|
|
372
|
+
changeset_id: z.string(),
|
|
373
|
+
diff: z.object({
|
|
374
|
+
patch: z.string(),
|
|
375
|
+
files: z.array(z.object({
|
|
376
|
+
path: z.string(),
|
|
377
|
+
additions: z.number(),
|
|
378
|
+
deletions: z.number(),
|
|
379
|
+
})),
|
|
380
|
+
}),
|
|
381
|
+
},
|
|
382
|
+
}, async ({ wiki, changeset_id: changesetId }) => {
|
|
383
|
+
const diff = await getWikiChangesetDiff(ctx, wiki, changesetId);
|
|
384
|
+
return {
|
|
385
|
+
content: [{ type: 'text', text: diff.diff.patch || JSON.stringify(diff, null, 2) }],
|
|
386
|
+
structuredContent: diff,
|
|
387
|
+
};
|
|
388
|
+
});
|
|
389
|
+
server.registerTool('wiki_propose', {
|
|
390
|
+
title: 'Propose Wiki Changeset',
|
|
391
|
+
description: 'Create or update a proposed wiki changeset from the current local mounted wiki workspace.',
|
|
392
|
+
inputSchema: {
|
|
393
|
+
wiki: z.string().describe('Wiki slug or ID'),
|
|
394
|
+
title: z.string().min(1).describe('Changeset title'),
|
|
395
|
+
message: z.string().optional().describe('Changeset commit message (supports prll:// refs)'),
|
|
396
|
+
changeset_id: z.string().optional().describe('Existing changeset ID to update'),
|
|
397
|
+
source_chat_id: z.string().optional().describe('Optional source chat ID for approval workflow linkage'),
|
|
398
|
+
source_message_id: z.string().optional().describe('Optional source message ID for approval workflow linkage'),
|
|
399
|
+
source_run_id: z.string().optional().describe('Optional source run ID for approval workflow linkage'),
|
|
400
|
+
},
|
|
401
|
+
outputSchema: {
|
|
402
|
+
wiki_id: z.string(),
|
|
403
|
+
wiki_slug: z.string(),
|
|
404
|
+
wiki_name: z.string(),
|
|
405
|
+
mount_path: z.string(),
|
|
406
|
+
changed_paths: z.array(z.string()),
|
|
407
|
+
diff_files: z.array(z.object({
|
|
408
|
+
path: z.string(),
|
|
409
|
+
additions: z.number(),
|
|
410
|
+
deletions: z.number(),
|
|
411
|
+
})),
|
|
412
|
+
source: z.literal('local_mount'),
|
|
413
|
+
changeset: z.object({
|
|
414
|
+
id: z.string(),
|
|
415
|
+
wiki_id: z.string(),
|
|
416
|
+
title: z.string(),
|
|
417
|
+
status: z.string(),
|
|
418
|
+
changed_paths: z.array(z.string()).optional(),
|
|
419
|
+
}).passthrough(),
|
|
420
|
+
},
|
|
421
|
+
}, async ({ wiki, title, message, changeset_id: changesetId, source_chat_id: sourceChatId, source_message_id: sourceMessageId, source_run_id: sourceRunId }) => {
|
|
422
|
+
const result = await proposeWikiChangeset(ctx, wiki, {
|
|
423
|
+
title,
|
|
424
|
+
message,
|
|
425
|
+
changesetId,
|
|
426
|
+
sourceChatId,
|
|
427
|
+
sourceMessageId,
|
|
428
|
+
sourceRunId,
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
432
|
+
structuredContent: result,
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
const transport = new StdioServerTransport();
|
|
436
|
+
await server.connect(transport);
|
|
437
|
+
console.error('parall MCP server running on stdio');
|
|
438
|
+
});
|
|
439
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/commands/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QAiGvD"}
|