@agentgrant.cash/cli 1.0.0 → 1.0.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/.env.example CHANGED
@@ -1,9 +1,14 @@
1
1
  # Grant Cash CLI — backend routing.
2
2
  #
3
- # Copy to `.env` (loaded from the PACKAGE ROOT only never the cwd). Every value
4
- # is optional; the defaults point at production. Set these to repoint at dev /
5
- # staging without touching code. You do NOT need to know the final URLs to build
6
- # the CLI wire them here whenever they're ready.
3
+ # Copy to `.env` in the folder you run the CLI from (the current working
4
+ # directory). The CLI loads it automatically this is how you point an
5
+ # installed / `npx` CLI at localhost or staging. Already-exported shell env vars
6
+ # take precedence over the file. Every value is optional; the defaults point at
7
+ # production. The active backend is shown by `grant status`.
8
+ #
9
+ # Example (local dev):
10
+ # GRANTCASH_API_URL=http://localhost:3001/api
11
+ # GRANTCASH_AGENT_URL=http://localhost:8080
7
12
 
8
13
  # ── Money side (Perfolio backend — gold, portfolio, cash) ──
9
14
  GRANTCASH_API_URL=https://api.perfolio.ai/api
package/dist/cli/index.js CHANGED
@@ -1,43 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { join } from "node:path";
4
5
  import { Command } from "commander";
5
- import { isJsonMode, printError } from "../lib/index.js";
6
+ import { isJsonMode, printError, loadDotEnvFile } from "../lib/index.js";
6
7
  /**
7
- * Load `.env` from the PACKAGE ROOT only (never the cwd), so an installed CLI
8
- * can't pick up a stray `.env` in whatever folder it's run from and silently
9
- * repoint at the wrong backend. Already-exported shell vars win.
8
+ * Load backend config from `.env`. Precedence (first writer wins the loader only
9
+ * sets keys that are still unset):
10
+ * 1. Already-exported shell env vars — always win.
11
+ * 2. `.env` in the current working directory — where you run the CLI. This is
12
+ * what lets you point an installed / `npx` CLI at localhost or staging:
13
+ * put GRANTCASH_API_URL=http://localhost:… in the `.env` of the folder you
14
+ * run `grant` from.
15
+ * 3. `.env` at the package root — only present when running from a source
16
+ * checkout; a published package ships no `.env`, so this is a dev fallback.
17
+ *
18
+ * Any non-default backend is shown by `grant config show` / `grant status`
19
+ * (`→ money` / `→ agent`), so a repoint is always visible rather than silent.
10
20
  */
