@floatonnear/float-adapter 0.1.0 → 0.1.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.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Read an env file without involving a shell.
3
+ *
4
+ * Every doc in this repo tells the operator to run:
5
+ *
6
+ * set -a && . .float/adapter.env && set +a && npx @floatonnear/float-adapter
7
+ *
8
+ * That idiom has already failed on a real box. From this project's memory
9
+ * (prod-env-file-cannot-be-sourced-by-bash): one unquoted value containing a
10
+ * space made `set -a; . .env; set +a` die with `Scan: command not found`, and
11
+ * every script opening with that line failed before it ran.
12
+ *
13
+ * Two reasons that is the wrong mechanism HERE specifically, rather than merely
14
+ * fragile:
15
+ *
16
+ * 1. The file is written by create-float and carries NEAR_VAULT_AGENT_KEY,
17
+ * FLOAT_QUOTE_SIGNER_KEY and FLOAT_API_TOKEN. Sourcing it means the shell
18
+ * EXECUTES its contents: `$(...)` and backticks in a value run as
19
+ * commands. A secrets file should be data.
20
+ * 2. It has to work the same way on Windows and on a VPS. `set -a` is not
21
+ * available in PowerShell at all, which is why this repo's memory carries
22
+ * a bespoke `Get-Content | ForEach-Object { Set-Item Env: }` incantation
23
+ * just to start the adapter locally.
24
+ *
25
+ * One parser, tested, used by both.
26
+ */
27
+ /**
28
+ * Parse `KEY=value` lines into a plain object.
29
+ *
30
+ * Deliberately NOT a dotenv clone: no variable interpolation, no escape
31
+ * sequences, no multi-line values. Each of those is a way for the contents of a
32
+ * secrets file to change meaning, and none is needed by the file create-float
33
+ * writes.
34
+ */
35
+ export declare function parseEnvFile(text: string): Record<string, string>;
36
+ /**
37
+ * Apply a parsed env file WITHOUT overriding anything already set.
38
+ *
39
+ * A real environment variable must outrank the file, so a deploy can point one
40
+ * service at a different port or contract without editing shared secrets. That
41
+ * precedence is the same one the pythscan sandbox relied on to keep a verify run
42
+ * aimed at a database COPY while every credential still came from the
43
+ * production file.
44
+ */
45
+ export declare function applyEnvFile(parsed: Record<string, string>, env?: Record<string, string | undefined>): string[];
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Read an env file without involving a shell.
3
+ *
4
+ * Every doc in this repo tells the operator to run:
5
+ *
6
+ * set -a && . .float/adapter.env && set +a && npx @floatonnear/float-adapter
7
+ *
8
+ * That idiom has already failed on a real box. From this project's memory
9
+ * (prod-env-file-cannot-be-sourced-by-bash): one unquoted value containing a
10
+ * space made `set -a; . .env; set +a` die with `Scan: command not found`, and
11
+ * every script opening with that line failed before it ran.
12
+ *
13
+ * Two reasons that is the wrong mechanism HERE specifically, rather than merely
14
+ * fragile:
15
+ *
16
+ * 1. The file is written by create-float and carries NEAR_VAULT_AGENT_KEY,
17
+ * FLOAT_QUOTE_SIGNER_KEY and FLOAT_API_TOKEN. Sourcing it means the shell
18
+ * EXECUTES its contents: `$(...)` and backticks in a value run as
19
+ * commands. A secrets file should be data.
20
+ * 2. It has to work the same way on Windows and on a VPS. `set -a` is not
21
+ * available in PowerShell at all, which is why this repo's memory carries
22
+ * a bespoke `Get-Content | ForEach-Object { Set-Item Env: }` incantation
23
+ * just to start the adapter locally.
24
+ *
25
+ * One parser, tested, used by both.
26
+ */
27
+ /**
28
+ * Parse `KEY=value` lines into a plain object.
29
+ *
30
+ * Deliberately NOT a dotenv clone: no variable interpolation, no escape
31
+ * sequences, no multi-line values. Each of those is a way for the contents of a
32
+ * secrets file to change meaning, and none is needed by the file create-float
33
+ * writes.
34
+ */
35
+ export function parseEnvFile(text) {
36
+ const out = {};
37
+ // Strip a BOM. PowerShell's Out-File and Set-Content write one by default, so
38
+ // any file the operator has touched from a one-liner has it, and it would
39
+ // otherwise become part of the FIRST key's name — producing a missing-variable
40
+ // failure whose cause is invisible.
41
+ for (const raw of text.replace(/^/, "").split(/\r?\n/)) {
42
+ const line = raw.trim();
43
+ if (!line || line.startsWith("#"))
44
+ continue;
45
+ // Split on the FIRST '=' only. Base64url tokens and ed25519 keys contain
46
+ // '=' as padding, and splitting on each one silently truncates a secret —
47
+ // which surfaces as a 401 that nobody can account for.
48
+ const eq = line.indexOf("=");
49
+ if (eq < 0)
50
+ continue; // not an assignment; not our business
51
+ const key = line.slice(0, eq).trim().replace(/^export\s+/, "");
52
+ if (!key)
53
+ continue;
54
+ let value = line.slice(eq + 1).trim();
55
+ // Strip ONE layer of matching surrounding quotes, so both `A="x y"` and the
56
+ // bare `A=x y` that broke the shell mean the same thing here.
57
+ if (value.length >= 2 && ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))) {
58
+ value = value.slice(1, -1);
59
+ }
60
+ out[key] = value;
61
+ }
62
+ return out;
63
+ }
64
+ /**
65
+ * Apply a parsed env file WITHOUT overriding anything already set.
66
+ *
67
+ * A real environment variable must outrank the file, so a deploy can point one
68
+ * service at a different port or contract without editing shared secrets. That
69
+ * precedence is the same one the pythscan sandbox relied on to keep a verify run
70
+ * aimed at a database COPY while every credential still came from the
71
+ * production file.
72
+ */
73
+ export function applyEnvFile(parsed, env = process.env) {
74
+ const applied = [];
75
+ for (const [k, v] of Object.entries(parsed)) {
76
+ if (env[k] !== undefined)
77
+ continue;
78
+ env[k] = v;
79
+ applied.push(k);
80
+ }
81
+ return applied;
82
+ }
package/dist/server.js CHANGED
@@ -28,6 +28,31 @@ import { planFreezeNode, planSetCooldown, planSetNodeCaps, planSetRecipients, to
28
28
  import { getAgentKeys, getDashboard, getSpendHistory, getVaultInfo, paymentFromOutcome, } from "./handlers.js";
29
29
  import { AgentSelectionError, loadKeyring, selectAgent } from "./keyring.js";
30
30
  import { BlockedFollower, createRpc, emptyStore } from "./blocked-scan.js";
31
+ import { parseEnvFile, applyEnvFile } from "./env-file.js";
32
+ // Load FLOAT_ENV_FILE before anything reads process.env.
33
+ //
34
+ // This replaces `set -a && . .float/adapter.env && set +a` in every deploy
35
+ // path. That idiom makes the SHELL execute a file holding the agent key, the
36
+ // quote signer and the API token, and it has already failed for real on a single
37
+ // unquoted value containing a space (see env-file.ts). It also does not exist in
38
+ // PowerShell, which is why starting the adapter on Windows needed a bespoke
39
+ // Get-Content incantation recorded in this project's memory.
40
+ //
41
+ // Real environment variables win over the file, so one service can be pointed
42
+ // at a different port or contract without editing shared secrets.
43
+ if (process.env.FLOAT_ENV_FILE) {
44
+ const envPath = process.env.FLOAT_ENV_FILE;
45
+ if (!existsSync(envPath)) {
46
+ // Fail loudly. A missing env file that is silently ignored starts an adapter
47
+ // with no API token, which disables every mutating route and looks like a
48
+ // code bug rather than a deploy mistake.
49
+ console.error(`FATAL: FLOAT_ENV_FILE=${envPath} does not exist`);
50
+ process.exit(1);
51
+ }
52
+ const applied = applyEnvFile(parseEnvFile(readFileSync(envPath, "utf8")));
53
+ // NAMES only, never values: this line goes to pm2's log on every restart.
54
+ console.log(`env file: ${envPath} — set ${applied.length} (${applied.join(", ") || "none"})`);
55
+ }
31
56
  const env = {
32
57
  vault: process.env.NEAR_VAULT_CONTRACT ?? "",
33
58
  // See DEFAULT_RPC_URL in @floatonnear/mcp: the old public testnet endpoint
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floatonnear/float-adapter",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "Serves the existing Float dashboard's /v1 API from NEAR vault views, so the Float UI runs unchanged against the NEAR hierarchical vault.",
6
6
  "license": "MIT",
@@ -17,6 +17,12 @@
17
17
  "test": "vitest run",
18
18
  "typecheck": "tsc -p tsconfig.json --noEmit"
19
19
  },
20
- "dependencies": { "@floatonnear/mcp": "^0.1.0" },
21
- "devDependencies": { "typescript": "^5.9.0", "vitest": "^5.0.5", "@types/node": "^24.0.0" }
20
+ "dependencies": {
21
+ "@floatonnear/mcp": "^0.1.0"
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5.9.0",
25
+ "vitest": "^5.0.5",
26
+ "@types/node": "^24.0.0"
27
+ }
22
28
  }