appback-remoteagent 0.13.4 → 0.13.5

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/bot.js CHANGED
@@ -1571,6 +1571,10 @@ function formatSecretHelp() {
1571
1571
  "```text",
1572
1572
  "node \"$REMOTEAGENT_SECRET_BIN\" get <KEY>",
1573
1573
  "```",
1574
+ "If an agent obtains a new secret value such as an OAuth refresh token, it can delegate storage without printing the value:",
1575
+ "```text",
1576
+ "printf '%s' \"$VALUE\" | node \"$REMOTEAGENT_SECRET_BIN\" set <KEY>",
1577
+ "```",
1574
1578
  ].join("\n");
1575
1579
  }
1576
1580
  function formatRuntimeOptions() {
@@ -2,17 +2,28 @@
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
- import { readSecretValue } from "./services/agent-memory-service.js";
6
- function main() {
5
+ import { readSecretValue, writeSecretValue } from "./services/agent-memory-service.js";
6
+ async function main() {
7
7
  const [command, key] = process.argv.slice(2);
8
- if (command !== "get" || !key) {
9
- process.stderr.write("Usage: secret-helper get <KEY>\n");
8
+ if (!["get", "set"].includes(command ?? "") || !key) {
9
+ process.stderr.write("Usage: secret-helper get <KEY>\n secret-helper set <KEY> # reads value from stdin\n");
10
10
  process.exitCode = 2;
11
11
  return;
12
12
  }
13
13
  const dataDir = process.env.REMOTEAGENT_DATA_DIR?.trim()
14
14
  || process.env.DATA_DIR?.trim()
15
15
  || path.join(os.homedir(), ".remoteagent");
16
+ if (command === "set") {
17
+ const value = await readStdin();
18
+ if (!value) {
19
+ process.stderr.write("Secret value was empty.\n");
20
+ process.exitCode = 2;
21
+ return;
22
+ }
23
+ writeSecretValue(dataDir, key.trim(), value);
24
+ process.stdout.write(`Stored secret: ${key.trim()}\n`);
25
+ return;
26
+ }
16
27
  const value = readSecretValue(dataDir, key.trim());
17
28
  if (!value) {
18
29
  process.stderr.write(`Secret was not found: ${key}\n`);
@@ -21,4 +32,11 @@ function main() {
21
32
  }
22
33
  process.stdout.write(value);
23
34
  }
24
- main();
35
+ async function readStdin() {
36
+ const chunks = [];
37
+ for await (const chunk of process.stdin) {
38
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
39
+ }
40
+ return Buffer.concat(chunks).toString("utf8").trim();
41
+ }
42
+ void main();
@@ -338,8 +338,16 @@ export class AgentMemoryService {
338
338
  ? ["Document index:", ...docs.map((doc) => `- ${doc.keyword}: ${doc.targetPath}${doc.note ? ` (${doc.note})` : ""}`)].join("\n")
339
339
  : undefined,
340
340
  secrets.length > 0
341
- ? ["Secret keys available through `node \"$REMOTEAGENT_SECRET_BIN\" get <KEY>`:", ...secrets.map((key) => `- ${key}`)].join("\n")
342
- : undefined,
341
+ ? [
342
+ "Secret keys available through `node \"$REMOTEAGENT_SECRET_BIN\" get <KEY>`:",
343
+ ...secrets.map((key) => `- ${key}`),
344
+ "If you generate a new secret value such as an OAuth refresh token, store it without printing it:",
345
+ "`printf '%s' \"$VALUE\" | node \"$REMOTEAGENT_SECRET_BIN\" set <KEY>`",
346
+ ].join("\n")
347
+ : [
348
+ "Secrets can be stored without printing values:",
349
+ "`printf '%s' \"$VALUE\" | node \"$REMOTEAGENT_SECRET_BIN\" set <KEY>`",
350
+ ].join("\n"),
343
351
  artifacts.length > 0
344
352
  ? ["Recent session artifacts:", ...artifacts.map((artifact) => `- ${artifact.id} ${artifact.kind}: ${artifact.path}`)].join("\n")
345
353
  : undefined,
@@ -735,3 +743,32 @@ export function readSecretValue(dataDir, key) {
735
743
  return undefined;
736
744
  }
737
745
  }
746
+ export function writeSecretValue(dataDir, key, value) {
747
+ if (!/^[A-Z0-9_.-]{1,80}$/.test(key)) {
748
+ throw new Error("Secret key must be 1-80 chars using A-Z, 0-9, dot, underscore, or dash.");
749
+ }
750
+ const filePath = path.join(dataDir, "managed", "secrets.json");
751
+ fsSync.mkdirSync(path.dirname(filePath), { recursive: true });
752
+ let secrets = {};
753
+ try {
754
+ const raw = fsSync.readFileSync(filePath, "utf8");
755
+ secrets = JSON.parse(raw);
756
+ }
757
+ catch {
758
+ secrets = {};
759
+ }
760
+ const now = new Date().toISOString();
761
+ secrets[key] = {
762
+ key,
763
+ value,
764
+ createdAt: secrets[key]?.createdAt ?? now,
765
+ updatedAt: now,
766
+ };
767
+ fsSync.writeFileSync(filePath, `${JSON.stringify(secrets, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
768
+ try {
769
+ fsSync.chmodSync(filePath, 0o600);
770
+ }
771
+ catch {
772
+ // Best effort only; some platforms do not support chmod.
773
+ }
774
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appback-remoteagent",
3
- "version": "0.13.4",
3
+ "version": "0.13.5",
4
4
  "description": "Personal installable session server for continuing local AI work across PC and Telegram",
5
5
  "license": "MIT",
6
6
  "type": "module",