data-source-cli
Version:
A CLI tool to manage and generate environment configuration for Claude/Goose or custom AI agents
165 lines (158 loc) • 5.25 kB
JavaScript
// bin/data-source-cli.ts
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
// lib/prompt.ts
import readline from "readline";
async function ask(question) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer.trim());
});
});
}
// lib/config.ts
import fs from "fs/promises";
import path from "path";
import os from "os";
import YAML from "yaml";
var CONFIG_DIR = path.join(os.homedir(), ".config", "data-source-cli");
var CONFIG_PATH = path.join(CONFIG_DIR, "config.yaml");
async function loadConfig() {
try {
const file = await fs.readFile(CONFIG_PATH, "utf-8");
return YAML.parse(file) || {};
} catch {
return { sources: {} };
}
}
async function saveConfig(config) {
await fs.mkdir(CONFIG_DIR, { recursive: true });
await fs.writeFile(CONFIG_PATH, YAML.stringify(config));
}
// lib/utils.ts
async function addSource() {
const name = await ask("\u{1F539} Source name (e.g. my_api): ");
const type = await ask("\u{1F538} Type (api | db | file): ");
const base_url = await ask("\u{1F310} Base URL: ");
const api_key = await ask("\u{1F511} API Key: ");
const sandboxInput = await ask("\u{1F3DD}\uFE0F Use sandbox mode? (y/N): ");
const sandbox = sandboxInput.toLowerCase() === "y";
const config = await loadConfig();
config.sources = config.sources || {};
config.sources[name] = { type, base_url, api_key, sandbox };
await saveConfig(config);
console.log(`\u2705 Saved source '${name}' to config.`);
}
async function listSources() {
const config = await loadConfig();
const sources = config.sources ?? {};
const keys = Object.keys(sources);
if (!keys.length) {
console.log("\u{1F4ED} No data sources found.");
return;
}
console.log("\n\u{1F4CB} Registered Data Sources:\n");
keys.forEach((name, i) => {
const s = sources[name];
console.log(`${i + 1}. ${name}`);
console.log(` Type: ${s.type}`);
console.log(` Base URL: ${s.base_url}`);
console.log(` Sandbox: ${s.sandbox}`);
console.log("");
});
}
async function removeSource() {
const config = await loadConfig();
const sources = config.sources ?? {};
const keys = Object.keys(sources);
if (!keys.length) {
console.log("\u{1F4ED} No sources to remove.");
return;
}
console.log("\n\u{1F5D1}\uFE0F Select a source to remove:\n");
keys.forEach((key, i) => {
console.log(`${i + 1}. ${key}`);
});
const input = await ask("\u2753 Enter the number of the source to remove: ");
const index = parseInt(input, 10);
if (isNaN(index) || index < 1 || index > keys.length) {
console.log("\u274C Invalid selection.");
return;
}
const name = keys[index - 1];
delete config.sources[name];
await saveConfig(config);
console.log(`\u2705 Removed source '${name}'`);
}
async function generateEnv(argv) {
let sourceName = argv._[1];
const config = await loadConfig();
const sources = config.sources ?? {};
if (!Object.keys(sources).length) {
console.log("\u{1F4ED} No data sources available.");
return;
}
if (!sourceName) {
console.log("\n\u{1F4CB} Available sources:");
Object.keys(sources).forEach((name, i) => {
console.log(`${i + 1}. ${name}`);
});
sourceName = await ask("Enter source name: ");
}
const source = sources[sourceName];
if (!source) {
console.log(`\u274C Source '${sourceName}' not found.`);
return;
}
console.log(`
\u{1F510} .env values for '${sourceName}':
`);
Object.entries(source).forEach(([key, value]) => {
const envKey = `${sourceName.toUpperCase()}_${key.toUpperCase()}`;
console.log(`${envKey}=${value}`);
});
}
async function getUrl(argv) {
let sourceName = argv._[1];
const config = await loadConfig();
const sources = config.sources ?? {};
if (!Object.keys(sources).length) {
console.log("\u{1F4ED} No data sources configured.");
return;
}
if (!sourceName) {
console.log("\n\u{1F4CB} Available sources:");
Object.keys(sources).forEach((name, i) => console.log(`${i + 1}. ${name}`));
sourceName = await ask("Enter source name: ");
}
const source = sources[sourceName];
if (!source) {
console.log(`\u274C Source '${sourceName}' not found.`);
return;
}
const base = "goose://extension";
const query = new URLSearchParams({
cmd: "npx",
arg: "data-source-cli",
arg2: "start",
id: sourceName,
name: sourceName,
...Object.entries(source).reduce((acc, [key, val]) => {
acc[`env[${key.toUpperCase()}]`] = String(val);
return acc;
}, {})
});
const url = `${base}?${query.toString()}`;
console.log(`
\u{1F517} Goose/Claude URL:
${url}
`);
}
// bin/data-source-cli.ts
yargs(hideBin(process.argv)).command("add", "Add a new data source", {}, addSource).command("list", "List all data sources", {}, listSources).command("remove", "Remove a data source", {}, removeSource).command("generate-env [name]", "Output .env format for a data source", {}, generateEnv).command("get-url [name]", "Generate Goose/Claude MCP-compatible URL", {}, getUrl).demandCommand().help().argv;