@widgetic/cli 0.0.0-stage → 0.1.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/LICENSE +122 -0
- package/README.md +184 -2
- package/bin/widgetic.js +5 -0
- package/dist/cli.js +106 -0
- package/dist/commands/auth.js +269 -0
- package/dist/commands/call.js +97 -0
- package/dist/commands/completion.js +148 -0
- package/dist/commands/doctor.js +291 -0
- package/dist/commands/schema.js +214 -0
- package/dist/commands/update.js +159 -0
- package/dist/commands/verify.js +64 -0
- package/dist/config.js +98 -0
- package/dist/confirm.js +15 -0
- package/dist/constants.js +51 -0
- package/dist/contract-note.js +42 -0
- package/dist/credentials.js +111 -0
- package/dist/environment.js +150 -0
- package/dist/errors.js +67 -0
- package/dist/http.js +122 -0
- package/dist/input.js +37 -0
- package/dist/main.js +30 -0
- package/dist/manifest/load.js +39 -0
- package/dist/manifest/types.js +14 -0
- package/dist/mcp/plan.js +32 -0
- package/dist/mcp/register.js +112 -0
- package/dist/mcp/session.js +185 -0
- package/dist/mcp/wait.js +68 -0
- package/dist/options.js +228 -0
- package/dist/output.js +90 -0
- package/dist/overlay.js +31 -0
- package/dist/patterns.js +11 -0
- package/dist/rest/execute.js +67 -0
- package/dist/rest/mime.js +37 -0
- package/dist/rest/plan.js +185 -0
- package/dist/rest/register.js +139 -0
- package/dist/retry.js +49 -0
- package/dist/secrets.js +149 -0
- package/dist/state.js +37 -0
- package/dist/update/installation.js +83 -0
- package/dist/update/lookup.js +128 -0
- package/dist/update/offer.js +165 -0
- package/dist/update/perform.js +33 -0
- package/dist/update/registry.js +68 -0
- package/dist/update/version.js +39 -0
- package/dist/validate.js +93 -0
- package/dist/version.js +59 -0
- package/generated/contracts.json +1 -0
- package/generated/mcp-manifest.json +1 -0
- package/generated/rest-manifest.json +1 -0
- package/package.json +56 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Widgetic Integration License
|
|
2
|
+
Version 1.0
|
|
3
|
+
|
|
4
|
+
Copyright (c) Widgetic. All rights reserved.
|
|
5
|
+
|
|
6
|
+
This license covers the software package that it came with, in source and
|
|
7
|
+
compiled form, together with its documentation (the "Software"). By
|
|
8
|
+
installing, copying or using the Software you accept this license. If you do
|
|
9
|
+
not accept it, do not install or use the Software.
|
|
10
|
+
|
|
11
|
+
In this license, "Widgetic" is the company that publishes the Software, "you"
|
|
12
|
+
is the person or organization that uses it, and "Your Application" is an
|
|
13
|
+
application, website, product or service that you build or operate, for
|
|
14
|
+
yourself or for a client. "Widgetic Services" are the services that Widgetic
|
|
15
|
+
operates: the Widgetic API, widgets, compositions, embeds, editors, file
|
|
16
|
+
browser, chat and AI creator, and the other services that can be reached from
|
|
17
|
+
widgetic.com or its API hosts.
|
|
18
|
+
|
|
19
|
+
1. What you may do
|
|
20
|
+
|
|
21
|
+
Widgetic grants you a worldwide, non-exclusive, non-transferable and
|
|
22
|
+
non-sublicensable license to use the Software, unmodified, for one purpose: to
|
|
23
|
+
integrate the Widgetic Services into Your Application. Within that purpose you
|
|
24
|
+
may:
|
|
25
|
+
|
|
26
|
+
(a) install the Software and run it in Your Application, in development,
|
|
27
|
+
testing and production;
|
|
28
|
+
|
|
29
|
+
(b) use it to call the Widgetic API, to show Widgetic widgets and
|
|
30
|
+
compositions, and to open Widgetic's editors, file browser, chat and AI
|
|
31
|
+
creator inside Your Application;
|
|
32
|
+
|
|
33
|
+
(c) bundle, minify and tree-shake the Software with the build tools of Your
|
|
34
|
+
Application, deliver the result to the people who use Your Application as a
|
|
35
|
+
part of it, and let them run it there;
|
|
36
|
+
|
|
37
|
+
(d) keep the copies of the Software that (a), (b) and (c) need, including
|
|
38
|
+
copies in your version control, your build systems and your backups.
|
|
39
|
+
|
|
40
|
+
2. What you may not do
|
|
41
|
+
|
|
42
|
+
You may not, and may not allow anyone else to:
|
|
43
|
+
|
|
44
|
+
(a) use the Software for any purpose other than the one in section 1;
|
|
45
|
+
|
|
46
|
+
(b) use the Software to build, run or improve a service that offers what the
|
|
47
|
+
Widgetic Services offer, or to connect Your Application to anything other than
|
|
48
|
+
the Widgetic Services (a proxy of yours that forwards calls to the Widgetic
|
|
49
|
+
Services is fine);
|
|
50
|
+
|
|
51
|
+
(c) copy, modify, translate, adapt or make derivative works of the Software,
|
|
52
|
+
except as section 1(c) allows;
|
|
53
|
+
|
|
54
|
+
(d) publish, sell, rent, lease, sublicense or distribute the Software on its
|
|
55
|
+
own, or inside a library, SDK, template or product that others could use for
|
|
56
|
+
anything other than integrating the Widgetic Services, including by
|
|
57
|
+
publishing it, or a copy of it, to a package registry or a public CDN;
|
|
58
|
+
|
|
59
|
+
(e) reverse engineer, decompile or disassemble the Software, or try to get at
|
|
60
|
+
its source code, except as far as the law lets you despite this restriction;
|
|
61
|
+
|
|
62
|
+
(f) remove or change a copyright, license or attribution notice in the
|
|
63
|
+
Software;
|
|
64
|
+
|
|
65
|
+
(g) bypass, disable or interfere with the authentication, rate limits, quotas,
|
|
66
|
+
credits, plan limits or other protections of the Widgetic Services, or help
|
|
67
|
+
anyone else to;
|
|
68
|
+
|
|
69
|
+
(h) use the name, logo or trademarks of Widgetic, except to say truthfully
|
|
70
|
+
that Your Application integrates Widgetic.
|
|
71
|
+
|
|
72
|
+
3. The Widgetic Services are separate
|
|
73
|
+
|
|
74
|
+
This license is for the Software only. It does not give you the right to use
|
|
75
|
+
the Widgetic Services. That right comes from your account or agreement with
|
|
76
|
+
Widgetic and from Widgetic's Terms of Service (https://widgetic.com/terms),
|
|
77
|
+
which this license does not change.
|
|
78
|
+
|
|
79
|
+
4. Ownership
|
|
80
|
+
|
|
81
|
+
The Software is licensed to you, not sold. Widgetic and its licensors keep all
|
|
82
|
+
rights in it. Any right that this license does not give you in plain words is
|
|
83
|
+
reserved to Widgetic.
|
|
84
|
+
|
|
85
|
+
5. Third-party software
|
|
86
|
+
|
|
87
|
+
The Software may include or depend on software from others. That software is
|
|
88
|
+
under its own license, which applies to it instead of this one, and nothing in
|
|
89
|
+
this license limits your rights under it.
|
|
90
|
+
|
|
91
|
+
6. How long it lasts
|
|
92
|
+
|
|
93
|
+
This license lasts while you follow it and while you are entitled to use the
|
|
94
|
+
Widgetic Services. It ends on its own if you break it or that entitlement
|
|
95
|
+
ends. When it ends, you stop using the Software and delete your copies of it,
|
|
96
|
+
except copies that the law requires you to keep. Sections 4, 8 and 9 still
|
|
97
|
+
apply after it has ended.
|
|
98
|
+
|
|
99
|
+
7. Versions
|
|
100
|
+
|
|
101
|
+
This license applies to the version of the Software that it came with and to
|
|
102
|
+
later versions that carry it. A version that Widgetic published under another
|
|
103
|
+
license stays under that license, for the copies of it that you already have.
|
|
104
|
+
|
|
105
|
+
8. No warranty
|
|
106
|
+
|
|
107
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
108
|
+
IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
109
|
+
PURPOSE AND NON-INFRINGEMENT.
|
|
110
|
+
|
|
111
|
+
9. Limit of liability
|
|
112
|
+
|
|
113
|
+
TO THE EXTENT THE LAW ALLOWS, WIDGETIC IS NOT LIABLE FOR ANY INDIRECT,
|
|
114
|
+
INCIDENTAL, SPECIAL, CONSEQUENTIAL OR PUNITIVE DAMAGES, OR FOR LOST PROFITS,
|
|
115
|
+
REVENUE, DATA OR GOODWILL, THAT ARISE FROM THE SOFTWARE OR FROM THIS LICENSE,
|
|
116
|
+
EVEN IF IT HAS BEEN TOLD THAT THEY ARE POSSIBLE. NOTHING IN THIS LICENSE
|
|
117
|
+
LIMITS A LIABILITY THAT THE LAW DOES NOT ALLOW TO BE LIMITED.
|
|
118
|
+
|
|
119
|
+
10. Questions
|
|
120
|
+
|
|
121
|
+
If you need to do something that this license does not allow, ask first:
|
|
122
|
+
support@widgetic.com.
|
package/README.md
CHANGED
|
@@ -1,3 +1,185 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @widgetic/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Widgetic command line. One binary, two namespaces:
|
|
4
|
+
|
|
5
|
+
- `widgetic api <group> <command>`: every operation of the public REST API.
|
|
6
|
+
- `widgetic mcp <command>`: every AI build tool of the Widgetic MCP server.
|
|
7
|
+
|
|
8
|
+
Every command is generated from the backend's own contracts, not written by hand, and every command takes `--json` (machine-readable output) and `--dry-run` (print the exact request, send nothing).
|
|
9
|
+
|
|
10
|
+
## Quick start
|
|
11
|
+
|
|
12
|
+
Needs Node 22.12 or later.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @widgetic/cli
|
|
16
|
+
|
|
17
|
+
widgetic auth login # asks for an API key without showing it, checks it, saves it
|
|
18
|
+
widgetic doctor # the install, the key, the REST API and the MCP server
|
|
19
|
+
widgetic api widgets get-widgets # a REST call
|
|
20
|
+
widgetic mcp create-widget --title "Launch countdown" --brief "A countdown to 14 March 2027" --wait
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`widgetic --help`, `widgetic api --help`, `widgetic api <group> --help` and `widgetic <command> --help` list what is there. The whole reference, one page per group, is generated into [`docs/commands`](docs/commands/index.mdx).
|
|
24
|
+
|
|
25
|
+
## The commands
|
|
26
|
+
|
|
27
|
+
| Command | What it does |
|
|
28
|
+
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| `widgetic api <group> <command>` | One REST operation. The group is the tag of the OpenAPI description, the command is its operation (`get-widgets`, `create-widget`). |
|
|
30
|
+
| `widgetic mcp <command>` | One MCP tool (`create_widget` is `widgetic mcp create-widget`). |
|
|
31
|
+
| `widgetic api call <operationId>`, `widgetic mcp call <tool>` | Run a command by the name its contract gives it. It also reaches the public embed operations, which `--help` does not list. |
|
|
32
|
+
| `widgetic schema api <group> <command>`, `widgetic schema mcp <command>` | The whole description of one command and the JSON Schema its input is checked against: the cheap way for an agent to learn one command. |
|
|
33
|
+
| `widgetic auth login`, `logout`, `status` | Save, forget and check the API key. |
|
|
34
|
+
| `widgetic doctor` | Check the CLI, the key and both servers, and say what is wrong (exit 1 when a check fails; `--json` prints every check). |
|
|
35
|
+
| `widgetic update` | Look on npm for a newer version of the CLI, show it beside the installed one, and install it after a yes (see [Updates](#updates)). |
|
|
36
|
+
| `widgetic completion bash\|zsh\|fish` | The script that completes commands and flags with the Tab key. |
|
|
37
|
+
|
|
38
|
+
### Every command takes
|
|
39
|
+
|
|
40
|
+
| Flag | What it does |
|
|
41
|
+
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `--json` | The result, and only the result, as one line of JSON on stdout. An error is one line on stderr: `{"error":{"code","message","status","requestId"}}`. It never asks anything. |
|
|
43
|
+
| `--dry-run` | Print the exact request (the API key is left out) and send nothing. It needs no key. |
|
|
44
|
+
| `--yes` | Go ahead without asking. Only a command that deletes or changes something asks (on a terminal), and without a terminal it needs `--yes`. |
|
|
45
|
+
| `--profile <name>` | The saved key to use (default: `WIDGETIC_PROFILE`, else `default`). |
|
|
46
|
+
| `--timeout <seconds>` | Give up on a call after this long (default 60). |
|
|
47
|
+
| `--data` / `--input` | The whole request document as JSON, `@file.json`, or `-` for standard input. The flags of the command override its keys. |
|
|
48
|
+
|
|
49
|
+
What goes on stdout with `--json`: for a REST command the response body as the server sent it; for an MCP tool its `structuredContent`.
|
|
50
|
+
|
|
51
|
+
### Build tools
|
|
52
|
+
|
|
53
|
+
The tools that build a widget (`create-widget`, `replicate-widget`, `revise-widget`) answer at once and the build goes on in the background. `--wait` keeps asking `get_widget` until the build has finished (up to 15 minutes) and prints the finished widget; a build that fails or is cancelled ends with exit code 5 and the widget in the error. A tool that takes `publish` sends `publish: false` unless you pass `--publish`, so what the CLI builds stays a draft.
|
|
54
|
+
|
|
55
|
+
### Retries
|
|
56
|
+
|
|
57
|
+
A call is tried at most three times. A `429` is always waited out (a `Retry-After` of up to 30 seconds is honoured; a longer one is reported with exit code 6). A dropped connection, a timeout, a `502`, `503` or `504` is tried again only when repeating cannot do anything twice: the call reads, or it carries an idempotency key. The CLI makes the key of a call that creates something, and sends the same one on every try.
|
|
58
|
+
|
|
59
|
+
## What it can reach
|
|
60
|
+
|
|
61
|
+
The CLI is generated from the **public** contracts and from nothing else. `@widgetic/api-contracts` carries the public OpenAPI description (the file the partners' SDKs, `@widgetic/api-sdk-ts` and its siblings, are generated from, and the published API reference) and the MCP tool contract. It is not generated from the private description that Widgetic's own apps use (`@widgetic/api-sdk`, which also lists the administrator and internal operations), so none of those is a command. A mistake in the gateway cannot add one: the package is only built from a description in which every operation is marked `x-visibility: public` and has a level that an API key passes, and the build stops at the first operation that is not.
|
|
62
|
+
|
|
63
|
+
| Level of the operation (`x-access-level`) | Who calls it | In the CLI |
|
|
64
|
+
| ----------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
65
|
+
| `user` | Any API key: your widgets, your account | Listed. Its help names no special key. |
|
|
66
|
+
| `partner` | A partner key (`wgt_prt_…`): managed users, credits, storage, analytics | Listed. Its help says `Needs a partner API key (wgt_prt_…).` |
|
|
67
|
+
| `public` | Browsers, with no key: the runtime of an embed | Left out of `--help`; `widgetic api call <operationId>` runs it. No key is sent. |
|
|
68
|
+
|
|
69
|
+
The CLI does not decide who may do what. The API does, on every call, from the key: a key without the scope or the level gets a 403 (exit code 3), the same answer as from the SDK or from `curl`. The CLI only makes what the key may already do easy to find and to call.
|
|
70
|
+
|
|
71
|
+
## The key
|
|
72
|
+
|
|
73
|
+
A command that needs a key uses, in this order:
|
|
74
|
+
|
|
75
|
+
1. `WIDGETIC_API_KEY` in the environment: used as it is, never written anywhere. Use it in CI and in agents.
|
|
76
|
+
2. The key of the profile that `widgetic auth login` saved in `credentials.json` (mode 0600, in a folder of mode 0700).
|
|
77
|
+
|
|
78
|
+
`widgetic auth login` reads the key from a hidden prompt, or from standard input with `--with-key` (`echo "$WIDGETIC_API_KEY" | widgetic auth login --with-key`); a key is never taken from an argument, because an argument is in the shell history. It checks the key with the server before it saves it. The help of a command says when it needs a partner key (`wgt_prt_…`) or none at all.
|
|
79
|
+
|
|
80
|
+
The key is sent to the API's own address and to nowhere else: a redirect to another origin does not carry it along ([`test/key-stays-home.test.ts`](test/key-stays-home.test.ts) holds that with the real `fetch`).
|
|
81
|
+
|
|
82
|
+
### Secrets in a command
|
|
83
|
+
|
|
84
|
+
A value the contract marks as a secret (the key of an adapter, the secret half of a storage key, a database connection string: `format: password` or `writeOnly`) is never typed as the text of a flag, because what is typed stays in the shell's history and in the list of processes. Its flag takes `@path` (a file) or `-` (a hidden prompt on a terminal, standard input otherwise):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
widgetic api partners set-partner-adapter-connection --partner-id <id> --adapter-id <adapter> --credential-key <name> --credential @./key.txt
|
|
88
|
+
pass show provider/key | widgetic api partners set-partner-adapter-connection --partner-id <id> --adapter-id <adapter> --credential-key <name> --credential -
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A secret typed as the text of the flag is refused (exit code 2), nothing is sent, and the error does not repeat it. `--dry-run` prints `[redacted]` in its place, whether it came from a flag or from the document of `--data`. Which values are secrets is the contract's word, not a list in the CLI ([`src/secrets.ts`](src/secrets.ts)): the generator stops at a secret it has no safe way to take (one in a URL, a header, a form field or an MCP tool argument).
|
|
92
|
+
|
|
93
|
+
### Environment variables
|
|
94
|
+
|
|
95
|
+
| Variable | What it is |
|
|
96
|
+
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
97
|
+
| `WIDGETIC_API_KEY` | The API key. The one secret. |
|
|
98
|
+
| `WIDGETIC_PROFILE` | The profile of the saved key when `--profile` is not given. |
|
|
99
|
+
| `WIDGETIC_API_URL` | The REST address, to call another environment (https; http only for localhost). Default: the server of the OpenAPI description. |
|
|
100
|
+
| `WIDGETIC_MCP_URL` | The MCP address. Default: `https://mcp.widgetic.com/`. |
|
|
101
|
+
| `WIDGETIC_CONFIG_DIR` | The folder of the CLI's own files. Default: `~/.config/widgetic` (`$XDG_CONFIG_HOME/widgetic`, `%APPDATA%\widgetic` on Windows). |
|
|
102
|
+
| `WIDGETIC_DEBUG=1` | Print a stack with an unexpected error, and say why the CLI did not look for a newer version. |
|
|
103
|
+
| `NO_UPDATE_NOTIFIER`, `WIDGETIC_NO_UPDATE_CHECK` | Set to anything but `0`, `false`, `no` or `off`: never look for a newer version or ask about one (`CI` does the same). |
|
|
104
|
+
| `npm_config_registry` | The registry that the look for a newer version asks (default `https://registry.npmjs.org`); npm's own variable, https or http for localhost. |
|
|
105
|
+
|
|
106
|
+
## Exit codes
|
|
107
|
+
|
|
108
|
+
| Code | Meaning |
|
|
109
|
+
| ---- | -------------------------------------------------------------------------------------- |
|
|
110
|
+
| 0 | ok |
|
|
111
|
+
| 1 | anything else (a bug, an unexpected 5xx, a build that did not finish in time) |
|
|
112
|
+
| 2 | usage or validation: the command line or a JSON document is wrong; nothing sent |
|
|
113
|
+
| 3 | authentication or plan: no key, 401, 402, 403 |
|
|
114
|
+
| 4 | not found (404, 410) |
|
|
115
|
+
| 5 | rejected by the server (400, 409, 422, other 4xx; an MCP tool result with error) |
|
|
116
|
+
| 6 | rate limited or unavailable (429, 502, 503, 504, timeout, no network) |
|
|
117
|
+
| 7 | this CLI is too old (426): `widgetic update`, or `npm install -g @widgetic/cli@latest` |
|
|
118
|
+
|
|
119
|
+
## Updates
|
|
120
|
+
|
|
121
|
+
After a command, at a terminal, the CLI says when a newer version is on npm, shows both versions and asks:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
A new version of the Widgetic CLI is available: 0.1.0 → 0.2.0
|
|
125
|
+
Update now? (runs `npm install -g @widgetic/cli@0.2.0`) [y/N]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- **It never gets in the way.** The look for the newest version is one small request to `https://registry.npmjs.org/@widgetic%2fcli/latest` (no key and nothing from your account: npm sees your address and the user agent `@widgetic/cli/<version> (update check)`). It starts with the command and runs beside it, with a 2 second limit, and its answer is kept for 24 hours (1 hour after a failure) under `updateCheck` in `state.json`. The question comes after the command has printed its result, on stderr, and nothing that happens in it changes the command's exit code.
|
|
129
|
+
- **It asks a person, and only a person.** Not with `--json` or `--yes`; not when standard input, output or errors is not a terminal; not in CI (`CI`); not with `NO_UPDATE_NOTIFIER` or `WIDGETIC_NO_UPDATE_CHECK` set; not for `--help`, `--version`, `help`, `completion` (the shell calls it on every Tab) or `update`; and not for a source checkout or a copy that `npx` fetched to run once. `WIDGETIC_DEBUG=1` says which of these applied.
|
|
130
|
+
- **The default is no.** Enter, Ctrl-C and 20 seconds of silence are all "no". A "no" is remembered for 7 days for that version, and a newer version asks at once. A server that refuses the CLI as too old (exit code 7) always gets the question.
|
|
131
|
+
- **It installs what it showed.** On yes it runs the global install command of the package manager that installed this copy (`npm install -g`, `pnpm add -g`, `yarn global add` or `bun add -g`), pinned to the version it showed, and then reads the installed copy again to check that it changed. A copy it cannot place (inside a project's `node_modules`, a custom prefix) is not touched: the CLI prints the new version and the command to run.
|
|
132
|
+
|
|
133
|
+
`widgetic update` does the same whenever you ask. It asks npm itself instead of using what was kept; `--check` only reports, `--yes` installs without asking, and `--json` prints `{package, current, latest, updateAvailable, updated, installedWith, command}` and never asks (add `--yes` to install). The code is in [`src/update`](src/update) and [`src/commands/update.ts`](src/commands/update.ts); the question lives in `runProcess` ([`src/main.ts`](src/main.ts)), so the commands themselves, and their tests, never ask it.
|
|
134
|
+
|
|
135
|
+
## Where the commands come from
|
|
136
|
+
|
|
137
|
+
The gateway (`Services/api-gateway-service`) builds a package, [`@widgetic/api-contracts`](https://www.npmjs.com/package/@widgetic/api-contracts): the public OpenAPI description and the MCP tool contract, version-stamped with the gateway's own version. This repository keeps **no copy** of either.
|
|
138
|
+
|
|
139
|
+
- `npm run generate` reads the installed `@widgetic/api-contracts` and writes `generated/` (git-ignored): the REST command manifest, the MCP command manifest and a small record of the contracts' versions.
|
|
140
|
+
- `npm run build` generates, then compiles `src/` to `dist/`. The package ships `dist/` and `generated/`.
|
|
141
|
+
- At run time the CLI builds its commands from `generated/`. A new endpoint or a new MCP tool is a new command the day the contracts package is bumped and the CLI is released; nobody writes it.
|
|
142
|
+
|
|
143
|
+
### Keeping the CLI and the MCP in step
|
|
144
|
+
|
|
145
|
+
There is nothing to keep in step by hand: the commands are a function of the contracts, and four guards fail when they are not.
|
|
146
|
+
|
|
147
|
+
- **Tests.** `test/command-snapshot.test.ts` holds the command tree (names, flags, groups) to a checked-in snapshot, so a new endpoint, a renamed tool or a changed flag shows up as a diff somebody reads; `test/command-reference.test.ts` does the same for the generated reference in `docs/commands`; the generator tests fail on an operation or a tool that has no command, or two with one name.
|
|
148
|
+
- **The release gate** (`npm run check:release`, run by the publish script before the version is raised, and by the `publish` job of `.gitlab-ci.yml`). It fails unless the contracts the commands were generated from are a release, are the newest `latest` of `@widgetic/api-contracts` on npm, and the tool names and the tool contract version are the ones production's MCP `tools/list` answers. It asks production with the key of `WIDGETIC_API_KEY` or of `widgetic auth login`; a read-only key is enough. Nothing is published and no version is raised when it fails.
|
|
149
|
+
- **At run time.** When an MCP command opens a session and the server's tool contract is not the one the CLI was built for, the CLI says so once in 24 hours on stderr (never with `--json`), and `widgetic doctor` always shows the two versions.
|
|
150
|
+
- **The server can refuse an old CLI.** Every request carries `X-SDK-Version: @widgetic/cli/<version>` (MCP: the client's `clientInfo`). The gateway counts the versions it sees and can answer `426` to one that is too old: exit code 7.
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
Needs Node 22.18 or later (the scripts run TypeScript directly with Node's type stripping; the published package is plain JavaScript and needs Node 22.12).
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npm install
|
|
158
|
+
npm run check # typecheck, tests, prettier
|
|
159
|
+
npm run build # dist/ (and generated/)
|
|
160
|
+
npm run docs # write docs/commands from the installed contracts
|
|
161
|
+
npm run snapshot # write the command snapshot from the installed contracts
|
|
162
|
+
npm run check:release # the release gate (needs the network and a key)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Tests run the real program in-process against a fake environment (`test/helpers/fake-environment.ts`): a fetch that answers from a function and records what it was asked, streams that collect the output, no terminal, no real network, and a package manager that is a function (nothing is ever installed by a test). A fake MCP server (`test/helpers/fake-mcp-server.ts`) answers the MCP tests. The question on the terminal is the one thing the fake replaces whole, so `test/terminal.test.ts` runs the real one on streams of its own.
|
|
166
|
+
|
|
167
|
+
### A new version of the contracts
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
npm update @widgetic/api-contracts # the gateway's release put a new version on npm
|
|
171
|
+
npm run generate && npm run snapshot && npm run docs
|
|
172
|
+
npm run check # the diff of the snapshot and of docs/commands is the change
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Commit the lock file, the snapshot and `docs/commands` together. To try a change of the gateway's description before it is published, build the contracts there (`npm run build-api-contracts`) and put them here with `npm run contracts:local -- <folder or tarball>`; it changes nothing but `node_modules`.
|
|
176
|
+
|
|
177
|
+
### Release
|
|
178
|
+
|
|
179
|
+
`npm run publish-to-npm` (the script of `Frontend/dev-tools`) merges what is on `origin`, runs `npm run check` and `npm run check:release`, raises the patch version, builds, publishes with `npm publish --ignore-scripts --access public`, commits the version, tags `v<version>` and pushes. A release is the owner's decision: nothing publishes by itself, and the `publish` job of the pipeline is manual.
|
|
180
|
+
|
|
181
|
+
Before a release, copy `docs/commands/*.mdx` into the docs site (`Frontend/docs-site/docs/cli/commands/`) and list the pages in its `docs.json`.
|
|
182
|
+
|
|
183
|
+
## Licence
|
|
184
|
+
|
|
185
|
+
Widgetic Integration License: see `LICENSE`.
|
package/bin/widgetic.js
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Command, CommanderError } from 'commander';
|
|
2
|
+
import { registerAuthCommands } from "./commands/auth.js";
|
|
3
|
+
import { registerApiCall, registerMcpCall } from "./commands/call.js";
|
|
4
|
+
import { registerCompletionCommands } from "./commands/completion.js";
|
|
5
|
+
import { registerDoctorCommand } from "./commands/doctor.js";
|
|
6
|
+
import { registerSchemaCommand } from "./commands/schema.js";
|
|
7
|
+
import { registerUpdateCommand } from "./commands/update.js";
|
|
8
|
+
import { CliError, EXIT_CODES, usageError } from "./errors.js";
|
|
9
|
+
import { loadMcpManifest, loadRestManifest } from "./manifest/load.js";
|
|
10
|
+
import { registerMcpCommands } from "./mcp/register.js";
|
|
11
|
+
import { asCliError, printError } from "./output.js";
|
|
12
|
+
import { registerRestCommands } from "./rest/register.js";
|
|
13
|
+
import { cliVersion } from "./version.js";
|
|
14
|
+
/**
|
|
15
|
+
* The program: the root `widgetic` command and what hangs from it. `run` parses an argument list against an
|
|
16
|
+
* Environment and returns the exit code; nothing here calls `process.exit`, so a test runs the real program in-process.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Commander codes that mean "nothing went wrong, the text was printed": --help, --version, and the help of a command
|
|
20
|
+
* that was run without its subcommand (`widgetic api` lists what is under `api`, it is not a failure).
|
|
21
|
+
*/
|
|
22
|
+
const CLEAN_COMMANDER_CODES = new Set(['commander.helpDisplayed', 'commander.version', 'commander.help']);
|
|
23
|
+
export function createRootCommand(environment) {
|
|
24
|
+
const root = new Command('widgetic');
|
|
25
|
+
root
|
|
26
|
+
.description('The Widgetic command line: the public REST API and the AI build tools, in one binary.')
|
|
27
|
+
.version(cliVersion(), '-V, --version', 'print the version of this CLI')
|
|
28
|
+
// A subcommand has its own options (a tool may have a `--version` of its own), so the root's are read only
|
|
29
|
+
// before the subcommand: `widgetic mcp publish-widget --version 3` is the tool's flag, never the CLI's.
|
|
30
|
+
.enablePositionalOptions()
|
|
31
|
+
.allowExcessArguments(false)
|
|
32
|
+
.showSuggestionAfterError(true)
|
|
33
|
+
.exitOverride()
|
|
34
|
+
.configureOutput({
|
|
35
|
+
writeOut: (text) => environment.stdout.write(text),
|
|
36
|
+
// Commander writes to this stream only to show the help of a command that was run without its subcommand
|
|
37
|
+
// (`widgetic api`). That is a question ("what can I do here?"), and its answer belongs on stdout.
|
|
38
|
+
writeErr: (text) => environment.stdout.write(text),
|
|
39
|
+
// Commander's own error line is not printed: `run` prints every error itself, the same way, so that
|
|
40
|
+
// `--json` gets JSON for a flag that is missing as well as for a server that refused.
|
|
41
|
+
outputError: () => { }
|
|
42
|
+
});
|
|
43
|
+
return root;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The namespace the arguments are about (`api` or `mcp`), when they are about one: the first word, or the one after
|
|
47
|
+
* `help`. Only that namespace's commands are built, so `widgetic mcp …` never reads the 500 KB REST manifest.
|
|
48
|
+
*/
|
|
49
|
+
export function namespaceOf(argv) {
|
|
50
|
+
const words = argv.filter((word) => !word.startsWith('-'));
|
|
51
|
+
const first = words[0] === 'help' ? words[1] : words[0];
|
|
52
|
+
return first === 'api' || first === 'mcp' ? first : undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The whole program for `argv`. The namespaces are listed in the help of `widgetic` itself, and their commands are
|
|
56
|
+
* added only for the one the arguments name; the commands that are not generated (`auth`, `doctor`, `update`,
|
|
57
|
+
* `schema`, `completion`) are always there.
|
|
58
|
+
*/
|
|
59
|
+
export function buildProgram(environment, argv = []) {
|
|
60
|
+
const root = createRootCommand(environment);
|
|
61
|
+
const api = root
|
|
62
|
+
.command('api')
|
|
63
|
+
.description('Call the public REST API: one command for every operation of its OpenAPI description.');
|
|
64
|
+
const mcp = root
|
|
65
|
+
.command('mcp')
|
|
66
|
+
.description('Run the AI build tools of the Widgetic MCP server: one command for every tool.');
|
|
67
|
+
const namespace = namespaceOf(argv);
|
|
68
|
+
if (namespace === 'api') {
|
|
69
|
+
const manifest = loadRestManifest();
|
|
70
|
+
registerRestCommands(api, manifest, environment);
|
|
71
|
+
registerApiCall(api, manifest);
|
|
72
|
+
}
|
|
73
|
+
if (namespace === 'mcp') {
|
|
74
|
+
const manifest = loadMcpManifest();
|
|
75
|
+
registerMcpCommands(mcp, manifest, environment);
|
|
76
|
+
registerMcpCall(mcp, manifest, { environment, manifest });
|
|
77
|
+
}
|
|
78
|
+
registerAuthCommands(root, environment);
|
|
79
|
+
registerDoctorCommand(root, environment);
|
|
80
|
+
registerUpdateCommand(root, environment);
|
|
81
|
+
registerSchemaCommand(root, environment);
|
|
82
|
+
registerCompletionCommands(root, environment, (words) => buildProgram(environment, words));
|
|
83
|
+
return root;
|
|
84
|
+
}
|
|
85
|
+
/** What a Commander error becomes: `null` when it only means "the help or the version was printed". */
|
|
86
|
+
function fromCommanderError(error) {
|
|
87
|
+
if (CLEAN_COMMANDER_CODES.has(error.code))
|
|
88
|
+
return null;
|
|
89
|
+
const message = error.message.replace(/^error:\s*/i, '');
|
|
90
|
+
return usageError(message, 'Run the command with --help to see what it takes.');
|
|
91
|
+
}
|
|
92
|
+
export async function run(argv, environment) {
|
|
93
|
+
const json = argv.includes('--json');
|
|
94
|
+
try {
|
|
95
|
+
const program = buildProgram(environment, argv);
|
|
96
|
+
await program.parseAsync([...argv], { from: 'user' });
|
|
97
|
+
return EXIT_CODES.ok;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
const failure = error instanceof CommanderError ? fromCommanderError(error) : asCliError(error, environment);
|
|
101
|
+
if (failure === null)
|
|
102
|
+
return EXIT_CODES.ok;
|
|
103
|
+
printError(environment, failure, json);
|
|
104
|
+
return failure.exitCode;
|
|
105
|
+
}
|
|
106
|
+
}
|