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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.21.1",
3
+ "version": "3.21.2",
4
4
  "description": "Personal AI system — database-free, SSSS-powered intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -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> <value> Store a secret (0600 file; optional AES via TR_SECRETS_PASSWORD)
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> <value> Replace value and mark rotated
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');