@thinger-io/cli-core 0.1.1 → 0.2.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/CHANGELOG.md +100 -0
- package/README.md +38 -6
- package/lib/api.js +3 -0
- package/lib/auth.js +29 -9
- package/lib/bucket.js +3 -0
- package/lib/cli.js +15 -2
- package/lib/config.js +55 -5
- package/lib/constants.js +8 -0
- package/lib/dashboard.js +605 -0
- package/lib/docs.js +182 -0
- package/lib/errors.js +29 -4
- package/lib/mcp/catalog.js +1235 -0
- package/lib/mcp/helpers.js +1 -1
- package/lib/mcp/registry.js +72 -30
- package/lib/mcp/resource-hooks.js +145 -0
- package/lib/mcp/server.js +46 -5
- package/lib/mcp/tools-buckets.js +79 -80
- package/lib/mcp/tools-dashboards.js +123 -0
- package/lib/mcp/tools-devices.js +88 -234
- package/lib/mcp/tools-misc.js +48 -0
- package/lib/mcp/tools-plugins.js +63 -0
- package/lib/mcp/tools-products.js +32 -385
- package/lib/mcp/tools-resource.js +760 -0
- package/lib/mcp/tools-storage.js +173 -0
- package/lib/openapi.js +169 -0
- package/lib/paths.js +8 -0
- package/lib/resource.js +2 -0
- package/lib/storage.js +2 -1
- package/openapi/swagger.json +1 -0
- package/package.json +5 -4
- package/lib/mcp/tools-alarms.js +0 -507
- package/lib/mcp/tools-profile.js +0 -635
- package/lib/mcp/tools-profiles.js +0 -30
- package/lib/mcp/tools-properties.js +0 -84
- package/lib/mcp/tools-resources.js +0 -83
- package/lib/mcp/tools-tokens.js +0 -370
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,105 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.1] - 2026-10-12
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Repository renamed to `thinger-io/thinger-cli-core` (the package keeps its
|
|
8
|
+
name). README: 23 MCP tools.
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - 2026-10-10
|
|
11
|
+
|
|
12
|
+
Incompatible change of the MCP tools: from 48 tools, one per type and
|
|
13
|
+
operation, to 23, less than half the size in `tools/list` (about 23,200
|
|
14
|
+
characters instead of 51,400, roughly 6,400 tokens instead of 14,300).
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Seven generic tools over a catalog of the server's resource types:
|
|
19
|
+
`resource_describe`, `resource_list`, `resource_get`, `resource_create`,
|
|
20
|
+
`resource_update` (`merge: true` patches property values; `projects` in
|
|
21
|
+
the body sets the resource's projects), `resource_delete`,
|
|
22
|
+
`resource_clone`. The catalog (`mcp/catalog.js`) covers devices,
|
|
23
|
+
products, buckets, dashboards, endpoints, storages, asset types and
|
|
24
|
+
groups, projects, tokens, alarm rules and instances, claims, account
|
|
25
|
+
properties and their children (device properties, tokens, buckets, bucket
|
|
26
|
+
tags, services and HTTP callback; product properties, profile entries,
|
|
27
|
+
code and services; bucket tags and exports; asset type, asset group and
|
|
28
|
+
project properties; roles, project members and roles, syncs) and the
|
|
29
|
+
server's administration (users, hosts, domains, brands, OAuth clients,
|
|
30
|
+
identity providers, proxies, under /v1 rather than an account). Each tool
|
|
31
|
+
offers only the types that support its verb; permissions are the
|
|
32
|
+
server's to enforce.
|
|
33
|
+
- `resource_describe` reads the request schemas and examples from the
|
|
34
|
+
server's own OpenAPI document (`/v1/server/swagger.json`), cached per
|
|
35
|
+
server and version in `~/.config/<cli>/cache`; servers that do not publish
|
|
36
|
+
it fall back to the bundled copy (`openapi/swagger.json`, the server's
|
|
37
|
+
develop as of 2026-10-10). It also lists, for each verb, the permission action a token
|
|
38
|
+
needs (`permissions`: `Device` / `ReadDeviceStatus`…), taken from the same
|
|
39
|
+
document.
|
|
40
|
+
- Resource hooks: generated device credentials, the identity's product
|
|
41
|
+
template, device token expiries as durations ("30d") and permission tree
|
|
42
|
+
checks.
|
|
43
|
+
CLIs add theirs with `runCli({ resourceHooks })`.
|
|
44
|
+
- Plugins: type `plugin` (installed by default; the installable catalog
|
|
45
|
+
with filter `available`, summarised) with actions install, upgrade,
|
|
46
|
+
remove, start, stop, restart, pause, unpause, kill, public, private;
|
|
47
|
+
`plugin_property`; and `plugin_call` for the API each plugin serves.
|
|
48
|
+
Lists that ignore count/index are paged client-side, and `resource_list`
|
|
49
|
+
takes a `search`: by id or name on the server (escaped, any case), or
|
|
50
|
+
over every field shown on lists the server answers whole (plugins).
|
|
51
|
+
- `resource_call`: actions of a type that are not CRUD, declared in the
|
|
52
|
+
catalog with their operationId (checked against the OpenAPI document):
|
|
53
|
+
verify a domain, restart a host or read/replace its settings, send a
|
|
54
|
+
brand's test email, call an endpoint, export a product, read a device's
|
|
55
|
+
usage, test an alarm rule (rule, source, notification).
|
|
56
|
+
- Tools that are not CRUD: `bucket_data_read`, `bucket_data_write`,
|
|
57
|
+
`device_resource_list`, `device_resource_call`, `device_data_send`,
|
|
58
|
+
`device_overview`, `product_resource_call`,
|
|
59
|
+
`storage_file_list` / `_read` / `_write` / `_delete`.
|
|
60
|
+
- `dashboard_publish`: publishes HTML apps as url-source dashboards
|
|
61
|
+
(servers with url-source dashboards). Uploads a local folder (a build
|
|
62
|
+
output, subfolders included) or inline files to a public storage, with
|
|
63
|
+
`prune` to delete files a new build no longer has; and/or points
|
|
64
|
+
dashboards at it: dashboards of the collection, or the `dashboard`
|
|
65
|
+
property of a device, product, asset type, asset group or project
|
|
66
|
+
(checked before anything is uploaded), each with its own entry page and
|
|
67
|
+
initial hash route. One storage can serve an app shared by many
|
|
68
|
+
dashboards. Entry urls carry `?v=` against stale caches; scripts and
|
|
69
|
+
styles without a content hash are reported.
|
|
70
|
+
- `doc_read`: the documentation the server publishes for agents, listed in
|
|
71
|
+
its index (`/sdk/docs.json`): a guide to the platform's concepts, the
|
|
72
|
+
dashboard SDK reference, its TypeScript declarations… A guide added to the
|
|
73
|
+
index reaches the model without a CLI release. The same documents are MCP
|
|
74
|
+
resources (`<prefix>://<id>`), the concepts guide is appended to
|
|
75
|
+
`resource_describe` without a type, and the SDK reference to
|
|
76
|
+
`resource_describe` of the dashboard type. Only paths on the profile's own
|
|
77
|
+
server are followed. CLIs add the guides they ship with
|
|
78
|
+
`runCli({ docs })`, listed first and readable without a server.
|
|
79
|
+
- `scripts/catalog-draft.js`: drafts catalog entries from an OpenAPI
|
|
80
|
+
document.
|
|
81
|
+
|
|
82
|
+
### Changed
|
|
83
|
+
|
|
84
|
+
- A 403 no longer says the token may have expired: it says the token or the
|
|
85
|
+
account is not allowed, with the server's reason. The error code is still
|
|
86
|
+
`unauthorized`.
|
|
87
|
+
- `profiles` is now `profile_list`; `bucket_read` is `bucket_data_read`;
|
|
88
|
+
`resource_list` / `resource_call` (device resources) are
|
|
89
|
+
`device_resource_list` / `device_resource_call`.
|
|
90
|
+
- `buildTools(extraTools, { admin, hooks })`; `genericTools` is now a
|
|
91
|
+
function of the same options.
|
|
92
|
+
|
|
93
|
+
### Removed
|
|
94
|
+
|
|
95
|
+
- The per-type tools, replaced by `resource_*` on the matching type:
|
|
96
|
+
`devices`, `device_info`, `device_create`, `device_set_product`,
|
|
97
|
+
`device_delete`, `products`, `product_create`, `product_update`,
|
|
98
|
+
`product_delete`, `product_property_*`, `product_profile_*`,
|
|
99
|
+
`property_get`, `property_set`, `token_*`, `device_token_*`,
|
|
100
|
+
`alarm_rules`, `alarm_rule_*`, `alarm_instances`, `alarm_instance_*`,
|
|
101
|
+
`buckets`.
|
|
102
|
+
|
|
3
103
|
## [0.1.1] - 2026-10-10
|
|
4
104
|
|
|
5
105
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @thinger-io/cli-core
|
|
2
2
|
|
|
3
3
|
The shared core of the CLIs built on Thinger.io servers — `thinger`
|
|
4
|
-
([@thinger-io/
|
|
4
|
+
([@thinger-io/cli](https://github.com/thinger-io/thinger-cli)) and
|
|
5
5
|
ThinRemote's `thinr` — and of the MCP servers they expose.
|
|
6
6
|
|
|
7
7
|
It holds everything that is not specific to a product:
|
|
@@ -18,7 +18,13 @@ It holds everything that is not specific to a product:
|
|
|
18
18
|
account path built in `paths`.
|
|
19
19
|
- **Generic commands** (`cli`, `commands/*`) — login, logout, profile, device,
|
|
20
20
|
product, bucket, mcp.
|
|
21
|
-
- **MCP** (`mcp/*`) — the stdio server and
|
|
21
|
+
- **MCP** (`mcp/*`) — the stdio server and 23 generic tools: eight
|
|
22
|
+
`resource_*` tools over a catalog of the server's resource types, and the
|
|
23
|
+
operations that are not CRUD (bucket data, device resources, storage
|
|
24
|
+
files, dashboard publishing, the server's documentation…), plus that
|
|
25
|
+
documentation as MCP resources.
|
|
26
|
+
- **OpenAPI** (`openapi`) — the server's own API description, cached per
|
|
27
|
+
server and version, with a bundled copy as fallback.
|
|
22
28
|
|
|
23
29
|
## Identity
|
|
24
30
|
|
|
@@ -38,7 +44,7 @@ defineIdentity({
|
|
|
38
44
|
token: { id: 'acme_cli', name: 'Acme CLI' }, // minted by the password login
|
|
39
45
|
oauth: { clientId: null }, // OAuth device-flow client, if the platform ships one
|
|
40
46
|
defaultServer: null, // suggested by the login prompt
|
|
41
|
-
mcp: { serverName: 'acme', toolPrefix: 'acme' }, // tools become
|
|
47
|
+
mcp: { serverName: 'acme', toolPrefix: 'acme' }, // tools become acme_resource_list, …
|
|
42
48
|
productTemplate: {}, // optional defaults for new products
|
|
43
49
|
});
|
|
44
50
|
|
|
@@ -58,11 +64,37 @@ servers.
|
|
|
58
64
|
- `onNoCommand(program)` — what to do when called without arguments
|
|
59
65
|
(default: help).
|
|
60
66
|
- `mcpTools` — the CLI's own MCP tools, declared with bare names
|
|
61
|
-
(`name: '
|
|
62
|
-
`{tool:
|
|
63
|
-
A tool flagged `replaces: true` swaps out the generic tool of
|
|
67
|
+
(`name: 'device_exec'`) and referring to others in descriptions as
|
|
68
|
+
`{tool:resource_list}` or `{family:storage_file}`. Unknown references fail
|
|
69
|
+
at startup. A tool flagged `replaces: true` swaps out the generic tool of
|
|
70
|
+
the same name.
|
|
71
|
+
- `resourceHooks` — the CLI's hooks on catalog types (`{ product: {
|
|
72
|
+
afterDelete } }`), run after the core's: see `mcp/resource-hooks.js`.
|
|
64
73
|
- `mcpInstructions` — server-level guidance sent to MCP clients.
|
|
65
74
|
|
|
75
|
+
## MCP tools
|
|
76
|
+
|
|
77
|
+
Tool names follow `<object>[_<subobject>]_<verb>`, with the verbs list, get,
|
|
78
|
+
describe, create, update, delete, clone, call, read, write, send.
|
|
79
|
+
|
|
80
|
+
The server's collections are reached through eight generic tools —
|
|
81
|
+
`resource_describe`, `resource_list`, `resource_get`, `resource_create`,
|
|
82
|
+
`resource_update`, `resource_delete`, `resource_clone`, `resource_call`
|
|
83
|
+
(actions that are not CRUD) — that take a `type`
|
|
84
|
+
from the catalog (`lib/mcp/catalog.js`), a `parent` for child types and an
|
|
85
|
+
`id`. The catalog is a reviewed table, never discovered at run time: each
|
|
86
|
+
type has its path, API version, parent, id and the verbs it allows, with the
|
|
87
|
+
server's operationId for each (the tests check them against the bundled
|
|
88
|
+
OpenAPI document). `resource_describe` reads the request schemas and
|
|
89
|
+
examples from the OpenAPI document of the server the profile points at.
|
|
90
|
+
|
|
91
|
+
To draft catalog entries after an API change:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
curl -s https://<server>/v1/server/swagger.json -o openapi/swagger.json
|
|
95
|
+
node scripts/catalog-draft.js
|
|
96
|
+
```
|
|
97
|
+
|
|
66
98
|
Modules are imported by path: `@thinger-io/cli-core/api`,
|
|
67
99
|
`@thinger-io/cli-core/mcp/registry`, …
|
|
68
100
|
|
package/lib/api.js
CHANGED
|
@@ -17,6 +17,9 @@ const api = axios.create({
|
|
|
17
17
|
timeout: TIMEOUTS.API_DEFAULT_MS,
|
|
18
18
|
httpAgent,
|
|
19
19
|
httpsAgent,
|
|
20
|
+
// Arrays as repeated keys (product=a&product=b), which the server reads
|
|
21
|
+
// as "any of"; axios's default product[]=a matches only the first.
|
|
22
|
+
paramsSerializer: { indexes: null },
|
|
20
23
|
});
|
|
21
24
|
|
|
22
25
|
api.interceptors.request.use(
|
package/lib/auth.js
CHANGED
|
@@ -4,8 +4,11 @@ import ora from 'ora';
|
|
|
4
4
|
import open, { apps } from 'open';
|
|
5
5
|
import {
|
|
6
6
|
getActiveProfile,
|
|
7
|
+
getProfile,
|
|
7
8
|
mergeConfig,
|
|
9
|
+
profileTarget,
|
|
8
10
|
readConfig,
|
|
11
|
+
restoreProfile,
|
|
9
12
|
setActiveProfile,
|
|
10
13
|
writeConfig,
|
|
11
14
|
} from './config.js';
|
|
@@ -90,15 +93,32 @@ export async function authenticate(opts = {}) {
|
|
|
90
93
|
// Persist the server first: the API client takes its base URL from the
|
|
91
94
|
// active profile, so the login requests below already reach it.
|
|
92
95
|
// Pin the profile just written, so the merges below land on it even
|
|
93
|
-
// when another profile is the default.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
// when another profile is the default. A login that fails or is
|
|
97
|
+
// interrupted puts the previous profile back: a wrong password must
|
|
98
|
+
// not leave the user (and their MCP servers) signed out.
|
|
99
|
+
const target = profileTarget(server);
|
|
100
|
+
const previous = target ? getProfile(target) : undefined;
|
|
101
|
+
const name = writeConfig({ server });
|
|
102
|
+
setActiveProfile(name);
|
|
103
|
+
const restore = () => restoreProfile(name, previous);
|
|
104
|
+
const onSigint = () => {
|
|
105
|
+
restore();
|
|
106
|
+
process.exit(130);
|
|
107
|
+
};
|
|
108
|
+
process.once('SIGINT', onSigint);
|
|
109
|
+
try {
|
|
110
|
+
if (method === 'userpass') {
|
|
111
|
+
await authenticateWithUserPass(opts);
|
|
112
|
+
} else if (method === 'oauth') {
|
|
113
|
+
await authenticateWithOAuth(opts, existing);
|
|
114
|
+
} else {
|
|
115
|
+
await authenticateWithToken(opts);
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
restore();
|
|
119
|
+
throw error;
|
|
120
|
+
} finally {
|
|
121
|
+
process.removeListener('SIGINT', onSigint);
|
|
102
122
|
}
|
|
103
123
|
}
|
|
104
124
|
|
package/lib/bucket.js
CHANGED
|
@@ -38,6 +38,7 @@ import { v1, v2, v3 } from './paths.js';
|
|
|
38
38
|
* max_ts?: number,
|
|
39
39
|
* agg?: string,
|
|
40
40
|
* agg_type?: string,
|
|
41
|
+
* tz?: string,
|
|
41
42
|
* fields?: string,
|
|
42
43
|
* group_by?: string | null,
|
|
43
44
|
* }} opts
|
|
@@ -53,6 +54,7 @@ export async function getBucketData({
|
|
|
53
54
|
max_ts,
|
|
54
55
|
agg,
|
|
55
56
|
agg_type,
|
|
57
|
+
tz,
|
|
56
58
|
fields,
|
|
57
59
|
group_by,
|
|
58
60
|
} = {}) {
|
|
@@ -67,6 +69,7 @@ export async function getBucketData({
|
|
|
67
69
|
}
|
|
68
70
|
if (agg) params.agg = agg;
|
|
69
71
|
if (agg_type) params.agg_type = agg_type;
|
|
72
|
+
if (tz) params.tz = tz;
|
|
70
73
|
if (group_by) params.group_by = group_by;
|
|
71
74
|
if (fields) params.fields = fields;
|
|
72
75
|
|
package/lib/cli.js
CHANGED
|
@@ -22,11 +22,16 @@ import { registerBucketCommands } from './commands/bucket.js';
|
|
|
22
22
|
* onNoCommand?: (program: Command) => Promise<void> | void,
|
|
23
23
|
* mcpTools?: Array<object>,
|
|
24
24
|
* mcpInstructions?: string,
|
|
25
|
+
* resourceHooks?: Record<string, import('./mcp/resource-hooks.js').ResourceHooks>,
|
|
26
|
+
* docs?: Parameters<typeof import('./docs.js').defineCliDocs>[0],
|
|
25
27
|
* }} opts
|
|
26
28
|
* `register` adds the CLI's own commands, at the top level or inside the
|
|
27
29
|
* generic groups. `onNoCommand` runs when the CLI is called without
|
|
28
30
|
* arguments (default: print the help). `mcpTools` are the CLI's own MCP
|
|
29
|
-
* tools (see buildTools)
|
|
31
|
+
* tools (see buildTools), and `resourceHooks` its hooks on the
|
|
32
|
+
* catalog types the generic `resource_*` tools serve. `docs` are guides
|
|
33
|
+
* the CLI ships, served by `doc_read` and as MCP resources next to the
|
|
34
|
+
* server's documentation.
|
|
30
35
|
*/
|
|
31
36
|
export async function runCli({
|
|
32
37
|
version,
|
|
@@ -35,6 +40,8 @@ export async function runCli({
|
|
|
35
40
|
onNoCommand,
|
|
36
41
|
mcpTools = [],
|
|
37
42
|
mcpInstructions,
|
|
43
|
+
resourceHooks,
|
|
44
|
+
docs,
|
|
38
45
|
}) {
|
|
39
46
|
const id = identity();
|
|
40
47
|
|
|
@@ -79,7 +86,13 @@ export async function runCli({
|
|
|
79
86
|
.description('Start the MCP server (stdio) for AI tool integration')
|
|
80
87
|
.action(async () => {
|
|
81
88
|
try {
|
|
82
|
-
await startMCPServer({
|
|
89
|
+
await startMCPServer({
|
|
90
|
+
version,
|
|
91
|
+
tools: mcpTools,
|
|
92
|
+
instructions: mcpInstructions,
|
|
93
|
+
resourceHooks,
|
|
94
|
+
docs,
|
|
95
|
+
});
|
|
83
96
|
} catch (error) {
|
|
84
97
|
console.error(`Error: ${classifyError(error).message}`);
|
|
85
98
|
process.exit(1);
|
package/lib/config.js
CHANGED
|
@@ -50,8 +50,20 @@ function getConfigPath() {
|
|
|
50
50
|
if (!fs.existsSync(configDir)) {
|
|
51
51
|
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
52
52
|
}
|
|
53
|
+
ownerOnly(configDir, 0o700);
|
|
54
|
+
const configPath = path.join(configDir, 'config.json');
|
|
55
|
+
if (fs.existsSync(configPath)) ownerOnly(configPath, 0o600);
|
|
56
|
+
return configPath;
|
|
57
|
+
}
|
|
53
58
|
|
|
54
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Folder for data the CLI can always rebuild (server API descriptions…),
|
|
61
|
+
* next to the profiles. Created on first access.
|
|
62
|
+
*/
|
|
63
|
+
export function cacheDir() {
|
|
64
|
+
const dir = path.join(os.homedir(), '.config', identity().configDir, 'cache');
|
|
65
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
66
|
+
return dir;
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
function emptyStore() {
|
|
@@ -79,10 +91,27 @@ function readStore() {
|
|
|
79
91
|
return raw;
|
|
80
92
|
}
|
|
81
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Take group and other permissions away from a file or folder made by an
|
|
96
|
+
* older version (or by hand): `mode` on create does not fix existing ones.
|
|
97
|
+
*/
|
|
98
|
+
function ownerOnly(file, mode) {
|
|
99
|
+
try {
|
|
100
|
+
if ((fs.statSync(file).mode & 0o077) !== 0) fs.chmodSync(file, mode);
|
|
101
|
+
} catch {
|
|
102
|
+
// Not ours to fix (another owner, a filesystem without modes).
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
82
106
|
function writeStore(store) {
|
|
83
107
|
const configPath = getConfigPath();
|
|
84
|
-
// The file holds API tokens:
|
|
85
|
-
|
|
108
|
+
// The file holds API tokens: readable by its owner only. Written aside
|
|
109
|
+
// and renamed, so a crash or a concurrent reader never sees half a file,
|
|
110
|
+
// and the rename leaves the new file's mode on the path.
|
|
111
|
+
const tmp = `${configPath}.${process.pid}.tmp`;
|
|
112
|
+
fs.writeFileSync(tmp, JSON.stringify(store, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
113
|
+
ownerOnly(tmp, 0o600);
|
|
114
|
+
fs.renameSync(tmp, configPath);
|
|
86
115
|
}
|
|
87
116
|
|
|
88
117
|
/**
|
|
@@ -247,8 +276,7 @@ export function requireConfig() {
|
|
|
247
276
|
*/
|
|
248
277
|
export function writeConfig(config) {
|
|
249
278
|
const store = readStore();
|
|
250
|
-
const name =
|
|
251
|
-
activeProfileOverride || profileNameFor(config && config.server) || getActiveProfile();
|
|
279
|
+
const name = profileTarget(config && config.server);
|
|
252
280
|
if (!name) {
|
|
253
281
|
throw new Error('Cannot write config without a server / profile name');
|
|
254
282
|
}
|
|
@@ -258,6 +286,28 @@ export function writeConfig(config) {
|
|
|
258
286
|
return name;
|
|
259
287
|
}
|
|
260
288
|
|
|
289
|
+
/** The profile writeConfig({ server }) writes to. */
|
|
290
|
+
export function profileTarget(server) {
|
|
291
|
+
return activeProfileOverride || profileNameFor(server) || getActiveProfile();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Put a profile back as it was (`undefined`: it did not exist), after a
|
|
296
|
+
* login that failed half-way.
|
|
297
|
+
*
|
|
298
|
+
* @param {string} name
|
|
299
|
+
* @param {Record<string, any> | undefined} profile
|
|
300
|
+
*/
|
|
301
|
+
export function restoreProfile(name, profile) {
|
|
302
|
+
const store = readStore();
|
|
303
|
+
if (profile) {
|
|
304
|
+
store.profiles[name] = profile;
|
|
305
|
+
writeStore(store);
|
|
306
|
+
} else if (store.profiles[name]) {
|
|
307
|
+
deleteProfile(name);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
261
311
|
/**
|
|
262
312
|
* Merge fields into the active profile.
|
|
263
313
|
*/
|
package/lib/constants.js
CHANGED
|
@@ -12,6 +12,14 @@ export const TIMEOUTS = {
|
|
|
12
12
|
/** Default axios request timeout for control-plane HTTP calls. */
|
|
13
13
|
API_DEFAULT_MS: 10_000,
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Requests that do work on the server or move files: resource actions
|
|
17
|
+
* (plugin install pulls a container, bucket import, product export),
|
|
18
|
+
* device and plugin calls, uploads. The server keeps working past a
|
|
19
|
+
* client timeout, so a short one only makes callers retry half-done jobs.
|
|
20
|
+
*/
|
|
21
|
+
API_LONG_MS: 10 * 60 * 1000,
|
|
22
|
+
|
|
15
23
|
/**
|
|
16
24
|
* Refresh a JWT access token this close to (or past) its `exp`
|
|
17
25
|
* before opening a WebSocket. WS handshakes lack the
|