@stackmemoryai/stackmemory
Version:
Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.
148 lines (147 loc) • 5.4 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import inquirer from "inquirer";
import chalk from "chalk";
import { homedir } from "os";
import { join } from "path";
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
import { Pool } from "pg";
function loadConfig() {
const cfgDir = join(homedir(), ".stackmemory");
if (!existsSync(cfgDir)) mkdirSync(cfgDir, { recursive: true });
const cfgPath = join(cfgDir, "config.json");
let cfg = {};
try {
if (existsSync(cfgPath)) cfg = JSON.parse(readFileSync(cfgPath, "utf-8"));
} catch {
}
return { cfgDir, cfgPath, cfg };
}
async function testPostgres(url) {
try {
const pool = new Pool({ connectionString: url });
const r = await pool.query("SELECT 1");
await pool.end();
return !!r;
} catch {
return false;
}
}
function registerLogoutCommand(program) {
program.command("logout").description("Log out from hosted database (switch back to local)").action(async () => {
const { cfgDir, cfgPath, cfg } = loadConfig();
cfg.database = { mode: "local" };
writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
const envPath = join(cfgDir, "railway.env");
try {
if (existsSync(envPath)) unlinkSync(envPath);
} catch {
}
console.log(chalk.green("\u2713 Switched to local storage and cleared hosted credentials."));
console.log(chalk.gray("Start the server without DATABASE_URL to use local SQLite."));
});
}
function registerDbCommands(program) {
const db = program.command("db").description("Database operations");
db.command("switch").description("Switch between local and hosted database").option("--mode <local|hosted>", "Target mode (local or hosted)").action(async (opts) => {
const { cfgDir, cfgPath, cfg } = loadConfig();
let mode = opts.mode;
if (!mode || mode !== "local" && mode !== "hosted") {
const ans = await inquirer.prompt([
{
type: "list",
name: "mode",
message: "Select database mode:",
choices: [
{ name: "Local (SQLite, free)", value: "local" },
{ name: "Hosted (Postgres, paid)", value: "hosted" }
],
default: cfg.database?.mode || "local"
}
]);
mode = ans.mode;
}
if (mode === "local") {
cfg.database = { mode: "local" };
writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
const envPath = join(cfgDir, "railway.env");
try {
if (existsSync(envPath)) unlinkSync(envPath);
} catch {
}
console.log(chalk.green("\u2713 Switched to local storage."));
return;
}
const { openSignup } = await inquirer.prompt([
{ type: "confirm", name: "openSignup", message: "Open hosted signup/login page?", default: false }
]);
if (openSignup) {
try {
const mod = await import("open");
await mod.default("https://stackmemory.ai/hosted");
} catch {
}
}
const { url } = await inquirer.prompt([
{
type: "password",
name: "url",
message: "Paste your hosted DATABASE_URL (postgres://...)",
validate: (input) => input.startsWith("postgres://") || input.startsWith("postgresql://") ? true : "Must start with postgres:// or postgresql://"
}
]);
process.stdout.write("Testing connection... ");
const ok = await testPostgres(url);
if (!ok) {
console.log(chalk.red("failed"));
console.log(chalk.red("\u2717 Could not connect to Postgres with provided URL."));
return;
}
console.log(chalk.green("ok"));
cfg.database = { mode: "hosted", url };
writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
const envFile = join(cfgDir, "railway.env");
writeFileSync(envFile, `# StackMemory hosted DB
DATABASE_URL=${url}
`);
console.log(chalk.green("\u2713 Switched to hosted database."));
console.log(chalk.gray("Tip: export DATABASE_URL before starting the server."));
});
db.command("status").description("Show current database mode and connection status").action(async () => {
const { cfgDir, cfg } = loadConfig();
const mode = cfg.database?.mode || "local";
console.log(`Mode: ${mode}`);
if (mode === "hosted") {
const url = process.env.DATABASE_URL || cfg.database?.url || "";
if (!url) {
console.log(chalk.yellow('DATABASE_URL not set and not found in config. Run "stackmemory login".'));
return;
}
const masked = maskDsn(url);
process.stdout.write(`Hosted DSN: ${masked} \u2192 testing... `);
const ok = await testPostgres(url);
console.log(ok ? chalk.green("ok") : chalk.red("failed"));
} else {
const sqlitePath = join(cfgDir, "railway.db");
const exists = existsSync(sqlitePath);
console.log(`Local SQLite path: ${sqlitePath} (${exists ? "exists" : "will be created at first run"})`);
}
});
}
function maskDsn(url) {
try {
const u = new URL(url);
if (u.password) u.password = "***";
if (u.username) u.username = "***";
return u.toString();
} catch {
return url.replace(/:\\?[^@]*@/, ":***@");
}
}
export {
registerDbCommands,
registerLogoutCommand
};
//# sourceMappingURL=db.js.map