@primitivedotdev/cli 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,6 +16,8 @@ primitive whoami
16
16
  prim whoami
17
17
  ```
18
18
 
19
+ The same CLI is also published unscoped as [`primcli`](https://www.npmjs.com/package/primcli) — `npm install -g primcli` installs an identical build with the same `primitive`/`prim` commands. Use whichever name you prefer; they track the same version.
20
+
19
21
  Or with no install:
20
22
 
21
23
  ```bash
@@ -2,7 +2,7 @@ import { A as PrimitiveApiClient, C as saveCliCredentials, D as loadActiveChatSt
2
2
  import { Args, Command, Errors, Flags, ux } from "@oclif/core";
3
3
  import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
4
4
  import { randomUUID } from "node:crypto";
5
- import { basename, dirname, join, relative, resolve, sep } from "node:path";
5
+ import path, { basename, dirname, join, relative, resolve, sep } from "node:path";
6
6
  import { hostname } from "node:os";
7
7
  import process$1 from "node:process";
8
8
  import { createInterface } from "node:readline/promises";
@@ -20942,6 +20942,74 @@ var SignupCommand = class SignupCommand extends Command {
20942
20942
  }
20943
20943
  }
20944
20944
  };
20945
+ function resolveVerificationCode(input) {
20946
+ const sources = [
20947
+ input.positional !== void 0 ? "positional" : null,
20948
+ input.fromStdin === true ? "--code-from-stdin" : null,
20949
+ input.fromFile !== void 0 ? "--code-from-file" : null,
20950
+ input.fromEnv !== void 0 ? "--code-from-env" : null
20951
+ ].filter((v) => v !== null);
20952
+ if (sources.length === 0) return {
20953
+ kind: "error",
20954
+ message: "Pass the verification code as a positional argument or via one of --code-from-stdin, --code-from-file, or --code-from-env."
20955
+ };
20956
+ if (sources.length > 1) return {
20957
+ kind: "error",
20958
+ message: `Pass exactly one source for the verification code; got ${sources.join(", ")}.`
20959
+ };
20960
+ if (input.positional !== void 0) return {
20961
+ kind: "ok",
20962
+ code: input.positional
20963
+ };
20964
+ if (input.fromEnv !== void 0) {
20965
+ const value = (input.env ?? process$1.env)[input.fromEnv];
20966
+ if (value === void 0) return {
20967
+ kind: "error",
20968
+ message: `--code-from-env ${input.fromEnv}: environment variable is not set.`
20969
+ };
20970
+ return {
20971
+ kind: "ok",
20972
+ code: stripTrailingNewline(value)
20973
+ };
20974
+ }
20975
+ if (input.fromFile !== void 0) {
20976
+ const readFile = input.readFile ?? defaultReadCodeFile;
20977
+ try {
20978
+ return {
20979
+ kind: "ok",
20980
+ code: stripTrailingNewline(readFile(input.fromFile))
20981
+ };
20982
+ } catch (error) {
20983
+ const detail = error instanceof Error ? error.message : String(error);
20984
+ return {
20985
+ kind: "error",
20986
+ message: `--code-from-file ${input.fromFile}: could not read file: ${detail}`
20987
+ };
20988
+ }
20989
+ }
20990
+ const readStdin = input.readStdin ?? defaultReadCodeStdin;
20991
+ try {
20992
+ return {
20993
+ kind: "ok",
20994
+ code: stripTrailingNewline(readStdin())
20995
+ };
20996
+ } catch (error) {
20997
+ return {
20998
+ kind: "error",
20999
+ message: `--code-from-stdin: ${error instanceof Error ? error.message : String(error)}`
21000
+ };
21001
+ }
21002
+ }
21003
+ function stripTrailingNewline(value) {
21004
+ return value.replace(/\r?\n$/, "");
21005
+ }
21006
+ function defaultReadCodeFile(path) {
21007
+ return readFileSync(path, "utf8");
21008
+ }
21009
+ function defaultReadCodeStdin() {
21010
+ if (process$1.stdin.isTTY) throw new Error("stdin is a TTY; pipe the code into this command or use --code-from-file / --code-from-env instead.");
21011
+ return readFileSync(0, "utf8");
21012
+ }
20945
21013
  var SignupConfirmCommand = class SignupConfirmCommand extends Command {
20946
21014
  static args = {
20947
21015
  email: Args.string({
@@ -20949,19 +21017,28 @@ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
20949
21017
  required: true
20950
21018
  }),
20951
21019
  code: Args.string({
20952
- description: "Verification code from the signup email",
20953
- required: true
21020
+ description: "Verification code from the signup email. Optional when one of --code-from-stdin / --code-from-file / --code-from-env is passed; exactly one source must be set.",
21021
+ required: false
20954
21022
  })
20955
21023
  };
20956
21024
  static description = "Confirm a pending Primitive signup, create an OAuth session, and save CLI credentials locally.";
20957
21025
  static summary = "Confirm account signup";
20958
- static examples = ["<%= config.bin %> signup confirm user@example.com 123456", "<%= config.bin %> signup confirm user@example.com 123456 --org-id 00000000-0000-4000-8000-000000000000"];
21026
+ static examples = [
21027
+ "<%= config.bin %> signup confirm user@example.com 123456",
21028
+ "<%= config.bin %> signup confirm user@example.com 123456 --org-id 00000000-0000-4000-8000-000000000000",
21029
+ "read -rs CODE && CODE=\"$CODE\" <%= config.bin %> signup confirm user@example.com --code-from-env CODE && unset CODE",
21030
+ "read -rs CODE && printf '%s' \"$CODE\" | <%= config.bin %> signup confirm user@example.com --code-from-stdin && unset CODE",
21031
+ "<%= config.bin %> signup confirm user@example.com --code-from-file /run/user/$(id -u)/verification-code"
21032
+ ];
20959
21033
  static flags = {
20960
21034
  "api-base-url": Flags.string({
20961
21035
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
20962
21036
  env: "PRIMITIVE_API_BASE_URL",
20963
21037
  hidden: true
20964
21038
  }),
21039
+ "code-from-stdin": Flags.boolean({ description: "Read the verification code from stdin instead of the positional argument. Use when an agent is constructing the command for the user to run, so the code never enters the agent's prompt context." }),
21040
+ "code-from-file": Flags.string({ description: "Read the verification code from a UTF-8 file at this path. Trailing newlines are stripped." }),
21041
+ "code-from-env": Flags.string({ description: "Read the verification code from this environment variable. Pair with `read -rs CODE && CODE=\"$CODE\" primitive signup confirm <email> --code-from-env CODE && unset CODE` so the value never appears on the command line or in shell history. Plain `read` creates a shell-local variable that child processes cannot see; the inline `CODE=\"$CODE\"` exports it for just the one command." }),
20965
21042
  force: Flags.boolean({
20966
21043
  char: "f",
20967
21044
  description: "Replace saved credentials after verification"
@@ -20970,6 +21047,13 @@ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
20970
21047
  };
20971
21048
  async run() {
20972
21049
  const { args, flags } = await this.parse(SignupConfirmCommand);
21050
+ const resolvedCode = resolveVerificationCode({
21051
+ positional: args.code,
21052
+ fromStdin: flags["code-from-stdin"] === true,
21053
+ fromFile: flags["code-from-file"],
21054
+ fromEnv: flags["code-from-env"]
21055
+ });
21056
+ if (resolvedCode.kind === "error") throw cliError$2(resolvedCode.message);
20973
21057
  let releaseCredentialsLock;
20974
21058
  try {
20975
21059
  releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
@@ -20978,7 +21062,7 @@ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
20978
21062
  }
20979
21063
  try {
20980
21064
  await runSignupConfirmWithCredentialLock({
20981
- code: args.code,
21065
+ code: resolvedCode.code,
20982
21066
  configDir: this.config.configDir,
20983
21067
  email: args.email,
20984
21068
  flags
@@ -22383,6 +22467,43 @@ function renderFishCompletion(binName) {
22383
22467
  return `${lines.join("\n")}\n`;
22384
22468
  }
22385
22469
  //#endregion
22470
+ //#region src/oclif/shell-completion-script.ts
22471
+ /**
22472
+ * Path to the sourceable completion *function* file that
22473
+ * `@oclif/plugin-autocomplete` writes under the CLI cache dir when its cache is
22474
+ * built. This is the artifact a shell is meant to source -- a package manager
22475
+ * dropping a file into `bash_completion.d/` or zsh's `site-functions/` wants
22476
+ * this, NOT the human-readable setup instructions printed by
22477
+ * `<bin> autocomplete <shell>`. The layout mirrors the plugin's own
22478
+ * `Create.bashCompletionFunctionPath` / `zshCompletionFunctionPath` getters:
22479
+ * <cacheDir>/autocomplete/functions/bash/<bin>.bash
22480
+ * <cacheDir>/autocomplete/functions/zsh/_<bin>
22481
+ *
22482
+ * This couples to a private path layout in `@oclif/plugin-autocomplete`
22483
+ * (pinned `^3.2.45` in package.json). If a major bump reorganises that cache
22484
+ * dir, `readCompletionFunction` will fail even after a successful
22485
+ * `--refresh-cache`; re-verify this layout when bumping the plugin.
22486
+ */
22487
+ function completionFunctionPath(cacheDir, bin, shell) {
22488
+ const functionsDir = path.join(cacheDir, "autocomplete", "functions", shell);
22489
+ const fileName = shell === "bash" ? `${bin}.bash` : `_${bin}`;
22490
+ return path.join(functionsDir, fileName);
22491
+ }
22492
+ /**
22493
+ * Read the generated completion function script, trimmed of trailing
22494
+ * whitespace so the caller can re-add a single newline. Throws an actionable
22495
+ * error (rather than a bare `ENOENT`) if the cached script is missing -- e.g.
22496
+ * the cache build failed, or the plugin changed its path layout.
22497
+ */
22498
+ function readCompletionFunction(cacheDir, bin, shell) {
22499
+ const filePath = completionFunctionPath(cacheDir, bin, shell);
22500
+ try {
22501
+ return readFileSync(filePath, "utf8").trimEnd();
22502
+ } catch (cause) {
22503
+ throw new Error(`Could not read the generated ${shell} completion script at ${filePath}. Run \`${bin} autocomplete ${shell} --refresh-cache\` and try again.`, { cause });
22504
+ }
22505
+ }
22506
+ //#endregion
22386
22507
  //#region src/oclif/index.ts
22387
22508
  var ListOperationsCommand = class extends Command {
22388
22509
  static description = "List all generated API operations as JSON. Useful for piping to `jq` to discover available commands, their request/response schemas, and per-field descriptions. For inspecting a single operation in detail, prefer `primitive describe <command-or-operation-name>`.";
@@ -22503,14 +22624,32 @@ var CompletionCommand = class CompletionCommand extends Command {
22503
22624
  ],
22504
22625
  required: true
22505
22626
  }) };
22506
- static description = "Show shell completion output or installation instructions for supported shells";
22507
- static summary = "Show shell completion output or installation instructions";
22627
+ static description = `Output a sourceable shell completion script, or print setup instructions.
22628
+
22629
+ For fish, and for bash/zsh when the output is piped or redirected (e.g. into a
22630
+ completion file under bash_completion.d or zsh's site-functions), this emits
22631
+ the raw completion script. For bash/zsh in an interactive terminal it prints
22632
+ the human-readable setup instructions instead. This keeps a redirected
22633
+ \`<%= config.bin %> completion bash > <file>\` safe to source -- the file holds an
22634
+ actual completion function, never instructional prose a shell would choke on.`;
22635
+ static summary = "Output a shell completion script or print setup instructions";
22636
+ static examples = [
22637
+ "<%= config.bin %> completion bash >> /etc/bash_completion.d/primitive",
22638
+ "<%= config.bin %> completion zsh > /usr/local/share/zsh/site-functions/_primitive",
22639
+ "<%= config.bin %> completion fish > ~/.config/fish/completions/primitive.fish"
22640
+ ];
22508
22641
  async run() {
22509
22642
  const { args } = await this.parse(CompletionCommand);
22510
- if (args.shell === "fish") {
22643
+ const shell = args.shell;
22644
+ if (shell === "fish") {
22511
22645
  this.log(renderFishCompletion(this.config.bin));
22512
22646
  return;
22513
22647
  }
22648
+ if ((shell === "bash" || shell === "zsh") && !process.stdout.isTTY) {
22649
+ await this.config.runCommand("autocomplete", [shell, "--refresh-cache"]);
22650
+ this.log(readCompletionFunction(this.config.cacheDir, this.config.bin, shell));
22651
+ return;
22652
+ }
22514
22653
  await this.config.runCommand("autocomplete", [args.shell]);
22515
22654
  }
22516
22655
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitivedotdev/cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
5
5
  "type": "module",
6
6
  "sideEffects": false,