total-recall-brain 3.21.1 → 3.21.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/package.json +1 -1
- package/src/cli/secret.mjs +34 -2
- package/src/core/secrets-remote-deploy.mjs +10 -1
package/package.json
CHANGED
package/src/cli/secret.mjs
CHANGED
|
@@ -60,6 +60,16 @@ import { resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
|
|
|
60
60
|
import fs from 'node:fs';
|
|
61
61
|
import path from 'node:path';
|
|
62
62
|
|
|
63
|
+
// Reads a secret value from stdin (fd 0) so it never has to appear as a CLI
|
|
64
|
+
// argument — CLI args land in shell history, `ps aux`, and any transcript
|
|
65
|
+
// that echoes the command. Trims exactly one trailing newline (what a
|
|
66
|
+
// pasted/piped value normally carries), not all whitespace, so a value that
|
|
67
|
+
// legitimately starts or ends with a space round-trips intact.
|
|
68
|
+
function readStdinValue() {
|
|
69
|
+
const raw = fs.readFileSync(0, 'utf8');
|
|
70
|
+
return raw.endsWith('\n') ? raw.slice(0, -1) : raw;
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
function printHelp() {
|
|
64
74
|
console.log(`
|
|
65
75
|
total-recall secret — Secrets store (separate from memory vault)
|
|
@@ -67,11 +77,13 @@ function printHelp() {
|
|
|
67
77
|
Usage: total-recall secret <command> [options]
|
|
68
78
|
|
|
69
79
|
Commands:
|
|
70
|
-
set <key>
|
|
80
|
+
set <key> [value] Store a secret (0600 file; optional AES via TR_SECRETS_PASSWORD)
|
|
81
|
+
--stdin reads the value from fd0 instead of argv,
|
|
82
|
+
so it never lands in shell history/ps/a transcript
|
|
71
83
|
generate <key> Generate a random secret for outside integrations
|
|
72
84
|
get <key> Print secret value (audited)
|
|
73
85
|
list List keys + metadata only (no values)
|
|
74
|
-
rotate <key>
|
|
86
|
+
rotate <key> [value] Replace value and mark rotated (also supports --stdin)
|
|
75
87
|
delete <key> Remove a secret
|
|
76
88
|
audit [--limit N] Recent set/get/rotate events (no values)
|
|
77
89
|
usage [--days N] Sum usage.jsonl for last N days (default 1)
|
|
@@ -212,6 +224,7 @@ function parseArgs(args) {
|
|
|
212
224
|
remoteUser: 'root',
|
|
213
225
|
remotePort: 22,
|
|
214
226
|
restartCmd: null,
|
|
227
|
+
stdin: false,
|
|
215
228
|
};
|
|
216
229
|
if (!args.length || args[0] === '--help' || args[0] === '-h') {
|
|
217
230
|
out.help = true;
|
|
@@ -393,6 +406,9 @@ function parseArgs(args) {
|
|
|
393
406
|
case '--no-shared-ok':
|
|
394
407
|
out.shared_ok = false;
|
|
395
408
|
break;
|
|
409
|
+
case '--stdin':
|
|
410
|
+
out.stdin = true;
|
|
411
|
+
break;
|
|
396
412
|
case '--help':
|
|
397
413
|
case '-h':
|
|
398
414
|
out.help = true;
|
|
@@ -424,6 +440,22 @@ export default async function secretCli(argv) {
|
|
|
424
440
|
process.exit(1);
|
|
425
441
|
}
|
|
426
442
|
|
|
443
|
+
if (opts.stdin) {
|
|
444
|
+
if (!['set', 'rotate'].includes(opts.command)) {
|
|
445
|
+
console.error('❌ --stdin is only valid with `set` or `rotate`');
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
if (!opts.key) {
|
|
449
|
+
console.error(`Usage: total-recall secret ${opts.command} <key> --stdin`);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
opts.value = readStdinValue();
|
|
453
|
+
if (!opts.value) {
|
|
454
|
+
console.error('❌ --stdin read an empty value');
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
427
459
|
try {
|
|
428
460
|
if (opts.command === 'set') {
|
|
429
461
|
if (!opts.key || opts.value == null) {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import fs from 'node:fs';
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { spawnSync } from 'node:child_process';
|
|
20
|
-
import { buildEnvProjection, mergeEnvManagedBlock } from './secrets-env-export.mjs';
|
|
20
|
+
import { buildEnvProjection, mergeEnvManagedBlock, projectSlugFromPath } from './secrets-env-export.mjs';
|
|
21
21
|
import { resolveSecretsPath } from './secrets-store.mjs';
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -176,9 +176,18 @@ export async function deployEnvToRemote(brainDir, targetName, opts = {}) {
|
|
|
176
176
|
);
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
// Match the same projection local `export-env --path .` builds: repo-bound
|
|
180
|
+
// secrets (set with --repo <name>) only resolve when the caller's project
|
|
181
|
+
// slug is passed through. Without this, deployEnvToRemote silently dropped
|
|
182
|
+
// every repo-scoped key and pushed only unbound/global ones — the opposite
|
|
183
|
+
// of what --repo scoping is for. cwd is the right default since this CLI
|
|
184
|
+
// is always invoked from the repo root (same assumption --path . makes).
|
|
185
|
+
const projectPath = opts.projectPath || process.cwd();
|
|
179
186
|
const projection = await buildEnvProjection(brainDir, {
|
|
180
187
|
includeGlobal: target.includeGlobal !== false,
|
|
181
188
|
keys: opts.keys?.length ? opts.keys : target.keys,
|
|
189
|
+
projectPath,
|
|
190
|
+
projectSlug: opts.projectSlug || projectSlugFromPath(projectPath),
|
|
182
191
|
});
|
|
183
192
|
|
|
184
193
|
const remoteFile = path.posix.join(target.remotePath, target.filename || '.env');
|