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.
@@ -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('Parent node id to nest the LOG store under'),
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('LOG store node id'),
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('LOG store node id'),
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('LOG store node id'),
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('LOG store node id'),
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) {
@@ -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('Parent folder/node id to create the deck under'),
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('PRESENTATION node id'),
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('PRESENTATION node id'),
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('PRESENTATION node id'),
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('PRESENTATION node id'),
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('PRESENTATION node id'),
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('Presentation/node id to attach the image to (permission-checked)'),
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('Presentation/node id to attach the result to (permission-checked)'),
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) {