rootr-cli 0.16.0 → 0.17.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/README.ko.md +811 -0
- package/README.md +360 -652
- package/bin/rootr.js +1 -1
- package/lib/client.js +1 -1
- package/lib/credentials.js +30 -14
- package/lib/mcp-tools/crm.js +4 -2
- package/lib/mcp-tools/databases.js +3 -1
- package/lib/mcp-tools/documents.js +5 -3
- package/lib/mcp-tools/forms.js +4 -2
- package/lib/mcp-tools/issues.js +3 -2
- package/lib/mcp-tools/logs.js +3 -2
- package/lib/mcp-tools/pages.js +5 -1
- package/lib/mcp-tools/presentations.js +4 -2
- package/lib/mcp-tools/recordings.js +3 -2
- package/lib/mcp-tools/shared.js +9 -0
- package/lib/mcp-tools/signed.js +4 -1
- package/lib/mcp-tools/spreadsheets.js +6 -3
- package/lib/mcp-tools/whiteboards.js +4 -2
- package/lib/mcp-tools/workspaces.js +8 -7
- package/package.json +1 -1
package/bin/rootr.js
CHANGED
|
@@ -91,7 +91,7 @@ const USAGE = `Rootr CLI v${pkg.version}
|
|
|
91
91
|
rootr memory add "<내용>" [--kind 지시|사람|프로젝트|참고] [--source <경로|id>] [--related a,b] 기억 저장
|
|
92
92
|
rootr memory edit <id> [--content "<내용>"] [--kind …] [--source …] [--related a,b] 기억 고치기(준 칸만)
|
|
93
93
|
rootr memory forget <id> [--hard] 기억 폐기(--hard면 행 삭제)
|
|
94
|
-
rootr mcp MCP stdio 서버 구동 (도구
|
|
94
|
+
rootr mcp MCP stdio 서버 구동 (도구 167종 — README 참고)
|
|
95
95
|
rootr --help 도움말
|
|
96
96
|
rootr --version 버전 출력
|
|
97
97
|
|
package/lib/client.js
CHANGED
|
@@ -1407,7 +1407,7 @@ export class RootrClient {
|
|
|
1407
1407
|
});
|
|
1408
1408
|
}
|
|
1409
1409
|
|
|
1410
|
-
/** Full spreadsheet: node meta + sheets[] (each with a sparse `cells` map). */
|
|
1410
|
+
/** Full spreadsheet: node meta + data.sheets[] (each with a sparse `cells` map). */
|
|
1411
1411
|
async getSpreadsheet(spreadsheetId) {
|
|
1412
1412
|
return this.request(`/spreadsheets/${encodeURIComponent(spreadsheetId)}`);
|
|
1413
1413
|
}
|
package/lib/credentials.js
CHANGED
|
@@ -27,16 +27,22 @@ export function candidateClients(cfg = loadConfig()) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Collect every workspace the registered keys can see, and
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
30
|
+
* Collect every workspace the registered keys can see, and which of them a
|
|
31
|
+
* key can actually open — listing is not a capability probe: a workspace key
|
|
32
|
+
* lists all of its owner's workspaces while being rejected by every one but
|
|
33
|
+
* its own. `GET /workspaces` now tags each entry with `accessible` (computed
|
|
34
|
+
* server-side from the calling key's own scope), so this reads straight off
|
|
35
|
+
* that field instead of firing one extra request per workspace. Older
|
|
36
|
+
* servers that predate the field fall back to the old per-workspace probe
|
|
37
|
+
* when `probe` is requested.
|
|
34
38
|
*/
|
|
35
39
|
export async function surveyWorkspaces(candidates, { probe = false, probeLimit = 12 } = {}) {
|
|
36
40
|
const seen = new Map();
|
|
37
41
|
const reachable = new Set();
|
|
38
42
|
let listed = 0;
|
|
39
43
|
let failures = 0;
|
|
44
|
+
let sawAccessibleField = false;
|
|
45
|
+
const lists = [];
|
|
40
46
|
for (const { client } of candidates) {
|
|
41
47
|
let list = [];
|
|
42
48
|
try {
|
|
@@ -46,19 +52,29 @@ export async function surveyWorkspaces(candidates, { probe = false, probeLimit =
|
|
|
46
52
|
failures += 1;
|
|
47
53
|
continue; // a revoked/foreign key shouldn't hide the others
|
|
48
54
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
lists.push({ client, list });
|
|
56
|
+
for (const w of list) {
|
|
57
|
+
if (!seen.has(w.id)) seen.set(w.id, w);
|
|
58
|
+
if (typeof w.accessible === 'boolean') {
|
|
59
|
+
sawAccessibleField = true;
|
|
60
|
+
if (w.accessible) reachable.add(w.id);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!sawAccessibleField && probe) {
|
|
65
|
+
for (const { client, list } of lists) {
|
|
66
|
+
for (const w of list.slice(0, probeLimit)) {
|
|
67
|
+
if (reachable.has(w.id)) continue;
|
|
68
|
+
try {
|
|
69
|
+
await client.request(`/workspaces/${encodeURIComponent(w.id)}`);
|
|
70
|
+
reachable.add(w.id);
|
|
71
|
+
} catch {
|
|
72
|
+
// scoped key rejected here; another key may still cover it
|
|
73
|
+
}
|
|
58
74
|
}
|
|
59
75
|
}
|
|
60
76
|
}
|
|
61
|
-
return { workspaces: [...seen.values()], reachable, listed, failures };
|
|
77
|
+
return { workspaces: [...seen.values()], reachable, listed, failures, probed: sawAccessibleField || probe };
|
|
62
78
|
}
|
|
63
79
|
|
|
64
80
|
/**
|
package/lib/mcp-tools/crm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, NODE_URL_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Shared context repeated (shortened) across tool descriptions so an agent
|
|
@@ -56,7 +56,9 @@ export function registerCrmTools(server) {
|
|
|
56
56
|
'activities and tasks all live inside it. Optionally seed custom `pipelines` (name + ordered stages, each ' +
|
|
57
57
|
'with an optional win probability/color) and/or custom company/contact/deal `fields`; sane defaults are ' +
|
|
58
58
|
'used if omitted. ' +
|
|
59
|
-
CRM_CONTEXT
|
|
59
|
+
CRM_CONTEXT +
|
|
60
|
+
' ' +
|
|
61
|
+
NODE_URL_HINT,
|
|
60
62
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
61
63
|
inputSchema: {
|
|
62
64
|
name: z.string().optional().describe('CRM node name'),
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
resolveNodeId,
|
|
9
9
|
resolveOptionalNodeId,
|
|
10
10
|
NODE_TARGET_HINT,
|
|
11
|
+
NODE_URL_HINT,
|
|
11
12
|
} from './shared.js';
|
|
12
13
|
|
|
13
14
|
/** Human text for the `databaseId` param — accepts a path just like the document tools. */
|
|
@@ -139,7 +140,8 @@ export function registerDatabaseTools(server) {
|
|
|
139
140
|
'COST NOTE: by default, every row added to this table (now or later) queues that table for graph extraction ' +
|
|
140
141
|
'(embeddings + LLM) — fine for a handful of rows, expensive for a bulk-loaded roster/log table. Pass ' +
|
|
141
142
|
'graphSync:false for tables you are about to bulk-load; flip it back with ' +
|
|
142
|
-
'`rootr_update_node({ graphSyncOff: true|false })` any time after.'
|
|
143
|
+
'`rootr_update_node({ graphSyncOff: true|false })` any time after. ' +
|
|
144
|
+
NODE_URL_HINT,
|
|
143
145
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
144
146
|
inputSchema: {
|
|
145
147
|
name: z.string().describe('Table name, e.g. "업무 목록"'),
|
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
|
|
3
3
|
import { RootrApiError } from '../client.js';
|
|
4
4
|
import { resolveTarget, requireResolved, resolveMoveDestination } from '../resolve.js';
|
|
5
|
-
import { textResult, jsonResult, errorResult, makeClient, NODE_TARGET_HINT } from './shared.js';
|
|
5
|
+
import { textResult, jsonResult, errorResult, makeClient, NODE_TARGET_HINT, NODE_URL_HINT } from './shared.js';
|
|
6
6
|
|
|
7
7
|
/** Registers the original 6 document tools: list/read/append/edit/write/search. */
|
|
8
8
|
export function registerDocumentTools(server) {
|
|
@@ -147,7 +147,8 @@ export function registerDocumentTools(server) {
|
|
|
147
147
|
description:
|
|
148
148
|
'Replace the ENTIRE content of a Rootr document, or create a new document at a path if it does not exist. ' +
|
|
149
149
|
'This is DESTRUCTIVE — it discards whatever is currently there. Read the document first and pass its etag ' +
|
|
150
|
-
'as ifMatch to avoid clobbering concurrent changes. Prefer rootr_append or rootr_edit for incremental changes.'
|
|
150
|
+
'as ifMatch to avoid clobbering concurrent changes. Prefer rootr_append or rootr_edit for incremental changes. ' +
|
|
151
|
+
`When this creates a new document, the result text includes its address (same idea as: ${NODE_URL_HINT}).`,
|
|
151
152
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
152
153
|
inputSchema: {
|
|
153
154
|
target: z.string().describe('Document path (starting with "/", created if missing) or node id, or a full rootr.io URL'),
|
|
@@ -174,7 +175,8 @@ export function registerDocumentTools(server) {
|
|
|
174
175
|
content,
|
|
175
176
|
});
|
|
176
177
|
const id = created.id || (created.node && created.node.id);
|
|
177
|
-
|
|
178
|
+
const url = created.url || (created.node && created.node.url);
|
|
179
|
+
return textResult(`created ${resolved.path} (id ${id})${url ? ` — ${url}` : ''}`);
|
|
178
180
|
}
|
|
179
181
|
const { etag } = await client.putDocument(resolved.id, content, { ifMatch });
|
|
180
182
|
return textResult(`wrote (etag ${etag || 'n/a'})`);
|
package/lib/mcp-tools/forms.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, NODE_URL_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
const fieldShape = z
|
|
6
6
|
.object({
|
|
@@ -88,7 +88,9 @@ export function registerFormTools(server) {
|
|
|
88
88
|
'Create a new FORM node (fillable intake) in a Rootr (루터) workspace, optionally seeded with field ' +
|
|
89
89
|
'definitions. Use FORM for surveys/request queues collected from people outside the tree — set ' +
|
|
90
90
|
'`targetDatabaseId` to also mirror each response into a DATABASE\'s rows. ' +
|
|
91
|
-
FIELDS_GUIDE
|
|
91
|
+
FIELDS_GUIDE +
|
|
92
|
+
' ' +
|
|
93
|
+
NODE_URL_HINT,
|
|
92
94
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
93
95
|
inputSchema: {
|
|
94
96
|
name: z.string().optional().describe('Form name/title'),
|
package/lib/mcp-tools/issues.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { textResult, jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT } from './shared.js';
|
|
3
|
+
import { textResult, jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, NODE_URL_HINT } from './shared.js';
|
|
4
4
|
|
|
5
5
|
/** Issue tracker tools: create tracker, list/create/get/update issues, comment. */
|
|
6
6
|
export function registerIssueTools(server) {
|
|
@@ -10,7 +10,8 @@ export function registerIssueTools(server) {
|
|
|
10
10
|
title: 'Create an issue tracker in a Rootr workspace',
|
|
11
11
|
description:
|
|
12
12
|
'Create a new GitHub-style issue tracker document in a Rootr (루터) workspace. Returns the tracker with ' +
|
|
13
|
-
'its id, plus its default labels/types — use rootr_create_issue afterward to add issues to it.'
|
|
13
|
+
'its id, plus its default labels/types — use rootr_create_issue afterward to add issues to it. ' +
|
|
14
|
+
NODE_URL_HINT,
|
|
14
15
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
15
16
|
inputSchema: {
|
|
16
17
|
name: z.string().describe('Name of the issue tracker'),
|
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, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, NODE_URL_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) {
|
|
@@ -12,7 +12,8 @@ export function registerLogTools(server) {
|
|
|
12
12
|
'Create a new typed LOG store (a structured, queryable table-like node) in a Rootr (루터) workspace. ' +
|
|
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
|
-
'Use rootr_add_log_entries afterward to insert rows, and rootr_query_log_entries / rootr_log_stats to read them back.'
|
|
15
|
+
'Use rootr_add_log_entries afterward to insert rows, and rootr_query_log_entries / rootr_log_stats to read them back. ' +
|
|
16
|
+
NODE_URL_HINT,
|
|
16
17
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
17
18
|
inputSchema: {
|
|
18
19
|
name: z.string().describe('Name of the LOG store'),
|
package/lib/mcp-tools/pages.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
resolveNodeId,
|
|
9
9
|
resolveOptionalNodeId,
|
|
10
10
|
NODE_TARGET_HINT,
|
|
11
|
+
NODE_URL_HINT,
|
|
11
12
|
} from './shared.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -66,7 +67,10 @@ export function registerPageTools(server) {
|
|
|
66
67
|
'can then be published to the web at a public address (see rootr_publish_node). Use this when someone asks ' +
|
|
67
68
|
'for a landing page, an announcement page, an event page, a product/pricing page or a signup page. ' +
|
|
68
69
|
'Omit `blocks` to start from a hero/text/footer skeleton. ' +
|
|
69
|
-
BLOCK_GUIDE
|
|
70
|
+
BLOCK_GUIDE +
|
|
71
|
+
' ' +
|
|
72
|
+
NODE_URL_HINT +
|
|
73
|
+
' (that is the editor address; use rootr_publish_node for the public address.)',
|
|
70
74
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
71
75
|
inputSchema: {
|
|
72
76
|
name: z.string().optional().describe('Page name (also the browser tab title of the published page)'),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, NODE_URL_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Authoring guide, repeated (shortened) across every tool description so an
|
|
@@ -123,7 +123,9 @@ export function registerPresentationTools(server) {
|
|
|
123
123
|
description:
|
|
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
|
-
AUTHORING_GUIDE
|
|
126
|
+
AUTHORING_GUIDE +
|
|
127
|
+
' ' +
|
|
128
|
+
NODE_URL_HINT,
|
|
127
129
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
128
130
|
inputSchema: {
|
|
129
131
|
name: z.string().optional().describe('Deck name/title'),
|
|
@@ -4,7 +4,7 @@ import { basename, extname, join, isAbsolute } from 'node:path';
|
|
|
4
4
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
7
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, NODE_URL_HINT, resolveOptionalNodeId } from './shared.js';
|
|
8
8
|
import { RootrApiError } from '../client.js';
|
|
9
9
|
|
|
10
10
|
/** 100MB — the backend's hard limit on a single audio upload. */
|
|
@@ -68,7 +68,8 @@ export function registerRecordingTools(server) {
|
|
|
68
68
|
'speech-to-text; nothing is transcribed until you call it) → poll rootr_read_recording until status is ' +
|
|
69
69
|
'READY → read the transcript (and optionally name the speakers with rootr_rename_recording_speakers) → ' +
|
|
70
70
|
'write the minutes yourself with rootr_write, or call rootr_summarize_recording to use Rootr\'s built-in ' +
|
|
71
|
-
'summarizer.'
|
|
71
|
+
'summarizer. ' +
|
|
72
|
+
NODE_URL_HINT,
|
|
72
73
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
73
74
|
inputSchema: {
|
|
74
75
|
name: z.string().optional().describe('Recording name/title, e.g. "2026-07-21 주간회의"'),
|
package/lib/mcp-tools/shared.js
CHANGED
|
@@ -10,6 +10,15 @@ import { resolveTarget, requireResolved } from '../resolve.js';
|
|
|
10
10
|
*/
|
|
11
11
|
export const NODE_TARGET_HINT = ', or its path starting with "/", or a full rootr.io URL';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* ISSUE-103 — every node create/read tool's result carries `url` (the address
|
|
15
|
+
* to hand a human) and `routeSegment` (the URL segment, in case `url` is
|
|
16
|
+
* missing). Never guess the segment yourself: DOCUMENT/db/log/sheet/whiteboard/
|
|
17
|
+
* form/issues/deck/crm/rec are all different, and SIGNED reuses DOCUMENT's 'd'.
|
|
18
|
+
*/
|
|
19
|
+
export const NODE_URL_HINT =
|
|
20
|
+
'The result includes `url` — the address to open this node at. Use it as-is; never guess or construct it.';
|
|
21
|
+
|
|
13
22
|
/**
|
|
14
23
|
* Resolve a path-or-id argument to a node id. Paths go through the by-path
|
|
15
24
|
* endpoint; anything else is already an id and is passed through untouched.
|
package/lib/mcp-tools/signed.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
resolveNodeId,
|
|
10
10
|
resolveOptionalNodeId,
|
|
11
11
|
NODE_TARGET_HINT,
|
|
12
|
+
NODE_URL_HINT,
|
|
12
13
|
} from './shared.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -77,7 +78,9 @@ export function registerSignedDocTools(server) {
|
|
|
77
78
|
'문서, 연구노트, 점검 기록, 회의 확인서. ' +
|
|
78
79
|
'Flow: create (body + approval line) → edit the body freely with rootr_write/rootr_append → ' +
|
|
79
80
|
'rootr_request_signing_code → ask the human for the 6-digit code → rootr_submit_signed_document ' +
|
|
80
|
-
'(freezes + first signature) → each remaining signer does request-code + rootr_sign_document.'
|
|
81
|
+
'(freezes + first signature) → each remaining signer does request-code + rootr_sign_document. ' +
|
|
82
|
+
NODE_URL_HINT +
|
|
83
|
+
' (SIGNED opens on the DOCUMENT screen, not a dedicated one.)',
|
|
81
84
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
82
85
|
inputSchema: {
|
|
83
86
|
name: z.string().optional().describe('문서 이름, 예: "2026-08-25 실험 기록"'),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, NODE_URL_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
const CELL_VALUE = z
|
|
6
6
|
.union([z.string(), z.number(), z.boolean(), z.null()])
|
|
@@ -28,7 +28,9 @@ export function registerSpreadsheetTools(server) {
|
|
|
28
28
|
'Create a new SPREADSHEET node (a grid with formulas) in a Rootr (루터) workspace. Use SPREADSHEET when ' +
|
|
29
29
|
'content needs calculation (budgets, models, estimates) — for plain tracked lists use DATABASE instead. ' +
|
|
30
30
|
'Defaults to a single empty "Sheet1" if no `data` is given. ' +
|
|
31
|
-
CELLS_GUIDE
|
|
31
|
+
CELLS_GUIDE +
|
|
32
|
+
' ' +
|
|
33
|
+
NODE_URL_HINT,
|
|
32
34
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
33
35
|
inputSchema: {
|
|
34
36
|
name: z.string().optional().describe('Spreadsheet name/title'),
|
|
@@ -60,7 +62,8 @@ export function registerSpreadsheetTools(server) {
|
|
|
60
62
|
{
|
|
61
63
|
title: 'Read a Rootr spreadsheet',
|
|
62
64
|
description:
|
|
63
|
-
'Read a Rootr (루터) SPREADSHEET node —
|
|
65
|
+
'Read a Rootr (루터) SPREADSHEET node — returns { data: { sheets: [...] }, ... }, where each sheet has ' +
|
|
66
|
+
'a sparse `cells` map and dimensions (sheets are under `data.sheets`, not top-level `sheets`). Call ' +
|
|
64
67
|
'this before rootr_update_spreadsheet / rootr_patch_spreadsheet_cells so you know the current sheetIds ' +
|
|
65
68
|
'and cell contents.',
|
|
66
69
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, NODE_URL_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
const shapeShape = z
|
|
6
6
|
.object({
|
|
@@ -63,7 +63,9 @@ export function registerWhiteboardTools(server) {
|
|
|
63
63
|
'Create a new WHITEBOARD node (a freeform visual canvas) in a Rootr (루터) workspace, optionally seeded ' +
|
|
64
64
|
'with an initial scene. Use WHITEBOARD for flow diagrams, brainstorming, journey maps — not for anything ' +
|
|
65
65
|
'that needs typed/trackable rows (use DATABASE) or calculation (use SPREADSHEET). ' +
|
|
66
|
-
AUTHORING_GUIDE
|
|
66
|
+
AUTHORING_GUIDE +
|
|
67
|
+
' ' +
|
|
68
|
+
NODE_URL_HINT,
|
|
67
69
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
68
70
|
inputSchema: {
|
|
69
71
|
name: z.string().optional().describe('Whiteboard name/title'),
|
|
@@ -118,12 +118,12 @@ export function registerWorkspaceTools(server) {
|
|
|
118
118
|
}
|
|
119
119
|
const cfg = loadConfig();
|
|
120
120
|
const candidates = candidateClients(cfg);
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
121
|
+
// Probe only matters as a fallback for servers that predate the
|
|
122
|
+
// `accessible` field on GET /workspaces — surveyWorkspaces() reads
|
|
123
|
+
// that field for free when present, so this no longer costs extra
|
|
124
|
+
// API calls in the normal case.
|
|
125
125
|
const probe = !cfg.apiKey && (cfg.keys || []).length > 0;
|
|
126
|
-
const { workspaces, reachable, failures } = await surveyWorkspaces(candidates, { probe });
|
|
126
|
+
const { workspaces, reachable, failures, probed } = await surveyWorkspaces(candidates, { probe });
|
|
127
127
|
if (!workspaces.length) {
|
|
128
128
|
return textResult(
|
|
129
129
|
(failures ? '등록된 키로 워크스페이스 목록을 조회하지 못했습니다 (키 만료/취소 가능성).\n' : '이 컴퓨터에 등록된 Rootr 키가 없습니다.\n') +
|
|
@@ -133,13 +133,14 @@ export function registerWorkspaceTools(server) {
|
|
|
133
133
|
const rows = workspaces.map(
|
|
134
134
|
(w) =>
|
|
135
135
|
`${w.id === cfg.workspace ? '* ' : ' '}${w.id}\t${w.name || w.slug || ''}` +
|
|
136
|
-
(!
|
|
136
|
+
(!probed || reachable.has(w.id) ? '' : '\t[등록된 키로는 접근 불가]'),
|
|
137
137
|
);
|
|
138
|
-
const blocked =
|
|
138
|
+
const blocked = probed && workspaces.some((w) => !reachable.has(w.id));
|
|
139
139
|
return textResult(
|
|
140
140
|
`${rows.join('\n')}\n\n(* = 현재 워크스페이스, rootr_use_workspace 로 전환)` +
|
|
141
141
|
(blocked
|
|
142
142
|
? '\n"접근 불가" = 내 소속 워크스페이스지만 등록된 키가 워크스페이스 전용이라 못 엽니다. ' +
|
|
143
|
+
'쓰기 전에 이 목록에서 "접근 불가"가 없는 워크스페이스만 고르세요. ' +
|
|
143
144
|
'계정 키(PAT)를 등록하면 전부 열립니다: rootr.io > 설정 > API 키 → `rootr config --api-key <키>`'
|
|
144
145
|
: ''),
|
|
145
146
|
);
|