feat(cli): add client commands and home-based local runtime defaults
This commit is contained in:
@@ -8,8 +8,6 @@ const JWT_SECRET_ENV_KEY = "PAPERCLIP_AGENT_JWT_SECRET";
|
||||
function resolveEnvFilePath() {
|
||||
return path.resolve(path.dirname(resolveConfigPath()), ".env");
|
||||
}
|
||||
|
||||
const ENV_FILE_PATH = resolveEnvFilePath();
|
||||
const loadedEnvFiles = new Set<string>();
|
||||
|
||||
function isNonEmpty(value: unknown): value is string {
|
||||
@@ -35,10 +33,10 @@ function renderEnvFile(entries: Record<string, string>) {
|
||||
}
|
||||
|
||||
export function resolveAgentJwtEnvFile(): string {
|
||||
return ENV_FILE_PATH;
|
||||
return resolveEnvFilePath();
|
||||
}
|
||||
|
||||
export function loadAgentJwtEnvFile(filePath = ENV_FILE_PATH): void {
|
||||
export function loadAgentJwtEnvFile(filePath = resolveEnvFilePath()): void {
|
||||
if (loadedEnvFiles.has(filePath)) return;
|
||||
|
||||
if (!fs.existsSync(filePath)) return;
|
||||
@@ -52,7 +50,7 @@ export function readAgentJwtSecretFromEnv(): string | null {
|
||||
return isNonEmpty(raw) ? raw!.trim() : null;
|
||||
}
|
||||
|
||||
export function readAgentJwtSecretFromEnvFile(filePath = ENV_FILE_PATH): string | null {
|
||||
export function readAgentJwtSecretFromEnvFile(filePath = resolveEnvFilePath()): string | null {
|
||||
if (!fs.existsSync(filePath)) return null;
|
||||
|
||||
const raw = fs.readFileSync(filePath, "utf-8");
|
||||
@@ -78,7 +76,7 @@ export function ensureAgentJwtSecret(): { secret: string; created: boolean } {
|
||||
return { secret, created };
|
||||
}
|
||||
|
||||
export function writeAgentJwtEnv(secret: string, filePath = ENV_FILE_PATH): void {
|
||||
export function writeAgentJwtEnv(secret: string, filePath = resolveEnvFilePath()): void {
|
||||
const dir = path.dirname(filePath);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
|
||||
|
||||
66
cli/src/config/home.ts
Normal file
66
cli/src/config/home.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const DEFAULT_INSTANCE_ID = "default";
|
||||
const INSTANCE_ID_RE = /^[a-zA-Z0-9_-]+$/;
|
||||
|
||||
export function resolvePaperclipHomeDir(): string {
|
||||
const envHome = process.env.PAPERCLIP_HOME?.trim();
|
||||
if (envHome) return path.resolve(expandHomePrefix(envHome));
|
||||
return path.resolve(os.homedir(), ".paperclip");
|
||||
}
|
||||
|
||||
export function resolvePaperclipInstanceId(override?: string): string {
|
||||
const raw = override?.trim() || process.env.PAPERCLIP_INSTANCE_ID?.trim() || DEFAULT_INSTANCE_ID;
|
||||
if (!INSTANCE_ID_RE.test(raw)) {
|
||||
throw new Error(
|
||||
`Invalid instance id '${raw}'. Allowed characters: letters, numbers, '_' and '-'.`,
|
||||
);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
export function resolvePaperclipInstanceRoot(instanceId?: string): string {
|
||||
const id = resolvePaperclipInstanceId(instanceId);
|
||||
return path.resolve(resolvePaperclipHomeDir(), "instances", id);
|
||||
}
|
||||
|
||||
export function resolveDefaultConfigPath(instanceId?: string): string {
|
||||
return path.resolve(resolvePaperclipInstanceRoot(instanceId), "config.json");
|
||||
}
|
||||
|
||||
export function resolveDefaultContextPath(): string {
|
||||
return path.resolve(resolvePaperclipHomeDir(), "context.json");
|
||||
}
|
||||
|
||||
export function resolveDefaultEmbeddedPostgresDir(instanceId?: string): string {
|
||||
return path.resolve(resolvePaperclipInstanceRoot(instanceId), "db");
|
||||
}
|
||||
|
||||
export function resolveDefaultLogsDir(instanceId?: string): string {
|
||||
return path.resolve(resolvePaperclipInstanceRoot(instanceId), "logs");
|
||||
}
|
||||
|
||||
export function resolveDefaultSecretsKeyFilePath(instanceId?: string): string {
|
||||
return path.resolve(resolvePaperclipInstanceRoot(instanceId), "secrets", "master.key");
|
||||
}
|
||||
|
||||
export function expandHomePrefix(value: string): string {
|
||||
if (value === "~") return os.homedir();
|
||||
if (value.startsWith("~/")) return path.resolve(os.homedir(), value.slice(2));
|
||||
return value;
|
||||
}
|
||||
|
||||
export function describeLocalInstancePaths(instanceId?: string) {
|
||||
const resolvedInstanceId = resolvePaperclipInstanceId(instanceId);
|
||||
const instanceRoot = resolvePaperclipInstanceRoot(resolvedInstanceId);
|
||||
return {
|
||||
homeDir: resolvePaperclipHomeDir(),
|
||||
instanceId: resolvedInstanceId,
|
||||
instanceRoot,
|
||||
configPath: resolveDefaultConfigPath(resolvedInstanceId),
|
||||
embeddedPostgresDataDir: resolveDefaultEmbeddedPostgresDir(resolvedInstanceId),
|
||||
logDir: resolveDefaultLogsDir(resolvedInstanceId),
|
||||
secretsKeyFilePath: resolveDefaultSecretsKeyFilePath(resolvedInstanceId),
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { paperclipConfigSchema, type PaperclipConfig } from "./schema.js";
|
||||
import {
|
||||
resolveDefaultConfigPath,
|
||||
resolvePaperclipInstanceId,
|
||||
} from "./home.js";
|
||||
|
||||
const DEFAULT_CONFIG_PATH = ".paperclip/config.json";
|
||||
const DEFAULT_CONFIG_BASENAME = "config.json";
|
||||
|
||||
function findConfigFileFromAncestors(startDir: string): string | null {
|
||||
@@ -26,7 +29,7 @@ function findConfigFileFromAncestors(startDir: string): string | null {
|
||||
export function resolveConfigPath(overridePath?: string): string {
|
||||
if (overridePath) return path.resolve(overridePath);
|
||||
if (process.env.PAPERCLIP_CONFIG) return path.resolve(process.env.PAPERCLIP_CONFIG);
|
||||
return findConfigFileFromAncestors(process.cwd()) ?? path.resolve(process.cwd(), DEFAULT_CONFIG_PATH);
|
||||
return findConfigFileFromAncestors(process.cwd()) ?? resolveDefaultConfigPath(resolvePaperclipInstanceId());
|
||||
}
|
||||
|
||||
function parseJson(filePath: string): unknown {
|
||||
|
||||
Reference in New Issue
Block a user