@safekb/mcp 0.7.0 → 0.9.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/README.md CHANGED
@@ -94,7 +94,7 @@ every tool that keeps a key this way; see [Where secrets are kept](#where-secret
94
94
  | `--url <api url>` | `KB_API_URL` | API base URL. Otherwise the URL in the token file, or `https://api.safekb.com` when authorizing. When serving, it must have the origin the token was issued for: the token is never sent anywhere else. |
95
95
  | `--config <path>` | `SAFEKB_MCP_CONFIG` | Token file. Default `~/.config/safekb/mcp.json`. Use one per project for per-project access. |
96
96
  | `--keyring <dir>` | `SAFEKB_SEALED_KEYRING` | Directory of sealed key files (`*.json`). Otherwise the one in the store settings, if any. |
97
- | `--sealed-only` | | Expose only the sealed tools; the hosted tools are not proxied. |
97
+ | `--sealed-only` | | Expose only the sealed and key tools; the hosted tools are not proxied, and `export_backup` and `import_backup` are not offered. |
98
98
  | `--authorize` | | Authorize again in the browser and replace the token file. |
99
99
  | `--configure` | | Choose again where new sealed keys are kept. Writes `stores.json` next to the token file. |
100
100
  | `--sidecar` | | Run the sidecar in this terminal. With `--confirm` it asks before every release of a secret. |
@@ -107,6 +107,7 @@ every tool that keeps a key this way; see [Where secrets are kept](#where-secret
107
107
  | `--rotate-key <ref>` | | Replace a seal root's secret with a new generated key; takes `--store`. |
108
108
  | `--move-key <ref>` | | Move a seal root's current key to `--store system\|file`. |
109
109
  | `--delete-key <ref>` | | Remove a seal root's keys from this machine, after a typed confirmation. |
110
+ | `--auto-unlock <ref>\|all` | | With `--set on\|off`: let the sealed tools unlock this root (or every root) by themselves. See [Unlocking automatically](#unlocking-automatically). |
110
111
  | `--export <file>` | | Write what this token reaches to a new passphrase-protected file. Takes `--space`, `--doc`, `--compress`, `--skip-locked`. See [Export and import](#export-and-import). |
111
112
  | `--import <file>` | | Create what such a file holds in this token's account, as new things. Takes `--into-space`, `--store`, `--keys`. |
112
113
  | | `SAFEKB_SEALED_IDLE_TTL` | Seconds an unlocked seal root may stay idle. Default 900. `unlock_sealed` can set it per root with `idle_ttl_seconds` (60 to 3600). |
@@ -136,18 +137,54 @@ Its secret is a generated key or a passphrase.
136
137
  - `unlock_sealed` with only `root_id` takes the secret from the store that has it; `secret_file` reads
137
138
  it from a file.
138
139
  - Then `search_sealed`, `list_sealed`, `read_sealed_document`, `get_sealed_section`,
139
- `update_sealed_document`, `replace_in_sealed_document`, and the move, archive, delete, export,
140
+ `update_sealed_document`, `replace_in_sealed_document`, and the move, archive, delete,
140
141
  attachment and deposit tools.
142
+ - `seal_document` and `unseal_document` move a document between hosted and sealed form; see
143
+ [Moving documents in and out of a seal root](#moving-documents-in-and-out-of-a-seal-root).
144
+ `unseal_document` is not a backup: see [Export and import](#export-and-import).
141
145
  - `lock_sealed`, the idle timeout, or the end of the process forget the keys.
142
146
  - `change_seal_password` replaces the root's secret. Once the server has taken the new one, the
143
147
  superseded key is removed from the store that kept it (the system keyring, or any key file in the
144
148
  key file directory). A superseded passphrase, or a key file you had written elsewhere with
145
149
  `key_out_path`, is yours to remove.
146
- - Deleting a seal root (`delete_sealed_document`, or `export_sealed_document` of a standalone sealed
147
- document) also removes its keys and its name from this machine.
150
+ - Deleting a seal root (`delete_sealed_document`, or `unseal_document` of a standalone sealed
151
+ document with `delete_original`) also removes its keys and its name from this machine.
148
152
  - A tool parameter that names a seal root (`root_id`, `new_root_id`) takes its id or its
149
153
  [name](#names-of-seal-roots). Documents and folders inside a root are named by id.
150
154
 
155
+ ### Moving documents in and out of a seal root
156
+
157
+ `seal_document` copies a hosted (unsealed) document into a sealed folder; `unseal_document` copies a
158
+ sealed document out as a normal one. The content is read, converted and written by the client: you pass
159
+ ids and get ids and counts back, so nothing is retyped by the model and nothing of the text enters the
160
+ conversation.
161
+
162
+ | | `seal_document` | `unseal_document` |
163
+ |---|---|---|
164
+ | Takes | `document_id` of a hosted document; `parent_id` (a sealed folder of the same space, or a seal root's id), or `store` / `key_out_path` to make the copy a seal root of its own | `document_id` of a sealed document; optional plain `parent_id`; `format` |
165
+ | Result | a sealed document with a new id | a normal document with a new id: a canvas (default), or a markdown document with `format: "markdown"` |
166
+ | Not available | with `--sealed-only` (it reads hosted content) | |
167
+
168
+ - **Canvas structure is kept.** A canvas becomes the markdown body of the sealed document in the
169
+ dialect of `@safekb/canvas-markdown` (headings, lists, tables, callouts, code, attachments, …) and is
170
+ rebuilt from it on `unseal_document`. A part the dialect cannot hold exactly is reported and stops the
171
+ copy. A sealed body that is not in the dialect (typed as plain markdown elsewhere) is reported too:
172
+ `format: "markdown"` keeps it as markdown text.
173
+ - **Attachments move with the document**, sealed or unsealed, as streams: no file is written to disk.
174
+ An attachment that is a derived crop, or larger than the server accepts, stops the copy.
175
+ - **Nothing is reported done before it was checked.** The copy is read back, decrypted where it is
176
+ sealed, and compared with the original: markdown byte for byte, blocks structurally, attachments by size
177
+ and hash. If anything differs the copy is deleted again and the original is untouched.
178
+ - **`dry_run` is true by default**: it creates nothing and reports the format, counts, attachments,
179
+ target, what stands in the way (`problems`) and what is not carried. Pass `dry_run: false` to do it.
180
+ - **`delete_original` is false by default.** With it, the original is deleted after the check, and only
181
+ if it was not edited meanwhile. A sealed document does not carry external refs, workflow status, due
182
+ date, links or version history: `delete_original` refuses while a hosted document has any, and says
183
+ which. Keep the original, or remove them first.
184
+ - The sealed copy stays in the space of its seal root, so `parent_id` must be in the document's space.
185
+ - Not atomic: a failure after the copy was made says what exists (the copy, the original, or both).
186
+ - Since 0.9.0 `unseal_document` no longer deletes the sealed original by default and makes a canvas, not a markdown document: pass `delete_original: true` and `format: "markdown"` for the old behavior. It also moves attachments instead of refusing documents that have them.
187
+
151
188
  ## Where secrets are kept
152
189
 
153
190
  Every seal root has its own secret, and each can be kept in a different place. All three work side by
@@ -187,6 +224,10 @@ npx -y @safekb/mcp --sidecar
187
224
  - A new seal root with `store: "sidecar"` makes the sidecar ask you for a new passphrase, twice, while
188
225
  the tool call waits.
189
226
  - With `--confirm` it asks for your approval before every release of a secret.
227
+ - The [key tools](#the-key-tools) and the backup tools ask here for the passphrase of a file, for
228
+ the name or id that confirms a deletion, and for the confirmation that turns auto-unlock on. Such a
229
+ passphrase is used for that one call and not held. An empty entry declines.
230
+ - The sidecar and the MCP client must be the same version of `@safekb/mcp`.
190
231
 
191
232
  Several roots can be held at once, each with its own passphrase and time. The sidecar listens on a Unix
192
233
  socket only your user can open; it is not available on Windows. Any program running as you can still
@@ -256,6 +297,44 @@ A seal root is given by its [name](#names-of-seal-roots) or its id.
256
297
  When a step after the point of no return fails (the superseded key cannot be removed after a rotation,
257
298
  the old copy cannot be removed after a move), the command fails and says where the key now is.
258
299
 
300
+ ### Unlocking automatically
301
+
302
+ ```sh
303
+ npx -y @safekb/mcp --auto-unlock "Bank" --set on # or a root id
304
+ npx -y @safekb/mcp --auto-unlock all --set on # every root without a setting of its own
305
+ npx -y @safekb/mcp --auto-unlock "Bank" --set off
306
+ ```
307
+
308
+ A sealed tool that needs a locked root it is allowed to open unlocks it by itself, as `unlock_sealed`
309
+ with only `root_id` would, with the same idle timeout, and goes on. **The trade-off: any session that
310
+ uses this token on this machine can then read such a root without unlocking it first.**
311
+
312
+ - Only a key kept in the system keyring or the key file directory is used. A root opened by a
313
+ passphrase never unlocks by itself: turning it on for one is refused, and with `all` such a root
314
+ stays locked and says so.
315
+ - A root's own setting wins over `all`. The settings are in `auto-unlock.json` next to the token file
316
+ (mode 0600), read on every use: a change applies to a running client at once.
317
+ - A failed automatic unlock (no key, a key in both stores) is the tool's error; nothing is guessed.
318
+ Without the setting a locked root stays the usual "call unlock_sealed first" error.
319
+ - `--list-keys` adds `unlocks automatically` to a current key whose root does; `list_sealed`,
320
+ `list_seal_keys` and `get_auto_unlock` show `auto_unlock`.
321
+ - `set_auto_unlock` turns it on only after you type the root's name or id (or `all`) at the sidecar:
322
+ the model cannot widen access on its own. Turning it off needs no confirmation.
323
+
324
+ ### The key tools
325
+
326
+ Everything above is also an MCP tool, and none of them takes or returns a secret: `list_seal_keys`,
327
+ `rename_seal_key`, `rotate_seal_key`, `move_seal_key`, `delete_seal_key`, `export_seal_keys`,
328
+ `import_seal_keys`, `get_auto_unlock` and `set_auto_unlock`, and for backups `export_backup` and
329
+ `import_backup` (see [Export and import](#export-and-import)).
330
+
331
+ What you type at the terminal for the command line, you type at the [sidecar](#the-sidecar) for a
332
+ tool, while the call waits: the passphrase of an export file or a key export (twice for a new one),
333
+ the name or id that confirms `delete_seal_key`, and the confirmation that turns auto-unlock on. It
334
+ reaches this client only, never the conversation. Without a running sidecar such a tool fails and says
335
+ to start one (`safekb-mcp --sidecar`); on Windows, which has no sidecar, it names the command to run
336
+ instead. File paths given to the tools must be absolute.
337
+
259
338
  ### Moving keys to another machine
260
339
 
261
340
  A generated key lives only where it was made: in the system keyring or in a key file. To carry all of
@@ -273,20 +352,22 @@ npx -y @safekb/mcp --import-keys keys.safekb # on the other machine, after --s
273
352
  Without the passphrase the file opens for nobody.
274
353
  - Import asks for that passphrase and keeps the keys in the default store of that
275
354
  machine. A key the machine already keeps is left as it is, so an import can be repeated.
276
- - Both need a terminal: the passphrase is never an argument, so it is in no shell history, no process
277
- list and no conversation.
355
+ - Both need a terminal, or the sidecar for `export_seal_keys` and `import_seal_keys`: the passphrase
356
+ is never an argument, so it is in no shell history, no process list and no conversation.
278
357
  - A root that is opened by a passphrase has no key on any machine. There is nothing to export: type
279
358
  the passphrase at the other machine's sidecar.
280
359
 
281
360
  ## Sealed credentials
282
361
 
283
- A sealed credential belongs to a seal root. Its password, TOTP seed, `sealed_notes` and the values of
362
+ A sealed credential belongs to a seal root and is kept in that root's space. Its password, TOTP seed, `sealed_notes` and the values of
284
363
  sealed custom fields are encrypted on your machine; its title, username, url, tags, `notes`, visible
285
364
  custom fields and the key name of every custom field (sealed ones too) stay readable by the server, so the standard `search_credentials`, `list_credentials` and
286
365
  `find_credential_duplicates` keep working. Never put a secret into `notes`.
287
366
 
288
367
  - `insert_sealed_credential`, `update_sealed_credential`, `delete_sealed_credential`: signed by the unlocked root.
289
- - `move_sealed_credential` hands a credential to another seal root; both roots must be unlocked.
368
+ - `move_sealed_credential` hands a credential to another seal root of its space; both roots must be
369
+ unlocked. A sealed credential changes space only with its root: moving the root's folder to another
370
+ space takes its credentials along (a credential's group in the old space is left behind).
290
371
  - `reveal_sealed_credential` and `get_sealed_totp` decrypt locally. The hosted `reveal_credential` and
291
372
  `get_credential_totp` cannot open a sealed credential.
292
373
  - Visible fields can be edited without the secret. Such an edit is flagged (`visible_tampered`) with the
@@ -294,7 +375,8 @@ custom fields and the key name of every custom field (sealed ones too) stay read
294
375
 
295
376
  ### Migrating server-encrypted credentials
296
377
 
297
- `migrate_credentials_to_sealed` turns every credential the token can see into a sealed one. It is
378
+ `migrate_credentials_to_sealed` turns every credential the token can see in the root's space into a
379
+ sealed one; `in_other_spaces` counts those that need a seal root of their own space. It is
298
380
  irreversible per credential: the server drops its own encrypted secrets and that credential's history.
299
381
 
300
382
  1. Unlock the target root, then run it with `dry_run: true` (the default). Nothing changes. It lists the
@@ -323,7 +405,10 @@ npx -y @safekb/mcp --import all.safekb --keys pc-a.keys --keys pc-b.keys # key
323
405
  ```
324
406
 
325
407
  Both need a terminal: the passphrase of the file is typed there (twice for an export) and is never an
326
- argument. The file is encrypted with AES-256-GCM under a key derived from the passphrase with Argon2id,
408
+ argument. The tools `export_backup` and `import_backup` do the same, with every passphrase typed at the
409
+ [sidecar](#the-sidecar); their result holds counts and names, never content or a secret. A large export
410
+ runs for minutes; the tool reports progress to an MCP client that asks for it. With `--sealed-only`
411
+ these two tools are not offered, since they reach unsealed content too. The file is encrypted with AES-256-GCM under a key derived from the passphrase with Argon2id,
327
412
  in parts that cannot be dropped, swapped or cut off unnoticed; `--compress` gzips before encrypting.
328
413
  Attachments are streamed, so the file may be large. **The file holds every secret in plaintext under
329
414
  that one passphrase**: passwords, TOTP seeds and the content of sealed documents.
@@ -369,6 +454,8 @@ What an import does:
369
454
  or the one named by `--store`; a passphrase root therefore becomes a key root. A seal root whose
370
455
  name is taken on this machine gets ` (imported)` appended. **Back the new keys up**
371
456
  (`--export-keys`).
457
+ - A sealed credential is created in the space of its new seal root. One exported from another space
458
+ than its root's is placed there, and the report says so.
372
459
  - A project whose slug is taken gets a numbered one. A task keeps its assignee only in the account
373
460
  the export came from.
374
461
  - When a step fails the import stops, says at which record, and leaves what it had created; nothing
@@ -119,3 +119,173 @@ Special thank you to the authors of original C algorithms:
119
119
  - Mark Adler
120
120
  - Yann Collet
121
121
  ```
122
+
123
+ ## entities
124
+
125
+ ```
126
+ Copyright (c) Felix Böhm
127
+ All rights reserved.
128
+
129
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
130
+
131
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
132
+
133
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
134
+
135
+ THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS,
136
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
137
+ ```
138
+
139
+ ## linkify-it
140
+
141
+ ```
142
+ Copyright (c) 2015 Vitaly Puzrin.
143
+
144
+ Permission is hereby granted, free of charge, to any person
145
+ obtaining a copy of this software and associated documentation
146
+ files (the "Software"), to deal in the Software without
147
+ restriction, including without limitation the rights to use,
148
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
149
+ copies of the Software, and to permit persons to whom the
150
+ Software is furnished to do so, subject to the following
151
+ conditions:
152
+
153
+ The above copyright notice and this permission notice shall be
154
+ included in all copies or substantial portions of the Software.
155
+
156
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
157
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
158
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
159
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
160
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
161
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
162
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
163
+ OTHER DEALINGS IN THE SOFTWARE.
164
+ ```
165
+
166
+ ## markdown-it
167
+
168
+ ```
169
+ Copyright (c) 2014 Vitaly Puzrin, Alex Kocharin.
170
+
171
+ Permission is hereby granted, free of charge, to any person
172
+ obtaining a copy of this software and associated documentation
173
+ files (the "Software"), to deal in the Software without
174
+ restriction, including without limitation the rights to use,
175
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
176
+ copies of the Software, and to permit persons to whom the
177
+ Software is furnished to do so, subject to the following
178
+ conditions:
179
+
180
+ The above copyright notice and this permission notice shall be
181
+ included in all copies or substantial portions of the Software.
182
+
183
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
184
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
185
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
186
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
187
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
188
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
189
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
190
+ OTHER DEALINGS IN THE SOFTWARE.
191
+ ```
192
+
193
+ ## mdurl
194
+
195
+ ```
196
+ Copyright (c) 2015 Vitaly Puzrin, Alex Kocharin.
197
+
198
+ Permission is hereby granted, free of charge, to any person
199
+ obtaining a copy of this software and associated documentation
200
+ files (the "Software"), to deal in the Software without
201
+ restriction, including without limitation the rights to use,
202
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
203
+ copies of the Software, and to permit persons to whom the
204
+ Software is furnished to do so, subject to the following
205
+ conditions:
206
+
207
+ The above copyright notice and this permission notice shall be
208
+ included in all copies or substantial portions of the Software.
209
+
210
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
211
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
212
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
213
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
214
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
215
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
216
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
217
+ OTHER DEALINGS IN THE SOFTWARE.
218
+
219
+ --------------------------------------------------------------------------------
220
+
221
+ .parse() is based on Joyent's node.js `url` code:
222
+
223
+ Copyright Joyent, Inc. and other Node contributors. All rights reserved.
224
+ Permission is hereby granted, free of charge, to any person obtaining a copy
225
+ of this software and associated documentation files (the "Software"), to
226
+ deal in the Software without restriction, including without limitation the
227
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
228
+ sell copies of the Software, and to permit persons to whom the Software is
229
+ furnished to do so, subject to the following conditions:
230
+
231
+ The above copyright notice and this permission notice shall be included in
232
+ all copies or substantial portions of the Software.
233
+
234
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
235
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
236
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
237
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
238
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
239
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
240
+ IN THE SOFTWARE.
241
+ ```
242
+
243
+ ## punycode.js
244
+
245
+ ```
246
+ Copyright Mathias Bynens <https://mathiasbynens.be/>
247
+
248
+ Permission is hereby granted, free of charge, to any person obtaining
249
+ a copy of this software and associated documentation files (the
250
+ "Software"), to deal in the Software without restriction, including
251
+ without limitation the rights to use, copy, modify, merge, publish,
252
+ distribute, sublicense, and/or sell copies of the Software, and to
253
+ permit persons to whom the Software is furnished to do so, subject to
254
+ the following conditions:
255
+
256
+ The above copyright notice and this permission notice shall be
257
+ included in all copies or substantial portions of the Software.
258
+
259
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
260
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
261
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
262
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
263
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
264
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
265
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
266
+ ```
267
+
268
+ ## uc.micro
269
+
270
+ ```
271
+ Copyright Mathias Bynens <https://mathiasbynens.be/>
272
+
273
+ Permission is hereby granted, free of charge, to any person obtaining
274
+ a copy of this software and associated documentation files (the
275
+ "Software"), to deal in the Software without restriction, including
276
+ without limitation the rights to use, copy, modify, merge, publish,
277
+ distribute, sublicense, and/or sell copies of the Software, and to
278
+ permit persons to whom the Software is furnished to do so, subject to
279
+ the following conditions:
280
+
281
+ The above copyright notice and this permission notice shall be
282
+ included in all copies or substantial portions of the Software.
283
+
284
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
285
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
286
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
287
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
288
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
289
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
290
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
291
+ ```