rootr-cli 0.2.1 → 0.4.1
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/LICENSE +21 -0
- package/README.md +101 -4
- package/bin/rootr.js +109 -3
- package/lib/client.js +531 -1
- package/lib/config.js +30 -1
- package/lib/context.js +38 -0
- package/lib/mcp-tools/ask.js +1 -0
- package/lib/mcp-tools/attachments.js +111 -0
- package/lib/mcp-tools/crm.js +725 -0
- package/lib/mcp-tools/databases.js +25 -14
- package/lib/mcp-tools/documents.js +164 -3
- package/lib/mcp-tools/forms.js +215 -0
- package/lib/mcp-tools/issues.js +13 -4
- package/lib/mcp-tools/logs.js +16 -6
- package/lib/mcp-tools/misc.js +2 -0
- package/lib/mcp-tools/presentations.js +25 -9
- package/lib/mcp-tools/recordings.js +433 -0
- package/lib/mcp-tools/shared.js +44 -3
- package/lib/mcp-tools/spreadsheets.js +231 -0
- package/lib/mcp-tools/webhooks.js +6 -2
- package/lib/mcp-tools/whiteboards.js +169 -0
- package/lib/mcp-tools/workspaces.js +148 -5
- package/lib/mcp.js +98 -5
- package/package.json +22 -3
package/lib/mcp-tools/logs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
/** LOG datastore tools: create/update-fields/add-entries/query-entries/stats. */
|
|
6
6
|
export function registerLogTools(server) {
|
|
@@ -13,6 +13,7 @@ export function registerLogTools(server) {
|
|
|
13
13
|
'Each field has a name/type; type is one of string|int|float|bool|datetime|json|enum|level|relation. ' +
|
|
14
14
|
'Relation fields additionally need target (a node id to relate to) and optional many/relation flags. ' +
|
|
15
15
|
'Use rootr_add_log_entries afterward to insert rows, and rootr_query_log_entries / rootr_log_stats to read them back.',
|
|
16
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
16
17
|
inputSchema: {
|
|
17
18
|
name: z.string().describe('Name of the LOG store'),
|
|
18
19
|
fields: z
|
|
@@ -21,7 +22,7 @@ export function registerLogTools(server) {
|
|
|
21
22
|
'Field definitions: [{name, type, ...}]. type=string|int|float|bool|datetime|json|enum|level|relation. ' +
|
|
22
23
|
'relation fields add target (node id), many?, relation?.',
|
|
23
24
|
),
|
|
24
|
-
parentId: z.string().optional().describe(
|
|
25
|
+
parentId: z.string().optional().describe(`Parent node id${NODE_TARGET_HINT}`),
|
|
25
26
|
workspace: z
|
|
26
27
|
.string()
|
|
27
28
|
.optional()
|
|
@@ -32,6 +33,7 @@ export function registerLogTools(server) {
|
|
|
32
33
|
try {
|
|
33
34
|
const ws = requireWorkspace(workspace);
|
|
34
35
|
const client = makeClient(ws);
|
|
36
|
+
parentId = await resolveOptionalNodeId(client, parentId);
|
|
35
37
|
const result = await client.createLogStore(ws, { name, fields, parentId });
|
|
36
38
|
return jsonResult(result);
|
|
37
39
|
} catch (err) {
|
|
@@ -48,8 +50,9 @@ export function registerLogTools(server) {
|
|
|
48
50
|
'Update the field schema of an existing LOG store. WARNING: this REPLACES THE ENTIRE FIELD SET — ' +
|
|
49
51
|
'you must resend every field you want to keep, not just the ones you are changing/adding. ' +
|
|
50
52
|
'Read the LOG store first (e.g. via rootr_read or a get-log call) to see its current fields before calling this.',
|
|
53
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
51
54
|
inputSchema: {
|
|
52
|
-
logId: z.string().describe(
|
|
55
|
+
logId: z.string().describe(`LOG store node id${NODE_TARGET_HINT}`),
|
|
53
56
|
fields: z
|
|
54
57
|
.array(z.record(z.any()))
|
|
55
58
|
.describe('COMPLETE field list to set — omitted existing fields will be dropped'),
|
|
@@ -58,6 +61,7 @@ export function registerLogTools(server) {
|
|
|
58
61
|
async ({ logId, fields }) => {
|
|
59
62
|
try {
|
|
60
63
|
const client = makeClient();
|
|
64
|
+
logId = await resolveNodeId(client, logId);
|
|
61
65
|
const result = await client.updateLogFields(logId, fields);
|
|
62
66
|
return jsonResult(result);
|
|
63
67
|
} catch (err) {
|
|
@@ -73,8 +77,9 @@ export function registerLogTools(server) {
|
|
|
73
77
|
description:
|
|
74
78
|
'Append one or more entries (rows) to a LOG store. Each entry may carry ts, source, level, message, data ' +
|
|
75
79
|
'(shaped per the store\'s field schema). This is additive — existing entries are untouched.',
|
|
80
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
76
81
|
inputSchema: {
|
|
77
|
-
logId: z.string().describe(
|
|
82
|
+
logId: z.string().describe(`LOG store node id${NODE_TARGET_HINT}`),
|
|
78
83
|
entries: z
|
|
79
84
|
.array(z.record(z.any()))
|
|
80
85
|
.describe('Entries to add: [{ts?, source?, level?, message?, data?}, ...]'),
|
|
@@ -83,6 +88,7 @@ export function registerLogTools(server) {
|
|
|
83
88
|
async ({ logId, entries }) => {
|
|
84
89
|
try {
|
|
85
90
|
const client = makeClient();
|
|
91
|
+
logId = await resolveNodeId(client, logId);
|
|
86
92
|
const result = await client.addLogEntries(logId, entries);
|
|
87
93
|
return jsonResult(result);
|
|
88
94
|
} catch (err) {
|
|
@@ -98,8 +104,9 @@ export function registerLogTools(server) {
|
|
|
98
104
|
description:
|
|
99
105
|
'Query/filter entries from a LOG store by time range, source, level, and/or anomaly flag. ' +
|
|
100
106
|
'Use this to inspect recent activity or find anomalous entries before asking rootr_ask for root-cause analysis.',
|
|
107
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
101
108
|
inputSchema: {
|
|
102
|
-
logId: z.string().describe(
|
|
109
|
+
logId: z.string().describe(`LOG store node id${NODE_TARGET_HINT}`),
|
|
103
110
|
from: z.string().optional().describe('ISO timestamp lower bound'),
|
|
104
111
|
to: z.string().optional().describe('ISO timestamp upper bound'),
|
|
105
112
|
source: z.string().optional().describe('Filter by source'),
|
|
@@ -112,6 +119,7 @@ export function registerLogTools(server) {
|
|
|
112
119
|
async ({ logId, from, to, source, level, anomalyOnly, limit, order }) => {
|
|
113
120
|
try {
|
|
114
121
|
const client = makeClient();
|
|
122
|
+
logId = await resolveNodeId(client, logId);
|
|
115
123
|
const result = await client.queryLogEntries(logId, {
|
|
116
124
|
from,
|
|
117
125
|
to,
|
|
@@ -136,8 +144,9 @@ export function registerLogTools(server) {
|
|
|
136
144
|
'Compute aggregate statistics over a LOG store\'s entries, grouped by hour/day/source/level, with a ' +
|
|
137
145
|
'metric of count/avg/max/min/sum (avg/max/min/sum require a numeric `field`). Useful for trend/volume ' +
|
|
138
146
|
'questions without pulling raw entries.',
|
|
147
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
139
148
|
inputSchema: {
|
|
140
|
-
logId: z.string().describe(
|
|
149
|
+
logId: z.string().describe(`LOG store node id${NODE_TARGET_HINT}`),
|
|
141
150
|
groupBy: z.string().optional().describe('hour | day | source | level'),
|
|
142
151
|
metric: z.string().optional().describe('count | avg | max | min | sum'),
|
|
143
152
|
field: z.string().optional().describe('Numeric field name, required for avg/max/min/sum'),
|
|
@@ -148,6 +157,7 @@ export function registerLogTools(server) {
|
|
|
148
157
|
async ({ logId, groupBy, metric, field, from, to }) => {
|
|
149
158
|
try {
|
|
150
159
|
const client = makeClient();
|
|
160
|
+
logId = await resolveNodeId(client, logId);
|
|
151
161
|
const result = await client.logStats(logId, { groupBy, metric, field, from, to });
|
|
152
162
|
return jsonResult(result);
|
|
153
163
|
} catch (err) {
|
package/lib/mcp-tools/misc.js
CHANGED
|
@@ -12,6 +12,7 @@ export function registerMiscTools(server) {
|
|
|
12
12
|
description:
|
|
13
13
|
'List saved versions of a Rootr (루터) document, most recent first. Useful for seeing what changed and ' +
|
|
14
14
|
'when, or to recover an older revision.',
|
|
15
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
15
16
|
inputSchema: {
|
|
16
17
|
target: z.string().describe('Document path (starting with "/") or node id'),
|
|
17
18
|
limit: z.number().optional().describe('Max number of versions to return'),
|
|
@@ -37,6 +38,7 @@ export function registerMiscTools(server) {
|
|
|
37
38
|
description:
|
|
38
39
|
'Delete a Rootr (루터) document or folder by moving it to the trash — this is recoverable, not a ' +
|
|
39
40
|
'permanent delete. Use rootr_read/rootr_list first to confirm you have the right target.',
|
|
41
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
40
42
|
inputSchema: {
|
|
41
43
|
target: z.string().describe('Document/folder path (starting with "/") or node id'),
|
|
42
44
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Authoring guide, repeated (shortened) across every tool description so an
|
|
@@ -124,9 +124,10 @@ export function registerPresentationTools(server) {
|
|
|
124
124
|
'Create a new PRESENTATION node (a slide deck) in a Rootr (루터) workspace, optionally seeded with an ' +
|
|
125
125
|
'initial theme and slides. ' +
|
|
126
126
|
AUTHORING_GUIDE,
|
|
127
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
127
128
|
inputSchema: {
|
|
128
129
|
name: z.string().optional().describe('Deck name/title'),
|
|
129
|
-
parentId: z.string().optional().describe(
|
|
130
|
+
parentId: z.string().optional().describe(`Parent folder/node id${NODE_TARGET_HINT}`),
|
|
130
131
|
theme: themeShape,
|
|
131
132
|
slides: z.array(slideShape).optional().describe('Initial slides, in order'),
|
|
132
133
|
config: z.record(z.unknown()).optional().describe('Free-form deck config/settings object'),
|
|
@@ -141,6 +142,7 @@ export function registerPresentationTools(server) {
|
|
|
141
142
|
try {
|
|
142
143
|
const ws = requireWorkspace(workspace);
|
|
143
144
|
const client = makeClient(ws);
|
|
145
|
+
parentId = await resolveOptionalNodeId(client, parentId);
|
|
144
146
|
const result = await client.createPresentation(ws, {
|
|
145
147
|
name,
|
|
146
148
|
parentId,
|
|
@@ -164,13 +166,15 @@ export function registerPresentationTools(server) {
|
|
|
164
166
|
'Read a Rootr (루터) PRESENTATION node — its theme and full slides array. Read this before ' +
|
|
165
167
|
'rootr_update_presentation / rootr_append_presentation_slides / rootr_reorder_presentation_slides so you ' +
|
|
166
168
|
'know the current slide ids and content.',
|
|
169
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
167
170
|
inputSchema: {
|
|
168
|
-
presentationId: z.string().describe(
|
|
171
|
+
presentationId: z.string().describe(`PRESENTATION node id${NODE_TARGET_HINT}`),
|
|
169
172
|
},
|
|
170
173
|
},
|
|
171
174
|
async ({ presentationId }) => {
|
|
172
175
|
try {
|
|
173
176
|
const client = makeClient();
|
|
177
|
+
presentationId = await resolveNodeId(client, presentationId);
|
|
174
178
|
const result = await client.getPresentation(presentationId);
|
|
175
179
|
return jsonResult(result);
|
|
176
180
|
} catch (err) {
|
|
@@ -189,8 +193,9 @@ export function registerPresentationTools(server) {
|
|
|
189
193
|
'slides you want to keep, or use rootr_append_presentation_slides / rootr_update_presentation_slide instead ' +
|
|
190
194
|
'for incremental changes). ' +
|
|
191
195
|
AUTHORING_GUIDE,
|
|
196
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
192
197
|
inputSchema: {
|
|
193
|
-
presentationId: z.string().describe(
|
|
198
|
+
presentationId: z.string().describe(`PRESENTATION node id${NODE_TARGET_HINT}`),
|
|
194
199
|
theme: themeShape,
|
|
195
200
|
slides: z
|
|
196
201
|
.array(slideShape)
|
|
@@ -203,6 +208,7 @@ export function registerPresentationTools(server) {
|
|
|
203
208
|
async ({ presentationId, theme, slides, config, icon }) => {
|
|
204
209
|
try {
|
|
205
210
|
const client = makeClient();
|
|
211
|
+
presentationId = await resolveNodeId(client, presentationId);
|
|
206
212
|
const result = await client.updatePresentation(presentationId, {
|
|
207
213
|
theme,
|
|
208
214
|
slides: ensureSlideIds(slides),
|
|
@@ -224,14 +230,16 @@ export function registerPresentationTools(server) {
|
|
|
224
230
|
'Append one or more slides to the END of a Rootr (루터) PRESENTATION, without touching existing slides. ' +
|
|
225
231
|
'PREFER this over rootr_update_presentation when you are only adding new slides. ' +
|
|
226
232
|
AUTHORING_GUIDE,
|
|
233
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
227
234
|
inputSchema: {
|
|
228
|
-
presentationId: z.string().describe(
|
|
235
|
+
presentationId: z.string().describe(`PRESENTATION node id${NODE_TARGET_HINT}`),
|
|
229
236
|
slides: z.array(slideShape).describe('Slides to append, in order'),
|
|
230
237
|
},
|
|
231
238
|
},
|
|
232
239
|
async ({ presentationId, slides }) => {
|
|
233
240
|
try {
|
|
234
241
|
const client = makeClient();
|
|
242
|
+
presentationId = await resolveNodeId(client, presentationId);
|
|
235
243
|
const result = await client.appendPresentationSlides(presentationId, ensureSlideIds(slides));
|
|
236
244
|
return jsonResult(result);
|
|
237
245
|
} catch (err) {
|
|
@@ -249,8 +257,9 @@ export function registerPresentationTools(server) {
|
|
|
249
257
|
'targeted edit (e.g. rewrite one slide\'s title/blocks); use rootr_append_presentation_slides instead when ' +
|
|
250
258
|
'you are only adding new slides. Call rootr_read_presentation first to get the exact slideId. ' +
|
|
251
259
|
AUTHORING_GUIDE,
|
|
260
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
252
261
|
inputSchema: {
|
|
253
|
-
presentationId: z.string().describe(
|
|
262
|
+
presentationId: z.string().describe(`PRESENTATION node id${NODE_TARGET_HINT}`),
|
|
254
263
|
slideId: z.string().describe('Slide id to update (must belong to the deck)'),
|
|
255
264
|
slide: slideShape.describe('Partial slide fields to merge into the existing slide'),
|
|
256
265
|
},
|
|
@@ -258,6 +267,7 @@ export function registerPresentationTools(server) {
|
|
|
258
267
|
async ({ presentationId, slideId, slide }) => {
|
|
259
268
|
try {
|
|
260
269
|
const client = makeClient();
|
|
270
|
+
presentationId = await resolveNodeId(client, presentationId);
|
|
261
271
|
const result = await client.updatePresentationSlide(presentationId, slideId, slide);
|
|
262
272
|
return jsonResult(result);
|
|
263
273
|
} catch (err) {
|
|
@@ -273,14 +283,16 @@ export function registerPresentationTools(server) {
|
|
|
273
283
|
description:
|
|
274
284
|
'Reorder a Rootr (루터) PRESENTATION\'s slides. `order` must be the FULL list of the deck\'s slide ids, ' +
|
|
275
285
|
'in the new desired order — call rootr_read_presentation first to get the current ids.',
|
|
286
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
276
287
|
inputSchema: {
|
|
277
|
-
presentationId: z.string().describe(
|
|
288
|
+
presentationId: z.string().describe(`PRESENTATION node id${NODE_TARGET_HINT}`),
|
|
278
289
|
order: z.array(z.string()).describe('Complete ordered array of slide ids'),
|
|
279
290
|
},
|
|
280
291
|
},
|
|
281
292
|
async ({ presentationId, order }) => {
|
|
282
293
|
try {
|
|
283
294
|
const client = makeClient();
|
|
295
|
+
presentationId = await resolveNodeId(client, presentationId);
|
|
284
296
|
const result = await client.reorderPresentationSlides(presentationId, order);
|
|
285
297
|
return jsonResult(result);
|
|
286
298
|
} catch (err) {
|
|
@@ -301,9 +313,10 @@ export function registerPresentationTools(server) {
|
|
|
301
313
|
'Prompt tips: describe a clean, flat, presentation-ready illustration; specify colors to match the deck ' +
|
|
302
314
|
'theme; and say "no text" — baked-in text is not indexed and often renders wrong (put the meaning in the ' +
|
|
303
315
|
'slide title/blocks instead, and always give the image an `alt` caption). Consumes AI credits.',
|
|
316
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
304
317
|
inputSchema: {
|
|
305
318
|
prompt: z.string().describe('English image description; ask for "no text" in the image'),
|
|
306
|
-
nodeId: z.string().optional().describe(
|
|
319
|
+
nodeId: z.string().optional().describe(`Presentation/node id${NODE_TARGET_HINT} to attach the image to (permission-checked)`),
|
|
307
320
|
workspace: z.string().optional().describe('Workspace id; defaults to ROOTR_WORKSPACE/config'),
|
|
308
321
|
},
|
|
309
322
|
},
|
|
@@ -311,6 +324,7 @@ export function registerPresentationTools(server) {
|
|
|
311
324
|
try {
|
|
312
325
|
const client = makeClient();
|
|
313
326
|
const ws = requireWorkspace(workspace);
|
|
327
|
+
nodeId = await resolveOptionalNodeId(client, nodeId);
|
|
314
328
|
const result = await client.generateImage(ws, { prompt, nodeId });
|
|
315
329
|
return jsonResult(result);
|
|
316
330
|
} catch (err) {
|
|
@@ -328,9 +342,10 @@ export function registerPresentationTools(server) {
|
|
|
328
342
|
'and store the result as a new transparent PNG. Pass the source attachment id; returns { id, url, ... } for ' +
|
|
329
343
|
'the cut-out image, which you can then place on a slide. Useful for product shots / logos / subjects that ' +
|
|
330
344
|
'should sit on the slide without a boxed background.',
|
|
345
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
331
346
|
inputSchema: {
|
|
332
347
|
attachmentId: z.string().describe('Source attachment id (from rootr_generate_image or an upload)'),
|
|
333
|
-
nodeId: z.string().optional().describe(
|
|
348
|
+
nodeId: z.string().optional().describe(`Presentation/node id${NODE_TARGET_HINT} to attach the result to (permission-checked)`),
|
|
334
349
|
workspace: z.string().optional().describe('Workspace id; defaults to ROOTR_WORKSPACE/config'),
|
|
335
350
|
},
|
|
336
351
|
},
|
|
@@ -338,6 +353,7 @@ export function registerPresentationTools(server) {
|
|
|
338
353
|
try {
|
|
339
354
|
const client = makeClient();
|
|
340
355
|
const ws = requireWorkspace(workspace);
|
|
356
|
+
nodeId = await resolveOptionalNodeId(client, nodeId);
|
|
341
357
|
const result = await client.removeImageBackground(ws, { attachmentId, nodeId });
|
|
342
358
|
return jsonResult(result);
|
|
343
359
|
} catch (err) {
|