11
- function loadDotEnv(path) {
12
- let raw;
13
- try {
14
- raw = readFileSync(path, "utf8");
15
- }
16
- catch {
17
- return;
18
- }
19
- for (const line of raw.split("\n")) {
20
- const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
21
- if (!m)
22
- continue;
23
- const key = m[1];
24
- if (process.env[key] !== undefined)
25
- continue;
26
- let val = m[2].trim();
27
- if ((val.startsWith('"') && val.endsWith('"')) ||
28
- (val.startsWith("'") && val.endsWith("'"))) {
29
- // Quoted value: keep everything inside the quotes verbatim (a `#` here is data).
30
- val = val.slice(1, -1);
31
- }
32
- else {
33
- // Unquoted value: strip a trailing inline comment (whitespace + `#…`), the
34
- // dotenv convention. Prevents `KEY=http://x # note` from capturing the note.
35
- val = val.replace(/\s+#.*$/, "").trim();
36
- }
37
- process.env[key] = val;
38
- }
39
- }
40
- loadDotEnv(fileURLToPath(new URL("../../.env", import.meta.url)));
21
+ loadDotEnvFile(join(process.cwd(), ".env")); // cwd — highest-precedence file
22
+ loadDotEnvFile(fileURLToPath(new URL("../../.env", import.meta.url))); // package root — dev fallback
41
23
  import { registerAuth } from "./commands/auth.js";
42
24
  import { registerPortfolio } from "./commands/portfolio.js";
43
25
  import { registerMoney } from "./commands/money.js";
@@ -5,9 +5,9 @@ import { mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
5
5
  * Backend base URLs — env-overridable, production defaults.
6
6
  *
7
7
  * Money side = Perfolio backend; Agent side = Agent-mode backend. Set the env
8
- * vars (e.g. in the package-root `.env`) to repoint at dev/staging without
9
- * touching code. You don't need final URLs to build — the agent default is a
10
- * placeholder; override GRANTCASH_AGENT_URL when it's known.
8
+ * vars (in a `.env` in the directory you run the CLI from, or as exported shell
9
+ * vars) to repoint at dev/staging/localhost without touching code. The active
10
+ * backend is shown in `grant status`.
11
11
  */
12
12
  export function baseUrls(env = process.env) {
13
13
  return {
@@ -0,0 +1,45 @@
1
+ import { readFileSync } from "node:fs";
2
+ /**
3
+ * Minimal, dependency-free `.env` loader.
4
+ *
5
+ * Reads `path` and applies each `KEY=value` line into `env`, but ONLY for keys
6
+ * that are still unset — so an already-exported shell var (or an earlier-loaded
7
+ * file) always wins. Missing/unreadable files are a no-op. Supports quoted values
8
+ * (verbatim, including `#`) and strips trailing inline comments on unquoted values
9
+ * (the dotenv convention). Returns the keys it actually set (for logging/tests).
10
+ */
11
+ export function applyDotEnv(raw, env = process.env) {
12
+ const applied = [];
13
+ for (const line of raw.split("\n")) {
14
+ const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
15
+ if (!m)
16
+ continue;
17
+ const key = m[1];
18
+ if (env[key] !== undefined)
19
+ continue;
20
+ let val = m[2].trim();
21
+ if ((val.startsWith('"') && val.endsWith('"')) ||
22
+ (val.startsWith("'") && val.endsWith("'"))) {
23
+ // Quoted value: keep everything inside the quotes verbatim (a `#` here is data).
24
+ val = val.slice(1, -1);
25
+ }
26
+ else {
27
+ // Unquoted value: strip a trailing inline comment (whitespace + `#…`).
28
+ val = val.replace(/\s+#.*$/, "").trim();
29
+ }
30
+ env[key] = val;
31
+ applied.push(key);
32
+ }
33
+ return applied;
34
+ }
35
+ /** Load a `.env` file by path into `env` (no-op if the file is missing). */
36
+ export function loadDotEnvFile(path, env = process.env) {
37
+ let raw;
38
+ try {
39
+ raw = readFileSync(path, "utf8");
40
+ }
41
+ catch {
42
+ return [];
43
+ }
44
+ return applyDotEnv(raw, env);
45
+ }
package/dist/lib/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./types.js";
2
2
  export * from "./config.js";
3
+ export * from "./dotenv.js";
3
4
  export * from "./errors.js";
4
5
  export * from "./output.js";
5
6
  export * from "./device.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentgrant.cash/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Grant Cash — one CLI for your money (gold) and your agent (pay-per-use services). Routes to the Perfolio backend and the Agent-mode backend behind a single, plain-language surface.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,23 @@ These are **two sides of the same Grant Cash account**, not two separate apps or
14
14
 
15
15
  > Note for assistants: do **not** describe this as a "portfolio service" plus an "agent service", and never mention internal system names. It is one product — Grant Cash — with an Investments side and a Spending side. Speak plainly: gold, cash, bitcoin, ethereum, "your worth", "spending money".
16
16
 
17
+ ## Install / run
18
+
19
+ This CLI is published on npm as **`@agentgrant.cash/cli`**. Run it without installing:
20
+
21
+ ```
22
+ npx @agentgrant.cash/cli <command> # e.g. npx @agentgrant.cash/cli status
23
+ ```
24
+
25
+ Or install it once to get the short `grant` command:
26
+
27
+ ```
28
+ npm install -g @agentgrant.cash/cli
29
+ grant status
30
+ ```
31
+
32
+ Throughout this skill, **`grant <command>` is shorthand for `npx @agentgrant.cash/cli <command>`** (or the global `grant` if you installed it). Requires Node ≥ 20.
33
+
17
34
  ## Output (for assistants driving this CLI)
18
35
 
19
36
  - Output is **JSON** when stdout is not a TTY (you captured it) or with `--json`; pretty for people otherwise.