Something went wrong. Try again.
I'm going to build my own OpenClaw, with blackjack... and bun!
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164/** * cli.ts – Command-line interface for piclaw sub-commands. * * Handles --help, --version, and the `keychain` sub-command family * (set, get, list, delete). If a CLI sub-command is detected, * handleCliOptions() processes it and returns true so index.ts can * exit without starting the runtime. * * Consumers: * - index.ts calls handleCliOptions() before main(). */
import { readFileSync } from "fs";import { join } from "path";import { initDatabase } from "./db.js";import { deleteKeychainEntry, getKeychainEntry, listKeychainEntries, setKeychainEntry,} from "./secure/keychain.js";
const HELP_TEXT = `piclaw - Pi Coding Agent Assistant
Usage: piclaw [options] piclaw keychain <command> [args]
Options: -h, --help Show this help -v, --version Show version -w, --workspace <path> Workspace root (also relocates .piclaw state under it) -p, --port <number> Web UI port (default: 8080) --host <addr> Web UI host (default: 0.0.0.0) --idle-timeout <secs> Web idle timeout in seconds (default: 0 = disabled)
Keychain commands: piclaw keychain set <name> --secret <value> [--type token|password|basic|secret] [--username <value>] piclaw keychain set <name> --secret-file <path> [--type token|password|basic|secret] [--username <value>] piclaw keychain get <name> piclaw keychain list piclaw keychain delete <name>`;
/** Read the version string from package.json. */export function getVersion(): string { try { const packagePath = join(import.meta.dir, "..", "..", "package.json"); const data = JSON.parse(readFileSync(packagePath, "utf-8")); return data.version || "unknown"; } catch { return "unknown"; }}
function getFlagValue(args: string[], flag: string): string | undefined { const index = args.indexOf(flag); if (index < 0) return undefined; const value = args[index + 1]; if (!value || value.startsWith("-")) return undefined; return value;}
function consumeLeadingGlobalOptions(args: string[]): string[] { const remaining = [...args]; while (remaining.length > 0) { const [current] = remaining; if (!current) break; if (current === "--") { remaining.shift(); break; } if (current === "-w" || current === "--workspace" || current === "-p" || current === "--port" || current === "--host" || current === "--idle-timeout" || current === "--tls-cert" || current === "--tls-key") { remaining.shift(); if (remaining.length > 0 && !remaining[0]?.startsWith("-")) { remaining.shift(); } continue; } if (current.startsWith("--workspace=") || current.startsWith("--port=") || current.startsWith("--host=") || current.startsWith("--idle-timeout=") || current.startsWith("--tls-cert=") || current.startsWith("--tls-key=")) { remaining.shift(); continue; } break; } return remaining;}
async function handleKeychainCommand(args: string[]): Promise<void> { const [subcommand, name] = args; initDatabase();
switch (subcommand) { case "set": { if (!name) throw new Error("Keychain name is required."); const type = (getFlagValue(args, "--type") || "secret") as | "token" | "password" | "basic" | "secret"; const username = getFlagValue(args, "--username") || null; const secret = getFlagValue(args, "--secret"); const secretFile = getFlagValue(args, "--secret-file"); let resolvedSecret = secret || ""; if (!resolvedSecret && secretFile) { resolvedSecret = readFileSync(secretFile, "utf8").trim(); } if (!resolvedSecret) throw new Error("Keychain secret is required.");
await setKeychainEntry({ name, type, secret: resolvedSecret, username }); console.log(`Stored keychain entry ${name}.`); return; } case "get": { if (!name) throw new Error("Keychain name is required."); const entry = await getKeychainEntry(name); console.log(JSON.stringify(entry, null, 2)); return; } case "list": { const entries = listKeychainEntries(); console.log(JSON.stringify(entries, null, 2)); return; } case "delete": { if (!name) throw new Error("Keychain name is required."); const removed = deleteKeychainEntry(name); if (!removed) { console.log(`Keychain entry not found: ${name}`); } else { console.log(`Deleted keychain entry ${name}.`); } return; } default: { throw new Error("Unknown keychain command."); } }}
/** Parse CLI args for sub-commands. Returns true if a sub-command was handled. */export async function handleCliOptions(args = process.argv.slice(2)): Promise<boolean> { if (args.includes("-h") || args.includes("--help")) { console.log(HELP_TEXT.trim()); return true; } if (args.includes("-v") || args.includes("--version")) { console.log(getVersion()); return true; }
const commandArgs = consumeLeadingGlobalOptions(args); if (commandArgs[0] === "keychain") { try { await handleKeychainCommand(commandArgs.slice(1)); } catch (error) { console.error((error as Error).message); return true; } return true; } return false;}