@gifflet/ccmd
Version:
Simple command-line tool for managing custom commands in Claude Code. Install and share commands from Git repositories with the ease of a package manager.
202 lines (168 loc) • 6.58 kB
JavaScript
;
// Thanks to author of https://github.com/sanathkr/go-npm, we were able to modify his code to work with private packages
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var path = require('path'),
mkdirp = require('mkdirp'),
fs = require('fs');
// Mapping from Node's `process.arch` to Golang's `$GOARCH`
var ARCH_MAPPING = {
"ia32": "386",
"x64": "amd64",
"arm": "arm",
"arm64": "arm64"
};
// Mapping between Node's `process.platform` to Golang's
var PLATFORM_MAPPING = {
"darwin": "darwin",
"linux": "linux",
"win32": "windows",
"freebsd": "freebsd"
};
async function getInstallationPath() {
// `npm bin` will output the path where binary files should be installed
const value = null //await execShellCommand("npm bin -g");
var dir = null;
if (!value || value.length === 0) {
// We couldn't infer path from `npm bin`. Let's try to get it from
// Environment variables set by NPM when it runs.
// npm_config_prefix points to NPM's installation directory where `bin` folder is available
// Ex: /Users/foo/.nvm/versions/node/v4.3.0
var env = process.env;
if (env && env.npm_config_prefix) {
dir = path.join(env.npm_config_prefix, "bin");
}
} else {
dir = value.trim();
}
await mkdirp(dir);
return dir;
}
async function verifyAndPlaceBinary(binName, binPath, callback) {
if (!fs.existsSync(path.join(binPath, binName))) return callback('Downloaded binary does not contain the binary specified in configuration - ' + binName);
// Get installation path for executables under node
const installationPath = await getInstallationPath();
// Copy the executable to the path
fs.rename(path.join(binPath, binName), path.join(installationPath, binName), (err) => {
if (!err) {
console.info("Installed cli successfully");
callback(null);
} else {
callback(err);
}
});
}
function validateConfiguration(packageJson) {
if (!packageJson.version) {
return "'version' property must be specified";
}
if (!packageJson.goBinary || _typeof(packageJson.goBinary) !== "object") {
return "'goBinary' property must be defined and be an object";
}
if (!packageJson.goBinary.name) {
return "'name' property is necessary";
}
if (!packageJson.goBinary.path) {
return "'path' property is necessary";
}
}
function parsePackageJson() {
if (process.arch !== "arm64" && process.platform !== "darwin") {
if (!(process.arch in ARCH_MAPPING)) {
console.error("Installation is not supported for this architecture: " + process.arch);
return;
}
}
if (!(process.platform in PLATFORM_MAPPING)) {
console.error("Installation is not supported for this platform: " + process.platform);
return;
}
var packageJsonPath = path.join(".", "package.json");
if (!fs.existsSync(packageJsonPath)) {
console.error("Unable to find package.json. " + "Please run this script at root of the package you want to be installed");
return;
}
var packageJson = JSON.parse(fs.readFileSync(packageJsonPath));
var error = validateConfiguration(packageJson);
if (error && error.length > 0) {
console.error("Invalid package.json: " + error);
return;
}
// We have validated the config. It exists in all its glory
var binName = packageJson.goBinary.name;
var binPath = packageJson.goBinary.path;
var version = packageJson.version;
if (version[0] === 'v') version = version.substr(1); // strip the 'v' if necessary v0.0.1 => 0.0.1
// Binary name on Windows has .exe suffix
if (process.platform === "win32") {
binName += ".exe";
}
return {
binName: binName,
binPath: binPath,
version: version
};
}
/**
* Reads the configuration from application's package.json,
* validates properties, copied the binary from the package and stores at
* ./bin in the package's root. NPM already has support to install binary files
* specific locations when invoked with "npm install -g"
*
* See: https://docs.npmjs.com/files/package.json#bin
*/
var INVALID_INPUT = "Invalid inputs";
async function install(callback) {
var opts = parsePackageJson();
if (!opts) return callback(INVALID_INPUT);
mkdirp.sync(opts.binPath);
console.info(`Copying the relevant binary for your platform ${process.platform}`);
let src = `./dist/ccmd-${PLATFORM_MAPPING[process.platform]}-${ARCH_MAPPING[process.arch]}_${PLATFORM_MAPPING[process.platform]}_${ARCH_MAPPING[process.arch]}/${opts.binName}`;
if (process.arch === "ia32" && process.platform === "win32") {
src = `./dist/ccmd-${PLATFORM_MAPPING[process.platform]}-amd64_${PLATFORM_MAPPING[process.platform]}_amd64/${opts.binName}`;
}
const dest = path.join(opts.binPath, opts.binName);
try {
await fs.promises.copyFile(src, dest);
} catch (err) {
return callback(`Failed to copy binary: ${err.message}`);
}
await verifyAndPlaceBinary(opts.binName, opts.binPath, callback);
console.log("\x1b[32m", "ccmd installed, run 'ccmd --help' for details\n\n")
}
async function uninstall(callback) {
var opts = parsePackageJson();
try {
const installationPath = await getInstallationPath();
fs.unlink(path.join(installationPath, opts.binName), (err) => {
if (err) {
return callback(err);
}
});
} catch (ex) {
// Ignore errors when deleting the file.
}
console.info("Uninstalled cli successfully");
return callback(null);
}
// Parse command line arguments and call the right method
var actions = {
"install": install,
"uninstall": uninstall
};
var argv = process.argv;
if (argv && argv.length > 2) {
var cmd = process.argv[2];
if (!actions[cmd]) {
console.log("Invalid command. `install` and `uninstall` are the only supported commands");
process.exit(1);
}
actions[cmd](function (err) {
if (err) {
console.error(err);
process.exit(1);
} else {
process.exit(0);
}
});
}