rootr-cli 0.2.1 → 0.4.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/lib/mcp.js CHANGED
@@ -9,8 +9,63 @@ import { registerIssueTools } from './mcp-tools/issues.js';
9
9
  import { registerDatabaseTools } from './mcp-tools/databases.js';
10
10
  import { registerWebhookTools } from './mcp-tools/webhooks.js';
11
11
  import { registerMiscTools } from './mcp-tools/misc.js';
12
+ import { registerCrmTools } from './mcp-tools/crm.js';
13
+ import { registerPresentationTools } from './mcp-tools/presentations.js';
14
+ import { registerSpreadsheetTools } from './mcp-tools/spreadsheets.js';
15
+ import { registerWhiteboardTools } from './mcp-tools/whiteboards.js';
16
+ import { registerFormTools } from './mcp-tools/forms.js';
17
+ import { registerRecordingTools } from './mcp-tools/recordings.js';
18
+ import { registerAttachmentTools } from './mcp-tools/attachments.js';
19
+ import { withRateLimitSink } from './context.js';
12
20
 
13
- export async function startMcpServer({ name, version }) {
21
+ /** Warn once the workspace has burned this share of its quota. */
22
+ const QUOTA_WARN_AT = 0.8;
23
+
24
+ function quotaNotice({ limit, remaining, window, upgradeUrl }) {
25
+ if (!Number.isFinite(limit) || !Number.isFinite(remaining) || limit <= 0) return null;
26
+ if (remaining <= 0) return null; // already blocked — the 429 says it better
27
+ if (remaining > Math.ceil(limit * (1 - QUOTA_WARN_AT))) return null;
28
+ const per = window === 'day' ? '오늘' : '이번 시간';
29
+ const refill = window === 'day' ? '내일 자정(KST)' : '다음 정시';
30
+ return (
31
+ `\n\n⚠️ ${per} 남은 조회 ${remaining}/${limit}회. ` +
32
+ `다 쓰면 ${refill}까지 조회가 멈춥니다` +
33
+ (upgradeUrl ? `. 한도를 늘리려면: ${upgradeUrl}` : '.')
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Wraps every tool handler so a nearly-exhausted quota is appended to the tool
39
+ * result. The assistant reads that text, so the user hears "you have 30 queries
40
+ * left today" while the work still succeeds — instead of hitting a wall with no
41
+ * warning mid-task.
42
+ */
43
+ function withQuotaWarnings(server) {
44
+ const original = server.registerTool.bind(server);
45
+ server.registerTool = (name, spec, handler) =>
46
+ original(name, spec, async (...args) => {
47
+ const sink = {};
48
+ const result = await withRateLimitSink(sink, () => handler(...args));
49
+ if (!result || result.isError || !Array.isArray(result.content)) return result;
50
+ const notice = quotaNotice(sink);
51
+ if (!notice) return result;
52
+ const last = [...result.content].reverse().find((c) => c && c.type === 'text');
53
+ if (!last) return result;
54
+ return {
55
+ ...result,
56
+ content: result.content.map((c) => (c === last ? { ...c, text: `${c.text}${notice}` } : c)),
57
+ };
58
+ });
59
+ return server;
60
+ }
61
+
62
+ /**
63
+ * Builds a fully-configured McpServer (all tool groups registered) without
64
+ * connecting a transport. Shared by the local stdio CLI server below and by
65
+ * the backend's hosted Streamable HTTP transport (src/mcp/mcp-transport.controller.ts),
66
+ * so the 89 tool definitions have exactly one implementation.
67
+ */
68
+ export function buildMcpServer({ name, version }) {
14
69
  const server = new McpServer(
15
70
  { name, version },
16
71
  {
@@ -23,14 +78,38 @@ export async function startMcpServer({ name, version }) {
23
78
  'datastores (structured entries + stats), GraphRAG root-cause Q&A (rootr_ask), GitHub-style issue ' +
24
79
  'trackers, DATABASE rows (read schema + rows, add/update/delete rows — e.g. move a kanban card by ' +
25
80
  'changing its status column, via rootr_read_database / rootr_add_row / rootr_update_row / rootr_delete_row), ' +
26
- 'and webhooks.\n\n' +
81
+ 'CRM nodes (companies/contacts/deals/activities/tasks via the rootr_crm_* tools), and webhooks. ' +
82
+ 'To put a screenshot or photo into a document, use rootr_attach_image_to_document (upload + insert in one ' +
83
+ 'step) or rootr_upload_attachment (upload only, returns a URL/markdown to place yourself).\n\n' +
27
84
  'Node types — pick the block that fits, never fake everything with markdown files: ' +
28
85
  'DOCUMENT = free-form markdown narrative (overviews, specs, decision logs); ' +
29
86
  'DATABASE = typed columns + views (table/board/calendar/timeline) for anything trackable — tasks, requirements, risks, milestones; ' +
30
- 'SPREADSHEET = grid with formulas when content needs calculation (budgets, models, estimates); ' +
31
- 'WHITEBOARD = freeform visual canvas (flow diagrams, brainstorming, journey maps); ' +
32
- 'FORM = fillable intake from people outside the tree, optionally feeding a DATABASE (surveys, request queues); ' +
87
+ 'SPREADSHEET = grid with formulas when content needs calculation (budgets, models, estimates) — author with ' +
88
+ 'the rootr_*_spreadsheet / rootr_*_sheet tools (create/read/update, per-sheet create/update/delete, ' +
89
+ 'rootr_patch_spreadsheet_cells for cell values/formulas); ' +
90
+ 'WHITEBOARD = freeform visual canvas (flow diagrams, brainstorming, journey maps) — author with the ' +
91
+ 'rootr_*_whiteboard tools (create/read/update the shapes[]/edges[] scene); ' +
92
+ 'FORM = fillable intake from people outside the tree, optionally feeding a DATABASE (surveys, request queues) ' +
93
+ '— author with the rootr_*_form tools (create/read/update fields, rootr_list_form_responses / ' +
94
+ 'rootr_submit_form_response for responses, rootr_*_form_share_link for public links); ' +
33
95
  'ISSUE_TRACKER = GitHub-style issues with #numbers, states, labels, comments (bug/work tracking with a lifecycle); ' +
96
+ 'CRM = full sales CRM: companies, contacts with lifecycle stages & auto lead-scoring, multi-pipeline deals ' +
97
+ '(kanban), an activity timeline, follow-up tasks, and CSV import/export — use for sales/lead/customer ' +
98
+ 'tracking; never fake a CRM with plain DATABASEs (use the rootr_crm_* tools: rootr_crm_create/get/update, ' +
99
+ 'rootr_crm_list_companies/upsert_company/get_company, rootr_crm_list_contacts/upsert_contact/get_contact, ' +
100
+ 'rootr_crm_list_deals/upsert_deal/get_deal/move_deal, rootr_crm_log_activity/list_activities, ' +
101
+ 'rootr_crm_upsert_task/list_tasks, rootr_crm_import); ' +
102
+ 'PRESENTATION = slide deck (16:9 HTML slides with themes) — author via the rootr_*_presentation tools ' +
103
+ '(rootr_create_presentation, rootr_read_presentation, rootr_update_presentation, ' +
104
+ 'rootr_append_presentation_slides, rootr_update_presentation_slide, rootr_reorder_presentation_slides, ' +
105
+ 'plus rootr_generate_image / rootr_remove_image_background for slide art), never as markdown documents; ' +
106
+ 'ALWAYS rootr_read_presentation before updating so slide ids line up; ' +
107
+ 'RECORDING = audio recording with a diarized (speaker-labeled) transcript and minutes — fully drivable from ' +
108
+ 'MCP: rootr_create_recording → rootr_upload_recording_audio (upload a local audio file; stores it as ' +
109
+ 'status UPLOADED, does NOT transcribe) → rootr_transcribe_recording (explicit step that runs speech-to-text ' +
110
+ 'and spends AI credits) → poll rootr_read_recording until status READY → read the transcript, ' +
111
+ 'rename speakers with rootr_rename_recording_speakers, and write the minutes yourself with rootr_write ' +
112
+ '(preferred — no AI credits) or call rootr_summarize_recording for Rootr\'s built-in summary document; ' +
34
113
  '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' +
35
114
  'Scaffolding discipline: ALWAYS rootr_scaffold_plan first, ask the user the 3-10 clarifying questions it ' +
36
115
  'prescribes, and only then design and create the tree — every answer must visibly shape the structure. ' +
@@ -40,6 +119,8 @@ export async function startMcpServer({ name, version }) {
40
119
  },
41
120
  );
42
121
 
122
+ withQuotaWarnings(server);
123
+
43
124
  registerDocumentTools(server);
44
125
  registerWorkspaceTools(server);
45
126
  registerLogTools(server);
@@ -48,7 +129,19 @@ export async function startMcpServer({ name, version }) {
48
129
  registerDatabaseTools(server);
49
130
  registerWebhookTools(server);
50
131
  registerMiscTools(server);
132
+ registerCrmTools(server);
133
+ registerPresentationTools(server);
134
+ registerSpreadsheetTools(server);
135
+ registerWhiteboardTools(server);
136
+ registerFormTools(server);
137
+ registerRecordingTools(server);
138
+ registerAttachmentTools(server);
51
139
 
140
+ return server;
141
+ }
142
+
143
+ export async function startMcpServer({ name, version }) {
144
+ const server = buildMcpServer({ name, version });
52
145
  const transport = new StdioServerTransport();
53
146
  await server.connect(transport);
54
147
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rootr-cli",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "Official Rootr CLI + MCP server — read, write, edit and search Rootr documents from the shell or from an AI agent.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,12 +9,31 @@
9
9
  "engines": {
10
10
  "node": ">=18"
11
11
  },
12
+ "homepage": "https://rootr.io/mcp",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/gwmage/rootr-cli.git"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/gwmage/rootr-cli/issues"
19
+ },
20
+ "keywords": [
21
+ "rootr",
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "knowledge-base",
25
+ "team-documentation",
26
+ "ai-agent",
27
+ "claude",
28
+ "cli"
29
+ ],
12
30
  "files": [
13
31
  "bin",
14
32
  "lib",
15
- "README.md"
33
+ "README.md",
34
+ "LICENSE"
16
35
  ],
17
- "license": "UNLICENSED",
36
+ "license": "MIT",
18
37
  "dependencies": {
19
38
  "@modelcontextprotocol/sdk": "^1.0.0",
20
39
  "zod": "^3.23.8"