+27
-3
@@ -22,3 +22,2 @@ #!/usr/bin/env node | ||
| import path from "path"; | ||
| import os from "os"; | ||
| import { fileURLToPath } from "url"; | ||
@@ -66,2 +65,3 @@ function outputJSON(data) { | ||
| if (options.force) args.push("--force"); | ||
| if (options.global) args.push("--global"); | ||
| args.push("--home", options.home); | ||
@@ -84,6 +84,30 @@ return new Promise((resolve, reject) => { | ||
| } | ||
| function runInstallerRemove(options) { | ||
| const installerPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../install/krow.mjs"); | ||
| const args = ["remove"]; | ||
| if (options.global) args.push("--global"); | ||
| args.push("--home", options.home); | ||
| return new Promise((resolve, reject) => { | ||
| const child = spawn(process.execPath, [installerPath, ...args], { stdio: "inherit" }); | ||
| child.on("error", reject); | ||
| child.on("exit", (code, signal) => { | ||
| if (signal) { | ||
| reject(new Error(`Installer exited due to signal ${signal}`)); | ||
| return; | ||
| } | ||
| if (code === 0) { | ||
| resolve(); | ||
| return; | ||
| } | ||
| reject(new Error(`Installer exited with code ${code ?? 1}`)); | ||
| }); | ||
| }); | ||
| } | ||
| var program = new Command().name("krow").description("Agent harness CLI").version("0.1.0"); | ||
| program.command("init").description("Install Codex, Claude Code, and Gemini CLI wrappers").option("--force", "Overwrite managed files even if they already exist").option("--home <dir>", "Target home directory", os.homedir()).action(async (options) => { | ||
| await runInstallerInit({ force: Boolean(options.force), home: options.home }); | ||
| program.command("init").description("Install Codex, Claude Code, and Gemini CLI wrappers (local by default, use -g for global)").option("--force", "Overwrite managed files even if they already exist").option("-g, --global", "Install to home directory (global)").option("--home <dir>", "Target directory", process.cwd()).action(async (options) => { | ||
| await runInstallerInit({ force: Boolean(options.force), global: Boolean(options.global), home: options.home }); | ||
| }); | ||
| program.command("remove").description("Remove installed Codex, Claude Code, and Gemini CLI wrappers").option("-g, --global", "Remove from home directory (global)").option("--home <dir>", "Target directory", process.cwd()).action(async (options) => { | ||
| await runInstallerRemove({ global: Boolean(options.global), home: options.home }); | ||
| }); | ||
| program.command("start").description("Start a new workflow").argument("<description>", "Task description").action((description) => { | ||
@@ -90,0 +114,0 @@ const result = createWorkflow(description); |
+85
-9
@@ -191,6 +191,13 @@ #!/usr/bin/env node | ||
| "Usage:", | ||
| " krow init [--force] [--home <dir>]", | ||
| " krow init [--force] [-g | --global] [--home <dir>]", | ||
| " krow remove [-g | --global] [--home <dir>]", | ||
| "", | ||
| "Commands:", | ||
| " init Install Codex $krow, Claude Code /krow, and Gemini CLI /krow wrappers", | ||
| " init Install Codex $krow, Claude Code /krow, and Gemini CLI /krow wrappers", | ||
| " remove Remove installed Codex, Claude Code, and Gemini CLI wrappers", | ||
| "", | ||
| "Flags:", | ||
| " --force Overwrite managed files even if they already exist", | ||
| " -g, --global Install to / remove from home directory (global)", | ||
| " --home <dir> Target directory override", | ||
| ].join("\n") + "\n", | ||
@@ -203,3 +210,4 @@ ); | ||
| let force = false; | ||
| let home = os.homedir(); | ||
| let global = false; | ||
| let home = null; | ||
@@ -212,2 +220,6 @@ for (let index = 1; index < argv.length; index += 1) { | ||
| } | ||
| if (value === "--global" || value === "-g") { | ||
| global = true; | ||
| continue; | ||
| } | ||
| if (value === "--home" && argv[index + 1]) { | ||
@@ -220,3 +232,3 @@ home = argv[index + 1]; | ||
| return { command, force, home }; | ||
| return { command, force, global, home }; | ||
| } | ||
@@ -251,3 +263,6 @@ | ||
| export async function runInit({ force, home }) { | ||
| export async function runInit({ force, global: isGlobal, home }) { | ||
| if (home == null) { | ||
| home = isGlobal ? os.homedir() : process.cwd(); | ||
| } | ||
| const scriptDir = path.dirname(fileURLToPath(import.meta.url)); | ||
@@ -289,11 +304,72 @@ const packageRoot = path.resolve(scriptDir, ".."); | ||
| export async function runRemove({ global: isGlobal, home }) { | ||
| if (home == null) { | ||
| home = isGlobal ? os.homedir() : process.cwd(); | ||
| } | ||
| const targets = [ | ||
| { | ||
| label: "Codex $krow skill", | ||
| path: path.join(home, ".codex", "skills", "krow", "SKILL.md"), | ||
| }, | ||
| { | ||
| label: "Claude Code /krow command", | ||
| path: path.join(home, ".claude", "commands", "krow.md"), | ||
| }, | ||
| { | ||
| label: "Gemini CLI /krow command", | ||
| path: path.join(home, ".gemini", "commands", "krow.toml"), | ||
| }, | ||
| ]; | ||
| const results = []; | ||
| for (const target of targets) { | ||
| const exists = await pathExists(target.path); | ||
| if (!exists) { | ||
| results.push({ ...target, status: "skipped (not found)" }); | ||
| continue; | ||
| } | ||
| const content = await fs.readFile(target.path, "utf8"); | ||
| if (!content.includes(MANAGED_MARKER)) { | ||
| results.push({ ...target, status: "skipped (not managed by krow)" }); | ||
| continue; | ||
| } | ||
| await fs.unlink(target.path); | ||
| results.push({ ...target, status: "removed" }); | ||
| // Try to remove empty parent directories | ||
| let dir = path.dirname(target.path); | ||
| while (dir !== home && dir !== path.dirname(dir)) { | ||
| try { | ||
| await fs.rmdir(dir); | ||
| dir = path.dirname(dir); | ||
| } catch { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| process.stdout.write( | ||
| [ | ||
| `Remove targets in ${home}`, | ||
| ...results.map((result) => `${result.status}: ${result.label} -> ${result.path}`), | ||
| ].join("\n") + "\n", | ||
| ); | ||
| } | ||
| export async function main() { | ||
| const parsed = parseArgs(process.argv.slice(2)); | ||
| if (parsed.command !== "init") { | ||
| printUsage(); | ||
| process.exitCode = parsed.command ? 1 : 0; | ||
| if (parsed.command === "init") { | ||
| await runInit(parsed); | ||
| return; | ||
| } | ||
| if (parsed.command === "remove") { | ||
| await runRemove(parsed); | ||
| return; | ||
| } | ||
| await runInit(parsed); | ||
| printUsage(); | ||
| process.exitCode = parsed.command ? 1 : 0; | ||
| } | ||
@@ -300,0 +376,0 @@ |
+1
-1
| { | ||
| "name": "krow-cli", | ||
| "version": "0.1.0", | ||
| "version": "0.2.0", | ||
| "description": "A host-agnostic agent harness for coding work", | ||
@@ -5,0 +5,0 @@ "type": "module", |
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
72402
4.89%1813
5.47%