@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.
173 lines (172 loc) • 6.63 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, writeFileSync, readFileSync } from "fs";
import open from "open";
import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
function registerLoginCommand(program) {
program.command("login").description("Login to hosted StackMemory service").option("--api-url <url>", "Custom API URL", "https://api.stackmemory.ai").option("--email <email>", "Email address for login").option("--password <password>", "Password (not recommended in CLI)").action(async (options) => {
const cfgDir = join(homedir(), ".stackmemory");
if (!existsSync(cfgDir)) mkdirSync(cfgDir, { recursive: true });
console.log(chalk.cyan("\u{1F510} StackMemory Hosted Service Login\n"));
const credentials = await inquirer.prompt([
{
type: "input",
name: "email",
message: "Email:",
default: options.email,
validate: (input) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(input) ? true : "Please enter a valid email";
}
},
{
type: "password",
name: "password",
message: "Password:",
default: options.password,
mask: "*",
validate: (input) => input.length >= 6 ? true : "Password must be at least 6 characters"
}
]);
console.log(chalk.gray("\nAuthenticating with StackMemory API..."));
try {
const apiUrl = options.apiUrl || process.env.STACKMEMORY_API_URL || "https://api.stackmemory.ai";
const response = await fetch(`${apiUrl}/auth/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": "StackMemory-CLI/0.3.19"
},
body: JSON.stringify({
email: credentials.email,
password: credentials.password
})
});
const data = await response.json();
if (!response.ok || !data.success) {
if (response.status === 404) {
console.log(
chalk.yellow("\n\u26A0\uFE0F Hosted API not available. Would you like to:")
);
const { choice } = await inquirer.prompt([
{
type: "list",
name: "choice",
message: "Select an option:",
choices: [
{ name: "Open signup page in browser", value: "signup" },
{ name: "Configure database URL manually", value: "manual" },
{ name: "Use local database", value: "local" },
{ name: "Cancel", value: "cancel" }
]
}
]);
if (choice === "signup") {
await open("https://stackmemory.ai/signup");
console.log(chalk.cyan("Opening signup page in browser..."));
return;
} else if (choice === "manual") {
const { databaseUrl } = await inquirer.prompt([
{
type: "password",
name: "databaseUrl",
message: "Enter your DATABASE_URL (postgres://...):",
validate: (input) => input.startsWith("postgres://") || input.startsWith("postgresql://") ? true : "Must start with postgres:// or postgresql://"
}
]);
const cfgPath2 = join(cfgDir, "config.json");
let cfg2 = {};
try {
if (existsSync(cfgPath2))
cfg2 = JSON.parse(readFileSync(cfgPath2, "utf-8"));
} catch {
}
cfg2.database = { mode: "hosted", url: databaseUrl };
cfg2.auth = { email: credentials.email };
writeFileSync(cfgPath2, JSON.stringify(cfg2, null, 2));
console.log(chalk.green("\u2713 Database configured successfully"));
return;
} else if (choice === "local") {
const cfgPath2 = join(cfgDir, "config.json");
let cfg2 = {};
try {
if (existsSync(cfgPath2))
cfg2 = JSON.parse(readFileSync(cfgPath2, "utf-8"));
} catch {
}
cfg2.database = { mode: "local" };
writeFileSync(cfgPath2, JSON.stringify(cfg2, null, 2));
console.log(chalk.green("\u2713 Switched to local database mode"));
return;
} else {
console.log(chalk.gray("Login cancelled"));
return;
}
}
throw new IntegrationError(
data.error || "Authentication failed",
ErrorCode.LINEAR_AUTH_FAILED,
{ email: credentials.email, apiUrl }
);
}
const cfgPath = join(cfgDir, "config.json");
let cfg = {};
try {
if (existsSync(cfgPath))
cfg = JSON.parse(readFileSync(cfgPath, "utf-8"));
} catch {
}
cfg.auth = {
apiKey: data.apiKey,
apiUrl,
email: credentials.email
};
if (data.databaseUrl) {
cfg.database = {
mode: "hosted",
url: data.databaseUrl
};
}
writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
const envFile = join(cfgDir, "stackmemory.env");
const envContent = `# StackMemory Authentication
STACKMEMORY_API_KEY=${data.apiKey}
STACKMEMORY_API_URL=${apiUrl}
${data.databaseUrl ? `DATABASE_URL=${data.databaseUrl}` : ""}
`;
writeFileSync(envFile, envContent);
console.log(chalk.green("\n\u2705 Successfully logged in to StackMemory"));
console.log(
chalk.green(`\u2713 Configuration saved to ~/.stackmemory/config.json`)
);
console.log(chalk.gray("\nYou can now use:"));
console.log(
chalk.cyan(" stackmemory sync ") + chalk.gray("- Sync your context to the cloud")
);
console.log(
chalk.cyan(" stackmemory db status") + chalk.gray("- Check database connection")
);
console.log(
chalk.cyan(" stackmemory context ") + chalk.gray("- Manage your contexts")
);
} catch (error) {
console.error(chalk.red("\n\u274C Login failed:"), error.message);
console.log(
chalk.yellow(
"\nTip: Visit https://stackmemory.ai/signup to create an account"
)
);
process.exit(1);
}
});
}
export {
registerLoginCommand
};
//# sourceMappingURL=login.js.map