@softr/cli 0.1.2 → 0.1.3
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 +10 -8
- package/dist/cli.mjs +67 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,15 +27,17 @@ the environment, one login per environment). In a terminal, `login` asks which o
|
|
|
27
27
|
```bash
|
|
28
28
|
softr login # asks: 1. browser (recommended, OAuth + PKCE) or 2. paste a personal API token
|
|
29
29
|
softr login --no-browser # browser login, but prints the URL to open elsewhere; the CLI waits for the answer
|
|
30
|
-
softr login --token
|
|
30
|
+
softr login --token # a personal API token created in the studio (API tokens), typed in with the input hidden
|
|
31
|
+
pbpaste | softr login --token # the same token from stdin, for scripts
|
|
31
32
|
softr whoami # who the stored token belongs to and what it grants
|
|
32
33
|
softr logout
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
The browser login grants the scopes registered for the CLI in the studio, on every workspace you can reach; the consent
|
|
36
37
|
page lists them. The access token renews itself, and a login is needed again after 30 days without use. A personal API
|
|
37
|
-
token needs a `workflows:*` action in its statements; `login` warns when it has none. `
|
|
38
|
-
|
|
38
|
+
token needs a `workflows:*` action in its statements; `login` warns when it has none. `--token` takes no value: a token
|
|
39
|
+
on the command line ends up in the shell history, so `login` reads it from stdin when piped and asks for it otherwise.
|
|
40
|
+
`SOFTR_TOKEN` overrides the stored token for scripts and CI. The studio session cookie is not accepted.
|
|
39
41
|
|
|
40
42
|
The CLI targets Softr production. For development, `SOFTR_WORKFLOWS_API_URL`, `SOFTR_STUDIO_API_URL` and
|
|
41
43
|
`SOFTR_STUDIO_URL` point it at another workflows service, studio API and studio, for example locally running
|
|
@@ -43,11 +45,11 @@ ones. A workflow directory remembers the target it was pulled from.
|
|
|
43
45
|
|
|
44
46
|
## Global commands
|
|
45
47
|
|
|
46
|
-
| Command
|
|
47
|
-
|
|
|
48
|
-
| `login [--token
|
|
49
|
-
| `workspaces`
|
|
50
|
-
| `integrations [--workspace <id>] [--type <TYPE>]`
|
|
48
|
+
| Command | What it does |
|
|
49
|
+
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
50
|
+
| `login [--token] [--no-browser]` / `whoami` / `logout` | Log in through the browser (OAuth with PKCE), or store a personal API token (piped to stdin or typed in hidden) after confirming it with the studio; show the stored identity and its grants; forget the token. |
|
|
51
|
+
| `workspaces` | The workspaces the stored token reaches, with your role in each and the default one. Commands that take `--workspace` default to `SOFTR_WORKSPACE_ID` when it is set, else to your only or default workspace. |
|
|
52
|
+
| `integrations [--workspace <id>] [--type <TYPE>]` | List the workspace's connected integrations (id, name, type, account, auth kind) from the studio API; the id goes into a node's `integrationId` input. Inside a workflow directory the workspace is the workflow's; `--type` filters. Credentials are never shown. |
|
|
51
53
|
|
|
52
54
|
Every command accepts `--json`. `SOFTR_TOKEN` overrides the stored token; `SOFTR_WORKSPACE_ID` is the default workspace.
|
|
53
55
|
|
package/dist/cli.mjs
CHANGED
|
@@ -15423,6 +15423,7 @@ ${renderIntegrations(rows)}`, {
|
|
|
15423
15423
|
import { execFile } from "node:child_process";
|
|
15424
15424
|
import { createServer } from "node:http";
|
|
15425
15425
|
import { stderr, stdin } from "node:process";
|
|
15426
|
+
import { emitKeypressEvents } from "node:readline";
|
|
15426
15427
|
import { createInterface } from "node:readline/promises";
|
|
15427
15428
|
var CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
15428
15429
|
async function identify(client) {
|
|
@@ -15439,10 +15440,11 @@ async function interactiveLogin(target) {
|
|
|
15439
15440
|
if (!stdin.isTTY) {
|
|
15440
15441
|
throw new CliError(
|
|
15441
15442
|
"No login method given and stdin is not a terminal.",
|
|
15442
|
-
`
|
|
15443
|
+
`Pipe a personal API token to "login --token", set ${TOKEN_ENV_VAR}, or run "login --no-browser" and open the printed URL.`
|
|
15443
15444
|
);
|
|
15444
15445
|
}
|
|
15445
15446
|
const rl = createInterface({ input: stdin, output: stderr });
|
|
15447
|
+
let answer = "";
|
|
15446
15448
|
try {
|
|
15447
15449
|
stderr.write(
|
|
15448
15450
|
`How do you want to log in to ${target.studioUrl}?
|
|
@@ -15450,7 +15452,6 @@ async function interactiveLogin(target) {
|
|
|
15450
15452
|
2. Personal API token: paste a pat_... created in the studio under API tokens
|
|
15451
15453
|
`
|
|
15452
15454
|
);
|
|
15453
|
-
let answer = "";
|
|
15454
15455
|
for (; ; ) {
|
|
15455
15456
|
answer = (await rl.question("Choose 1 or 2 [1]: ")).trim();
|
|
15456
15457
|
if (answer === "" || answer === "1" || answer === "2") {
|
|
@@ -15458,17 +15459,59 @@ async function interactiveLogin(target) {
|
|
|
15458
15459
|
}
|
|
15459
15460
|
stderr.write("Please answer 1 or 2.\n");
|
|
15460
15461
|
}
|
|
15461
|
-
if (answer === "2") {
|
|
15462
|
-
const token = (await rl.question("Personal API token: ")).trim();
|
|
15463
|
-
if (!token) {
|
|
15464
|
-
throw new CliError("No token given.", 'Run "softr login" again.');
|
|
15465
|
-
}
|
|
15466
|
-
return pastedToken(token);
|
|
15467
|
-
}
|
|
15468
15462
|
} finally {
|
|
15469
15463
|
rl.close();
|
|
15470
15464
|
}
|
|
15471
|
-
return browserLogin(target, false);
|
|
15465
|
+
return answer === "2" ? pastedToken(await promptToken()) : browserLogin(target, false);
|
|
15466
|
+
}
|
|
15467
|
+
function promptToken() {
|
|
15468
|
+
stderr.write("Personal API token: ");
|
|
15469
|
+
emitKeypressEvents(stdin);
|
|
15470
|
+
stdin.setRawMode(true);
|
|
15471
|
+
stdin.resume();
|
|
15472
|
+
return new Promise((resolve, reject) => {
|
|
15473
|
+
let token = "";
|
|
15474
|
+
const finish = (error) => {
|
|
15475
|
+
stdin.off("keypress", onKey);
|
|
15476
|
+
stdin.setRawMode(false);
|
|
15477
|
+
stdin.pause();
|
|
15478
|
+
stderr.write("\n");
|
|
15479
|
+
if (error) {
|
|
15480
|
+
reject(error);
|
|
15481
|
+
} else if (token.trim()) {
|
|
15482
|
+
resolve(token.trim());
|
|
15483
|
+
} else {
|
|
15484
|
+
reject(new CliError("No token given.", 'Run "softr login" again.'));
|
|
15485
|
+
}
|
|
15486
|
+
};
|
|
15487
|
+
const onKey = (text, key = {}) => {
|
|
15488
|
+
if (key.ctrl && (key.name === "c" || key.name === "d")) {
|
|
15489
|
+
finish(new CliError("Login cancelled."));
|
|
15490
|
+
} else if (key.name === "return" || key.name === "enter") {
|
|
15491
|
+
finish();
|
|
15492
|
+
} else if (key.name === "backspace") {
|
|
15493
|
+
if (token) {
|
|
15494
|
+
token = token.slice(0, -1);
|
|
15495
|
+
stderr.write("\b \b");
|
|
15496
|
+
}
|
|
15497
|
+
} else if (text && !key.ctrl && !key.meta && text >= " ") {
|
|
15498
|
+
token += text;
|
|
15499
|
+
stderr.write("*".repeat(text.length));
|
|
15500
|
+
}
|
|
15501
|
+
};
|
|
15502
|
+
stdin.on("keypress", onKey);
|
|
15503
|
+
});
|
|
15504
|
+
}
|
|
15505
|
+
async function readTokenFromStdin() {
|
|
15506
|
+
let input = "";
|
|
15507
|
+
for await (const chunk of stdin) {
|
|
15508
|
+
input += chunk;
|
|
15509
|
+
}
|
|
15510
|
+
const token = input.trim();
|
|
15511
|
+
if (!token) {
|
|
15512
|
+
throw new CliError("No token on stdin.", 'Pipe the token in, for example "pbpaste | softr login --token".');
|
|
15513
|
+
}
|
|
15514
|
+
return token;
|
|
15472
15515
|
}
|
|
15473
15516
|
function openBrowser(url) {
|
|
15474
15517
|
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
|
|
@@ -15568,17 +15611,26 @@ function registerLogin(program3) {
|
|
|
15568
15611
|
program3.command("login").description(
|
|
15569
15612
|
"Log in through the browser (recommended) or with a personal API token; asks which when run in a terminal"
|
|
15570
15613
|
).option(
|
|
15571
|
-
"--token
|
|
15572
|
-
`a personal API token (pat_...) from the studio,
|
|
15573
|
-
).option("--no-browser", "browser login, but print the URL instead of opening it (for remote shells and agents)").action(async (opts, command) => {
|
|
15614
|
+
"--token",
|
|
15615
|
+
`log in with a personal API token (pat_...) from the studio: read from stdin when piped, else asked for with the input hidden; without the flag, $${TOKEN_ENV_VAR} is used when set`
|
|
15616
|
+
).option("--no-browser", "browser login, but print the URL instead of opening it (for remote shells and agents)").allowExcessArguments().allowUnknownOption().action(async (opts, command) => {
|
|
15574
15617
|
const options = globalOptions(command);
|
|
15575
15618
|
const target = resolveTarget();
|
|
15619
|
+
if (command.args.length > 0) {
|
|
15620
|
+
throw opts.token || command.args.some((arg) => arg.startsWith("--token=")) ? new CliError(
|
|
15621
|
+
'"--token" takes no value any more: a token on the command line ends up in the shell history.',
|
|
15622
|
+
'Pipe it in ("pbpaste | softr login --token") or run "softr login --token" and paste it at the prompt. Revoke the token you passed in the studio (API tokens) and create a new one.'
|
|
15623
|
+
) : new CliError(
|
|
15624
|
+
'"login" takes no arguments and knows only the options its help lists.',
|
|
15625
|
+
'Run "softr login --help".'
|
|
15626
|
+
);
|
|
15627
|
+
}
|
|
15576
15628
|
if (isProxyAuth()) {
|
|
15577
15629
|
throw new CliError(
|
|
15578
15630
|
`Login is not needed: the target authenticates the requests itself ($${AUTH_ENV_VAR}=proxy).`
|
|
15579
15631
|
);
|
|
15580
15632
|
}
|
|
15581
|
-
const token =
|
|
15633
|
+
const token = opts.token ? stdin.isTTY ? await promptToken() : await readTokenFromStdin() : (process.env[TOKEN_ENV_VAR] ?? "").trim();
|
|
15582
15634
|
const saved = token ? pastedToken(token) : opts.browser ? await interactiveLogin(target) : await browserLogin(target, true);
|
|
15583
15635
|
const identity = await identify({ target, token: saved.token, kind: saved.kind });
|
|
15584
15636
|
await report(options, target, saved, identity);
|
|
@@ -16766,7 +16818,7 @@ Pass --workspace <id> to a command, or set SOFTR_WORKSPACE_ID, to work in one of
|
|
|
16766
16818
|
// package.json
|
|
16767
16819
|
var package_default = {
|
|
16768
16820
|
name: "@softr/cli",
|
|
16769
|
-
version: "0.1.
|
|
16821
|
+
version: "0.1.3",
|
|
16770
16822
|
description: "The Softr command line: build, test and publish Softr workflows from local files; more Softr APIs to come",
|
|
16771
16823
|
keywords: [
|
|
16772
16824
|
"softr",
|