feat(credentials): add JSON5 credential system
This commit is contained in:
parent
b1d80f29ae
commit
3ee8946e29
10 changed files with 454 additions and 110 deletions
160
src/agent/credentials-cli.ts
Normal file
160
src/agent/credentials-cli.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Credentials CLI
|
||||
*
|
||||
* Commands:
|
||||
* init Create credentials.json5 and skills.env.json5
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, writeFileSync, chmodSync } from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
import { getCredentialsPath, getSkillsEnvPath } from "./credentials.js";
|
||||
|
||||
type Command = "init" | "help";
|
||||
|
||||
function printUsage(): void {
|
||||
console.log("Usage: pnpm credentials:cli <command> [options]");
|
||||
console.log("");
|
||||
console.log("Commands:");
|
||||
console.log(" init Create credentials.json5 and skills.env.json5 (empty templates)");
|
||||
console.log(" help Show this help");
|
||||
console.log("");
|
||||
console.log("Options:");
|
||||
console.log(" --force Overwrite existing files");
|
||||
console.log(" --core-only Only create credentials.json5");
|
||||
console.log(" --skills-only Only create skills.env.json5");
|
||||
console.log(" --path Override credentials path (SMC_CREDENTIALS_PATH)");
|
||||
console.log(" --skills-path Override skills env path (SMC_SKILLS_ENV_PATH)");
|
||||
console.log("");
|
||||
console.log("Examples:");
|
||||
console.log(" pnpm credentials:cli init");
|
||||
console.log(" pnpm credentials:cli init --force");
|
||||
console.log(" pnpm credentials:cli init --core-only");
|
||||
console.log(" pnpm credentials:cli init --skills-only");
|
||||
}
|
||||
|
||||
function buildCoreTemplate(): string {
|
||||
return `{
|
||||
version: 1,
|
||||
llm: {
|
||||
// provider: "openai",
|
||||
providers: {
|
||||
// openai: { apiKey: "sk-...", baseUrl: "https://api.openai.com/v1", model: "gpt-4.1" }
|
||||
}
|
||||
},
|
||||
tools: {
|
||||
// brave: { apiKey: "brv-..." },
|
||||
// perplexity: { apiKey: "pplx-...", baseUrl: "https://api.perplexity.ai", model: "perplexity/sonar-pro" }
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function buildSkillsTemplate(): string {
|
||||
return `{
|
||||
env: {
|
||||
// Dynamic keys (skills, plugins, integrations)
|
||||
// LINEAR_API_KEY: "lin-..."
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function parseArgs(argv: string[]) {
|
||||
const args = [...argv];
|
||||
let force = false;
|
||||
let pathOverride: string | undefined;
|
||||
let skillsPathOverride: string | undefined;
|
||||
let coreOnly = false;
|
||||
let skillsOnly = false;
|
||||
const positional: string[] = [];
|
||||
|
||||
while (args.length > 0) {
|
||||
const arg = args.shift();
|
||||
if (!arg) break;
|
||||
if (arg === "--force" || arg === "-f") {
|
||||
force = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--core-only") {
|
||||
coreOnly = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--skills-only") {
|
||||
skillsOnly = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--path") {
|
||||
pathOverride = args.shift();
|
||||
continue;
|
||||
}
|
||||
if (arg === "--skills-path") {
|
||||
skillsPathOverride = args.shift();
|
||||
continue;
|
||||
}
|
||||
if (arg === "--help" || arg === "-h") {
|
||||
return { command: "help" as Command, force, pathOverride, skillsPathOverride, coreOnly, skillsOnly };
|
||||
}
|
||||
positional.push(arg);
|
||||
}
|
||||
|
||||
const command = (positional[0] || "help") as Command;
|
||||
return { command, force, pathOverride, skillsPathOverride, coreOnly, skillsOnly };
|
||||
}
|
||||
|
||||
function cmdInit(force: boolean, pathOverride?: string, skillsPathOverride?: string, coreOnly?: boolean, skillsOnly?: boolean): void {
|
||||
const createCore = skillsOnly ? false : true;
|
||||
const createSkills = coreOnly ? false : true;
|
||||
|
||||
if (!createCore && !createSkills) {
|
||||
console.error("Error: both --core-only and --skills-only were provided.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (createCore) {
|
||||
const path = pathOverride ?? getCredentialsPath();
|
||||
if (existsSync(path) && !force) {
|
||||
console.error(`Error: credentials file already exists at ${path}`);
|
||||
console.error("Use --force to overwrite.");
|
||||
process.exit(1);
|
||||
}
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
writeFileSync(path, buildCoreTemplate(), "utf8");
|
||||
chmodSync(path, 0o600);
|
||||
console.log(`Created: ${path}`);
|
||||
}
|
||||
|
||||
if (createSkills) {
|
||||
const skillsPath = skillsPathOverride ?? getSkillsEnvPath();
|
||||
if (existsSync(skillsPath) && !force) {
|
||||
console.error(`Error: skills env file already exists at ${skillsPath}`);
|
||||
console.error("Use --force to overwrite.");
|
||||
process.exit(1);
|
||||
}
|
||||
mkdirSync(dirname(skillsPath), { recursive: true });
|
||||
writeFileSync(skillsPath, buildSkillsTemplate(), "utf8");
|
||||
chmodSync(skillsPath, 0o600);
|
||||
console.log(`Created: ${skillsPath}`);
|
||||
}
|
||||
|
||||
console.log("Edit these files to add your credentials.");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { command, force, pathOverride, skillsPathOverride, coreOnly, skillsOnly } = parseArgs(process.argv.slice(2));
|
||||
|
||||
switch (command) {
|
||||
case "init":
|
||||
cmdInit(force, pathOverride, skillsPathOverride, coreOnly, skillsOnly);
|
||||
break;
|
||||
case "help":
|
||||
default:
|
||||
printUsage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err?.stack || String(err));
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue