import fs from "node:fs/promises"; import path from "node:path"; import { spawn } from "node:child_process"; import { randomUUID } from "node:crypto"; import { afterEach, describe, expect, test } from "vitest"; import { parseEnvironmentAssignments, splitServiceCredentialFile, } from "../src/runtime/credential-compartments.js"; import { temporaryProject } from "./helpers.js"; const roots: string[] = []; afterEach(async () => { await Promise.all(roots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true }))); }); describe("service credential compartments", () => { test("writes exact service-specific variable sets without exposing values in its receipt", async () => { const root = await temporaryProject("thoughtstream-credential-split-"); roots.push(root); const values = { letta: generatedValue("letta"), tinker: generatedValue("tinker"), agent: generatedValue("agent"), coAgent: generatedValue("co-agent"), blueskyListener: generatedValue("bluesky-listener"), xListener: generatedValue("x-listener"), socialListener: generatedValue("social-listener"), memory: generatedValue("memory-root"), policy: generatedValue("policy-path"), catalog: generatedValue("catalog-root"), bot: generatedValue("bot"), webhook: generatedValue("webhook"), xConsumer: generatedValue("x-consumer"), xManagement: generatedValue("x-management"), xUserManagement: generatedValue("x-user-management"), fastmail: generatedValue("fastmail"), }; const source = path.join(root, "source.env"); await fs.writeFile(source, [ `LETTA_API_KEY=${values.letta}`, `TINKER_API_KEY=${values.tinker}`, `THOUGHTSTREAM_LETTA_TELEGRAM_AGENT_ID=${values.agent}`, `THOUGHTSTREAM_LETTA_CO_AGENT_ID=${values.coAgent}`, `THOUGHTSTREAM_LETTA_BLUESKY_LISTENER_AGENT_ID=${values.blueskyListener}`, `THOUGHTSTREAM_LETTA_X_LISTENER_AGENT_ID=${values.xListener}`, `THOUGHTSTREAM_LETTA_SOCIAL_LISTENER_AGENT_ID=${values.socialListener}`, "THOUGHTSTREAM_LETTA_ENABLE_BLUESKY_LISTENER=1", "THOUGHTSTREAM_LETTA_ENABLE_X_LISTENER=1", "THOUGHTSTREAM_LETTA_ENABLE_SOCIAL_LISTENER=1", `THOUGHTSTREAM_LETTA_CO_MEMORY_DIR=${values.memory}`, "THOUGHTSTREAM_ENABLE_COIL_PUBLIC_KNOWLEDGE=1", `THOUGHTSTREAM_PUBLIC_KNOWLEDGE_POLICY_PATH=${values.policy}`, `THOUGHTSTREAM_PUBLIC_KNOWLEDGE_CATALOG_ROOT=${values.catalog}`, `THOUGHTSTREAM_TELEGRAM_BOT_TOKEN=${values.bot}`, `THOUGHTSTREAM_TELEGRAM_WEBHOOK_SECRET=${values.webhook}`, `THOUGHTSTREAM_X_CONSUMER_SECRET=${values.xConsumer}`, `THOUGHTSTREAM_X_MANAGEMENT_BEARER_TOKEN=${values.xManagement}`, `THOUGHTSTREAM_X_CAMERON_USER_ACCESS_TOKEN=${values.xUserManagement}`, `FASTMAIL_API_KEY=${values.fastmail}`, "THOUGHTSTREAM_X_API_BASE_URL=https://api.x.com", "THOUGHTSTREAM_TELEGRAM_API_BASE_URL=https://telegram.example.invalid", "THOUGHTSTREAM_JETSTREAM_URL=wss://jetstream.example.invalid/subscribe", "THOUGHTSTREAM_TINKER_REASONING_SMALL_MODEL=fixture/reasoning", "UNRELATED_CONFIGURATION=present", "", ].join("\n"), { mode: 0o600 }); const output = path.join(root, "credentials"); const receipt = await splitServiceCredentialFile(source, output, { consumerProviders: ["letta"], publicContentRoots: [], }); expect(receipt.files["telegram-webhook"].variableNames).toEqual([ "THOUGHTSTREAM_TELEGRAM_API_BASE_URL", "THOUGHTSTREAM_TELEGRAM_BOT_TOKEN", "THOUGHTSTREAM_TELEGRAM_WEBHOOK_SECRET", ]); expect(receipt.files["telegram-dispatcher"].variableNames).toEqual([ "THOUGHTSTREAM_TELEGRAM_API_BASE_URL", "THOUGHTSTREAM_TELEGRAM_BOT_TOKEN", ]); expect(receipt.files["x-webhook"].variableNames).toEqual([ "THOUGHTSTREAM_X_CONSUMER_SECRET", ]); expect(receipt.files["x-management"].variableNames).toEqual([ "THOUGHTSTREAM_X_API_BASE_URL", "THOUGHTSTREAM_X_MANAGEMENT_BEARER_TOKEN", ]); expect(receipt.files["x-user-management"].variableNames).toEqual([ "THOUGHTSTREAM_X_API_BASE_URL", "THOUGHTSTREAM_X_CAMERON_USER_ACCESS_TOKEN", ]); expect(receipt.files["fastmail-jmap"].variableNames).toEqual(["FASTMAIL_API_KEY"]); expect(receipt.files.consumer.variableNames).toEqual([ "LETTA_API_KEY", "THOUGHTSTREAM_ENABLE_COIL_PUBLIC_KNOWLEDGE", "THOUGHTSTREAM_LETTA_BLUESKY_LISTENER_AGENT_ID", "THOUGHTSTREAM_LETTA_CO_AGENT_ID", "THOUGHTSTREAM_LETTA_CO_MEMORY_DIR", "THOUGHTSTREAM_LETTA_ENABLE_BLUESKY_LISTENER", "THOUGHTSTREAM_LETTA_ENABLE_SOCIAL_LISTENER", "THOUGHTSTREAM_LETTA_ENABLE_X_LISTENER", "THOUGHTSTREAM_LETTA_SOCIAL_LISTENER_AGENT_ID", "THOUGHTSTREAM_LETTA_TELEGRAM_AGENT_ID", "THOUGHTSTREAM_LETTA_X_LISTENER_AGENT_ID", "THOUGHTSTREAM_PUBLIC_KNOWLEDGE_CATALOG_ROOT", "THOUGHTSTREAM_PUBLIC_KNOWLEDGE_POLICY_PATH", ]); expect(receipt.files.jetstream.variableNames).toEqual(["THOUGHTSTREAM_JETSTREAM_URL"]); expect(receipt.omittedVariableNames).toEqual([ "THOUGHTSTREAM_TINKER_REASONING_SMALL_MODEL", "TINKER_API_KEY", "UNRELATED_CONFIGURATION", ]); const receiptText = JSON.stringify(receipt); for (const value of Object.values(values)) expect(receiptText).not.toContain(value); const webhookText = await fs.readFile(receipt.files["telegram-webhook"].path, "utf8"); const dispatcherText = await fs.readFile(receipt.files["telegram-dispatcher"].path, "utf8"); const consumerText = await fs.readFile(receipt.files.consumer.path, "utf8"); const jetstreamText = await fs.readFile(receipt.files.jetstream.path, "utf8"); const xWebhookText = await fs.readFile(receipt.files["x-webhook"].path, "utf8"); const xManagementText = await fs.readFile(receipt.files["x-management"].path, "utf8"); const xUserManagementText = await fs.readFile(receipt.files["x-user-management"].path, "utf8"); const fastmailText = await fs.readFile(receipt.files["fastmail-jmap"].path, "utf8"); expect(webhookText).toContain(values.bot); expect(webhookText).toContain(values.webhook); expect(webhookText).not.toContain(values.letta); expect(dispatcherText).toContain(values.bot); expect(dispatcherText).not.toContain(values.webhook); expect(dispatcherText).not.toContain(values.letta); expect(consumerText).toContain(values.letta); expect(consumerText).toContain(values.agent); expect(consumerText).toContain(values.coAgent); expect(consumerText).toContain(values.blueskyListener); expect(consumerText).toContain(values.xListener); expect(consumerText).toContain(values.socialListener); expect(consumerText).toContain(values.memory); expect(consumerText).toContain(values.policy); expect(consumerText).toContain(values.catalog); expect(consumerText).not.toContain(values.bot); expect(consumerText).not.toContain(values.tinker); expect(xWebhookText).toContain(values.xConsumer); expect(xWebhookText).not.toContain(values.xManagement); expect(xWebhookText).not.toContain(values.letta); expect(xManagementText).toContain(values.xManagement); expect(xManagementText).not.toContain(values.xConsumer); expect(xManagementText).not.toContain(values.letta); expect(xUserManagementText).toContain(values.xUserManagement); expect(xUserManagementText).not.toContain(values.xManagement); expect(xUserManagementText).not.toContain(values.xConsumer); expect(xUserManagementText).not.toContain(values.letta); expect(fastmailText).toContain(values.fastmail); expect(fastmailText).not.toContain(values.letta); expect(fastmailText).not.toContain(values.bot); for (const value of Object.values(values)) expect(jetstreamText).not.toContain(value); expect((await fs.stat(output)).mode & 0o777).toBe(0o700); for (const file of Object.values(receipt.files)) { expect((await fs.stat(file.path)).mode & 0o777).toBe(0o600); } }); test("CLI receipt remains value-dark", async () => { const root = await temporaryProject("thoughtstream-credential-cli-"); roots.push(root); const source = path.join(root, "source.env"); const values = [generatedValue("letta"), generatedValue("agent"), generatedValue("bot"), generatedValue("webhook")]; await fs.writeFile(source, [ `LETTA_API_KEY=${values[0]}`, `THOUGHTSTREAM_LETTA_TELEGRAM_AGENT_ID=${values[1]}`, `THOUGHTSTREAM_TELEGRAM_BOT_TOKEN=${values[2]}`, `THOUGHTSTREAM_TELEGRAM_WEBHOOK_SECRET=${values[3]}`, "", ].join("\n"), { mode: 0o600 }); const result = await runSplitter(source, path.join(root, "credentials")); expect(result.code).toBe(0); expect(JSON.parse(result.stdout)).toMatchObject({ files: { "telegram-webhook": { variableNames: expect.any(Array) }, consumer: { variableNames: expect.any(Array) }, }, }); for (const value of values) { expect(result.stdout).not.toContain(value); expect(result.stderr).not.toContain(value); } }, 15_000); test("refuses a partial source that would erase existing compartments before any write", async () => { const root = await temporaryProject("thoughtstream-credential-removal-preflight-"); roots.push(root); const source = path.join(root, "source.env"); const output = path.join(root, "credentials"); await fs.writeFile(source, validSource({ includeX: true }), { mode: 0o600 }); await splitServiceCredentialFile(source, output, { consumerProviders: ["letta"], publicContentRoots: [], }); const before = await directoryEntries(output); await fs.writeFile(source, validSource(), { mode: 0o600 }); await expect(splitServiceCredentialFile(source, output, { consumerProviders: ["letta"], publicContentRoots: [], })).rejects.toThrow( "Credential split would remove existing assignments without explicit authorization: x-management:THOUGHTSTREAM_X_MANAGEMENT_BEARER_TOKEN, x-webhook:THOUGHTSTREAM_X_CONSUMER_SECRET", ); expect(await directoryEntries(output)).toEqual(before); }); test("requires the exact current variable-name set for intentional credential removal", async () => { const root = await temporaryProject("thoughtstream-credential-explicit-removal-"); roots.push(root); const source = path.join(root, "source.env"); const output = path.join(root, "credentials"); const fullSource = validSource({ includeX: true }); await fs.writeFile(source, fullSource, { mode: 0o600 }); await splitServiceCredentialFile(source, output, { consumerProviders: ["letta"], publicContentRoots: [], }); const before = await directoryEntries(output); await fs.writeFile(source, withoutXAssignments(fullSource), { mode: 0o600 }); await expect(splitServiceCredentialFile(source, output, { consumerProviders: ["letta"], publicContentRoots: [], allowedRemovedVariableNames: [ "THOUGHTSTREAM_X_CONSUMER_SECRET", "THOUGHTSTREAM_X_MANAGEMENT_BEARER_TOKEN", "NOT_IN_PLAN", ], })).rejects.toThrow("Explicitly removed variable names do not match the current removal plan: NOT_IN_PLAN"); const receipt = await splitServiceCredentialFile(source, output, { consumerProviders: ["letta"], publicContentRoots: [], allowedRemovedVariableNames: [ "THOUGHTSTREAM_X_CONSUMER_SECRET", "THOUGHTSTREAM_X_MANAGEMENT_BEARER_TOKEN", ], }); expect(receipt.removedVariableNames).toEqual([ "THOUGHTSTREAM_X_CONSUMER_SECRET", "THOUGHTSTREAM_X_MANAGEMENT_BEARER_TOKEN", ]); expect(parseEnvironmentAssignments(await fs.readFile(receipt.files["x-webhook"].path, "utf8")).size).toBe(0); expect(parseEnvironmentAssignments(await fs.readFile(receipt.files["x-management"].path, "utf8")).size).toBe(0); const after = await directoryEntries(output); for (const name of Object.keys(before).filter((name) => !["x-webhook.env", "x-management.env"].includes(name))) { expect(after[name]).toBe(before[name]); } }); test("forwards repeated explicit removals through the value-dark CLI", async () => { const root = await temporaryProject("thoughtstream-credential-cli-removal-"); roots.push(root); const source = path.join(root, "source.env"); const output = path.join(root, "credentials"); const fullSource = validSource({ includeX: true }); await fs.writeFile(source, fullSource, { mode: 0o600 }); await splitServiceCredentialFile(source, output, { consumerProviders: ["letta"], publicContentRoots: [], }); await fs.writeFile(source, withoutXAssignments(fullSource), { mode: 0o600 }); const result = await runSplitter(source, output, [ "--allow-remove", "THOUGHTSTREAM_X_CONSUMER_SECRET", "--allow-remove", "THOUGHTSTREAM_X_MANAGEMENT_BEARER_TOKEN", ]); expect(result.code).toBe(0); expect(JSON.parse(result.stdout).removedVariableNames).toEqual([ "THOUGHTSTREAM_X_CONSUMER_SECRET", "THOUGHTSTREAM_X_MANAGEMENT_BEARER_TOKEN", ]); expect(result.stdout).not.toContain("x-consumer-"); expect(result.stdout).not.toContain("x-management-"); expect(result.stderr).toBe(""); }, 15_000); test("refuses a symlinked existing compartment during removal preflight", async () => { const root = await temporaryProject("thoughtstream-credential-preflight-symlink-"); roots.push(root); const source = path.join(root, "source.env"); const output = path.join(root, "credentials"); const target = path.join(root, "outside.env"); await fs.writeFile(source, validSource(), { mode: 0o600 }); await fs.mkdir(output, { mode: 0o700 }); await fs.writeFile(target, "THOUGHTSTREAM_X_CONSUMER_SECRET=synthetic\n", { mode: 0o600 }); await fs.symlink(target, path.join(output, "x-webhook.env")); await expect(splitServiceCredentialFile(source, output, { consumerProviders: ["letta"], publicContentRoots: [], })).rejects.toThrow("Existing credential compartment must be a regular non-symlink file: x-webhook"); expect(await fs.readFile(target, "utf8")).toBe("THOUGHTSTREAM_X_CONSUMER_SECRET=synthetic\n"); }); test("selects Tinker and fixed OpenAI credentials without retaining Letta authority", async () => { const root = await temporaryProject("thoughtstream-credential-pi-openai-"); roots.push(root); const values = { letta: generatedValue("letta"), agent: generatedValue("agent"), tinker: generatedValue("tinker"), openai: generatedValue("openai"), bot: generatedValue("bot"), webhook: generatedValue("webhook"), }; const source = path.join(root, "source.env"); await fs.writeFile(source, [ `LETTA_API_KEY=${values.letta}`, `THOUGHTSTREAM_LETTA_TELEGRAM_AGENT_ID=${values.agent}`, `TINKER_API_KEY=${values.tinker}`, `THOUGHTSTREAM_TINKER_ESCALATION_MODEL=openai/gpt-oss-120b`, `OPENAI_API_KEY=${values.openai}`, `THOUGHTSTREAM_TELEGRAM_BOT_TOKEN=${values.bot}`, `THOUGHTSTREAM_TELEGRAM_WEBHOOK_SECRET=${values.webhook}`, "", ].join("\n"), { mode: 0o600 }); const receipt = await splitServiceCredentialFile(source, path.join(root, "credentials"), { consumerProviders: ["tinker", "openai"], publicContentRoots: [], }); expect(receipt.files.consumer.variableNames).toEqual([ "OPENAI_API_KEY", "THOUGHTSTREAM_TINKER_ESCALATION_MODEL", "TINKER_API_KEY", ]); const consumerText = await fs.readFile(receipt.files.consumer.path, "utf8"); expect(consumerText).toContain(values.tinker); expect(consumerText).toContain(values.openai); expect(consumerText).not.toContain(values.letta); expect(consumerText).not.toContain(values.agent); expect(JSON.stringify(receipt)).not.toContain(values.tinker); expect(JSON.stringify(receipt)).not.toContain(values.openai); }); test("routes local Agent SDK identity without requiring or copying a Cloud API key", async () => { const root = await temporaryProject("thoughtstream-credential-local-letta-"); roots.push(root); const source = path.join(root, "source.env"); await fs.writeFile(source, [ `THOUGHTSTREAM_LETTA_CO_AGENT_ID=${generatedValue("co-agent")}`, "THOUGHTSTREAM_LETTA_CO_MEMORY_DIR=/private/co-memory", "THOUGHTSTREAM_ENABLE_COIL_PUBLIC_KNOWLEDGE=1", "THOUGHTSTREAM_PUBLIC_KNOWLEDGE_POLICY_PATH=/private/policy.json", "THOUGHTSTREAM_PUBLIC_KNOWLEDGE_CATALOG_ROOT=/private/catalog", `THOUGHTSTREAM_TELEGRAM_BOT_TOKEN=${generatedValue("bot")}`, `THOUGHTSTREAM_TELEGRAM_WEBHOOK_SECRET=${generatedValue("webhook")}`, "", ].join("\n"), { mode: 0o600 }); const receipt = await splitServiceCredentialFile(source, path.join(root, "credentials"), { consumerProviders: ["letta-local"], publicContentRoots: [], }); expect(receipt.files.consumer.variableNames).toEqual([ "THOUGHTSTREAM_ENABLE_COIL_PUBLIC_KNOWLEDGE", "THOUGHTSTREAM_LETTA_CO_AGENT_ID", "THOUGHTSTREAM_LETTA_CO_MEMORY_DIR", "THOUGHTSTREAM_PUBLIC_KNOWLEDGE_CATALOG_ROOT", "THOUGHTSTREAM_PUBLIC_KNOWLEDGE_POLICY_PATH", ]); expect(await fs.readFile(receipt.files.consumer.path, "utf8")).not.toContain("LETTA_API_KEY"); }); test("refuses Git and configured public-content destinations before creating credential files", async () => { const root = await temporaryProject("thoughtstream-credential-path-"); roots.push(root); const source = path.join(root, "source.env"); await fs.writeFile(source, validSource(), { mode: 0o600 }); const gitRoot = path.join(root, "repo"); await fs.mkdir(path.join(gitRoot, ".git"), { recursive: true }); const gitOutput = path.join(gitRoot, "private", "credentials"); await expect(splitServiceCredentialFile(source, gitOutput, { consumerProviders: ["letta"], publicContentRoots: [], })).rejects.toThrow("inside a Git worktree"); await expect(fs.stat(gitOutput)).rejects.toMatchObject({ code: "ENOENT" }); const publicRoot = path.join(root, "published"); const publicOutput = path.join(publicRoot, "credentials"); await expect(splitServiceCredentialFile(source, publicOutput, { consumerProviders: ["letta"], publicContentRoots: [publicRoot], })).rejects.toThrow("inside a public-content root"); await expect(fs.stat(publicOutput)).rejects.toMatchObject({ code: "ENOENT" }); }); test("anchors writes to the approved directory inode and fails closed when its path is swapped", async () => { const root = await temporaryProject("thoughtstream-credential-swap-"); roots.push(root); const source = path.join(root, "source.env"); const sentinel = generatedValue("credential-sentinel"); await fs.writeFile(source, validSource().replace(/^LETTA_API_KEY=.*$/m, `LETTA_API_KEY=${sentinel}`), { mode: 0o600 }); const output = path.join(root, "private-credentials"); const displaced = path.join(root, "approved-directory-inode"); const publicRoot = path.join(root, "synthetic-public-repository"); await fs.mkdir(path.join(publicRoot, ".git"), { recursive: true }); let swapped = false; await expect(splitServiceCredentialFile(source, output, { consumerProviders: ["letta"], publicContentRoots: [publicRoot], beforeFinalize: async () => { if (swapped) return; swapped = true; await fs.rename(output, displaced); await fs.symlink(publicRoot, output, "dir"); }, })).rejects.toThrow("parent changed during write"); const redirectedEntries = await fs.readdir(publicRoot); expect(redirectedEntries).toEqual([".git"]); expect(JSON.stringify(redirectedEntries)).not.toContain(sentinel); expect(await directoryText(displaced)).not.toContain(sentinel); }); test("fails closed on malformed, duplicate, or unknown credential assignments", async () => { expect(() => parseEnvironmentAssignments("VALID=one\nnot an assignment\n")).toThrow("Invalid environment assignment at line 2"); expect(() => parseEnvironmentAssignments("DUPLICATE=one\nDUPLICATE=two\n")).toThrow("Duplicate environment assignment: DUPLICATE"); const root = await temporaryProject("thoughtstream-credential-unknown-"); roots.push(root); const source = path.join(root, "source.env"); await fs.writeFile(source, `${validSource()}UNROUTED_PRIVATE_KEY=${generatedValue("unknown")}\n`, { mode: 0o600 }); await expect(splitServiceCredentialFile(source, path.join(root, "credentials"), { consumerProviders: ["letta"], publicContentRoots: [], })).rejects.toThrow("Credential-like variables are not assigned to a service compartment: UNROUTED_PRIVATE_KEY"); }); test("systemd drop-ins clear the shared environment file before loading one compartment", async () => { const root = path.resolve(import.meta.dirname, "..", "deploy", "systemd", "credential-compartments"); const expected = { "thoughtstream-telegram-webhook.service.conf": "telegram-webhook.env", "thoughtstream-consumers.service.conf": "consumer.env", "thoughtstream-telegram-dispatcher.service.conf": "telegram-dispatcher.env", "thoughtstream-jetstream.service.conf": "jetstream.env", "thoughtstream-fastmail-jmap.service.conf": "fastmail-jmap.env", }; for (const [file, environmentFile] of Object.entries(expected)) { const contents = await fs.readFile(path.join(root, file), "utf8"); expect(contents).toContain("EnvironmentFile=\n"); expect(contents).toContain(`EnvironmentFile=%h/.config/thoughtstream/credentials/${environmentFile}`); expect(contents).not.toContain("/live/.env"); } const batcher = await fs.readFile(path.resolve(root, "..", "thoughtstream-batches.service"), "utf8"); expect(batcher).toContain("ExecStart=/usr/bin/env pnpm thought batches"); expect(batcher).toContain("EnvironmentFile=\n"); expect(batcher).not.toContain("WantedBy="); }); }); async function directoryText(directory: string): Promise { const entries = await fs.readdir(directory).catch(() => []); return (await Promise.all(entries.map((entry) => fs.readFile(path.join(directory, entry), "utf8").catch(() => "")))).join("\n"); } async function runSplitter( source: string, outputDirectory: string, extraArguments: string[] = [], ): Promise<{ code: number | null; stdout: string; stderr: string }> { return await new Promise((resolve, reject) => { const child = spawn(process.execPath, [ "--import", "tsx", "scripts/split-service-credentials.ts", "--source", source, "--output-dir", outputDirectory, "--consumer-providers", "letta", ...extraArguments, ], { cwd: path.resolve(import.meta.dirname, ".."), env: { PATH: process.env.PATH, HOME: process.env.HOME }, stdio: ["ignore", "pipe", "pipe"], }); let stdout = ""; let stderr = ""; child.stdout.on("data", (chunk) => { stdout += String(chunk); }); child.stderr.on("data", (chunk) => { stderr += String(chunk); }); child.once("error", reject); child.once("exit", (code) => resolve({ code, stdout, stderr })); }); } function withoutXAssignments(contents: string): string { return contents .split("\n") .filter((line) => !line.startsWith("THOUGHTSTREAM_X_")) .join("\n"); } function validSource(options: { includeX?: boolean } = {}): string { return [ `LETTA_API_KEY=${generatedValue("letta")}`, `THOUGHTSTREAM_LETTA_TELEGRAM_AGENT_ID=${generatedValue("agent")}`, `THOUGHTSTREAM_TELEGRAM_BOT_TOKEN=${generatedValue("bot")}`, `THOUGHTSTREAM_TELEGRAM_WEBHOOK_SECRET=${generatedValue("webhook")}`, ...(options.includeX ? [ `THOUGHTSTREAM_X_CONSUMER_SECRET=${generatedValue("x-consumer")}`, `THOUGHTSTREAM_X_MANAGEMENT_BEARER_TOKEN=${generatedValue("x-management")}`, ] : []), "", ].join("\n"); } async function directoryEntries(directory: string): Promise> { return Object.fromEntries(await Promise.all((await fs.readdir(directory)).sort().map(async (name) => [ name, await fs.readFile(path.join(directory, name), "utf8"), ]))); } function generatedValue(label: string): string { return `${label}-${randomUUID()}`; }