@thinger-io/cli-core 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/CHANGELOG.md +87 -0
- package/README.md +31 -6
- package/docs/thinger-concepts.md +389 -0
- package/docs/thinger-product-code.md +159 -0
- package/docs/thinger-server-admin.md +165 -0
- package/lib/cli.js +13 -3
- package/lib/commands/auth.js +20 -1
- package/lib/commands/dashboard.js +262 -0
- package/lib/config.js +42 -0
- package/lib/dashboard-dev.js +180 -0
- package/lib/dashboard-init.js +417 -0
- package/lib/dashboard-sdk.js +184 -0
- package/lib/dashboard.js +171 -28
- package/lib/docs.js +110 -110
- package/lib/identity.js +2 -0
- package/lib/mcp/catalog.js +12 -2
- package/lib/mcp/registry.js +8 -6
- package/lib/mcp/server.js +11 -2
- package/lib/mcp/tools-dashboards.js +169 -30
- package/lib/mcp/tools-misc.js +1 -1
- package/lib/mcp/tools-resource.js +387 -50
- package/lib/tgz.js +66 -0
- package/package.json +3 -1
|
@@ -1,6 +1,74 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DASHBOARD_TARGETS,
|
|
4
|
+
checkDestination,
|
|
5
|
+
collectFiles,
|
|
6
|
+
destinationContext,
|
|
7
|
+
manifestName,
|
|
8
|
+
publishApp,
|
|
9
|
+
publishSummary,
|
|
10
|
+
} from '../dashboard.js';
|
|
11
|
+
import { devLink } from '../dashboard-dev.js';
|
|
12
|
+
import { DEFAULT_TEMPLATE, initApp } from '../dashboard-init.js';
|
|
3
13
|
import { inputError } from '../errors.js';
|
|
14
|
+
import { toolName } from './naming.js';
|
|
15
|
+
|
|
16
|
+
async function toolInit(args) {
|
|
17
|
+
if (args.to) checkDestination(args.to);
|
|
18
|
+
const r = await initApp({
|
|
19
|
+
dir: args.dir,
|
|
20
|
+
template: args.template,
|
|
21
|
+
to: args.to,
|
|
22
|
+
storage: args.storage,
|
|
23
|
+
from: args.from,
|
|
24
|
+
ci: args.ci,
|
|
25
|
+
install: args.install,
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: 'text',
|
|
31
|
+
text: [
|
|
32
|
+
`Created ${r.dir}: ${r.template} app on @thinger-io/dashboard-sdk ${r.dependency}, with ${manifestName()} (publishes ${r.build} to storage ${r.storage}${args.to ? '' : '; add destinations under dashboard.to'}) and AGENTS.md (how to develop it and the rules of the console's frame: read it).`,
|
|
33
|
+
`Next: ${r.installed ? '' : 'npm install, then '}${r.devCommand} in the background (it serves ${r.devUrl}), ${toolName('dashboard_dev')} with url ${r.devUrl}${Object.keys(r.context).length ? '' : ' and a context'} and dir ${r.dir}, open the link with browser tools; when it works, npm run build and ${toolName('dashboard_publish')} with dir ${r.dir}/${r.build}.`,
|
|
34
|
+
].join('\n'),
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
isError: false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function toolDev(args) {
|
|
42
|
+
if (!args.url) throw inputError("pass url: the app's dev server, e.g. http://localhost:4200/");
|
|
43
|
+
if (args.to && args.context) throw inputError('pass to or context, not both');
|
|
44
|
+
if (args.to) checkDestination(args.to);
|
|
45
|
+
const dev = await devLink({
|
|
46
|
+
url: args.url,
|
|
47
|
+
context: args.to ? destinationContext(args.to) : args.context,
|
|
48
|
+
route: args.route ?? args.to?.route,
|
|
49
|
+
hours: args.hours,
|
|
50
|
+
user: args.user,
|
|
51
|
+
dir: args.dir || process.cwd(),
|
|
52
|
+
allowRemote: args.allow_remote === true,
|
|
53
|
+
});
|
|
54
|
+
const ctx = Object.entries(dev.context).map(([k, v]) => `${k} ${v}`);
|
|
55
|
+
const until = new Date(dev.expire).toISOString().slice(0, 16).replace('T', ' ');
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: 'text',
|
|
60
|
+
text: [
|
|
61
|
+
`Open this link in a browser: ${dev.link}`,
|
|
62
|
+
`It opens ${dev.app} as a standalone page against ${dev.server} as ${dev.user}${ctx.length ? `, context ${ctx.join(', ')}` : ''}, with a temporary token (${dev.token}, full access, until ${until} UTC; a new link replaces it). Calls reach the real server: writes are real.`,
|
|
63
|
+
...dev.notes.map((n) => `Check: ${n}`),
|
|
64
|
+
"The app's dev server reloads it on every change; its errors are in the browser console. Unlike in the console's sandboxed iframe, localStorage, cookies and a <base> tag work here, so do not rely on them.",
|
|
65
|
+
`When it works, build it and publish the build once with ${toolName('dashboard_publish')}.`,
|
|
66
|
+
].join('\n'),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
isError: false,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
4
72
|
|
|
5
73
|
async function toolPublish(args) {
|
|
6
74
|
if (args.dir && args.files) throw inputError('pass dir or files, not both');
|
|
@@ -21,45 +89,116 @@ async function toolPublish(args) {
|
|
|
21
89
|
point: args.point,
|
|
22
90
|
bump: args.bump,
|
|
23
91
|
user: args.user,
|
|
92
|
+
dir: args.dir,
|
|
24
93
|
});
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
(result.deleted.length
|
|
30
|
-
? `; deleted ${result.deleted.length} no longer in the app.`
|
|
31
|
-
: '.'),
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
for (const p of result.pointed) lines.push(`${p.to} → ${p.url}\n open: ${p.open}`);
|
|
35
|
-
if (!result.pointed.length && args.point !== false) {
|
|
36
|
-
lines.push(
|
|
37
|
-
'No dashboard was pointed: those already showing this storage load the new files the next time they open.',
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
if (result.previews.length) {
|
|
41
|
-
const until = new Date(result.previews[0].expire)
|
|
42
|
-
.toISOString()
|
|
43
|
-
.slice(0, 16)
|
|
44
|
-
.replace('T', ' ');
|
|
45
|
-
lines.push(
|
|
46
|
-
`Preview links (standalone pages for a browser, read-only token valid until ${until} UTC; servers that ignore token expiry keep it until the next preview replaces it; the previous preview links of this storage stop working):`,
|
|
47
|
-
);
|
|
48
|
-
for (const p of result.previews) {
|
|
49
|
-
lines.push(`${p.to} → ${p.url}\n layout check (website and phone sizes): ${p.layout}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return { content: [{ type: 'text', text: lines.join('\n') }], isError: false };
|
|
94
|
+
return {
|
|
95
|
+
content: [{ type: 'text', text: publishSummary(result, { point: args.point }) }],
|
|
96
|
+
isError: false,
|
|
97
|
+
};
|
|
53
98
|
}
|
|
54
99
|
|
|
55
100
|
export const tools = [
|
|
101
|
+
{
|
|
102
|
+
name: 'dashboard_init',
|
|
103
|
+
description: `Create a dashboard app: a TypeScript project on @thinger-io/dashboard-sdk from one of the templates the SDK package ships (react by default, angular…), with its manifest for {tool:dashboard_dev} and {tool:dashboard_publish} and an AGENTS.md with the rules of the console's frame. Start every new dashboard app here instead of writing one from scratch.`,
|
|
104
|
+
inputSchema: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
dir: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
description:
|
|
110
|
+
'New (or empty) folder for the app. Absolute, or relative to the folder this MCP server runs in.',
|
|
111
|
+
},
|
|
112
|
+
template: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
description: `Template of the SDK package (default ${DEFAULT_TEMPLATE}).`,
|
|
115
|
+
},
|
|
116
|
+
to: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
description:
|
|
119
|
+
'Where it publishes, as in dashboard_publish: {type, id}, or {dashboard}. Its context is the one dashboard_dev uses.',
|
|
120
|
+
properties: {
|
|
121
|
+
dashboard: { type: 'string' },
|
|
122
|
+
type: { type: 'string', enum: DASHBOARD_TARGETS },
|
|
123
|
+
id: { type: 'string' },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
storage: {
|
|
127
|
+
type: 'string',
|
|
128
|
+
description: 'Storage it publishes to (default app_<folder>_<random>).',
|
|
129
|
+
},
|
|
130
|
+
from: {
|
|
131
|
+
type: 'string',
|
|
132
|
+
description:
|
|
133
|
+
'SDK package to take the template from: a version or tag on npm (default the latest release), or a local folder or .tgz of it.',
|
|
134
|
+
},
|
|
135
|
+
ci: {
|
|
136
|
+
type: 'boolean',
|
|
137
|
+
description: 'Add a GitHub workflow that publishes it on push to main.',
|
|
138
|
+
},
|
|
139
|
+
install: { type: 'boolean', description: 'Run npm install.' },
|
|
140
|
+
},
|
|
141
|
+
required: ['dir'],
|
|
142
|
+
},
|
|
143
|
+
handler: toolInit,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'dashboard_dev',
|
|
147
|
+
description: `Develop a dashboard app on its own dev server (ng serve, npm run dev…) against this server, then publish it once with {tool:dashboard_publish}, instead of publishing to try every change.
|
|
148
|
+
|
|
149
|
+
Returns a link to the app's local url that opens it as a standalone page: the SDK reads the server, a temporary token and the context from the link and calls the server directly, so the app gets real data, its dev server reloads it on every change and its errors are in the browser console. Start the app's dev server first (the answer says when nothing answers at url). Open the link with the browser tools to see the app and read its console. Each call replaces the previous link's token.`,
|
|
150
|
+
inputSchema: {
|
|
151
|
+
type: 'object',
|
|
152
|
+
properties: {
|
|
153
|
+
url: {
|
|
154
|
+
type: 'string',
|
|
155
|
+
description:
|
|
156
|
+
'The app on its dev server, e.g. http://localhost:4200/ (a #route in it is kept).',
|
|
157
|
+
},
|
|
158
|
+
to: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
description:
|
|
161
|
+
'Destination whose context the app gets, as in dashboard_publish: {type, id}, or {dashboard} for a collection dashboard (no context).',
|
|
162
|
+
properties: {
|
|
163
|
+
dashboard: { type: 'string' },
|
|
164
|
+
type: { type: 'string', enum: DASHBOARD_TARGETS },
|
|
165
|
+
id: { type: 'string' },
|
|
166
|
+
route: { type: 'string' },
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
context: {
|
|
170
|
+
type: 'object',
|
|
171
|
+
description:
|
|
172
|
+
'Or the SDK context itself: device, product, type, group, project ids.',
|
|
173
|
+
},
|
|
174
|
+
route: { type: 'string', description: 'Initial route, e.g. "/devices".' },
|
|
175
|
+
dir: {
|
|
176
|
+
type: 'string',
|
|
177
|
+
description:
|
|
178
|
+
"The app's project folder, to check its SDK against this server (default: the folder this MCP server runs in).",
|
|
179
|
+
},
|
|
180
|
+
hours: {
|
|
181
|
+
type: 'number',
|
|
182
|
+
description: 'Hours the temporary token lasts (default 8, up to 168).',
|
|
183
|
+
},
|
|
184
|
+
allow_remote: {
|
|
185
|
+
type: 'boolean',
|
|
186
|
+
description:
|
|
187
|
+
'Allow a url that is not on this machine. The link carries a full-access token: only for a dev server the user named and trusts.',
|
|
188
|
+
},
|
|
189
|
+
user: { type: 'string', description: 'Account to act on (admin only).' },
|
|
190
|
+
},
|
|
191
|
+
required: ['url'],
|
|
192
|
+
},
|
|
193
|
+
handler: toolDev,
|
|
194
|
+
},
|
|
56
195
|
{
|
|
57
196
|
name: 'dashboard_publish',
|
|
58
197
|
description: `Publish an HTML app to a public storage and point dashboards at it. Upload (dir or files) and point (to) are both optional: upload a new build, point more dashboards at an app already uploaded, or both. Read the dashboard SDK reference with {tool:resource_describe} type "dashboard" first.
|
|
59
198
|
|
|
60
199
|
Destinations (to): {dashboard} for the dashboards collection (created if missing, empty context), or {type, id} to set the "dashboard" property of a device, product, asset_type, asset_group or project, whose id the page gets in Thinger.context. Each may open its own entry page and an initial hash route.
|
|
61
200
|
|
|
62
|
-
One app can serve many dashboards: name its storage (e.g. app_<project>) and upload the build output folder with prune: true (hashed file names change between builds). Dashboards point at the storage folder (…/files/, served as the storage's index) or at another entry page by name, without version queries: files are served with no-cache and an ETag, so viewers always get the latest upload. Apps in the iframe:
|
|
201
|
+
One app can serve many dashboards: name its storage (e.g. app_<project>) and upload the build output folder with prune: true (hashed file names change between builds). Dashboards point at the storage folder (…/files/, served as the storage's index) or at another entry page by name, without version queries: files are served with no-cache and an ETag, so viewers always get the latest upload. Apps in the iframe: start them with {tool:dashboard_init} (a TypeScript project on @thinger-io/dashboard-sdk with these rules set up and an AGENTS.md); hash routing, relative asset URLs and no <base> tag, render after Thinger.ready(), and no localStorage or cookies (opaque origin: keep state in properties through the SDK). Publish the build output folder; the answer warns when the app's SDK needs a newer server than this one, is not the latest on npm, or a page loads the deprecated script build (thinger-dashboard.js).`,
|
|
63
202
|
inputSchema: {
|
|
64
203
|
type: 'object',
|
|
65
204
|
properties: {
|
package/lib/mcp/tools-misc.js
CHANGED
|
@@ -30,7 +30,7 @@ export const tools = [
|
|
|
30
30
|
{
|
|
31
31
|
name: 'doc_read',
|
|
32
32
|
description:
|
|
33
|
-
"Read the guides for agents and app developers:
|
|
33
|
+
"Read the guides for agents and app developers: the platform's concepts, product code, server administration, the dashboard SDK reference and types (the version the project in this folder uses), and this CLI's own. Without `id`, lists them. Read the relevant guide before building something new.",
|
|
34
34
|
inputSchema: {
|
|
35
35
|
type: 'object',
|
|
36
36
|
properties: { id: { type: 'string', description: 'Document id from the list.' } },
|