@deepdockai/cli 1.0.2 → 1.0.4
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 +14 -0
- package/README.md +37 -10
- package/dist/deepdock.js +194 -86
- package/package.json +3 -1
- package/skill/references/auth-and-keys.md +3 -3
- package/skill/references/cli.md +6 -4
- package/skill/references/testing.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.4
|
|
4
|
+
|
|
5
|
+
- `DEEPDOCK_CONFIG_DIR` moves the settings file (and only it) to a directory of your choice, on every platform; it must be an absolute path. Useful for keeping separate sign-ins apart, for example one per project or account.
|
|
6
|
+
|
|
7
|
+
## 1.0.3
|
|
8
|
+
|
|
9
|
+
- `auth logout --keep-keys` revokes only the sign-in. On a terminal, plain `auth logout` lists the API keys and asks before revoking them (default: keep); with `--json`, `--yes` or no terminal it still revokes them. `keys create` says so.
|
|
10
|
+
- `keys create` no longer advises revoking the key "when you ship"; it prints the expiry and how to revoke.
|
|
11
|
+
- With exactly one organisation it is used automatically (and named), and `auth login` selects it. With several and none chosen, `org_required` stays.
|
|
12
|
+
- The approval page opens in your browser on a terminal (not in CI, without a display, with `--no-browser` or `DEEPDOCK_NO_BROWSER=1`). The line says "Opening the approval page in your browser" when it does.
|
|
13
|
+
- `Organisation:` is printed once per command, not again after the wait.
|
|
14
|
+
- Output colours by role: warning and error labels, success verbs, bold organisation and approval prompt, dim waiting line.
|
|
15
|
+
- The approval link is blue and underlined, and clickable in terminals that support links. Plain text when output is piped, under `--json`, or with `NO_COLOR`.
|
|
16
|
+
|
|
3
17
|
## 1.0.2
|
|
4
18
|
|
|
5
19
|
- The install commands no longer pin a major version: `npx @deepdockai/cli auth login`, `npm install -g @deepdockai/cli`.
|
package/README.md
CHANGED
|
@@ -30,14 +30,16 @@ holding only invented, marked data. While unpaired, the client shows a pairing c
|
|
|
30
30
|
|
|
31
31
|
## Quick start
|
|
32
32
|
|
|
33
|
-
Sign in. The CLI prints
|
|
33
|
+
Sign in. The CLI prints the link and opens it in your browser (not in CI, without a display, with
|
|
34
|
+
`--no-browser` or `DEEPDOCK_NO_BROWSER=1`). Check that the page names your computer and CLI version,
|
|
34
35
|
and click Approve. No password is typed into the terminal.
|
|
35
36
|
|
|
36
37
|
```
|
|
37
38
|
$ deepdock auth login
|
|
38
|
-
|
|
39
|
+
Opening the approval page in your browser (it expires in 10 minutes):
|
|
39
40
|
https://dash.deepdock.ai/cli?request=rq_...
|
|
40
41
|
The page shows this computer's name and the CLI version: check them before you approve.
|
|
42
|
+
Opened in your browser.
|
|
41
43
|
Waiting for approval in the browser.
|
|
42
44
|
|
|
43
45
|
Signed in as anna.mueller@muster-it.example.
|
|
@@ -91,7 +93,7 @@ deepdock skill
|
|
|
91
93
|
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
92
94
|
| `auth login` | sign in as yourself, approved in the browser; the sign-in lasts 90 days |
|
|
93
95
|
| `auth status` | who is signed in, the organisations, and the keys this CLI created (id, name, prefix, expiry, file; never the value); exits 5 when not signed in, like `gh auth status` |
|
|
94
|
-
| `auth logout`
|
|
96
|
+
| `auth logout [--keep-keys]` | revoke the sign-in and every key this CLI created (`--keep-keys`: only the sign-in) |
|
|
95
97
|
| `org list`, `org select [slug]` | your organisations; set the default one |
|
|
96
98
|
| `systems list` | the systems and actions assigned to your organisation |
|
|
97
99
|
| `systems openapi <system>` | a system's OpenAPI document on stdout, bare when redirected; save it to a file |
|
|
@@ -110,7 +112,8 @@ and `keys revoke` without a question, `-h, --help` everywhere and `-v, --version
|
|
|
110
112
|
|
|
111
113
|
Two things are never guessed. A device is always named by its full id: no default device, no
|
|
112
114
|
lookup by label. The organisation comes from `--org`, then `DEEPDOCK_ORG`, then the default
|
|
113
|
-
chosen with `deepdock org select
|
|
115
|
+
chosen with `deepdock org select`. With exactly one organisation it is used without asking (and
|
|
116
|
+
named on stderr); with several and none chosen the command stops and lists them.
|
|
114
117
|
|
|
115
118
|
## The approval flow
|
|
116
119
|
|
|
@@ -222,7 +225,8 @@ macOS and Linux need nothing special. On Windows:
|
|
|
222
225
|
- **Keys expire and can be revoked.** A key reaches every device of the organisation until it
|
|
223
226
|
expires (at most 90 days from the CLI) or is revoked. `deepdock keys revoke <id>` revokes it and
|
|
224
227
|
removes the line the CLI wrote; `deepdock auth logout` revokes the sign-in and every key this
|
|
225
|
-
CLI created
|
|
228
|
+
CLI created, unless you pass `--keep-keys`. On a terminal it lists the keys and asks first
|
|
229
|
+
(default: keep); with `--json` or without a terminal it revokes them unless `--keep-keys` is passed. In `--json`, `data.removed` says what happened to the line: `null` for a key this
|
|
226
230
|
CLI never wrote, otherwise `{ file, name, removal }` with `removal` `done`, `absent` (the line
|
|
227
231
|
was already gone or holds another value) or `failed` (with a `reason`). If the file is held open
|
|
228
232
|
by another program (a dev server watching it, on Windows), the key is still revoked and the
|
|
@@ -232,12 +236,13 @@ macOS and Linux need nothing special. On Windows:
|
|
|
232
236
|
revoked, and `data.alreadyRevoked` says so). `auth logout` reports the failure in
|
|
233
237
|
`apiKeysRevoked[].removed` and in `meta.hint`, and clears its local records either way.
|
|
234
238
|
- **Local state** lives in `$XDG_CONFIG_HOME/deepdock/config.json`, else
|
|
235
|
-
`~/.config/deepdock/config.json` (`%APPDATA%\deepdock\config.json` on Windows),
|
|
236
|
-
|
|
239
|
+
`~/.config/deepdock/config.json` (`%APPDATA%\deepdock\config.json` on Windows), or in
|
|
240
|
+
`$DEEPDOCK_CONFIG_DIR/config.json` when that absolute path is set; mode 600, in a directory the
|
|
241
|
+
CLI creates mode 700 when it does not exist. It holds the sign-in, the default organisation, the ids of the keys this
|
|
237
242
|
CLI created (never their values) and a pending approval request. A file other users can read
|
|
238
243
|
is refused with the `chmod` to run.
|
|
239
244
|
- **Environment:** `DEEPDOCK_ORG`, `DEEPDOCK_BASE_URL` (https, or http for localhost only),
|
|
240
|
-
`NO_COLOR`, `FORCE_COLOR` (colours messages on a stream that is not a terminal; `NO_COLOR` wins). A sign-in or pending request is never sent to a different base URL than the one it
|
|
245
|
+
`DEEPDOCK_CONFIG_DIR`, `NO_COLOR`, `FORCE_COLOR` (colours messages on a stream that is not a terminal; `NO_COLOR` wins). A sign-in or pending request is never sent to a different base URL than the one it
|
|
241
246
|
was made against.
|
|
242
247
|
|
|
243
248
|
Report a vulnerability by email to torben@deepdock.ai, not in a public issue: [SECURITY.md](SECURITY.md).
|
|
@@ -252,11 +257,33 @@ bun run test:node # the same tests under Node (needs dist/ from the build)
|
|
|
252
257
|
bun run format && bun run lint && bun run typecheck && bun run check:public
|
|
253
258
|
```
|
|
254
259
|
|
|
255
|
-
|
|
260
|
+
### Trying a change locally
|
|
261
|
+
|
|
262
|
+
`bun run dev` runs the CLI from source. Each API gets a settings directory of its own under
|
|
263
|
+
`<config dir>/deepdock-dev/<api host>/`, so a sign-in made against one API never sits next to
|
|
264
|
+
another's; only the CLI's own settings move (`DEEPDOCK_CONFIG_DIR`), nothing else on the machine:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
bun run dev -- auth status # the production API
|
|
268
|
+
DEEPDOCK_BASE_URL=http://localhost:8787 bun run dev -- auth login --no-wait
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The script does not depend on the current directory: from elsewhere, call it by its path
|
|
272
|
+
(`bun /path/to/devkit/scripts/dev.ts ...`), or put the `DEEPDOCK_BASE_URL=... bun .../dev.ts` line
|
|
273
|
+
in a shell alias per API. To run the exact tarball npm would publish, from any directory:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
bun run pack:try -- auth status # builds, packs, prints the one-line command to run it
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
It prints `DEEPDOCK_BASE_URL=... DEEPDOCK_CONFIG_DIR=... npx -y --package=/path/to/deepdockai-cli-<version>.tgz deepdock auth status`,
|
|
280
|
+
for the same API and settings directory `bun run dev` would use (`npx <tarball>` alone would try
|
|
281
|
+
to execute the archive). `tests/fakeApi.ts` is a local stand-in that approves every request at
|
|
282
|
+
once:
|
|
256
283
|
|
|
257
284
|
```bash
|
|
258
285
|
bun scripts/fakeServer.ts & # prints its URL
|
|
259
|
-
DEEPDOCK_BASE_URL=http://127.0.0.1:<port>
|
|
286
|
+
DEEPDOCK_BASE_URL=http://127.0.0.1:<port> bun run dev -- auth login
|
|
260
287
|
```
|
|
261
288
|
|
|
262
289
|
## Licence
|
package/dist/deepdock.js
CHANGED
|
@@ -11323,6 +11323,18 @@ var parseAnswer = async (name, value) => {
|
|
|
11323
11323
|
return result;
|
|
11324
11324
|
};
|
|
11325
11325
|
|
|
11326
|
+
// src/approval/browser.ts
|
|
11327
|
+
var isSet = (value) => value !== undefined && !["", "0", "false"].includes(value.toLowerCase());
|
|
11328
|
+
var browserAllowed = (rt, noBrowser) => {
|
|
11329
|
+
if (noBrowser || isSet(rt.env.DEEPDOCK_NO_BROWSER) || isSet(rt.env.CI))
|
|
11330
|
+
return false;
|
|
11331
|
+
if (isSet(rt.env.SSH_CONNECTION) && rt.platform !== "linux")
|
|
11332
|
+
return false;
|
|
11333
|
+
if (rt.platform === "linux" && !rt.env.DISPLAY && !rt.env.WAYLAND_DISPLAY)
|
|
11334
|
+
return false;
|
|
11335
|
+
return true;
|
|
11336
|
+
};
|
|
11337
|
+
|
|
11326
11338
|
// src/approval/poll.ts
|
|
11327
11339
|
var MAX_CONSECUTIVE_FAILURES = 5;
|
|
11328
11340
|
var EXPIRY_GRACE_MS = 15000;
|
|
@@ -11412,7 +11424,7 @@ import { spawn } from "node:child_process";
|
|
|
11412
11424
|
import { homedir, hostname } from "node:os";
|
|
11413
11425
|
|
|
11414
11426
|
// src/version.ts
|
|
11415
|
-
var VERSION = "1.0.
|
|
11427
|
+
var VERSION = "1.0.4";
|
|
11416
11428
|
|
|
11417
11429
|
// src/runtime.ts
|
|
11418
11430
|
var DEFAULT_BASE_URL = "https://api.deepdock.ai";
|
|
@@ -11423,16 +11435,19 @@ var browserCommand = (platform, url) => {
|
|
|
11423
11435
|
return { command: "rundll32", args: ["url.dll,FileProtocolHandler", url] };
|
|
11424
11436
|
return { command: "xdg-open", args: [url] };
|
|
11425
11437
|
};
|
|
11426
|
-
var openInBrowser = (url) => {
|
|
11438
|
+
var openInBrowser = (url) => new Promise((resolve) => {
|
|
11427
11439
|
const { command, args } = browserCommand(process.platform, url);
|
|
11428
11440
|
try {
|
|
11429
11441
|
const child = spawn(command, args, { stdio: "ignore", detached: true });
|
|
11430
|
-
child.
|
|
11431
|
-
|
|
11442
|
+
child.once("error", () => resolve(false));
|
|
11443
|
+
child.once("spawn", () => {
|
|
11444
|
+
child.unref();
|
|
11445
|
+
resolve(true);
|
|
11432
11446
|
});
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
}
|
|
11447
|
+
} catch {
|
|
11448
|
+
resolve(false);
|
|
11449
|
+
}
|
|
11450
|
+
});
|
|
11436
11451
|
var defaultRuntime = () => {
|
|
11437
11452
|
const interrupt = new AbortController;
|
|
11438
11453
|
process.once("SIGINT", () => interrupt.abort());
|
|
@@ -11638,6 +11653,9 @@ var requestedSlug = (flag, env, config) => flag || env.DEEPDOCK_ORG || config.de
|
|
|
11638
11653
|
var resolveOrg = (requested, orgs, fromStoredDefault = false) => {
|
|
11639
11654
|
const choices = selectableOrgs(orgs);
|
|
11640
11655
|
const slugs = choices.map((org) => org.slug);
|
|
11656
|
+
const [only] = choices;
|
|
11657
|
+
if (!requested && only && choices.length === 1)
|
|
11658
|
+
return { ...only, onlyOne: true };
|
|
11641
11659
|
if (!requested) {
|
|
11642
11660
|
throw new CliError({
|
|
11643
11661
|
code: "org_required",
|
|
@@ -11696,17 +11714,21 @@ var renameWithRetry = async (from, to) => {
|
|
|
11696
11714
|
// src/config/store.ts
|
|
11697
11715
|
var PENDING_KEPT_MS = 60 * 60 * 1000;
|
|
11698
11716
|
var emptyConfig = () => ({ keys: [], pending: [] });
|
|
11699
|
-
var
|
|
11700
|
-
|
|
11701
|
-
|
|
11702
|
-
|
|
11717
|
+
var absoluteDir = (env, name) => {
|
|
11718
|
+
const value = env[name];
|
|
11719
|
+
if (value && !isAbsolute(value)) {
|
|
11720
|
+
throw usageError(`${name} must be an absolute path.`, "Unset it or give a full path.");
|
|
11703
11721
|
}
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
return
|
|
11722
|
+
return value || undefined;
|
|
11723
|
+
};
|
|
11724
|
+
var configBaseDir = (rt) => {
|
|
11725
|
+
if (rt.platform === "win32")
|
|
11726
|
+
return rt.env.APPDATA || join(rt.homedir, "AppData", "Roaming");
|
|
11727
|
+
return absoluteDir(rt.env, "XDG_CONFIG_HOME") ?? join(rt.homedir, ".config");
|
|
11728
|
+
};
|
|
11729
|
+
var configPath = (rt) => {
|
|
11730
|
+
const own = absoluteDir(rt.env, "DEEPDOCK_CONFIG_DIR");
|
|
11731
|
+
return own ? join(own, "config.json") : join(configBaseDir(rt), "deepdock", "config.json");
|
|
11710
11732
|
};
|
|
11711
11733
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11712
11734
|
var text = (value) => typeof value === "string" ? value : undefined;
|
|
@@ -11747,7 +11769,16 @@ var pickKey = (value) => {
|
|
|
11747
11769
|
const { org, id, name, prefix, expiresAt, file, variable } = entry;
|
|
11748
11770
|
if (!org || !id || !name || !prefix || !expiresAt || !file || !variable)
|
|
11749
11771
|
return;
|
|
11750
|
-
return {
|
|
11772
|
+
return {
|
|
11773
|
+
org,
|
|
11774
|
+
id,
|
|
11775
|
+
name,
|
|
11776
|
+
prefix,
|
|
11777
|
+
expiresAt,
|
|
11778
|
+
file,
|
|
11779
|
+
variable,
|
|
11780
|
+
...value.kept === true ? { kept: true } : {}
|
|
11781
|
+
};
|
|
11751
11782
|
};
|
|
11752
11783
|
var pickPending = (value) => {
|
|
11753
11784
|
if (!isRecord3(value))
|
|
@@ -11900,11 +11931,29 @@ var updateConfig = (rt, change) => withLock(rt, async () => {
|
|
|
11900
11931
|
|
|
11901
11932
|
// src/output/styles.ts
|
|
11902
11933
|
import { styleText } from "node:util";
|
|
11934
|
+
var OSC = "\x1B]";
|
|
11935
|
+
var ST = "\x1B\\";
|
|
11936
|
+
var ROLES = {
|
|
11937
|
+
warn: "yellow",
|
|
11938
|
+
error: "red",
|
|
11939
|
+
success: "green",
|
|
11940
|
+
emphasis: "bold",
|
|
11941
|
+
quiet: "dim",
|
|
11942
|
+
link: "link"
|
|
11943
|
+
};
|
|
11903
11944
|
var makeStyler = (rt, stream) => {
|
|
11904
11945
|
const forced = rt.env.FORCE_COLOR !== undefined && !["", "0"].includes(rt.env.FORCE_COLOR);
|
|
11905
11946
|
const enabled = (stream.isTTY || forced) && !rt.env.NO_COLOR && rt.env.TERM !== "dumb";
|
|
11906
|
-
return (name, text) =>
|
|
11947
|
+
return (name, text) => {
|
|
11948
|
+
if (!enabled)
|
|
11949
|
+
return text;
|
|
11950
|
+
if (name !== "link")
|
|
11951
|
+
return styleText(name, text, { validateStream: false });
|
|
11952
|
+
const styled = styleText(["blue", "underline"], text, { validateStream: false });
|
|
11953
|
+
return `${OSC}8;;${text}${ST}${styled}${OSC}8;;${ST}`;
|
|
11954
|
+
};
|
|
11907
11955
|
};
|
|
11956
|
+
var paint = (style, role, text) => style(ROLES[role], text);
|
|
11908
11957
|
|
|
11909
11958
|
// src/output/terminal.ts
|
|
11910
11959
|
var isInteractive = (rt, json) => rt.stdin.isTTY && rt.stdout.isTTY && !json;
|
|
@@ -11926,6 +11975,7 @@ var createContext = (rt, opts) => {
|
|
|
11926
11975
|
const baseUrl = baseUrlFor(rt);
|
|
11927
11976
|
const api = createApi(rt);
|
|
11928
11977
|
let loaded;
|
|
11978
|
+
let announcedOrg = false;
|
|
11929
11979
|
const config = async () => {
|
|
11930
11980
|
loaded ??= await loadConfig(rt);
|
|
11931
11981
|
return loaded;
|
|
@@ -11946,7 +11996,8 @@ var createContext = (rt, opts) => {
|
|
|
11946
11996
|
return loaded;
|
|
11947
11997
|
},
|
|
11948
11998
|
note: (line) => {
|
|
11949
|
-
|
|
11999
|
+
const warning = "Warning:";
|
|
12000
|
+
rt.stderr.write(`${line.startsWith(warning) ? `${paint(ctx.err, "warn", warning)}${line.slice(warning.length)}` : line}
|
|
11950
12001
|
`);
|
|
11951
12002
|
},
|
|
11952
12003
|
signInSecret: async () => {
|
|
@@ -11978,7 +12029,10 @@ var createContext = (rt, opts) => {
|
|
|
11978
12029
|
}
|
|
11979
12030
|
const resolved = resolveOrg(requested, orgs, fromStoredDefault);
|
|
11980
12031
|
ctx.org = { slug: resolved.slug, name: resolved.name };
|
|
11981
|
-
|
|
12032
|
+
if (!announcedOrg) {
|
|
12033
|
+
announcedOrg = true;
|
|
12034
|
+
ctx.note(resolved.onlyOne ? `${paint(ctx.err, "quiet", "Using organisation")} ${paint(ctx.err, "emphasis", resolved.name)} ${paint(ctx.err, "quiet", `(${resolved.slug}).`)}` : `${paint(ctx.err, "quiet", "Organisation:")} ${paint(ctx.err, "emphasis", resolved.name)} ${paint(ctx.err, "quiet", `(${resolved.slug})`)}`);
|
|
12035
|
+
}
|
|
11982
12036
|
return resolved;
|
|
11983
12037
|
},
|
|
11984
12038
|
callInOrg: async (req) => {
|
|
@@ -12029,7 +12083,7 @@ Examples
|
|
|
12029
12083
|
|
|
12030
12084
|
More
|
|
12031
12085
|
Exit codes: \`deepdock help exit-codes\`. API reference per system: \`deepdock systems openapi <system>\`.
|
|
12032
|
-
Environment: DEEPDOCK_ORG, DEEPDOCK_BASE_URL, NO_COLOR, FORCE_COLOR.
|
|
12086
|
+
Environment: DEEPDOCK_ORG, DEEPDOCK_BASE_URL, DEEPDOCK_CONFIG_DIR, NO_COLOR, FORCE_COLOR.
|
|
12033
12087
|
Actions run with an API key from your own code, never from this CLI: \`deepdock keys create --help\`.
|
|
12034
12088
|
`;
|
|
12035
12089
|
var AUTH_LOGIN_HELP = `Sign in to Deepdock as yourself.
|
|
@@ -12044,12 +12098,14 @@ an action; actions run from your code with an API key (\`deepdock keys create\`)
|
|
|
12044
12098
|
Usage
|
|
12045
12099
|
deepdock auth login [flags]
|
|
12046
12100
|
|
|
12047
|
-
The link is printed either way
|
|
12101
|
+
The link is printed either way. On a terminal the CLI also opens it in your browser, unless
|
|
12102
|
+
CI is set, there is no display, DEEPDOCK_NO_BROWSER=1 is set or you pass --no-browser.
|
|
12048
12103
|
|
|
12049
12104
|
Flags
|
|
12050
12105
|
--no-wait Print the request and exit; finish it later with --wait (the default
|
|
12051
12106
|
when there is no terminal)
|
|
12052
12107
|
--wait <request> Finish a request started with --no-wait
|
|
12108
|
+
--no-browser Do not open the link in a browser
|
|
12053
12109
|
|
|
12054
12110
|
Examples
|
|
12055
12111
|
$ deepdock auth login
|
|
@@ -12057,7 +12113,7 @@ Examples
|
|
|
12057
12113
|
$ deepdock auth login --wait <request>
|
|
12058
12114
|
|
|
12059
12115
|
More
|
|
12060
|
-
\`deepdock auth status\` shows who is signed in (exit 5 when nobody is). \`deepdock auth logout\` revokes the sign-in.
|
|
12116
|
+
\`deepdock auth status\` shows who is signed in (exit 5 when nobody is). \`deepdock auth logout\` revokes the sign-in and, unless you pass --keep-keys, the API keys this CLI created.
|
|
12061
12117
|
`;
|
|
12062
12118
|
var KEYS_CREATE_HELP = `Create an API key for your organisation (admin).
|
|
12063
12119
|
|
|
@@ -12066,7 +12122,8 @@ key once, writes it into the env file named by --write-env, and keeps no copy. T
|
|
|
12066
12122
|
never printed and the CLI never uses it; your code reads it from that file.
|
|
12067
12123
|
|
|
12068
12124
|
A key reaches every device of the organisation until it expires or is revoked. Keep the
|
|
12069
|
-
expiry short
|
|
12125
|
+
expiry short, and revoke a key you no longer need: \`deepdock keys revoke <id>\`. \`deepdock auth logout\`
|
|
12126
|
+
revokes the keys this CLI created unless you pass --keep-keys.
|
|
12070
12127
|
|
|
12071
12128
|
Usage
|
|
12072
12129
|
deepdock keys create <name> --expires <duration> --write-env <file> [flags]
|
|
@@ -12081,6 +12138,7 @@ Flags
|
|
|
12081
12138
|
the CLI warns
|
|
12082
12139
|
--no-wait Print the request and exit; finish it with --wait
|
|
12083
12140
|
--wait <request> Finish a request started with --no-wait
|
|
12141
|
+
--no-browser Do not open the link in a browser (also DEEPDOCK_NO_BROWSER=1)
|
|
12084
12142
|
|
|
12085
12143
|
Examples
|
|
12086
12144
|
$ deepdock keys create "myapp dev" --expires 30d --write-env .env.local
|
|
@@ -12153,6 +12211,34 @@ var formatHelp = (command) => {
|
|
|
12153
12211
|
`;
|
|
12154
12212
|
};
|
|
12155
12213
|
|
|
12214
|
+
// src/output/confirm.ts
|
|
12215
|
+
import { createInterface } from "node:readline";
|
|
12216
|
+
var ask = (rt, prompt) => new Promise((resolve) => {
|
|
12217
|
+
const rl = createInterface({ input: rt.stdin.stream, output: undefined, terminal: false });
|
|
12218
|
+
rt.stderr.write(prompt);
|
|
12219
|
+
let settled = false;
|
|
12220
|
+
rl.once("line", (line) => {
|
|
12221
|
+
settled = true;
|
|
12222
|
+
rl.close();
|
|
12223
|
+
resolve(line.trim());
|
|
12224
|
+
});
|
|
12225
|
+
rl.once("close", () => {
|
|
12226
|
+
if (!settled)
|
|
12227
|
+
resolve("");
|
|
12228
|
+
});
|
|
12229
|
+
});
|
|
12230
|
+
var confirm = async (rt, question) => {
|
|
12231
|
+
const answer = await ask(rt, `${question} [y/N]: `);
|
|
12232
|
+
return /^y(es)?$/i.test(answer);
|
|
12233
|
+
};
|
|
12234
|
+
var pickIndex = async (rt, count, defaultIndex) => {
|
|
12235
|
+
const answer = await ask(rt, `Number [${defaultIndex}]: `);
|
|
12236
|
+
const chosen = answer === "" ? defaultIndex : Number.parseInt(answer, 10);
|
|
12237
|
+
if (!Number.isInteger(chosen) || chosen < 1 || chosen > count)
|
|
12238
|
+
return;
|
|
12239
|
+
return chosen;
|
|
12240
|
+
};
|
|
12241
|
+
|
|
12156
12242
|
// src/writeEnv/ledger.ts
|
|
12157
12243
|
import { relative, sep } from "node:path";
|
|
12158
12244
|
var addKey = (config, entry) => {
|
|
@@ -12393,11 +12479,19 @@ var settleWrittenLine = async (ctx, entry, keepEntryOnFailure) => {
|
|
|
12393
12479
|
|
|
12394
12480
|
// src/commands/auth.ts
|
|
12395
12481
|
var minutesLeft = (ctx, expiresAt) => Math.max(1, Math.ceil((Date.parse(expiresAt) - ctx.rt.now().getTime()) / 60000));
|
|
12396
|
-
var
|
|
12397
|
-
ctx.note(
|
|
12398
|
-
|
|
12399
|
-
|
|
12400
|
-
|
|
12482
|
+
var noteWaiting = (ctx) => {
|
|
12483
|
+
ctx.note(paint(ctx.err, "quiet", "Waiting for approval in the browser."));
|
|
12484
|
+
};
|
|
12485
|
+
var announceApproval = async (ctx, what, url, expiresAt, options) => {
|
|
12486
|
+
const open = browserAllowed(ctx.rt, options.noBrowser);
|
|
12487
|
+
const minutes = minutesLeft(ctx, expiresAt);
|
|
12488
|
+
ctx.note(open ? `Opening the approval page in your browser (it expires in ${minutes} minutes):` : `Open this link to approve ${what} (it expires in ${minutes} minutes):`);
|
|
12489
|
+
ctx.note(` ${ctx.err("link", url)}`);
|
|
12490
|
+
ctx.note(options.extra ?? "The page shows this computer's name and the CLI version: check them before you approve.");
|
|
12491
|
+
if (open) {
|
|
12492
|
+
ctx.note(await ctx.rt.openBrowser(url) ? "Opened in your browser." : "Could not open a browser; use the link above.");
|
|
12493
|
+
}
|
|
12494
|
+
noteWaiting(ctx);
|
|
12401
12495
|
};
|
|
12402
12496
|
var pendingResult = (pending, url, text) => ({
|
|
12403
12497
|
data: { pending: { request: pending.request, url, expiresAt: pending.expiresAt } },
|
|
@@ -12441,14 +12535,22 @@ var finishLogin = async (ctx, pending) => {
|
|
|
12441
12535
|
current.organisations = answer.organisations;
|
|
12442
12536
|
if (current.defaultOrg && !slugs.includes(current.defaultOrg))
|
|
12443
12537
|
current.defaultOrg = undefined;
|
|
12538
|
+
if (!current.defaultOrg && slugs.length === 1)
|
|
12539
|
+
current.defaultOrg = slugs[0];
|
|
12444
12540
|
current.pending = current.pending.filter((entry) => entry.request !== pending.request);
|
|
12445
12541
|
});
|
|
12446
|
-
const lines = [
|
|
12542
|
+
const lines = [`${paint(ctx.out, "success", "Signed in")} as ${answer.user.email}.`, ""];
|
|
12447
12543
|
if (answer.organisations.length === 0) {
|
|
12448
12544
|
lines.push("You are not a member of an organisation yet.");
|
|
12449
12545
|
} else {
|
|
12450
12546
|
lines.push("Your organisations:", indented(orgTable(answer.organisations, config.defaultOrg, ctx.out)), "");
|
|
12451
|
-
|
|
12547
|
+
const only = selectableOrgs(answer.organisations);
|
|
12548
|
+
const [sole] = only;
|
|
12549
|
+
if (sole && only.length === 1 && config.defaultOrg === sole.slug) {
|
|
12550
|
+
lines.push(`${paint(ctx.out, "success", "Selected")} ${sole.name} (${sole.slug}) as your organisation.`);
|
|
12551
|
+
} else {
|
|
12552
|
+
lines.push("Choose one with `deepdock org select <slug>` or pass --org to each command.");
|
|
12553
|
+
}
|
|
12452
12554
|
}
|
|
12453
12555
|
return {
|
|
12454
12556
|
data: {
|
|
@@ -12471,7 +12573,7 @@ var login = async (ctx, options) => {
|
|
|
12471
12573
|
}
|
|
12472
12574
|
assertSameBaseUrl(pending.baseUrl, ctx.baseUrl, "request");
|
|
12473
12575
|
assertNotExpired(ctx.rt, "login", pending.expiresAt);
|
|
12474
|
-
ctx
|
|
12576
|
+
noteWaiting(ctx);
|
|
12475
12577
|
return finishLogin(ctx, pending);
|
|
12476
12578
|
}
|
|
12477
12579
|
const started = await startLogin(ctx.api, ctx.rt);
|
|
@@ -12485,13 +12587,14 @@ var login = async (ctx, options) => {
|
|
|
12485
12587
|
await upsertPending(ctx, pending);
|
|
12486
12588
|
if (options.wait === false || !ctx.interactive) {
|
|
12487
12589
|
return pendingResult(pending, started.url, [
|
|
12488
|
-
`Open this link to approve the sign-in: ${started.url}`,
|
|
12590
|
+
`Open this link to approve the sign-in: ${ctx.out("link", started.url)}`,
|
|
12489
12591
|
`Then run \`deepdock auth login --wait ${started.request}\`.`
|
|
12490
12592
|
].join(`
|
|
12491
12593
|
`));
|
|
12492
12594
|
}
|
|
12493
|
-
announceApproval(ctx, "the sign-in", started.url, started.expiresAt
|
|
12494
|
-
|
|
12595
|
+
await announceApproval(ctx, "the sign-in", started.url, started.expiresAt, {
|
|
12596
|
+
noBrowser: options.browser === false
|
|
12597
|
+
});
|
|
12495
12598
|
return finishLogin(ctx, pending);
|
|
12496
12599
|
};
|
|
12497
12600
|
var keyRows = (ctx, keys) => keys.map((key) => [
|
|
@@ -12602,7 +12705,19 @@ var revokeSignInById = async (ctx, id) => {
|
|
|
12602
12705
|
throw error;
|
|
12603
12706
|
}
|
|
12604
12707
|
};
|
|
12605
|
-
var
|
|
12708
|
+
var keyLabel = (key) => `"${key.name}" (${key.id})`;
|
|
12709
|
+
var wantsKeysKept = async (ctx, keys, options) => {
|
|
12710
|
+
if (options.keepKeys)
|
|
12711
|
+
return true;
|
|
12712
|
+
if (keys.length === 0 || ctx.opts.yes || !ctx.interactive)
|
|
12713
|
+
return false;
|
|
12714
|
+
ctx.note(`Logging out revokes the API keys this CLI created (${keys.length}):`);
|
|
12715
|
+
for (const key of keys)
|
|
12716
|
+
ctx.note(` ${key.org} ${keyLabel(key)} ${key.prefix}…`);
|
|
12717
|
+
ctx.note("Programs using them stop working. Answer N to keep them active.");
|
|
12718
|
+
return !await confirm(ctx.rt, `Revoke ${keys.length === 1 ? "this key" : `these ${keys.length} keys`} too?`);
|
|
12719
|
+
};
|
|
12720
|
+
var logout = async (ctx, options) => {
|
|
12606
12721
|
const config = await ctx.config();
|
|
12607
12722
|
const signIn = config.signIn;
|
|
12608
12723
|
const email = signIn?.email ?? null;
|
|
@@ -12610,8 +12725,10 @@ var logout = async (ctx) => {
|
|
|
12610
12725
|
const revokedKeys = [];
|
|
12611
12726
|
const failedKeys = [];
|
|
12612
12727
|
let signInRevoked = false;
|
|
12728
|
+
const candidates = config.keys.filter((entry) => !entry.kept);
|
|
12729
|
+
const keepKeys = usable ? await wantsKeysKept(ctx, candidates, options) : Boolean(options.keepKeys);
|
|
12613
12730
|
if (usable) {
|
|
12614
|
-
for (const entry of [
|
|
12731
|
+
for (const entry of keepKeys ? [] : candidates) {
|
|
12615
12732
|
const outcome = await revokeRecordedKey(ctx, entry);
|
|
12616
12733
|
if ("error" in outcome)
|
|
12617
12734
|
failedKeys.push(outcome);
|
|
@@ -12623,7 +12740,7 @@ var logout = async (ctx) => {
|
|
|
12623
12740
|
signInRevoked = true;
|
|
12624
12741
|
}
|
|
12625
12742
|
} else {
|
|
12626
|
-
for (const entry of
|
|
12743
|
+
for (const entry of keepKeys ? [] : candidates) {
|
|
12627
12744
|
failedKeys.push({
|
|
12628
12745
|
...factsOf(entry),
|
|
12629
12746
|
error: { code: "sign_in_needed", message: "Your sign-in is missing or expired." }
|
|
@@ -12635,27 +12752,40 @@ var logout = async (ctx) => {
|
|
|
12635
12752
|
current.defaultOrg = undefined;
|
|
12636
12753
|
current.organisations = undefined;
|
|
12637
12754
|
current.pending = [];
|
|
12755
|
+
if (keepKeys) {
|
|
12756
|
+
for (const entry of current.keys)
|
|
12757
|
+
entry.kept = true;
|
|
12758
|
+
}
|
|
12638
12759
|
});
|
|
12760
|
+
const signedOut = paint(ctx.out, "success", "Signed out");
|
|
12761
|
+
const keptKeys = (await ctx.config()).keys.filter((entry) => entry.kept).map((entry) => ({
|
|
12762
|
+
...factsOf(entry),
|
|
12763
|
+
written: { file: displayPath(ctx.rt, entry.file), name: entry.variable }
|
|
12764
|
+
}));
|
|
12639
12765
|
const lines = [];
|
|
12640
12766
|
if (!email)
|
|
12641
12767
|
lines.push("Not signed in.");
|
|
12642
12768
|
else if (signInRevoked)
|
|
12643
|
-
lines.push(
|
|
12769
|
+
lines.push(`${signedOut} ${email}. The sign-in is revoked.`);
|
|
12644
12770
|
else if (usable) {
|
|
12645
|
-
lines.push(
|
|
12771
|
+
lines.push(`${signedOut} ${email}. The stored sign-in has no id (the settings file is damaged), so it was only removed from this computer; revoke it in the Deepdock dashboard.`);
|
|
12646
12772
|
} else {
|
|
12647
|
-
lines.push(
|
|
12773
|
+
lines.push(`${signedOut} ${email}. The sign-in was only removed from this computer; revoke it in the Deepdock dashboard.`);
|
|
12648
12774
|
}
|
|
12649
12775
|
if (revokedKeys.length > 0) {
|
|
12650
|
-
lines.push("Revoked the API keys this CLI created
|
|
12776
|
+
lines.push(`${paint(ctx.out, "success", "Revoked")} the API keys this CLI created:`);
|
|
12651
12777
|
for (const key of revokedKeys) {
|
|
12652
12778
|
const where = key.removed.removal === "done" ? ` ${describeRemoval(key.removed)}` : "";
|
|
12653
12779
|
lines.push(` ${key.org} "${key.name}" ${key.prefix}… ${key.id}${where}`);
|
|
12654
12780
|
}
|
|
12655
12781
|
lines.push("Programs still using one of them stop working now.");
|
|
12656
12782
|
}
|
|
12783
|
+
if (keptKeys.length > 0) {
|
|
12784
|
+
lines.push(`${paint(ctx.out, "success", "Kept")} ${keptKeys.length} API ${keptKeys.length === 1 ? "key" : "keys"} active: ${keptKeys.map(keyLabel).join(", ")}`);
|
|
12785
|
+
lines.push("Revoke one later with `deepdock keys revoke <id>` after `deepdock auth login`.");
|
|
12786
|
+
}
|
|
12657
12787
|
for (const key of failedKeys) {
|
|
12658
|
-
lines.push(
|
|
12788
|
+
lines.push(`${paint(ctx.out, "error", "Could not revoke")} "${key.name}" (${key.id}): run \`deepdock keys revoke ${key.id}\` after \`deepdock auth login\`.`);
|
|
12659
12789
|
}
|
|
12660
12790
|
const hints = [
|
|
12661
12791
|
...failedKeys.length > 0 ? ["Revoke the remaining keys with `deepdock keys revoke <id>`."] : [],
|
|
@@ -12666,6 +12796,7 @@ var logout = async (ctx) => {
|
|
|
12666
12796
|
revoked: signInRevoked,
|
|
12667
12797
|
email,
|
|
12668
12798
|
apiKeysRevoked: revokedKeys,
|
|
12799
|
+
apiKeysKept: keptKeys,
|
|
12669
12800
|
...failedKeys.length > 0 ? { apiKeysNotRevoked: failedKeys } : {}
|
|
12670
12801
|
},
|
|
12671
12802
|
meta: hints.length > 0 ? { hint: hints.join(" ") } : {},
|
|
@@ -12675,38 +12806,10 @@ var logout = async (ctx) => {
|
|
|
12675
12806
|
};
|
|
12676
12807
|
var registerAuth = (program, run) => {
|
|
12677
12808
|
const auth = program.command("auth").description("Sign in to Deepdock as yourself, see who is signed in, sign out");
|
|
12678
|
-
const loginCommand = auth.command("login").description("Sign in to Deepdock as yourself").option("--wait <request>", "finish a request started with --no-wait").option("--no-wait", "print the request and exit").action((options, cmd) => run(cmd, (ctx) => login(ctx, options)));
|
|
12809
|
+
const loginCommand = auth.command("login").description("Sign in to Deepdock as yourself").option("--wait <request>", "finish a request started with --no-wait").option("--no-wait", "print the request and exit").option("--no-browser", "do not open the link in a browser").action((options, cmd) => run(cmd, (ctx) => login(ctx, options)));
|
|
12679
12810
|
loginCommand.helpInformation = () => AUTH_LOGIN_HELP;
|
|
12680
12811
|
auth.command("status").description("Show who is signed in").action((_options, cmd) => run(cmd, status));
|
|
12681
|
-
auth.command("logout").description("Revoke the sign-in and the API keys this CLI created").action((
|
|
12682
|
-
};
|
|
12683
|
-
|
|
12684
|
-
// src/output/confirm.ts
|
|
12685
|
-
import { createInterface } from "node:readline";
|
|
12686
|
-
var ask = (rt, prompt) => new Promise((resolve) => {
|
|
12687
|
-
const rl = createInterface({ input: rt.stdin.stream, output: undefined, terminal: false });
|
|
12688
|
-
rt.stderr.write(prompt);
|
|
12689
|
-
let settled = false;
|
|
12690
|
-
rl.once("line", (line) => {
|
|
12691
|
-
settled = true;
|
|
12692
|
-
rl.close();
|
|
12693
|
-
resolve(line.trim());
|
|
12694
|
-
});
|
|
12695
|
-
rl.once("close", () => {
|
|
12696
|
-
if (!settled)
|
|
12697
|
-
resolve("");
|
|
12698
|
-
});
|
|
12699
|
-
});
|
|
12700
|
-
var confirm = async (rt, question) => {
|
|
12701
|
-
const answer = await ask(rt, `${question} [y/N]: `);
|
|
12702
|
-
return /^y(es)?$/i.test(answer);
|
|
12703
|
-
};
|
|
12704
|
-
var pickIndex = async (rt, count, defaultIndex) => {
|
|
12705
|
-
const answer = await ask(rt, `Number [${defaultIndex}]: `);
|
|
12706
|
-
const chosen = answer === "" ? defaultIndex : Number.parseInt(answer, 10);
|
|
12707
|
-
if (!Number.isInteger(chosen) || chosen < 1 || chosen > count)
|
|
12708
|
-
return;
|
|
12709
|
-
return chosen;
|
|
12812
|
+
auth.command("logout").description("Revoke the sign-in and the API keys this CLI created").option("--keep-keys", "revoke only the sign-in and leave the API keys active").action((options, cmd) => run(cmd, (ctx) => logout(ctx, options)));
|
|
12710
12813
|
};
|
|
12711
12814
|
|
|
12712
12815
|
// src/commands/devices.ts
|
|
@@ -12751,7 +12854,9 @@ var pair = async (ctx, code, label) => {
|
|
|
12751
12854
|
const device = isRecord(response.data) ? response.data : {};
|
|
12752
12855
|
const id = field(device, "id");
|
|
12753
12856
|
const name = field(device, "label");
|
|
12754
|
-
const lines = [
|
|
12857
|
+
const lines = [
|
|
12858
|
+
`${paint(ctx.out, "success", "Paired")} device ${id}${name ? ` (${stripControl(name)})` : ""}.`
|
|
12859
|
+
];
|
|
12755
12860
|
const status = field(device, "status");
|
|
12756
12861
|
if (status)
|
|
12757
12862
|
lines.push(` Status: ${status}`);
|
|
@@ -12773,7 +12878,7 @@ var rename = async (ctx, id, label) => {
|
|
|
12773
12878
|
return {
|
|
12774
12879
|
data: response.data,
|
|
12775
12880
|
meta: meta(response.requestId),
|
|
12776
|
-
text:
|
|
12881
|
+
text: `${paint(ctx.out, "success", "Renamed")} device ${deviceId} to ${stripControl(label)}.`
|
|
12777
12882
|
};
|
|
12778
12883
|
};
|
|
12779
12884
|
var release = async (ctx, id) => {
|
|
@@ -12802,7 +12907,7 @@ var release = async (ctx, id) => {
|
|
|
12802
12907
|
return {
|
|
12803
12908
|
data: response.data,
|
|
12804
12909
|
meta: meta(response.requestId),
|
|
12805
|
-
text:
|
|
12910
|
+
text: `${paint(ctx.out, "success", "Released")} device ${deviceId}.`
|
|
12806
12911
|
};
|
|
12807
12912
|
};
|
|
12808
12913
|
var renderSystems = (ctx, data) => {
|
|
@@ -12925,13 +13030,14 @@ var finishKey = async (ctx, pending, warned) => {
|
|
|
12925
13030
|
const file = displayPath(ctx.rt, pending.file);
|
|
12926
13031
|
const days = Math.round((Date.parse(created.expiresAt) - ctx.rt.now().getTime()) / DAY_MS);
|
|
12927
13032
|
const text = [
|
|
12928
|
-
|
|
13033
|
+
`${paint(ctx.out, "success", "Created")} API key "${stripControl(created.name)}".`,
|
|
12929
13034
|
` ID: ${created.id}`,
|
|
12930
13035
|
` Prefix: ${created.keyPrefix}`,
|
|
12931
13036
|
` Expires: ${created.expiresAt.slice(0, 10)} (${days} days)`,
|
|
12932
13037
|
` Written: ${file} (${pending.variable})`,
|
|
12933
13038
|
`The key value was shown to nobody and this CLI keeps no copy; your code reads it from ${file}.`,
|
|
12934
|
-
`
|
|
13039
|
+
`To revoke it: ${paint(ctx.out, "emphasis", `deepdock keys revoke ${created.id}`)}`,
|
|
13040
|
+
`${paint(ctx.out, "emphasis", "`deepdock auth logout`")} revokes this key; use ${paint(ctx.out, "emphasis", "`auth logout --keep-keys`")} to keep it.`
|
|
12935
13041
|
].join(`
|
|
12936
13042
|
`);
|
|
12937
13043
|
return {
|
|
@@ -12953,7 +13059,7 @@ var create = async (ctx, name, options) => {
|
|
|
12953
13059
|
if (!pending) {
|
|
12954
13060
|
throw usageError(`There is no pending key request ${options.wait}.`, "Start one with `deepdock keys create ... --no-wait`.");
|
|
12955
13061
|
}
|
|
12956
|
-
ctx
|
|
13062
|
+
noteWaiting(ctx);
|
|
12957
13063
|
return finishKey(ctx, pending, false);
|
|
12958
13064
|
}
|
|
12959
13065
|
if (name === undefined || name.trim() === "" || name.length > 100 || stripControl(name) !== name) {
|
|
@@ -12990,13 +13096,15 @@ var create = async (ctx, name, options) => {
|
|
|
12990
13096
|
await upsertPending(ctx, pending);
|
|
12991
13097
|
if (options.wait === false || !ctx.interactive) {
|
|
12992
13098
|
return pendingResult(pending, started.url, [
|
|
12993
|
-
`Open this link to approve the key: ${started.url}`,
|
|
13099
|
+
`Open this link to approve the key: ${ctx.out("link", started.url)}`,
|
|
12994
13100
|
`Then run \`deepdock keys create --wait ${started.request}\`.`
|
|
12995
13101
|
].join(`
|
|
12996
13102
|
`));
|
|
12997
13103
|
}
|
|
12998
|
-
announceApproval(ctx, "the key", started.url, started.expiresAt,
|
|
12999
|
-
|
|
13104
|
+
await announceApproval(ctx, "the key", started.url, started.expiresAt, {
|
|
13105
|
+
extra: `A human approves ${paint(ctx.err, "emphasis", `"Create an API key in ${org.name}?"`)} there; the page shows this computer's name.`,
|
|
13106
|
+
noBrowser: options.browser === false
|
|
13107
|
+
});
|
|
13000
13108
|
return finishKey(ctx, pending, true);
|
|
13001
13109
|
};
|
|
13002
13110
|
var revoke = async (ctx, id) => {
|
|
@@ -13038,7 +13146,7 @@ Every program using it stops working at once.`;
|
|
|
13038
13146
|
const removed = entry ? await settleWrittenLine(ctx, entry, true) : null;
|
|
13039
13147
|
const apiData = isRecord(response.data) ? response.data : {};
|
|
13040
13148
|
const label = entry ? `API key "${stripControl(entry.name)}"` : `API key ${keyId}`;
|
|
13041
|
-
const text = removed?.removal === "done" ?
|
|
13149
|
+
const text = removed?.removal === "done" ? `${paint(ctx.out, "success", "Revoked")} ${label} and ${describeRemoval(removed)}.` : `${paint(ctx.out, "success", "Revoked")} ${label}.`;
|
|
13042
13150
|
return {
|
|
13043
13151
|
data: { ...apiData, ...alreadyRevoked ? { alreadyRevoked: true } : {}, removed },
|
|
13044
13152
|
meta: { requestId: response.requestId },
|
|
@@ -13049,7 +13157,7 @@ var registerKeys = (program, run) => {
|
|
|
13049
13157
|
const keys = program.command("keys").description("Create, list and revoke your organisation's API keys");
|
|
13050
13158
|
keys.command("list").description("List the API keys of the organisation").action((_options, cmd) => run(cmd, list2));
|
|
13051
13159
|
const allowed = Object.keys(EXPIRY_DAYS).join("|");
|
|
13052
|
-
const createCommand = keys.command("create").description("Create an API key for your organisation (admin)").argument("[name]", "a name that says what the key is for").option("--expires <duration>", `${allowed}; 90 days is the maximum from the CLI`).option("--write-env <file>", "write DEEPDOCK_API_KEY=<key> into this env file").option("--wait <request>", "finish a request started with --no-wait").option("--no-wait", "print the request and exit").action((name, options, cmd) => run(cmd, (ctx) => create(ctx, name, options)));
|
|
13160
|
+
const createCommand = keys.command("create").description("Create an API key for your organisation (admin)").argument("[name]", "a name that says what the key is for").option("--expires <duration>", `${allowed}; 90 days is the maximum from the CLI`).option("--write-env <file>", "write DEEPDOCK_API_KEY=<key> into this env file").option("--wait <request>", "finish a request started with --no-wait").option("--no-wait", "print the request and exit").option("--no-browser", "do not open the link in a browser").action((name, options, cmd) => run(cmd, (ctx) => create(ctx, name, options)));
|
|
13053
13161
|
createCommand.helpInformation = () => KEYS_CREATE_HELP;
|
|
13054
13162
|
keys.command("revoke").description("Revoke an API key (admin)").argument("<id>", "the key id").action((id, _options, cmd) => run(cmd, (ctx) => revoke(ctx, id)));
|
|
13055
13163
|
};
|
|
@@ -13118,7 +13226,7 @@ var select = async (ctx, slug) => {
|
|
|
13118
13226
|
return {
|
|
13119
13227
|
data: { slug: found.slug, name: found.name, role: found.role },
|
|
13120
13228
|
meta: response.requestId ? { requestId: response.requestId } : {},
|
|
13121
|
-
text:
|
|
13229
|
+
text: `${paint(ctx.out, "success", "Default organisation:")} ${found.name} (${found.slug}).`
|
|
13122
13230
|
};
|
|
13123
13231
|
};
|
|
13124
13232
|
var registerOrg = (program, run) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepdockai/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Deepdock partner CLI: pair devices, see systems and actions, create the API key your code runs them with.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "bun scripts/build.ts",
|
|
26
|
+
"dev": "bun scripts/dev.ts",
|
|
27
|
+
"pack:try": "bun scripts/packTry.ts",
|
|
26
28
|
"format": "biome format .",
|
|
27
29
|
"format:fix": "biome format --write .",
|
|
28
30
|
"lint": "biome lint .",
|
|
@@ -42,8 +42,8 @@ in the dashboard" and stop retrying.
|
|
|
42
42
|
- Never ask for, read, print or paste a key value, and never try to extract it from the file.
|
|
43
43
|
Code reads it at runtime from where the CLI put it.
|
|
44
44
|
- A development key reaches every device of the organisation until it expires or is revoked; keep
|
|
45
|
-
the expiry short (`30d`), use it only against the demo device, and tell the human
|
|
46
|
-
|
|
45
|
+
the expiry short (`30d`), use it only against the demo device, and tell the human its expiry
|
|
46
|
+
date and that `deepdock keys revoke <id>` ends it. The live key is created the same way,
|
|
47
47
|
by the human from their own CLI, into the live secret store.
|
|
48
48
|
|
|
49
49
|
## The CLI sign-in (CLI only)
|
|
@@ -54,7 +54,7 @@ permissions. The sign-in belongs to the person, not to an organisation: every co
|
|
|
54
54
|
`--org <slug>`. It does what that person can do in the dashboard (members read, pair and rename;
|
|
55
55
|
admins also release devices, create and revoke API keys) and never runs an action.
|
|
56
56
|
`deepdock auth status` shows the signed-in person, organisations and expiry (exit 5 when not signed in); `deepdock auth
|
|
57
|
-
logout` revokes the sign-in and every API key the CLI created.
|
|
57
|
+
logout` revokes the sign-in and every API key the CLI created, unless `--keep-keys` is passed (it then revokes only the sign-in). On a terminal it lists the keys and asks first; with `--json` or without a terminal it revokes them unless `--keep-keys` is passed.
|
|
58
58
|
|
|
59
59
|
Without a terminal `auth login` prints the request and exits; show the human the link, wait for
|
|
60
60
|
them to approve, then run `deepdock auth login --wait <request>`. Do not try to answer for them.
|
package/skill/references/cli.md
CHANGED
|
@@ -14,7 +14,7 @@ the API key the CLI wrote to the app's env file.
|
|
|
14
14
|
|---|---|---|
|
|
15
15
|
| `auth login` | opens a link to the dashboard where the human clicks Approve; prints `Signed in as <email>.` and the person's organisations; the sign-in lasts 90 days | anyone |
|
|
16
16
|
| `auth status` | signed-in person, sign-in expiry, organisations, default organisation, the API keys this CLI created (id, prefix, expiry, where it was written); prints the same document and exits 5 when not signed in | anyone |
|
|
17
|
-
| `auth logout` | revokes the sign-in and every API key this CLI created, removes the entries it wrote, says what it revoked | anyone |
|
|
17
|
+
| `auth logout [--keep-keys]` | revokes the sign-in and, unless `--keep-keys`, every API key this CLI created (on a terminal it lists them and asks, default keep; with `--json` or no terminal it revokes them unless `--keep-keys`); removes the entries it wrote, says what it revoked or kept | anyone |
|
|
18
18
|
| `org list` | the person's organisations: slug, name, role, which one is the default | member |
|
|
19
19
|
| `org select [slug]` | sets the default organisation; without a slug, a pick-list on a terminal | member |
|
|
20
20
|
| `systems list` | the systems and actions assigned to the organisation | member |
|
|
@@ -32,11 +32,13 @@ the API key the CLI wrote to the app's env file.
|
|
|
32
32
|
| `skill [--agent claude\|codex\|cursor\|all]` | writes this skill into the current repository | anyone |
|
|
33
33
|
|
|
34
34
|
Every command that acts in an organisation uses `--org <slug>` after the command, else
|
|
35
|
-
`DEEPDOCK_ORG`, else the default from `org select`;
|
|
36
|
-
lists
|
|
35
|
+
`DEEPDOCK_ORG`, else the default from `org select`; if you belong to exactly one organisation it is used and named on
|
|
36
|
+
stderr; with several and none chosen it stops with `org_required` and lists them. Always pass `--org` from an agent: `org select` changes the human's
|
|
37
37
|
default in every terminal.
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
`auth login` and `keys create` also open the approval link in the human's browser on a terminal, unless `--no-browser`, `DEEPDOCK_NO_BROWSER=1`, `CI` or a missing display says otherwise.
|
|
40
|
+
|
|
41
|
+
Global flags: `--org <slug>`, `--json`, `--yes`, `-h`, `-v`. Environment: `DEEPDOCK_ORG`, `DEEPDOCK_NO_BROWSER`,
|
|
40
42
|
`DEEPDOCK_BASE_URL`, `NO_COLOR`.
|
|
41
43
|
|
|
42
44
|
## Waiting for the browser
|
|
@@ -60,8 +60,8 @@ The proof is your client; the CLI only shows what the device offers.
|
|
|
60
60
|
Neither you nor the human sees the value; you work with its id, prefix and file name only.
|
|
61
61
|
- Never ask for, read, print, echo, log or commit a key value. Use the key only against the demo
|
|
62
62
|
device.
|
|
63
|
-
- It reaches every device of the organisation until it expires or is revoked. Tell the human
|
|
64
|
-
|
|
63
|
+
- It reaches every device of the organisation until it expires or is revoked. Tell the human its
|
|
64
|
+
expiry date and that `deepdock keys revoke <id>` ends it, and that they create the live key with the same flow from their
|
|
65
65
|
own CLI.
|
|
66
66
|
|
|
67
67
|
## Logs and prompts
|