envspot 0.1.0 → 0.1.2

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
@@ -1,26 +1,87 @@
1
- # EnvSpot CLI (`envspot`)
1
+ # envspot
2
2
 
3
- Minimal reference for the **`login` · `link` · `run`** surface. Full command reference: **[`../docs/cli-reference.md`](../docs/cli-reference.md)**. Historical SD-02 sequence + rationale: **[`../docs/historical/SD-02-cli-sequence.md`](../docs/historical/SD-02-cli-sequence.md)**.
3
+ Encrypted environment variables for your team, managed from the command line.
4
4
 
5
- ## Trust model (matches SD-01 §8 / SD-02 §7)
5
+ `envspot` is the command-line client for [EnvSpot](https://envspot.com). Link a
6
+ directory to a project, pull your secrets straight into a process, and keep
7
+ `.env` files off disk and out of git.
6
8
 
7
- - **Plaintext secrets** only in process memory during `run`, after **`GET /api/cli/decrypt`** over TLS. Nothing is written to disk as a secret bundle.
8
- - **No DEK / no master key on the client** — unwrap and decrypt happen server-side.
9
- - **`.envspot.json`**project id + environment label only; safe to commit unless you treat the project id as sensitive.
10
- - **Token** — stored with **keytar** (OS keychain); on **401** / `token_invalid`, `run` clears the stored credential and exits **4** so you re-`login`.
9
+ This package is the CLI. EnvSpot is also a hosted platform a web dashboard for
10
+ projects, environments, team access, audit history, and syncing secrets to your
11
+ deploy targetsat [envspot.com](https://envspot.com).
11
12
 
12
- ## User-Agent
13
+ ## Install
13
14
 
14
- All requests send **`User-Agent: envspot-cli/<semver>`** (`cli/src/config.ts`) for server-side audits (SD-01 §5.2).
15
+ ```bash
16
+ npm install -g envspot
17
+ ```
18
+
19
+ Or run it without installing:
15
20
 
16
- ## Resolved product choices
21
+ ```bash
22
+ npx envspot <command>
23
+ ```
17
24
 
18
- Exit codes, retries, **403** tier responses, and operational note on auth-tag / decrypt failures: **[`../docs/historical/sd-open-questions-resolved.md`](../docs/historical/sd-open-questions-resolved.md)**.
25
+ Requires Node.js 18 or newer.
19
26
 
20
- ## Build
27
+ ## Quick start
21
28
 
22
29
  ```bash
23
- cd cli && npm ci && npm run build
30
+ envspot init # create + link a project, import your .env, start your dev server
31
+ envspot login # sign in via browser device pairing
32
+ envspot link # link this directory to a project + environment
33
+ envspot run -- npm start # run a command with your secrets injected as env vars
24
34
  ```
25
35
 
26
- Bin: **`envspot`** → **`./dist/index.js`**.
36
+ ## Commands
37
+
38
+ | Command | What it does |
39
+ | ----------------- | --------------------------------------------------------------------------------------- |
40
+ | `init` | Create a project from this directory, link it, import `.env`, and start your dev server |
41
+ | `login` | Sign in via browser device pairing, or store an API key for token login |
42
+ | `logout` | Remove the stored credential |
43
+ | `link` | Write `./.envspot.json` (project id + environment) |
44
+ | `run -- <cmd>` | Run a command with the linked project's secrets injected as environment variables |
45
+ | `dump` | Print the linked project's secrets as `KEY=value` to stdout (nothing written to disk) |
46
+ | `set <key> <val>` | Set one secret for the linked project |
47
+ | `unset <key>` | Delete one secret |
48
+ | `status` | Show the API/app URL and your credential + link state |
49
+ | `whoami` | Show the signed-in user, workspace, and active link or token scope |
50
+ | `fly deploy` | Stage the linked project's secrets to Fly.io and deploy |
51
+
52
+ Run `envspot help` or `envspot <command> --help` for full options.
53
+
54
+ ## Integrations
55
+
56
+ EnvSpot syncs your secrets to your deployment targets so you set a value once and
57
+ it lands everywhere. Supported targets: **GitHub Actions, Vercel, Render, Railway,
58
+ and Fly.io** — connected and managed in the [dashboard](https://envspot.com).
59
+
60
+ `fly deploy` is the one target you can push to directly from the CLI; the rest
61
+ sync automatically once connected in the dashboard.
62
+
63
+ ## Configuration
64
+
65
+ | Variable | Purpose |
66
+ | --------------- | ------------------------------------------------------------ |
67
+ | `ENVSPOT_TOKEN` | API key for non-interactive / CI use (skips the OS keychain) |
68
+
69
+ ## How your secrets are handled
70
+
71
+ - **Decryption happens server-side.** No master key or data-encryption key ever
72
+ lives on your machine.
73
+ - **Plaintext secrets stay in memory.** `run` holds them only for the lifetime of
74
+ the child process; nothing is written to disk as a secret bundle.
75
+ - **Your credential lives in the OS keychain**, not a plaintext file. On an expired
76
+ or revoked token, the CLI clears it and asks you to sign in again.
77
+ - **`.envspot.json` is safe to commit** — it holds only the project id and
78
+ environment label, never secrets.
79
+
80
+ ## Links
81
+
82
+ - Website: https://envspot.com
83
+ - Questions or bug reports: contact@envspot.com
84
+
85
+ ## License
86
+
87
+ MIT
package/dist/config.js CHANGED
@@ -2,6 +2,9 @@ import { readFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ // Brand display name for CLI prose. The binary/package/config-file name stays
6
+ // lowercase `envspot`; this is only the human-facing company name.
7
+ export const BRAND_NAME = "envSpot";
5
8
  export function packageVersion() {
6
9
  const pjPath = join(__dirname, "..", "package.json");
7
10
  const pj = JSON.parse(readFileSync(pjPath, "utf8"));
@@ -19,7 +22,9 @@ export function apiBase() {
19
22
  return process.env.ENVV_API_URL.trim();
20
23
  if (process.env.NODE_ENV === "development")
21
24
  return "http://localhost:3000";
22
- return "https://api.envspot.com";
25
+ // API routes live under /api on the main app origin (single Next.js app);
26
+ // there is no api.* subdomain. Keep in sync with appOrigin() below.
27
+ return "https://envspot.com";
23
28
  }
24
29
  /** Browser/dashboard origin for CLI login approval (dashboard lives here, not on API origin). */
25
30
  export function appOrigin() {
@@ -35,7 +40,8 @@ export function appOrigin() {
35
40
  return "https://envspot.com";
36
41
  }
37
42
  export function statusPageHint() {
38
- return process.env.ENVSPOT_STATUS_URL?.trim() || "https://status.envspot.com";
43
+ // No public status page yet; surface a link only if one is configured.
44
+ return process.env.ENVSPOT_STATUS_URL?.trim() || "";
39
45
  }
40
46
  export function upgradePageHint(jsonUpgradeUrl) {
41
47
  const u = typeof jsonUpgradeUrl === "string" && jsonUpgradeUrl.trim().length > 0
@@ -7,6 +7,7 @@ import { authRequired, flyctlNotInstalled, flyTomlNotFound } from "./errors.js";
7
7
  import * as out from "./output.js";
8
8
  import { fetchProjectSecrets, resolveLinkScope } from "./secrets.js";
9
9
  import { isHeadlessToken } from "./token-kind.js";
10
+ import { BRAND_NAME } from "./config.js";
10
11
  /**
11
12
  * Parse the tail of `envspot fly deploy …`. `--app`/`--no-secrets` are ours;
12
13
  * every other token forwards verbatim to `flyctl deploy`. `--app` is also
@@ -124,7 +125,7 @@ export async function executeFlyDeploy(parsed, deps = {}) {
124
125
  ? await fetchProjectSecrets(token)
125
126
  : await fetchProjectSecrets(token, resolveLinkScope());
126
127
  const keys = Object.keys(secrets).sort();
127
- out.step(`Fetched ${keys.length} secret${keys.length === 1 ? "" : "s"} from EnvSpot`);
128
+ out.step(`Fetched ${keys.length} secret${keys.length === 1 ? "" : "s"} from ${BRAND_NAME}`);
128
129
  if (keys.length === 0) {
129
130
  // Likely a wrong env/scope rather than a genuinely empty project — make
130
131
  // the silent "deployed with no secrets" footgun visible.
package/dist/http.js CHANGED
@@ -1,4 +1,4 @@
1
- import { apiBase, cliUserAgent, statusPageHint } from "./config.js";
1
+ import { apiBase, BRAND_NAME, cliUserAgent, statusPageHint } from "./config.js";
2
2
  import { CliError, ERR } from "./errors.js";
3
3
  export function apiUrl(path) {
4
4
  const base = apiBase().replace(/\/$/, "");
@@ -29,8 +29,9 @@ export function describeTransportFailure(err) {
29
29
  const msg = err instanceof Error
30
30
  ? `${err.message}${err.cause instanceof Error ? ` (${err.cause.message})` : ""}`
31
31
  : String(err);
32
- return (`Couldn't reach EnvSpot. Check your connection.\n` +
33
- `${statusPageHint()}\n` +
32
+ const status = statusPageHint();
33
+ return (`Couldn't reach ${BRAND_NAME}. Check your connection.\n` +
34
+ (status ? `${status}\n` : "") +
34
35
  ` Detail: ${msg}`);
35
36
  }
36
37
  /** 3× backoff for transport faults (plus first attempt ⇒ 4 tries). */
package/dist/whoami.js CHANGED
@@ -4,6 +4,7 @@ import { CliError, ERR, notLoggedIn } from "./errors.js";
4
4
  import { apiUrl, backoffTransport, fetchWithCliHeaders, parseJsonSafely, rethrowTransport, } from "./http.js";
5
5
  import { findEnvspotLink } from "./paths.js";
6
6
  import { isHeadlessToken } from "./token-kind.js";
7
+ import { BRAND_NAME } from "./config.js";
7
8
  /** Bearer signing tolerates this much clock skew; past it, auth gets flaky. */
8
9
  const CLOCK_SKEW_TOLERANCE_MS = 5 * 60 * 1000;
9
10
  /** Mask a stored token for display: kind prefix + a few chars, rest hidden.
@@ -29,7 +30,7 @@ export function clockSkewWarning(serverDateHeader, nowMs) {
29
30
  return (`warning[E0074]: System clock differs from envspot.com by ${minutes}m ` +
30
31
  `(local: ${new Date(nowMs).toISOString()}, server: ${new Date(serverMs).toISOString()}).\n` +
31
32
  ` help: Bearer signing tolerates 5 minutes of skew; past that, expect intermittent auth failures. Ensure NTP is running.\n` +
32
- ` docs: https://docs.envspot.com/errors/E0074`);
33
+ ` docs: https://envspot.com/docs/errors/E0074`);
33
34
  }
34
35
  function titleCaseTier(tier) {
35
36
  return tier.length ? tier[0].toUpperCase() + tier.slice(1) : tier;
@@ -74,7 +75,7 @@ export async function executeWhoami(opts, nowMs = Date.now()) {
74
75
  // otherwise null-deref in formatWhoami or print "null" under --json.
75
76
  const data = analyzed.json;
76
77
  if (!data || typeof data.user !== "object" || data.user === null) {
77
- throw new CliError(ERR.UNEXPECTED, "Malformed response from envspot (expected whoami JSON).");
78
+ throw new CliError(ERR.UNEXPECTED, `Malformed response from ${BRAND_NAME} (expected whoami JSON).`);
78
79
  }
79
80
  // Emit the skew warning before the --json early return: CI uses --json, and
80
81
  // that is exactly where clock skew silently breaks bearer auth. It goes to
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "envspot",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "CLI for envspot — encrypted environment variables for your team",
5
5
  "license": "MIT",
6
- "author": "EnvSpot",
6
+ "author": "envSpot",
7
7
  "homepage": "https://envspot.com",
8
8
  "repository": {
9
9
  "type": "git",
@@ -11,7 +11,7 @@
11
11
  "directory": "cli"
12
12
  },
13
13
  "bugs": {
14
- "url": "https://github.com/EnvSpot/envspot/issues"
14
+ "email": "contact@envspot.com"
15
15
  },
16
16
  "keywords": [
17
17
  "envspot",