@alfe.ai/integrations 0.2.0 → 0.2.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/dist/index.d.ts +45 -18
- package/dist/index.js +149 -43
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -796,6 +796,26 @@ declare class OpenClawApplier implements RuntimeApplier {
|
|
|
796
796
|
* without leaking the value payload.
|
|
797
797
|
*/
|
|
798
798
|
private runConfigCommand;
|
|
799
|
+
/**
|
|
800
|
+
* Read back a single config path and compare it to the value we tried to
|
|
801
|
+
* write. Used by `applyConfig`'s verify-after-write: a `config set` can exit
|
|
802
|
+
* non-zero (a hot-reload races the write, OpenClaw's clobber protection
|
|
803
|
+
* fires) even though the value actually landed, exactly like `applyPlugin`'s
|
|
804
|
+
* install-then-check tolerance.
|
|
805
|
+
*
|
|
806
|
+
* OpenClaw REDACTS sensitive values on `config get`, returning the literal
|
|
807
|
+
* `__OPENCLAW_REDACTED__` instead of the real value. Treat that as a match:
|
|
808
|
+
* the key exists and OpenClaw is hiding it, so a deep-equal against the
|
|
809
|
+
* intended value would otherwise be a false negative and force a re-throw.
|
|
810
|
+
*/
|
|
811
|
+
private configValueMatches;
|
|
812
|
+
/**
|
|
813
|
+
* Run a read-back `check` with the same retry/backoff cadence as the config
|
|
814
|
+
* writes — the settling hot-reload may still be rewriting openclaw.json when
|
|
815
|
+
* we first read back, so a single check can be a false negative. Returns true
|
|
816
|
+
* on the first success, false once all attempts are exhausted.
|
|
817
|
+
*/
|
|
818
|
+
private verifyApplied;
|
|
799
819
|
applyPlugin(spec: string, _installPath?: string, opts?: {
|
|
800
820
|
force?: boolean;
|
|
801
821
|
}): Promise<void>;
|
|
@@ -893,9 +913,11 @@ declare class HermesApplier implements RuntimeApplier {
|
|
|
893
913
|
readonly runtime = "hermes";
|
|
894
914
|
private home;
|
|
895
915
|
private trackingPath;
|
|
916
|
+
/** `~/.hermes/config.yaml` — read-merge-written on removal (no `config unset`). */
|
|
917
|
+
private configYamlPath;
|
|
896
918
|
private configSetRetries;
|
|
897
919
|
private configSetRetryDelayMs;
|
|
898
|
-
/** Serializes all `
|
|
920
|
+
/** Serializes all config mutations (CLI `set` + config.yaml delete) so they never interleave. */
|
|
899
921
|
private configSetQueue;
|
|
900
922
|
constructor(options?: HermesApplierOptions);
|
|
901
923
|
/**
|
|
@@ -909,8 +931,9 @@ declare class HermesApplier implements RuntimeApplier {
|
|
|
909
931
|
/**
|
|
910
932
|
* Remove config previously applied by an integration.
|
|
911
933
|
*
|
|
912
|
-
* Reads the tracking file to find which keys this integration set, then
|
|
913
|
-
*
|
|
934
|
+
* Reads the tracking file to find which dotted keys this integration set, then
|
|
935
|
+
* DELETES each from `~/.hermes/config.yaml` via a read-merge-write (there is no
|
|
936
|
+
* `hermes config unset` verb — see the file header). Clears the tracking entry.
|
|
914
937
|
*/
|
|
915
938
|
removeConfig(integrationId: string): Promise<void>;
|
|
916
939
|
/**
|
|
@@ -947,21 +970,25 @@ declare class HermesApplier implements RuntimeApplier {
|
|
|
947
970
|
/** Convenience: `hermes config set <args>`, serialized + retried. */
|
|
948
971
|
private runConfigSet;
|
|
949
972
|
/**
|
|
950
|
-
*
|
|
951
|
-
*
|
|
952
|
-
*
|
|
953
|
-
*
|
|
954
|
-
*
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
*
|
|
973
|
+
* Delete a set of dotted config keys from `~/.hermes/config.yaml`. There is no
|
|
974
|
+
* `hermes config unset` verb (see file header), so removal is a read-merge-write
|
|
975
|
+
* of the YAML via the Document API — preserving user-authored keys, comments,
|
|
976
|
+
* and formatting. Serialized against every other config mutation on the same
|
|
977
|
+
* queue so a delete can't interleave with an in-flight `hermes config set`.
|
|
978
|
+
*/
|
|
979
|
+
private deleteConfigKeys;
|
|
980
|
+
/**
|
|
981
|
+
* Synchronous config.yaml read-merge-write: parse the doc, delete each dotted
|
|
982
|
+
* key (`model.base_url` → `deleteIn(['model','base_url'])`), and write back only
|
|
983
|
+
* if the text actually changed. No-op if config.yaml doesn't exist yet.
|
|
984
|
+
*/
|
|
985
|
+
private deleteConfigKeysSync;
|
|
986
|
+
/**
|
|
987
|
+
* Run `hermes config <args>` (only `set` is used — removal goes via
|
|
988
|
+
* {@link deleteConfigKeys}), serialized against every other config mutation and
|
|
989
|
+
* retried with backoff. Throws an Error whose (scrubbed) message includes stderr
|
|
990
|
+
* after retries are exhausted, so the real cause propagates to the integration
|
|
991
|
+
* errorMessage without leaking the value.
|
|
965
992
|
*/
|
|
966
993
|
private runConfigCommand;
|
|
967
994
|
private readTracking;
|
package/dist/index.js
CHANGED
|
@@ -1702,10 +1702,40 @@ const BUNDLED_BASELINE_ALLOW = ["browser"];
|
|
|
1702
1702
|
*/
|
|
1703
1703
|
const CONFIG_SET_RETRIES$1 = 3;
|
|
1704
1704
|
const CONFIG_SET_RETRY_DELAY_MS$1 = 750;
|
|
1705
|
+
/**
|
|
1706
|
+
* Sentinel OpenClaw returns from `config get` in place of a sensitive value —
|
|
1707
|
+
* the key IS set, OpenClaw is just hiding it. Verify-after-write must treat a
|
|
1708
|
+
* read-back of this as "present/matches" rather than a mismatch (see
|
|
1709
|
+
* `configValueMatches`).
|
|
1710
|
+
*/
|
|
1711
|
+
const OPENCLAW_REDACTED = "__OPENCLAW_REDACTED__";
|
|
1705
1712
|
const delay$1 = (ms) => new Promise((resolve) => {
|
|
1706
1713
|
setTimeout(resolve, ms);
|
|
1707
1714
|
});
|
|
1708
1715
|
/**
|
|
1716
|
+
* Structural equality for config read-back comparison. Primitives compare by
|
|
1717
|
+
* `Object.is`; arrays are order-SENSITIVE (config arrays are positional);
|
|
1718
|
+
* plain objects are order-INSENSITIVE (key order in JSON is not meaningful).
|
|
1719
|
+
* Used only to confirm a value landed after a `config set` exited non-zero.
|
|
1720
|
+
*/
|
|
1721
|
+
function deepEqual(a, b) {
|
|
1722
|
+
if (Object.is(a, b)) return true;
|
|
1723
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
1724
|
+
const aIsArr = Array.isArray(a);
|
|
1725
|
+
const bIsArr = Array.isArray(b);
|
|
1726
|
+
if (aIsArr !== bIsArr) return false;
|
|
1727
|
+
if (aIsArr && bIsArr) {
|
|
1728
|
+
if (a.length !== b.length) return false;
|
|
1729
|
+
return a.every((v, i) => deepEqual(v, b[i]));
|
|
1730
|
+
}
|
|
1731
|
+
const ao = a;
|
|
1732
|
+
const bo = b;
|
|
1733
|
+
const aKeys = Object.keys(ao);
|
|
1734
|
+
const bKeys = Object.keys(bo);
|
|
1735
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
1736
|
+
return aKeys.every((k) => Object.prototype.hasOwnProperty.call(bo, k) && deepEqual(ao[k], bo[k]));
|
|
1737
|
+
}
|
|
1738
|
+
/**
|
|
1709
1739
|
* Describe a `config set` target WITHOUT leaking values. The value argument is
|
|
1710
1740
|
* the JSON payload (model config, the gateway loopback token via the alfe hook,
|
|
1711
1741
|
* etc.) and must never reach the integration's `errorMessage`, which projects
|
|
@@ -1793,6 +1823,45 @@ var OpenClawApplier = class {
|
|
|
1793
1823
|
this.configSetQueue = result.catch(() => void 0);
|
|
1794
1824
|
return result;
|
|
1795
1825
|
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Read back a single config path and compare it to the value we tried to
|
|
1828
|
+
* write. Used by `applyConfig`'s verify-after-write: a `config set` can exit
|
|
1829
|
+
* non-zero (a hot-reload races the write, OpenClaw's clobber protection
|
|
1830
|
+
* fires) even though the value actually landed, exactly like `applyPlugin`'s
|
|
1831
|
+
* install-then-check tolerance.
|
|
1832
|
+
*
|
|
1833
|
+
* OpenClaw REDACTS sensitive values on `config get`, returning the literal
|
|
1834
|
+
* `__OPENCLAW_REDACTED__` instead of the real value. Treat that as a match:
|
|
1835
|
+
* the key exists and OpenClaw is hiding it, so a deep-equal against the
|
|
1836
|
+
* intended value would otherwise be a false negative and force a re-throw.
|
|
1837
|
+
*/
|
|
1838
|
+
async configValueMatches(path, expected) {
|
|
1839
|
+
try {
|
|
1840
|
+
const { stdout } = await execFileAsync$1("openclaw", [
|
|
1841
|
+
"config",
|
|
1842
|
+
"get",
|
|
1843
|
+
path
|
|
1844
|
+
], { timeout: 1e4 });
|
|
1845
|
+
const actual = JSON.parse(stdout.trim());
|
|
1846
|
+
if (actual === OPENCLAW_REDACTED) return true;
|
|
1847
|
+
return deepEqual(actual, expected);
|
|
1848
|
+
} catch {
|
|
1849
|
+
return false;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Run a read-back `check` with the same retry/backoff cadence as the config
|
|
1854
|
+
* writes — the settling hot-reload may still be rewriting openclaw.json when
|
|
1855
|
+
* we first read back, so a single check can be a false negative. Returns true
|
|
1856
|
+
* on the first success, false once all attempts are exhausted.
|
|
1857
|
+
*/
|
|
1858
|
+
async verifyApplied(check) {
|
|
1859
|
+
for (let attempt = 0; attempt <= this.configSetRetries; attempt++) {
|
|
1860
|
+
if (await check()) return true;
|
|
1861
|
+
if (attempt < this.configSetRetries && this.configSetRetryDelayMs > 0) await delay$1(this.configSetRetryDelayMs * 2 ** attempt);
|
|
1862
|
+
}
|
|
1863
|
+
return false;
|
|
1864
|
+
}
|
|
1796
1865
|
async applyPlugin(spec, _installPath, opts) {
|
|
1797
1866
|
const pkg = stripPluginVersion(spec);
|
|
1798
1867
|
await this.ensurePluginsAllow(pkg);
|
|
@@ -2023,6 +2092,13 @@ var OpenClawApplier = class {
|
|
|
2023
2092
|
try {
|
|
2024
2093
|
await this.runConfigSet([parentPath, JSON.stringify(merged)]);
|
|
2025
2094
|
} catch (err) {
|
|
2095
|
+
if (await this.verifyApplied(async () => {
|
|
2096
|
+
const after = await readParentObject(parentPath);
|
|
2097
|
+
return [...dottedKvs.entries()].every(([k, v]) => deepEqual(after[k], v));
|
|
2098
|
+
})) {
|
|
2099
|
+
log$3.warn({ parentPath }, "openclaw config set exited non-zero but config landed — continuing");
|
|
2100
|
+
continue;
|
|
2101
|
+
}
|
|
2026
2102
|
log$3.error({
|
|
2027
2103
|
err: err instanceof Error ? err.message : String(err),
|
|
2028
2104
|
parentPath
|
|
@@ -2033,11 +2109,16 @@ var OpenClawApplier = class {
|
|
|
2033
2109
|
if (leaves.length > 0) try {
|
|
2034
2110
|
await this.runConfigSet(["--batch-json", JSON.stringify(leaves)]);
|
|
2035
2111
|
} catch (err) {
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2112
|
+
if (await this.verifyApplied(async () => {
|
|
2113
|
+
return (await Promise.all(leaves.map((l) => this.configValueMatches(l.path, l.value)))).every(Boolean);
|
|
2114
|
+
})) log$3.warn({ count: leaves.length }, "openclaw config set --batch-json exited non-zero but config landed — continuing");
|
|
2115
|
+
else {
|
|
2116
|
+
log$3.error({
|
|
2117
|
+
err: err instanceof Error ? err.message : String(err),
|
|
2118
|
+
batch: leaves
|
|
2119
|
+
}, "Failed to set config via openclaw config set --batch-json");
|
|
2120
|
+
throw err;
|
|
2121
|
+
}
|
|
2041
2122
|
}
|
|
2042
2123
|
}
|
|
2043
2124
|
/**
|
|
@@ -2112,11 +2193,17 @@ var OpenClawApplier = class {
|
|
|
2112
2193
|
* HermesApplier — applies integration config (and, later, native plugins) to
|
|
2113
2194
|
* the Hermes runtime (Nous Research's Python agent).
|
|
2114
2195
|
*
|
|
2115
|
-
* Hermes config lives in `~/.hermes/config.yaml` (YAML)
|
|
2116
|
-
* `hermes config set
|
|
2117
|
-
*
|
|
2118
|
-
*
|
|
2119
|
-
*
|
|
2196
|
+
* Hermes config lives in `~/.hermes/config.yaml` (YAML). We APPLY config via the
|
|
2197
|
+
* `hermes config set <dotted.key> <value>` CLI (letting the runtime own its
|
|
2198
|
+
* write path, mirroring the OpenClaw rule). REMOVAL is different: `hermes config
|
|
2199
|
+
* unset` does NOT exist — CONFIRMED against Hermes source, whose `config`
|
|
2200
|
+
* subcommands are only show/set/edit/path/env-path/check/migrate. So teardown is
|
|
2201
|
+
* a config.yaml read-merge-write that deletes the Alfe-applied dotted keys via
|
|
2202
|
+
* the `yaml` Document API (`parseDocument` + `deleteIn`), preserving
|
|
2203
|
+
* user-authored keys, comments, and formatting (the same approach
|
|
2204
|
+
* `hermes-mcp-sync.ts` uses for `mcp_servers`). Per-integration contributions are
|
|
2205
|
+
* tracked in a separate file (`~/.hermes/.alfe-integrations.json`) so we know
|
|
2206
|
+
* exactly which keys to delete.
|
|
2120
2207
|
*
|
|
2121
2208
|
* Scope (Phase 1, MCP-first hybrid):
|
|
2122
2209
|
* - config: SUPPORTED — consumes `installs.runtimes.hermes.config` (the AI-proxy
|
|
@@ -2162,9 +2249,9 @@ function stringifyConfigValue(value) {
|
|
|
2162
2249
|
return typeof value === "string" ? value : JSON.stringify(value);
|
|
2163
2250
|
}
|
|
2164
2251
|
/**
|
|
2165
|
-
* Describe a `config set
|
|
2166
|
-
*
|
|
2167
|
-
*
|
|
2252
|
+
* Describe a `config set` target WITHOUT leaking values. The value argument can
|
|
2253
|
+
* be a secret (`model.api_key`), so it must never reach the integration's
|
|
2254
|
+
* user-facing `errorMessage`. Keep only the program + verb + key.
|
|
2168
2255
|
*/
|
|
2169
2256
|
function redactConfigTarget(args) {
|
|
2170
2257
|
return `hermes config ${args.slice(0, 2).join(" ")}`.trim();
|
|
@@ -2189,13 +2276,16 @@ var HermesApplier = class {
|
|
|
2189
2276
|
runtime = "hermes";
|
|
2190
2277
|
home;
|
|
2191
2278
|
trackingPath;
|
|
2279
|
+
/** `~/.hermes/config.yaml` — read-merge-written on removal (no `config unset`). */
|
|
2280
|
+
configYamlPath;
|
|
2192
2281
|
configSetRetries;
|
|
2193
2282
|
configSetRetryDelayMs;
|
|
2194
|
-
/** Serializes all `
|
|
2283
|
+
/** Serializes all config mutations (CLI `set` + config.yaml delete) so they never interleave. */
|
|
2195
2284
|
configSetQueue = Promise.resolve();
|
|
2196
2285
|
constructor(options = {}) {
|
|
2197
2286
|
this.home = options.home ?? options.workspace ?? DEFAULT_HERMES_HOME$1;
|
|
2198
2287
|
this.trackingPath = options.configPath ?? join(this.home, ".alfe-integrations.json");
|
|
2288
|
+
this.configYamlPath = join(this.home, "config.yaml");
|
|
2199
2289
|
this.configSetRetries = options.configSetRetries ?? CONFIG_SET_RETRIES;
|
|
2200
2290
|
this.configSetRetryDelayMs = options.configSetRetryDelayMs ?? CONFIG_SET_RETRY_DELAY_MS;
|
|
2201
2291
|
}
|
|
@@ -2230,8 +2320,9 @@ var HermesApplier = class {
|
|
|
2230
2320
|
/**
|
|
2231
2321
|
* Remove config previously applied by an integration.
|
|
2232
2322
|
*
|
|
2233
|
-
* Reads the tracking file to find which keys this integration set, then
|
|
2234
|
-
*
|
|
2323
|
+
* Reads the tracking file to find which dotted keys this integration set, then
|
|
2324
|
+
* DELETES each from `~/.hermes/config.yaml` via a read-merge-write (there is no
|
|
2325
|
+
* `hermes config unset` verb — see the file header). Clears the tracking entry.
|
|
2235
2326
|
*/
|
|
2236
2327
|
async removeConfig(integrationId) {
|
|
2237
2328
|
const tracking = this.readTracking();
|
|
@@ -2239,13 +2330,13 @@ var HermesApplier = class {
|
|
|
2239
2330
|
if (!(integrationId in integrations)) return;
|
|
2240
2331
|
const integrationConfig = integrations[integrationId];
|
|
2241
2332
|
const { leaves } = partitionEntries(flattenConfig(integrationConfig));
|
|
2242
|
-
|
|
2243
|
-
await this.
|
|
2333
|
+
try {
|
|
2334
|
+
await this.deleteConfigKeys(leaves.map(({ path }) => path));
|
|
2244
2335
|
} catch (err) {
|
|
2245
2336
|
log$2.warn({
|
|
2246
2337
|
err: err instanceof Error ? err.message : String(err),
|
|
2247
|
-
|
|
2248
|
-
}, "Failed to
|
|
2338
|
+
integrationId
|
|
2339
|
+
}, "Failed to delete Alfe config keys from ~/.hermes/config.yaml");
|
|
2249
2340
|
}
|
|
2250
2341
|
tracking._integrations = Object.fromEntries(Object.entries(integrations).filter(([key]) => key !== integrationId));
|
|
2251
2342
|
this.writeTracking(tracking);
|
|
@@ -2329,23 +2420,40 @@ var HermesApplier = class {
|
|
|
2329
2420
|
return this.runConfigCommand(["set", ...setArgs]);
|
|
2330
2421
|
}
|
|
2331
2422
|
/**
|
|
2332
|
-
*
|
|
2333
|
-
*
|
|
2334
|
-
*
|
|
2335
|
-
*
|
|
2336
|
-
*
|
|
2337
|
-
* read-merge-write of config.yaml. Not implemented now — `unset` is the
|
|
2338
|
-
* documented verb; confirm in the Phase-0 spike before adding a fallback.
|
|
2339
|
-
* TODO(phase-0 spike): confirm `hermes config unset <key>` exists.
|
|
2423
|
+
* Delete a set of dotted config keys from `~/.hermes/config.yaml`. There is no
|
|
2424
|
+
* `hermes config unset` verb (see file header), so removal is a read-merge-write
|
|
2425
|
+
* of the YAML via the Document API — preserving user-authored keys, comments,
|
|
2426
|
+
* and formatting. Serialized against every other config mutation on the same
|
|
2427
|
+
* queue so a delete can't interleave with an in-flight `hermes config set`.
|
|
2340
2428
|
*/
|
|
2341
|
-
|
|
2342
|
-
|
|
2429
|
+
deleteConfigKeys(paths) {
|
|
2430
|
+
const run = () => {
|
|
2431
|
+
this.deleteConfigKeysSync(paths);
|
|
2432
|
+
return Promise.resolve();
|
|
2433
|
+
};
|
|
2434
|
+
const result = this.configSetQueue.then(run, run);
|
|
2435
|
+
this.configSetQueue = result.catch(() => void 0);
|
|
2436
|
+
return result;
|
|
2343
2437
|
}
|
|
2344
2438
|
/**
|
|
2345
|
-
*
|
|
2346
|
-
*
|
|
2347
|
-
*
|
|
2348
|
-
|
|
2439
|
+
* Synchronous config.yaml read-merge-write: parse the doc, delete each dotted
|
|
2440
|
+
* key (`model.base_url` → `deleteIn(['model','base_url'])`), and write back only
|
|
2441
|
+
* if the text actually changed. No-op if config.yaml doesn't exist yet.
|
|
2442
|
+
*/
|
|
2443
|
+
deleteConfigKeysSync(paths) {
|
|
2444
|
+
if (paths.length === 0 || !existsSync(this.configYamlPath)) return;
|
|
2445
|
+
const doc = parseDocument(readFileSync(this.configYamlPath, "utf-8"));
|
|
2446
|
+
const before = doc.toString();
|
|
2447
|
+
for (const path of paths) doc.deleteIn(path.split("."));
|
|
2448
|
+
const after = doc.toString();
|
|
2449
|
+
if (before !== after) writeFileSync(this.configYamlPath, after, "utf-8");
|
|
2450
|
+
}
|
|
2451
|
+
/**
|
|
2452
|
+
* Run `hermes config <args>` (only `set` is used — removal goes via
|
|
2453
|
+
* {@link deleteConfigKeys}), serialized against every other config mutation and
|
|
2454
|
+
* retried with backoff. Throws an Error whose (scrubbed) message includes stderr
|
|
2455
|
+
* after retries are exhausted, so the real cause propagates to the integration
|
|
2456
|
+
* errorMessage without leaking the value.
|
|
2349
2457
|
*/
|
|
2350
2458
|
runConfigCommand(args) {
|
|
2351
2459
|
const run = async () => {
|
|
@@ -2421,15 +2529,13 @@ var HermesApplier = class {
|
|
|
2421
2529
|
const log$1 = createLogger("HermesMcpSync");
|
|
2422
2530
|
const DEFAULT_HERMES_HOME = join(homedir(), ".hermes");
|
|
2423
2531
|
/**
|
|
2424
|
-
*
|
|
2425
|
-
*
|
|
2426
|
-
*
|
|
2427
|
-
*
|
|
2428
|
-
*
|
|
2429
|
-
*
|
|
2430
|
-
*
|
|
2431
|
-
* into each `mcp_servers.<id>.env` and skip the `.env` file entirely — change
|
|
2432
|
-
* `withAlfeApiKey()` + `ensureEnvApiKey()` together in that one case.
|
|
2532
|
+
* SEAM #1 — `${VAR}` interpolation from `~/.hermes/.env`. CONFIRMED (Hermes
|
|
2533
|
+
* source `config.py:5790` `_expand_env_vars`): Hermes loads `~/.hermes/.env` via
|
|
2534
|
+
* dotenv into `os.environ`, then recursively expands `${VAR}` refs across the
|
|
2535
|
+
* ENTIRE merged config — including `mcp_servers.<id>.env.*` (and `model.api_key`
|
|
2536
|
+
* / `model.base_url`). So injecting `ALFE_API_KEY=${ALFE_API_KEY}` into every
|
|
2537
|
+
* Alfe-owned stdio server's env and writing the real value to `~/.hermes/.env`
|
|
2538
|
+
* resolves at spawn time. The inline-literal fallback is therefore not needed.
|
|
2433
2539
|
*/
|
|
2434
2540
|
const ALFE_API_KEY_ENV_REF = "${ALFE_API_KEY}";
|
|
2435
2541
|
const DEFAULT_DEBOUNCE_MS = 250;
|
package/package.json
CHANGED