@thinger-io/cli-core 0.1.0 → 0.2.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.
@@ -1,635 +0,0 @@
1
- // @ts-check
2
- import {
3
- PROFILE_API_TARGETS,
4
- PROFILE_DATA_SOURCES,
5
- PROFILE_PAYLOAD_TYPES,
6
- deleteProductApiResource,
7
- deleteProductBucket,
8
- deleteProductProfileProperty,
9
- getProductApiResource,
10
- getProductApiResources,
11
- getProductBucket,
12
- getProductBuckets,
13
- getProductProfileProperties,
14
- getProductProfileProperty,
15
- setProfileApiResource,
16
- setProfileBucket,
17
- setProfileProperty,
18
- } from '../profile.js';
19
- import { inputError } from '../errors.js';
20
-
21
- // ─── API resources ───────────────────────────────────────────────────
22
-
23
- async function toolProfileApiList(args) {
24
- if (!args.product) throw inputError('product is required');
25
- const map = await getProductApiResources(args.product, args.user);
26
- const names = Object.keys(map);
27
- if (names.length === 0) {
28
- return {
29
- content: [
30
- {
31
- type: 'text',
32
- text: `No profile API resources configured on product "${args.product}".`,
33
- },
34
- ],
35
- isError: false,
36
- };
37
- }
38
- const lines = names.map((n) => {
39
- const def = map[n] || {};
40
- const target = def?.request?.data?.target || '?';
41
- const enabled = def.enabled === false ? '✗' : '✓';
42
- return ` ${enabled} ${n} → target=${target}`;
43
- });
44
- return {
45
- content: [
46
- {
47
- type: 'text',
48
- text: `${names.length} profile API resource(s) on "${args.product}":\n${lines.join('\n')}`,
49
- },
50
- ],
51
- isError: false,
52
- };
53
- }
54
-
55
- async function toolProfileApiGet(args) {
56
- if (!args.product) throw inputError('product is required');
57
- if (!args.name) throw inputError('name is required');
58
- const def = await getProductApiResource(args.product, args.name, args.user);
59
- return {
60
- content: [{ type: 'text', text: JSON.stringify(def, null, 2) }],
61
- isError: false,
62
- };
63
- }
64
-
65
- async function toolProfileApiSet(args) {
66
- if (!args.product) throw inputError('product is required');
67
- if (!args.name) throw inputError('name is required');
68
- if (!args.target) throw inputError('target is required');
69
-
70
- const payload = await setProfileApiResource(args.product, args.name, args.user, {
71
- target: args.target,
72
- target_args: args.target_args,
73
- payload_type: args.payload_type,
74
- payload: args.payload,
75
- payload_function: args.payload_function,
76
- response: args.response,
77
- enabled: args.enabled,
78
- });
79
-
80
- return {
81
- content: [
82
- {
83
- type: 'text',
84
- text: `Upserted profile API resource "${args.name}" on product "${args.product}" (target=${args.target}).\n${JSON.stringify(payload, null, 2)}`,
85
- },
86
- ],
87
- isError: false,
88
- };
89
- }
90
-
91
- async function toolProfileApiDelete(args) {
92
- if (!args.product) throw inputError('product is required');
93
- if (!args.name) throw inputError('name is required');
94
- const removed = await deleteProductApiResource(args.product, args.name, args.user);
95
- return {
96
- content: [
97
- {
98
- type: 'text',
99
- text: removed
100
- ? `Deleted profile API resource "${args.name}" from product "${args.product}".`
101
- : `Profile API resource "${args.name}" was not configured on product "${args.product}".`,
102
- },
103
- ],
104
- isError: false,
105
- };
106
- }
107
-
108
- // ─── Buckets ─────────────────────────────────────────────────────────
109
-
110
- async function toolProfileBucketList(args) {
111
- if (!args.product) throw inputError('product is required');
112
- const map = await getProductBuckets(args.product, args.user);
113
- const names = Object.keys(map);
114
- if (names.length === 0) {
115
- return {
116
- content: [
117
- {
118
- type: 'text',
119
- text: `No profile buckets configured on product "${args.product}".`,
120
- },
121
- ],
122
- isError: false,
123
- };
124
- }
125
- const lines = names.map((n) => {
126
- const def = map[n] || {};
127
- const source = def?.data?.source || '?';
128
- const enabled = def.enabled === false ? '✗' : '✓';
129
- return ` ${enabled} ${n} → source=${source}`;
130
- });
131
- return {
132
- content: [
133
- {
134
- type: 'text',
135
- text: `${names.length} profile bucket(s) on "${args.product}":\n${lines.join('\n')}`,
136
- },
137
- ],
138
- isError: false,
139
- };
140
- }
141
-
142
- async function toolProfileBucketGet(args) {
143
- if (!args.product) throw inputError('product is required');
144
- if (!args.name) throw inputError('name is required');
145
- const def = await getProductBucket(args.product, args.name, args.user);
146
- return {
147
- content: [{ type: 'text', text: JSON.stringify(def, null, 2) }],
148
- isError: false,
149
- };
150
- }
151
-
152
- async function toolProfileBucketSet(args) {
153
- if (!args.product) throw inputError('product is required');
154
- if (!args.name) throw inputError('name is required');
155
- if (!args.source) throw inputError('source is required');
156
-
157
- const payload = await setProfileBucket(args.product, args.name, args.user, {
158
- source: args.source,
159
- source_args: args.source_args,
160
- payload_type: args.payload_type,
161
- payload: args.payload,
162
- payload_function: args.payload_function,
163
- name_label: args.name_label,
164
- description: args.description,
165
- enabled: args.enabled,
166
- backend: args.backend,
167
- retention: args.retention,
168
- tags: args.tags,
169
- });
170
-
171
- return {
172
- content: [
173
- {
174
- type: 'text',
175
- text: `Upserted profile bucket "${args.name}" on product "${args.product}" (source=${args.source}).\n${JSON.stringify(payload, null, 2)}`,
176
- },
177
- ],
178
- isError: false,
179
- };
180
- }
181
-
182
- async function toolProfileBucketDelete(args) {
183
- if (!args.product) throw inputError('product is required');
184
- if (!args.name) throw inputError('name is required');
185
- const removed = await deleteProductBucket(args.product, args.name, args.user);
186
- return {
187
- content: [
188
- {
189
- type: 'text',
190
- text: removed
191
- ? `Deleted profile bucket "${args.name}" from product "${args.product}".`
192
- : `Profile bucket "${args.name}" was not configured on product "${args.product}".`,
193
- },
194
- ],
195
- isError: false,
196
- };
197
- }
198
-
199
- // ─── Properties ──────────────────────────────────────────────────────
200
-
201
- async function toolProfilePropertyList(args) {
202
- if (!args.product) throw inputError('product is required');
203
- const map = await getProductProfileProperties(args.product, args.user);
204
- const names = Object.keys(map);
205
- if (names.length === 0) {
206
- return {
207
- content: [
208
- {
209
- type: 'text',
210
- text: `No profile property handlers configured on product "${args.product}".`,
211
- },
212
- ],
213
- isError: false,
214
- };
215
- }
216
- const lines = names.map((n) => {
217
- const def = map[n] || {};
218
- const source = def?.data?.source || '?';
219
- const enabled = def.enabled === false ? '✗' : '✓';
220
- const patch = def?.data?.patch ? ' patch' : '';
221
- return ` ${enabled} ${n} → source=${source}${patch}`;
222
- });
223
- return {
224
- content: [
225
- {
226
- type: 'text',
227
- text: `${names.length} profile property handler(s) on "${args.product}":\n${lines.join('\n')}`,
228
- },
229
- ],
230
- isError: false,
231
- };
232
- }
233
-
234
- async function toolProfilePropertyGet(args) {
235
- if (!args.product) throw inputError('product is required');
236
- if (!args.name) throw inputError('name is required');
237
- const def = await getProductProfileProperty(args.product, args.name, args.user);
238
- return {
239
- content: [{ type: 'text', text: JSON.stringify(def, null, 2) }],
240
- isError: false,
241
- };
242
- }
243
-
244
- async function toolProfilePropertySet(args) {
245
- if (!args.product) throw inputError('product is required');
246
- if (!args.name) throw inputError('name is required');
247
- if (!args.source) throw inputError('source is required');
248
-
249
- const payload = await setProfileProperty(args.product, args.name, args.user, {
250
- source: args.source,
251
- source_args: args.source_args,
252
- payload_type: args.payload_type,
253
- payload: args.payload,
254
- payload_function: args.payload_function,
255
- name_label: args.name_label,
256
- description: args.description,
257
- enabled: args.enabled,
258
- patch: args.patch,
259
- });
260
-
261
- return {
262
- content: [
263
- {
264
- type: 'text',
265
- text: `Upserted profile property handler "${args.name}" on product "${args.product}" (source=${args.source}${args.patch ? ', patch' : ''}).\n${JSON.stringify(payload, null, 2)}`,
266
- },
267
- ],
268
- isError: false,
269
- };
270
- }
271
-
272
- async function toolProfilePropertyDelete(args) {
273
- if (!args.product) throw inputError('product is required');
274
- if (!args.name) throw inputError('name is required');
275
- const removed = await deleteProductProfileProperty(args.product, args.name, args.user);
276
- return {
277
- content: [
278
- {
279
- type: 'text',
280
- text: removed
281
- ? `Deleted profile property handler "${args.name}" from product "${args.product}".`
282
- : `Profile property handler "${args.name}" was not configured on product "${args.product}".`,
283
- },
284
- ],
285
- isError: false,
286
- };
287
- }
288
-
289
- // ─── Tool registration ───────────────────────────────────────────────
290
-
291
- const SOURCE_ARGS_DESCRIPTION = `Required keys depend on \`source\`:
292
- - "resource": { resource: "<name>", update?: "interval"|"event", interval?: <number>, magnitude?: "second"|"minute"|"hour" }
293
- - "resource_stream": { resource_stream: "<name>" }
294
- - "product_stream": { product_stream: "<name>", product?: "<other-product-id>" }
295
- - "topic": { topic: "<mqtt-topic>" }
296
- - "event": { event: "<event-name>", filter?: { ...filters... } }`;
297
-
298
- const TARGET_ARGS_DESCRIPTION = `Required keys depend on \`target\`:
299
- - "resource": { resource: "<device-resource-name>" }
300
- - "resource_stream": { resource_stream: "<stream-name>" } (defaults to the API resource name when omitted — pass null target_args for that)
301
- - "product_stream": { product: "<product-id>", product_stream: "<stream-name>" } (BOTH required)
302
- - "property": { property: "<device-property-name>" }
303
- - "function": { function: "<product-script-function-name>" }
304
- - "endpoint": { endpoint: "<endpoint-name>" }
305
- - "topic": { topic: "<mqtt-topic>" }`;
306
-
307
- const BACKUP_USE_CASE = `Canonical use case: fan out a "backup_complete" stream resource → "backups" bucket (history) and "backups" device property (last-known). Three calls — one to declare the stream resource, one for the bucket, one for the property — and the platform handles the wiring on every device of the product automatically.`;
308
-
309
- export const tools = [
310
- // ── API resources ──
311
- {
312
- name: 'product_profile_api_list',
313
- description: `List the API resources declared in a product's \`profile.api\`. These are platform-managed REST endpoints exposed for every device of the product, dispatched server-side to a target (device resource, stream, property, function, endpoint, …) without the device having to implement them.`,
314
- inputSchema: {
315
- type: 'object',
316
- properties: {
317
- product: { type: 'string', description: 'Product ID.' },
318
- user: { type: 'string', description: 'API user.' },
319
- },
320
- required: ['product'],
321
- },
322
- handler: toolProfileApiList,
323
- },
324
- {
325
- name: 'product_profile_api_get',
326
- description: `Read the full JSON definition of a single profile API resource (request/response/payload blocks). Use this before \`{tool:product_profile_api_set}\` when you want to tweak only one field of an existing resource — read, modify, write back.`,
327
- inputSchema: {
328
- type: 'object',
329
- properties: {
330
- product: { type: 'string', description: 'Product ID.' },
331
- name: { type: 'string', description: 'API resource name.' },
332
- user: { type: 'string', description: 'API user.' },
333
- },
334
- required: ['product', 'name'],
335
- },
336
- handler: toolProfileApiGet,
337
- },
338
- {
339
- name: 'product_profile_api_set',
340
- description: `Upsert a profile API resource: creates it if missing, replaces it in place otherwise. Pick a \`target\` (where the request is dispatched) and a \`payload_type\` (how the body is rendered for the target). Validation is client-side: missing target_args.<key> for the chosen target raises a clean error before contacting the server.
341
-
342
- ${BACKUP_USE_CASE}
343
-
344
- Example: declare a stream-style fire-and-forget resource named "backup_complete" that simply forwards the payload onto the device's own \`backup_complete\` resource_stream:
345
- { product: "myprod", name: "backup_complete", target: "resource_stream", payload_type: "source_payload", response: { payload_type: "none" } }
346
-
347
- Gotchas:
348
- - "resource_stream" target defaults the destination stream name to the API resource name itself. Override only when they should differ.
349
- - "product_stream" target needs BOTH \`target_args.product\` AND \`target_args.product_stream\` — the destination is identified by (product id, stream name).
350
- - payload_type "template_payload" (default) renders a Mustache-style template using \`{{payload}}\` etc.; "source_payload" forwards the incoming body verbatim; "source_event" sends the full event envelope; "none" sends an empty body.
351
- - response defaults to { payload_type: "none" } when omitted, which is the right shape for fire-and-forget stream-style resources. Set \`response: { payload_type: "source_payload" }\` (or similar) when the caller needs to receive a body back.`,
352
- inputSchema: {
353
- type: 'object',
354
- properties: {
355
- product: { type: 'string', description: 'Product ID.' },
356
- name: {
357
- type: 'string',
358
- description: 'API resource name (becomes profile.api.<name>).',
359
- },
360
- target: {
361
- type: 'string',
362
- enum: PROFILE_API_TARGETS,
363
- description: 'Where the platform dispatches the request.',
364
- },
365
- target_args: {
366
- type: 'object',
367
- description: TARGET_ARGS_DESCRIPTION,
368
- additionalProperties: true,
369
- },
370
- payload_type: {
371
- type: 'string',
372
- enum: PROFILE_PAYLOAD_TYPES,
373
- description:
374
- 'How the request body is rendered. Default: "template_payload" (server fallback).',
375
- },
376
- payload: {
377
- type: 'string',
378
- description:
379
- 'Mustache-style template body (only meaningful for payload_type="template_payload"; ignored otherwise).',
380
- },
381
- payload_function: {
382
- type: 'string',
383
- description:
384
- 'Optional product-code function name applied to the payload before dispatch.',
385
- },
386
- response: {
387
- type: 'object',
388
- description:
389
- 'Optional response shape. Defaults to { payload_type: "none" } (fire-and-forget). Pass { payload_type, source?, payload?, payload_function? } to send something back.',
390
- properties: {
391
- payload_type: { type: 'string', enum: PROFILE_PAYLOAD_TYPES },
392
- source: {
393
- type: 'string',
394
- description:
395
- 'Where the response payload is sourced from (e.g. "request_response" to reuse the upstream body).',
396
- },
397
- payload: { type: 'string' },
398
- payload_function: { type: 'string' },
399
- },
400
- additionalProperties: false,
401
- },
402
- enabled: {
403
- type: 'boolean',
404
- description:
405
- 'Default: true. Set to false to stage a definition without activating it.',
406
- },
407
- user: { type: 'string', description: 'API user.' },
408
- },
409
- required: ['product', 'name', 'target'],
410
- },
411
- handler: toolProfileApiSet,
412
- },
413
- {
414
- name: 'product_profile_api_delete',
415
- description: `Remove a profile API resource. Idempotent — reports cleanly when the resource was already absent.`,
416
- inputSchema: {
417
- type: 'object',
418
- properties: {
419
- product: { type: 'string', description: 'Product ID.' },
420
- name: { type: 'string', description: 'API resource name.' },
421
- user: { type: 'string', description: 'API user.' },
422
- },
423
- required: ['product', 'name'],
424
- },
425
- handler: toolProfileApiDelete,
426
- },
427
-
428
- // ── Buckets ──
429
- {
430
- name: 'product_profile_bucket_list',
431
- description: `List buckets declared in a product's \`profile.buckets\`. These are auto-created/maintained time-series buckets fed from a declared source (resource poll, resource_stream, product_stream, topic, or event).`,
432
- inputSchema: {
433
- type: 'object',
434
- properties: {
435
- product: { type: 'string', description: 'Product ID.' },
436
- user: { type: 'string', description: 'API user.' },
437
- },
438
- required: ['product'],
439
- },
440
- handler: toolProfileBucketList,
441
- },
442
- {
443
- name: 'product_profile_bucket_get',
444
- description: `Read the full JSON definition of a single profile bucket (source/payload/retention/backend).`,
445
- inputSchema: {
446
- type: 'object',
447
- properties: {
448
- product: { type: 'string', description: 'Product ID.' },
449
- name: { type: 'string', description: 'Bucket name.' },
450
- user: { type: 'string', description: 'API user.' },
451
- },
452
- required: ['product', 'name'],
453
- },
454
- handler: toolProfileBucketGet,
455
- },
456
- {
457
- name: 'product_profile_bucket_set',
458
- description: `Upsert a profile bucket: creates it if missing, replaces it in place otherwise. The underlying time-series bucket is created lazily by the server on the first event delivery — no separate provisioning step. Picks a \`source\` (where samples come from), \`source_args\` (the matching key), and an optional \`payload\` template / \`payload_function\` to shape the row.
459
-
460
- ${BACKUP_USE_CASE}
461
-
462
- Example: feed every "backup_complete" stream payload into a bucket called "backups" with one-year retention.
463
- { product: "myprod", name: "backups", source: "resource_stream", source_args: { resource_stream: "backup_complete" }, payload_type: "source_payload", payload: "{{payload}}", retention: { period: 1, unit: "years" } }
464
-
465
- Gotchas:
466
- - backend defaults to "mongodb". Use "influxdb" only on accounts where it's enabled.
467
- - If the bucket name (or template) does NOT contain "{{device}}", the server tags samples with a "device" and "group" tag automatically (group_bucket_ in product_bucket_handler.cpp).
468
- - retention.unit accepts: minutes, hours, days, months, years.`,
469
- inputSchema: {
470
- type: 'object',
471
- properties: {
472
- product: { type: 'string', description: 'Product ID.' },
473
- name: { type: 'string', description: 'Bucket name.' },
474
- source: {
475
- type: 'string',
476
- enum: PROFILE_DATA_SOURCES,
477
- description: 'Where samples come from.',
478
- },
479
- source_args: {
480
- type: 'object',
481
- description: SOURCE_ARGS_DESCRIPTION,
482
- additionalProperties: true,
483
- },
484
- payload_type: {
485
- type: 'string',
486
- enum: PROFILE_PAYLOAD_TYPES,
487
- description: 'How the row payload is rendered.',
488
- },
489
- payload: {
490
- type: 'string',
491
- description: 'Optional template body (e.g. "{{payload}}" to forward verbatim).',
492
- },
493
- payload_function: { type: 'string', description: 'Optional payload function.' },
494
- name_label: {
495
- type: 'string',
496
- description:
497
- 'Display name shown in the platform UI. Defaults to the bucket name.',
498
- },
499
- description: { type: 'string', description: 'Free-form description.' },
500
- enabled: { type: 'boolean', description: 'Default: true.' },
501
- backend: {
502
- type: 'string',
503
- description: 'Storage backend. Default: "mongodb".',
504
- },
505
- retention: {
506
- type: 'object',
507
- description:
508
- 'Retention policy: { period: number, unit: "minutes"|"hours"|"days"|"months"|"years" }.',
509
- properties: {
510
- period: { type: 'number' },
511
- unit: {
512
- type: 'string',
513
- enum: ['minutes', 'hours', 'days', 'months', 'years'],
514
- },
515
- },
516
- required: ['period', 'unit'],
517
- },
518
- tags: {
519
- type: 'array',
520
- items: { type: 'string' },
521
- description: 'Extra tags propagated to every row.',
522
- },
523
- user: { type: 'string', description: 'API user.' },
524
- },
525
- required: ['product', 'name', 'source'],
526
- },
527
- handler: toolProfileBucketSet,
528
- },
529
- {
530
- name: 'product_profile_bucket_delete',
531
- description: `Remove a profile bucket *handler*. Note: this drops the listener configuration only; the underlying time-series bucket is not deleted (preserve historical data unless explicitly removed via the buckets API).`,
532
- inputSchema: {
533
- type: 'object',
534
- properties: {
535
- product: { type: 'string', description: 'Product ID.' },
536
- name: { type: 'string', description: 'Bucket name.' },
537
- user: { type: 'string', description: 'API user.' },
538
- },
539
- required: ['product', 'name'],
540
- },
541
- handler: toolProfileBucketDelete,
542
- },
543
-
544
- // ── Properties ──
545
- {
546
- name: 'product_profile_property_list',
547
- description: `List property *handlers* declared in a product's \`profile.properties\`. NOT to be confused with product-level properties (\`{family:product_property}\`) — those are JSON values stored on the product itself; these handlers fan listener output into per-device properties.`,
548
- inputSchema: {
549
- type: 'object',
550
- properties: {
551
- product: { type: 'string', description: 'Product ID.' },
552
- user: { type: 'string', description: 'API user.' },
553
- },
554
- required: ['product'],
555
- },
556
- handler: toolProfilePropertyList,
557
- },
558
- {
559
- name: 'product_profile_property_get',
560
- description: `Read the full JSON definition of a single profile property handler.`,
561
- inputSchema: {
562
- type: 'object',
563
- properties: {
564
- product: { type: 'string', description: 'Product ID.' },
565
- name: { type: 'string', description: 'Property handler name.' },
566
- user: { type: 'string', description: 'API user.' },
567
- },
568
- required: ['product', 'name'],
569
- },
570
- handler: toolProfilePropertyGet,
571
- },
572
- {
573
- name: 'product_profile_property_set',
574
- description: `Upsert a profile property *handler* — server-side wiring that fans listener output into per-device properties named \`<name>\`. Same source/payload grammar as buckets, plus an optional \`patch\` flag.
575
-
576
- ${BACKUP_USE_CASE}
577
-
578
- Example: keep the latest "backup_complete" payload as the device's "backups" property.
579
- { product: "myprod", name: "backups", source: "resource_stream", source_args: { resource_stream: "backup_complete" }, payload_type: "source_payload", payload: "{{payload}}" }
580
-
581
- Gotchas:
582
- - This DOES NOT manage \`/products/{p}/properties/{name}\` (product-level JSON values) — that's the \`{family:product_property}\` family.
583
- - \`patch: true\` flips the handler from "replace" mode to "JSON-merge" mode (the value is merge-patched onto the existing property instead of overwriting it). Useful for listeners that publish only a delta.
584
- - A property handler with source "event" and the same property as event filter is short-circuited server-side to avoid infinite loops.`,
585
- inputSchema: {
586
- type: 'object',
587
- properties: {
588
- product: { type: 'string', description: 'Product ID.' },
589
- name: { type: 'string', description: 'Property handler name.' },
590
- source: {
591
- type: 'string',
592
- enum: PROFILE_DATA_SOURCES,
593
- description: 'Where samples come from.',
594
- },
595
- source_args: {
596
- type: 'object',
597
- description: SOURCE_ARGS_DESCRIPTION,
598
- additionalProperties: true,
599
- },
600
- payload_type: { type: 'string', enum: PROFILE_PAYLOAD_TYPES },
601
- payload: { type: 'string' },
602
- payload_function: { type: 'string' },
603
- name_label: {
604
- type: 'string',
605
- description:
606
- 'Display name shown in the platform UI. Defaults to the handler name.',
607
- },
608
- description: { type: 'string' },
609
- enabled: { type: 'boolean', description: 'Default: true.' },
610
- patch: {
611
- type: 'boolean',
612
- description:
613
- 'When true, apply the value as a JSON merge-patch on top of the existing device property instead of replacing it (default: false).',
614
- },
615
- user: { type: 'string', description: 'API user.' },
616
- },
617
- required: ['product', 'name', 'source'],
618
- },
619
- handler: toolProfilePropertySet,
620
- },
621
- {
622
- name: 'product_profile_property_delete',
623
- description: `Remove a profile property handler. Idempotent.`,
624
- inputSchema: {
625
- type: 'object',
626
- properties: {
627
- product: { type: 'string', description: 'Product ID.' },
628
- name: { type: 'string', description: 'Property handler name.' },
629
- user: { type: 'string', description: 'API user.' },
630
- },
631
- required: ['product', 'name'],
632
- },
633
- handler: toolProfilePropertyDelete,
634
- },
635
- ];
@@ -1,30 +0,0 @@
1
- // @ts-check
2
- import { listProfiles, getActiveProfile, getProfile } from '../config.js';
3
-
4
- async function toolProfiles() {
5
- const names = listProfiles();
6
- const active = getActiveProfile();
7
- const lines = names.map((n) => {
8
- const data = getProfile(n) || {};
9
- const marker = n === active ? '* ' : ' ';
10
- return `${marker}${n} (${data.username || ''}@${data.server || n})`;
11
- });
12
- return {
13
- content: [{ type: 'text', text: lines.join('\n') || 'No profiles configured' }],
14
- isError: false,
15
- };
16
- }
17
-
18
- export const tools = [
19
- {
20
- name: 'profiles',
21
- description:
22
- 'List configured CLI profiles (each profile targets a server, with its own credentials). Use the returned profile name as the optional `profile` parameter on any other tool to target that environment for a single call.\n\nProfiles are not accounts. Every tool also takes an optional `user`, which addresses another account on the same server when the profile has admin rights — so a device belonging to `demo` is reached with `user: "demo"`, not by looking for a profile of that name.',
23
- inputSchema: {
24
- type: 'object',
25
- properties: {},
26
- required: [],
27
- },
28
- handler: toolProfiles,
29
- },
30
- ];