@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.
Files changed (3) hide show
  1. package/README.md +10 -8
  2. package/dist/cli.mjs +67 -15
  3. 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 pat_... # a personal API token created in the studio (API tokens), no questions asked
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. `SOFTR_TOKEN` overrides the
38
- stored token for scripts and CI. The studio session cookie is not accepted.
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 | What it does |
47
- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
48
- | `login [--token <pat_...>] [--no-browser]` / `whoami` / `logout` | Log in through the browser (OAuth with PKCE), or store a personal API token after confirming it with the studio; show the stored identity and its grants; forget the token. |
49
- | `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. |
50
- | `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. |
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
- `Pass --token <pat_...>, set ${TOKEN_ENV_VAR}, or run "login --no-browser" and open the printed URL.`
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 <value>",
15572
- `a personal API token (pat_...) from the studio, no questions asked; defaults to $${TOKEN_ENV_VAR}`
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 = (opts.token ?? process.env[TOKEN_ENV_VAR] ?? "").trim();
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.2",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softr/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "The Softr command line: build, test and publish Softr workflows from local files; more Softr APIs to come",
5
5
  "keywords": [
6
6
  "softr",