rootr-cli 0.10.0 → 0.14.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/README.md +162 -6
- package/bin/rootr.js +638 -13
- package/lib/client.js +252 -3
- package/lib/config.js +5 -0
- package/lib/mcp-tools/databases.js +242 -9
- package/lib/mcp-tools/documents.js +10 -4
- package/lib/mcp-tools/feedback.js +97 -0
- package/lib/mcp-tools/forms.js +62 -7
- package/lib/mcp-tools/issues.js +3 -2
- package/lib/mcp-tools/misc.js +143 -1
- package/lib/mcp-tools/presentations.js +5 -1
- package/lib/mcp-tools/public.js +148 -0
- package/lib/mcp-tools/signed.js +44 -16
- package/lib/mcp-tools/workspaces.js +157 -13
- package/lib/mcp.js +35 -4
- package/package.json +1 -1
|
@@ -152,15 +152,27 @@ export function registerWorkspaceTools(server) {
|
|
|
152
152
|
server.registerTool(
|
|
153
153
|
'rootr_scaffold_plan',
|
|
154
154
|
{
|
|
155
|
-
title: '
|
|
155
|
+
title: 'Step 1 of scaffolding — get the design method, then think and ask',
|
|
156
156
|
description:
|
|
157
|
-
'
|
|
158
|
-
'
|
|
159
|
-
'
|
|
160
|
-
'
|
|
161
|
-
'
|
|
162
|
-
'
|
|
163
|
-
'
|
|
157
|
+
'ALWAYS call this first. There are NO templates and NO domain skeletons behind it — deliberately. It ' +
|
|
158
|
+
'does not classify your intent and it will not hand you a structure; what comes back is the METHOD: ' +
|
|
159
|
+
'`analyzeFirst` (the questions to answer about how this job actually runs before you design anything — ' +
|
|
160
|
+
'the daily and weekly rhythm, what piles up, what moves through stages, what arrives from outside, where ' +
|
|
161
|
+
'money is added up, what causes trouble when it is late, and above all what is UNIQUE to this job), ' +
|
|
162
|
+
'`designRules` (how to turn those answers into structure — nouns that pile up become DATABASEs, "one A ' +
|
|
163
|
+
'has many B" becomes a relation, stages become select + board, dates become calendar, outside input ' +
|
|
164
|
+
'becomes FORM + collectInto, calculation becomes SPREADSHEET, counting becomes rollup), `buildingBlocks` ' +
|
|
165
|
+
'(what the product actually offers), `questionPolicy` (ask the user 3-10 questions — you WRITE them, ' +
|
|
166
|
+
'none are provided), and `loop` (the whole sequence). Design it from scratch every time: a bakery, a ' +
|
|
167
|
+
'dental clinic and an SRE team need different things, and no catalogue of skeletons could hold them. ' +
|
|
168
|
+
'Treat it exactly like a coding request. When someone says "build me this" you ask a few clarifying ' +
|
|
169
|
+
'questions, write the code, compile it, run the tests, leave a README, and report. Same sequence here: ' +
|
|
170
|
+
'analyzeFirst = understanding the requirement · questionPolicy = the questions you ask · designRules = ' +
|
|
171
|
+
'writing the code · rootr_scaffold_review = compiling (fix what it flags, compile again) · ' +
|
|
172
|
+
'rootr_scaffold_apply = building · the how-to-use manual = the README · rootr_test_workspace = running ' +
|
|
173
|
+
'the tests. The user should only ever have to type that one sentence — ask your questions, take their ' +
|
|
174
|
+
'answers, and finish the whole thing in this conversation. If they do not answer or say "just do it", ' +
|
|
175
|
+
'assume the most plausible answers, build it anyway, and record the assumptions in the manual.',
|
|
164
176
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
165
177
|
inputSchema: {
|
|
166
178
|
intent: z.string().describe('Natural-language description of what the workspace/tree should be for'),
|
|
@@ -184,8 +196,10 @@ export function registerWorkspaceTools(server) {
|
|
|
184
196
|
description:
|
|
185
197
|
'Create an entirely new Rootr (루터) workspace pre-populated with the given document tree. ' +
|
|
186
198
|
'REQUIRES an account key (PAT) with the workspaces:create scope — a workspace-scoped key cannot do this. ' +
|
|
187
|
-
'PRECONDITION:
|
|
188
|
-
'
|
|
199
|
+
'PRECONDITION: rootr_scaffold_plan first, ask the user its 3-10 clarifying questions, design the tree from ' +
|
|
200
|
+
'their answers, then run rootr_scaffold_review and fix everything it flags at `error` level. The ' +
|
|
201
|
+
'response carries the same `review` back, so a low score here means you shipped something thin. ' +
|
|
202
|
+
'Build a real tool, not a pile of thin markdown files: mix DATABASE (board/' +
|
|
189
203
|
'timeline views, seed rows), SPREADSHEET (formulas), FORM, ISSUE_TRACKER and rich multi-section DOCUMENTs ' +
|
|
190
204
|
'where they fit, and set an emoji `icon` on folders/databases/key documents. The `tree` shape follows ' +
|
|
191
205
|
'the scaffold schema in llms.txt (max 200 nodes). AFTER creating: if your runtime can receive HTTP ' +
|
|
@@ -209,15 +223,145 @@ export function registerWorkspaceTools(server) {
|
|
|
209
223
|
},
|
|
210
224
|
);
|
|
211
225
|
|
|
226
|
+
/**
|
|
227
|
+
* WS-CLEANUP — **짓다 실패해서 남은 빈 워크스페이스를 에이전트가 치울 수 있게.**
|
|
228
|
+
*
|
|
229
|
+
* 스캐폴딩이 중간에 실패하면 껍데기만 남는데 지우는 문이 없었다. 무료 한도가
|
|
230
|
+
* 5개라 몇 번 실패하면 사용자가 바로 막히고, 그 잔해를 사람이 화면에서 하나씩
|
|
231
|
+
* 지워야 했다(2026-08-30 실측: 치과 3개·아파트 4개).
|
|
232
|
+
*
|
|
233
|
+
* 이름을 그대로 다시 적게 한다 — 서버가 요구하는 안전장치이자, 모델이 엉뚱한
|
|
234
|
+
* 워크스페이스를 지목했을 때 걸리는 그물이다.
|
|
235
|
+
*/
|
|
236
|
+
server.registerTool(
|
|
237
|
+
'rootr_delete_workspace',
|
|
238
|
+
{
|
|
239
|
+
title: 'Delete a workspace you created (cleanup after a failed scaffold)',
|
|
240
|
+
description:
|
|
241
|
+
'Permanently delete a Rootr (루터) workspace. Intended for cleaning up EMPTY leftovers from a scaffold ' +
|
|
242
|
+
'run that failed halfway — the free plan allows only a handful of workspaces, so abandoned shells block ' +
|
|
243
|
+
'the user from trying again. Requires OWNER on that workspace, and you must pass `confirmName` exactly ' +
|
|
244
|
+
'matching the workspace name (call rootr_workspaces first to read it). THIS CANNOT BE UNDONE: everything ' +
|
|
245
|
+
'inside is removed. Never call this on a workspace that holds the user\'s real work — if you did not ' +
|
|
246
|
+
'create it in this session, ask the user first.',
|
|
247
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
248
|
+
inputSchema: {
|
|
249
|
+
workspaceId: z.string().describe('Id of the workspace to delete'),
|
|
250
|
+
confirmName: z
|
|
251
|
+
.string()
|
|
252
|
+
.describe('The workspace name, typed again exactly — the server rejects the call if it does not match'),
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
async ({ workspaceId, confirmName }) => {
|
|
256
|
+
try {
|
|
257
|
+
const client = makeClient();
|
|
258
|
+
await client.deleteWorkspace({ workspaceId, confirmName });
|
|
259
|
+
return jsonResult({ deleted: true, workspaceId });
|
|
260
|
+
} catch (err) {
|
|
261
|
+
return errorResult(err);
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
server.registerTool(
|
|
267
|
+
'rootr_scaffold_review',
|
|
268
|
+
{
|
|
269
|
+
title: 'Check a designed tree BEFORE building it — the compiler pass',
|
|
270
|
+
description:
|
|
271
|
+
'Step 3 of scaffolding (plan → ask → design → REVIEW → fix → apply). Give it the tree you designed and ' +
|
|
272
|
+
'it returns { score 0-100, counts, findings[], summary } — every finding is a concrete defect with the ' +
|
|
273
|
+
'node path, e.g. a table whose only column is the title (that is a list, not a table), a lifecycle ' +
|
|
274
|
+
'column with no board view, a date column with no calendar, a form with nowhere to collect answers, ' +
|
|
275
|
+
'tables that are never related to each other, an empty guide document. Fix what it flags and review ' +
|
|
276
|
+
'again — like re-running a compiler — until nothing at `error` level is left. The rules are ' +
|
|
277
|
+
'domain-agnostic, so this works for a bakery or a dental clinic exactly as well as for a SaaS team. ' +
|
|
278
|
+
'Creates nothing and needs no workspace, so run it as many times as you like before committing.',
|
|
279
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
280
|
+
inputSchema: {
|
|
281
|
+
tree: treeArg('The scaffold tree you designed — an ARRAY of node objects, same shape you would pass to rootr_scaffold_apply'),
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
async ({ tree }) => {
|
|
285
|
+
try {
|
|
286
|
+
const client = makeClient();
|
|
287
|
+
const result = await client.scaffoldReview(parseTree(tree));
|
|
288
|
+
return jsonResult(result);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
return errorResult(err);
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
server.registerTool(
|
|
296
|
+
'rootr_review_workspace',
|
|
297
|
+
{
|
|
298
|
+
title: 'Check the workspace you ACTUALLY built — not the tree you sent',
|
|
299
|
+
description:
|
|
300
|
+
'Step 5, after building. `rootr_scaffold_review` grades the tree you DESIGNED; this grades what really ' +
|
|
301
|
+
'exists in the workspace right now — whatever tools made it. Run it after every build: a design can score ' +
|
|
302
|
+
'100 and still land broken (a form whose answers go nowhere, a view whose filter vanished, rows that never ' +
|
|
303
|
+
'got linked). Returns the same { score, counts, findings[], summary }. Pass `rootPath` to grade only the ' +
|
|
304
|
+
'subtree you just added, otherwise an older workspace drags the score down and the number stops meaning ' +
|
|
305
|
+
'anything. Fix what it flags with the normal tools (add rows, write the guide document, add the missing ' +
|
|
306
|
+
'columns/views) and run it again until it is clean.',
|
|
307
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
308
|
+
inputSchema: {
|
|
309
|
+
rootPath: z.string().optional().describe('Grade only this subtree, e.g. "/동네 빵집 운영"'),
|
|
310
|
+
workspace: z.string().optional().describe('Workspace id; defaults to ROOTR_WORKSPACE/config'),
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
async ({ rootPath, workspace }) => {
|
|
314
|
+
try {
|
|
315
|
+
const ws = requireWorkspace(workspace);
|
|
316
|
+
const client = makeClient(ws);
|
|
317
|
+
return jsonResult(await client.scaffoldReviewBuilt(ws, rootPath));
|
|
318
|
+
} catch (err) {
|
|
319
|
+
return errorResult(err);
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
server.registerTool(
|
|
325
|
+
'rootr_test_workspace',
|
|
326
|
+
{
|
|
327
|
+
title: 'RUN the workspace you built — does it actually work?',
|
|
328
|
+
description:
|
|
329
|
+
'The last step, and the one that catches what the two review tools cannot. `rootr_scaffold_review` grades ' +
|
|
330
|
+
'the tree you designed; `rootr_review_workspace` grades the shape of what got built; this one **exercises ' +
|
|
331
|
+
'it**: does every relation point at a row that really exists, do rollups and formulas produce a value on ' +
|
|
332
|
+
'real rows, do board/calendar/filter views reference columns that exist, can the form actually deposit an ' +
|
|
333
|
+
'answer (target table alive AND every question name matching a column — a mismatch makes answers vanish ' +
|
|
334
|
+
'with no error), are sheet formulas computed and free of #REF!/#DIV/0!. Returns { ran, passed, failed, ' +
|
|
335
|
+
'warned, checks[] }. It writes nothing, so run it as often as you like. A workspace can score 100 on both ' +
|
|
336
|
+
'reviews and still fail here — that is exactly the case this exists for. Pass `rootPath` to test only the ' +
|
|
337
|
+
'subtree you just built. Fix every `fail` and run it again.',
|
|
338
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
339
|
+
inputSchema: {
|
|
340
|
+
rootPath: z.string().optional().describe('Test only this subtree, e.g. "/동네 빵집 운영"'),
|
|
341
|
+
workspace: z.string().optional().describe('Workspace id; defaults to ROOTR_WORKSPACE/config'),
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
async ({ rootPath, workspace }) => {
|
|
345
|
+
try {
|
|
346
|
+
const ws = requireWorkspace(workspace);
|
|
347
|
+
const client = makeClient(ws);
|
|
348
|
+
return jsonResult(await client.scaffoldSelfTest(ws, rootPath));
|
|
349
|
+
} catch (err) {
|
|
350
|
+
return errorResult(err);
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
);
|
|
354
|
+
|
|
212
355
|
server.registerTool(
|
|
213
356
|
'rootr_scaffold_apply',
|
|
214
357
|
{
|
|
215
358
|
title: 'Apply a scaffold tree to an existing workspace',
|
|
216
359
|
description:
|
|
217
360
|
'Create the given document tree inside an existing Rootr (루터) workspace, optionally rooted under an ' +
|
|
218
|
-
'existing path. Works with a workspace key (docs:write scope). PRECONDITION:
|
|
219
|
-
'
|
|
220
|
-
'bar as rootr_create_workspace — real building blocks with icons/views/
|
|
361
|
+
'existing path. Works with a workspace key (docs:write scope). PRECONDITION: rootr_scaffold_plan first, ask ' +
|
|
362
|
+
'its 3-10 clarifying questions, design from the answers, then rootr_scaffold_review and fix every ' +
|
|
363
|
+
'`error` finding (same quality bar as rootr_create_workspace — real building blocks with icons/views/' +
|
|
364
|
+
'seed rows, not thin markdown). The response carries the `review` back so you can see what stayed weak, and AFTER it lands run rootr_review_workspace (rootPath = your new subtree) — that one grades what really exists. ' +
|
|
221
365
|
'AFTER creating: if your runtime can receive HTTP callbacks, offer a webhook on the new subtree ' +
|
|
222
366
|
'(rootr_create_webhook with scopeNodeId) so changes flow back to you — skip your own actorId.',
|
|
223
367
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
package/lib/mcp.js
CHANGED
|
@@ -10,6 +10,7 @@ import { registerDatabaseTools } from './mcp-tools/databases.js';
|
|
|
10
10
|
import { registerTemplateTools } from './mcp-tools/templates.js';
|
|
11
11
|
import { registerWebhookTools } from './mcp-tools/webhooks.js';
|
|
12
12
|
import { registerMiscTools } from './mcp-tools/misc.js';
|
|
13
|
+
import { registerPublicTools } from './mcp-tools/public.js';
|
|
13
14
|
import { registerCrmTools } from './mcp-tools/crm.js';
|
|
14
15
|
import { registerPresentationTools } from './mcp-tools/presentations.js';
|
|
15
16
|
import { registerSpreadsheetTools } from './mcp-tools/spreadsheets.js';
|
|
@@ -18,6 +19,7 @@ import { registerFormTools } from './mcp-tools/forms.js';
|
|
|
18
19
|
import { registerRecordingTools } from './mcp-tools/recordings.js';
|
|
19
20
|
import { registerAttachmentTools } from './mcp-tools/attachments.js';
|
|
20
21
|
import { registerSignedDocTools } from './mcp-tools/signed.js';
|
|
22
|
+
import { registerFeedbackTools } from './mcp-tools/feedback.js';
|
|
21
23
|
import { withRateLimitSink } from './context.js';
|
|
22
24
|
|
|
23
25
|
/** Warn once the workspace has burned this share of its quota. */
|
|
@@ -65,7 +67,7 @@ function withQuotaWarnings(server) {
|
|
|
65
67
|
* Builds a fully-configured McpServer (all tool groups registered) without
|
|
66
68
|
* connecting a transport. Shared by the local stdio CLI server below and by
|
|
67
69
|
* the backend's hosted Streamable HTTP transport (src/mcp/mcp-transport.controller.ts),
|
|
68
|
-
* so the
|
|
70
|
+
* so the 151 tool definitions have exactly one implementation.
|
|
69
71
|
*/
|
|
70
72
|
export function buildMcpServer({ name, version }) {
|
|
71
73
|
const server = new McpServer(
|
|
@@ -123,11 +125,38 @@ export function buildMcpServer({ name, version }) {
|
|
|
123
125
|
'rootr_request_signing_code first and ASK THE HUMAN for the 6 digits that arrive in their mail — ' +
|
|
124
126
|
'that code is the only evidence a person (not an agent) pressed sign; ' +
|
|
125
127
|
'FOLDER = grouping; LOG store = typed event table whose relation fields auto-build a lineage graph (metrics, incidents, pipeline events — use the rootr_*_log_* tools).\n\n' +
|
|
126
|
-
'Scaffolding discipline
|
|
127
|
-
'
|
|
128
|
+
'Scaffolding discipline — treat "set up a workspace for X" exactly like "build me this app": you ask a ' +
|
|
129
|
+
'few clarifying questions, write it, compile it, run the tests, leave a README, and report. The user ' +
|
|
130
|
+
'should only have to type that one sentence; everything else finishes inside the conversation. Never ask ' +
|
|
131
|
+
'and then stop. The loop is plan → ask → design → review → fix → apply → manual → test, and skipping a step is ' +
|
|
132
|
+
'how a workspace ends up as a pile of thin markdown files: (1) rootr_scaffold_plan on the raw intent — it ' +
|
|
133
|
+
'hands back the METHOD, never a template or a domain skeleton (there are none, deliberately): ' +
|
|
134
|
+
'analyzeFirst, designRules, buildingBlocks, questionPolicy, loop; ' +
|
|
135
|
+
'(2) work out how the job ACTUALLY runs using the `analyzeFirst` questions it returns — the daily and ' +
|
|
136
|
+
'weekly rhythm, what piles up, what has stages, what comes in from outside, where money is added up, ' +
|
|
137
|
+
'what causes an accident if it is late, and above all what is unique to THIS job and would be missing ' +
|
|
138
|
+
'from a generic template; (3) ask the user the 3-10 clarifying questions you generated — every answer ' +
|
|
139
|
+
'must visibly shape the structure; (4) design the tree FROM SCRATCH with `designRules`; (5) rootr_scaffold_review ' +
|
|
140
|
+
'and fix every `error` finding, then review again; (6) apply; (7) write the HOW-TO-USE manual as a ' +
|
|
141
|
+
'DOCUMENT in the tree — the daily/weekly/monthly routine naming the actual tables and views, how the ' +
|
|
142
|
+
'tables link to each other, where form answers land, and what the OWNER still has to fill in (the ' +
|
|
143
|
+
'assumptions you made because you could not ask, the example rows to replace, the notification address ' +
|
|
144
|
+
'only they can decide); (8) rootr_test_workspace — the unit-test step: it RUNS what you built and catches ' +
|
|
145
|
+
'what no review can (relations pointing at rows that do not exist, rollups/formulas that yield nothing, ' +
|
|
146
|
+
'views referencing gone columns, form questions with no matching column so answers vanish silently, sheet ' +
|
|
147
|
+
'formulas left uncomputed or at #REF!); fix every fail and run it again; (9) rootr_review_workspace to ' +
|
|
148
|
+
'grade the shape of what really got built. Both reviews can say 100 while the thing is broken — only ' +
|
|
149
|
+
'step 8 catches that. ' +
|
|
150
|
+
'A workspace without a manual is a pile of empty grids. ' +
|
|
128
151
|
'If your runtime can receive HTTP callbacks, offer to close the loop after scaffolding: register a webhook ' +
|
|
129
152
|
'scoped to the new tree (rootr_create_webhook) so changes flow back to you — and ALWAYS skip deliveries ' +
|
|
130
|
-
'whose actorId is your own key, or you will trigger yourself in an infinite loop
|
|
153
|
+
'whose actorId is your own key, or you will trigger yourself in an infinite loop.\n\n' +
|
|
154
|
+
'Telling us when something is wrong: if a tool errors unexpectedly, does not do what its ' +
|
|
155
|
+
'description says, forces you into a workaround, or the user complains that Rootr is broken or ' +
|
|
156
|
+
'confusing, call rootr_send_feedback — on your own, without being asked and without asking ' +
|
|
157
|
+
'permission — and then carry on with the task. Once per distinct problem, never in a retry loop. ' +
|
|
158
|
+
'It is free, never spends credits, never counts against the daily limit, and works even when ' +
|
|
159
|
+
'your key or quota does not. Nobody else is going to report it.',
|
|
131
160
|
},
|
|
132
161
|
);
|
|
133
162
|
|
|
@@ -142,6 +171,7 @@ export function buildMcpServer({ name, version }) {
|
|
|
142
171
|
registerDatabaseTools(server);
|
|
143
172
|
registerWebhookTools(server);
|
|
144
173
|
registerMiscTools(server);
|
|
174
|
+
registerPublicTools(server);
|
|
145
175
|
registerCrmTools(server);
|
|
146
176
|
registerPresentationTools(server);
|
|
147
177
|
registerSpreadsheetTools(server);
|
|
@@ -150,6 +180,7 @@ export function buildMcpServer({ name, version }) {
|
|
|
150
180
|
registerRecordingTools(server);
|
|
151
181
|
registerAttachmentTools(server);
|
|
152
182
|
registerSignedDocTools(server);
|
|
183
|
+
registerFeedbackTools(server);
|
|
153
184
|
|
|
154
185
|
return server;
|
|
155
186
|
}
|