@gifflet/ccmd 1.0.0-test.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # ccmd - Claude Command Manager
2
+
3
+ [![Go Version](https://img.shields.io/badge/Go-1.23+-00ADD8.svg)](https://go.dev)
4
+ [![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
5
+ [![Go Report Card](https://goreportcard.com/badge/github.com/gifflet/ccmd)](https://goreportcard.com/report/github.com/gifflet/ccmd)
6
+ [![Sponsor](https://img.shields.io/badge/sponsor-30363D?logo=GitHub-Sponsors&color=5c5c5c)](https://github.com/sponsors/gifflet)
7
+
8
+ 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.
9
+
10
+ ## Why ccmd?
11
+
12
+ Managing custom Claude Code commands across multiple projects can be challenging. ccmd solves this by treating commands as versioned, reusable packages:
13
+
14
+ - **Keep commands out of your codebase**: Store command definitions (.md files and AI context) in separate repositories, keeping your project repositories clean
15
+ - **Version control**: Each command has its own version, allowing you to use different versions in different projects
16
+ - **Reusability**: Install the same command in multiple projects without duplication
17
+ - **Easy sharing**: Share commands with your team or the community through Git repositories
18
+ - **Simple management**: Install, update, and remove commands with familiar package manager semantics
19
+
20
+ Think of ccmd as "npm for Claude Code commands" - centralize your AI tooling configurations and use them anywhere.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ go install github.com/gifflet/ccmd/cmd/ccmd@latest
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ### 1. Initialize your project
31
+ ```bash
32
+ cd your-project
33
+ ccmd init
34
+ ```
35
+
36
+ ### 2. Install a demo command
37
+ ```bash
38
+ ccmd install gifflet/hello-world
39
+ ```
40
+
41
+ ### 3. Use it in Claude Code
42
+ ```
43
+ /hello-world
44
+ ```
45
+
46
+ That's it! You've just installed and used your first ccmd command.
47
+
48
+ ## Commands
49
+
50
+ | Command | Description |
51
+ |---------|-------------|
52
+ | `ccmd init` | Initialize a new command project |
53
+ | `ccmd install <repo>` | Install a command from a Git repository |
54
+ | `ccmd install` | Install all commands from ccmd.yaml |
55
+ | `ccmd list` | List installed commands |
56
+ | `ccmd update <command>` | Update a specific command |
57
+ | `ccmd remove <command>` | Remove an installed command |
58
+ | `ccmd search <keyword>` | Search for commands in the registry |
59
+ | `ccmd info <command>` | Show detailed command information |
60
+
61
+ > For detailed usage and options, see [commands reference](docs/commands.md)
62
+
63
+ ## Creating Your Own Commands
64
+
65
+ Creating a command for ccmd is simple. Your repository needs:
66
+
67
+ 1. **ccmd.yaml** - Command metadata (created by `ccmd init`)
68
+ 2. **index.md** - Command instructions for Claude
69
+
70
+ ### Quick Start
71
+
72
+ ```bash
73
+ mkdir my-command && cd my-command
74
+ ccmd init # Creates ccmd.yaml interactively
75
+ ```
76
+
77
+ ### Example Structure
78
+
79
+ ```
80
+ my-command/
81
+ ├── ccmd.yaml # Command metadata (required)
82
+ └── index.md # Command for Claude (required)
83
+ ```
84
+
85
+ ### Example ccmd.yaml
86
+
87
+ ```yaml
88
+ name: my-command
89
+ version: 1.0.0
90
+ description: Automates tasks in Claude Code
91
+ author: Your Name
92
+ repository: https://github.com/username/my-command
93
+ entry: index.md # Optional, defaults to index.md
94
+ ```
95
+
96
+ > For complete guide with examples, see [Creating Commands](docs/creating-commands.md)
97
+
98
+ ## Example Commands
99
+
100
+ Here are some commands you can install and try:
101
+
102
+ - **hello-world**: Simple demo command
103
+ ```bash
104
+ ccmd install https://github.com/gifflet/hello-world
105
+ ```
106
+
107
+ ## Documentation
108
+
109
+ - **[Full Documentation](docs/)** - Complete guides and references
110
+ - **[Command Creation Guide](docs/creating-commands.md)** - Create your own commands
111
+
112
+ ## Community
113
+
114
+ - **Issues**: [GitHub Issues](https://github.com/gifflet/ccmd/issues)
115
+ - **Discussions**: [GitHub Discussions](https://github.com/gifflet/ccmd/discussions)
116
+ - **Contributing**: See [CONTRIBUTING.md](CONTRIBUTING.md)
117
+
118
+ ## License
119
+
120
+ MIT License - see [LICENSE](LICENSE) for details
package/index.js ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require('node:path');
4
+ const childProcess = require('node:child_process');
5
+ const fsPromises = require('node:fs/promises');
6
+
7
+ // Mapping from Node's `process.arch` to Golang's `$GOARCH`
8
+ const ARCH_MAPPING = {
9
+ x64: 'amd64',
10
+ arm64: 'arm64',
11
+ };
12
+ const PLATFORM_MACOS = 'darwin';
13
+ // Mapping between Node's `process.platform` to Golang's
14
+ const PLATFORM_MAPPING = {
15
+ [PLATFORM_MACOS]: PLATFORM_MACOS,
16
+ linux: 'linux',
17
+ win32: 'windows',
18
+ };
19
+ /** @type {string?} */
20
+ let fullPath = null;
21
+
22
+ /**
23
+ * @return {Promise<string>}
24
+ */
25
+ export async function findCcmdBinary() {
26
+ if (fullPath) {
27
+ // return the previously cached value
28
+ return fullPath;
29
+ }
30
+
31
+ const binaryName = `ccmd${process.platform === 'win32' ? '.exe' : ''}`;
32
+ const binaryRoot = path.join(__dirname, 'dist');
33
+ const goPlatform = PLATFORM_MAPPING[process.platform];
34
+ const goArch = ARCH_MAPPING[process.arch];
35
+ if (goPlatform && goArch) {
36
+ const sysFolderName = `ccmd-${goPlatform}-${goArch}_${goPlatform}_${goArch}`;
37
+ fullPath = path.join(binaryRoot, sysFolderName, binaryName);
38
+ try {
39
+ await fsPromises.access(fullPath, fsPromises.constants.R_OK);
40
+ } catch (ign) {
41
+ fullPath = null;
42
+ }
43
+ }
44
+ if (!fullPath) {
45
+ throw new Error(
46
+ `There is no precompiled ccmd binary for ${process.platform}@${process.arch} at '${binaryRoot}'`
47
+ );
48
+ }
49
+ return fullPath;
50
+ }
51
+
52
+ /**
53
+ * @returns {Promise<void>}
54
+ */
55
+ async function main() {
56
+ const binaryPath = await findCcmdBinary();
57
+ const child = childProcess.spawn(binaryPath, process.argv.slice(2), {
58
+ cwd: process.cwd(),
59
+ env: process.env,
60
+ stdio: [process.stdin, process.stdout, process.stderr],
61
+ });
62
+ await new Promise((resolve, reject) => {
63
+ child.once('error', reject);
64
+ child.once('exit', (code) => process.exit(code));
65
+ });
66
+ }
67
+
68
+
69
+ if (require.main === module) {
70
+ (async () => await main())();
71
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@gifflet/ccmd",
3
+ "version": "1.0.0-test.1",
4
+ "description": "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.",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "postinstall": "node postinstall.js install",
8
+ "preuninstall": "node postinstall.js uninstall"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/gifflet/ccmd.git"
13
+ },
14
+ "author": "gifflet",
15
+ "license": "MIT",
16
+ "bugs": {
17
+ "url": "https://github.com/gifflet/ccmd/issues"
18
+ },
19
+ "homepage": "https://github.com/gifflet/ccmd#readme",
20
+ "keywords": [
21
+ "claude",
22
+ "claude-code",
23
+ "command-manager",
24
+ "cli",
25
+ "package-manager"
26
+ ],
27
+ "bin": {
28
+ "ccmd": "./index.js"
29
+ },
30
+ "goBinary": {
31
+ "name": "ccmd",
32
+ "path": "./bin"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "postinstall.js",
37
+ "index.js"
38
+ ],
39
+ "dependencies": {
40
+ "mkdirp": "^1.0.4"
41
+ },
42
+ "engines": {
43
+ "node": ">=14.0.0"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ }
48
+ }
package/postinstall.js ADDED
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+ // Thanks to author of https://github.com/sanathkr/go-npm, we were able to modify his code to work with private packages
5
+ 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; };
6
+
7
+ var path = require('path'),
8
+ mkdirp = require('mkdirp'),
9
+ fs = require('fs');
10
+
11
+ // Mapping from Node's `process.arch` to Golang's `$GOARCH`
12
+ var ARCH_MAPPING = {
13
+ "ia32": "386",
14
+ "x64": "amd64",
15
+ "arm": "arm",
16
+ "arm64": "arm64"
17
+ };
18
+
19
+ // Mapping between Node's `process.platform` to Golang's
20
+ var PLATFORM_MAPPING = {
21
+ "darwin": "darwin",
22
+ "linux": "linux",
23
+ "win32": "windows",
24
+ "freebsd": "freebsd"
25
+ };
26
+
27
+ async function getInstallationPath() {
28
+
29
+ // `npm bin` will output the path where binary files should be installed
30
+
31
+ const value = null //await execShellCommand("npm bin -g");
32
+
33
+
34
+ var dir = null;
35
+ if (!value || value.length === 0) {
36
+
37
+ // We couldn't infer path from `npm bin`. Let's try to get it from
38
+ // Environment variables set by NPM when it runs.
39
+ // npm_config_prefix points to NPM's installation directory where `bin` folder is available
40
+ // Ex: /Users/foo/.nvm/versions/node/v4.3.0
41
+ var env = process.env;
42
+ if (env && env.npm_config_prefix) {
43
+ dir = path.join(env.npm_config_prefix, "bin");
44
+ }
45
+ } else {
46
+ dir = value.trim();
47
+ }
48
+ //throw (dir)
49
+ ///Users/danielpaulus/.nvm/versions/node/v19.7.0/lib/node_modules/go-ios/node_modules/.bin
50
+ await mkdirp(dir);
51
+ return dir;
52
+ }
53
+
54
+ async function verifyAndPlaceBinary(binName, binPath, callback) {
55
+ if (!fs.existsSync(path.join(binPath, binName))) return callback('Downloaded binary does not contain the binary specified in configuration - ' + binName);
56
+
57
+ // Get installation path for executables under node
58
+ const installationPath = await getInstallationPath();
59
+ // Copy the executable to the path
60
+ fs.rename(path.join(binPath, binName), path.join(installationPath, binName), (err) => {
61
+ if (!err) {
62
+ console.info("Installed cli successfully");
63
+ callback(null);
64
+ } else {
65
+ callback(err);
66
+ }
67
+ });
68
+ }
69
+
70
+ function validateConfiguration(packageJson) {
71
+
72
+ if (!packageJson.version) {
73
+ return "'version' property must be specified";
74
+ }
75
+
76
+ if (!packageJson.goBinary || _typeof(packageJson.goBinary) !== "object") {
77
+ return "'goBinary' property must be defined and be an object";
78
+ }
79
+
80
+ if (!packageJson.goBinary.name) {
81
+ return "'name' property is necessary";
82
+ }
83
+
84
+ if (!packageJson.goBinary.path) {
85
+ return "'path' property is necessary";
86
+ }
87
+ }
88
+
89
+ function parsePackageJson() {
90
+ if (process.arch !== "arm64" && process.platform !== "darwin") {
91
+ if (!(process.arch in ARCH_MAPPING)) {
92
+ console.error("Installation is not supported for this architecture: " + process.arch);
93
+ return;
94
+ }
95
+ }
96
+
97
+ if (!(process.platform in PLATFORM_MAPPING)) {
98
+ console.error("Installation is not supported for this platform: " + process.platform);
99
+ return;
100
+ }
101
+
102
+ var packageJsonPath = path.join(".", "package.json");
103
+ if (!fs.existsSync(packageJsonPath)) {
104
+ console.error("Unable to find package.json. " + "Please run this script at root of the package you want to be installed");
105
+ return;
106
+ }
107
+
108
+ var packageJson = JSON.parse(fs.readFileSync(packageJsonPath));
109
+ var error = validateConfiguration(packageJson);
110
+ if (error && error.length > 0) {
111
+ console.error("Invalid package.json: " + error);
112
+ return;
113
+ }
114
+
115
+ // We have validated the config. It exists in all its glory
116
+ var binName = packageJson.goBinary.name;
117
+ var binPath = packageJson.goBinary.path;
118
+ var version = packageJson.version;
119
+ if (version[0] === 'v') version = version.substr(1); // strip the 'v' if necessary v0.0.1 => 0.0.1
120
+
121
+ // Binary name on Windows has .exe suffix
122
+ if (process.platform === "win32") {
123
+ binName += ".exe";
124
+ }
125
+
126
+
127
+ return {
128
+ binName: binName,
129
+ binPath: binPath,
130
+ version: version
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Reads the configuration from application's package.json,
136
+ * validates properties, copied the binary from the package and stores at
137
+ * ./bin in the package's root. NPM already has support to install binary files
138
+ * specific locations when invoked with "npm install -g"
139
+ *
140
+ * See: https://docs.npmjs.com/files/package.json#bin
141
+ */
142
+ var INVALID_INPUT = "Invalid inputs";
143
+ async function install(callback) {
144
+
145
+ var opts = parsePackageJson();
146
+ if (!opts) return callback(INVALID_INPUT);
147
+ mkdirp.sync(opts.binPath);
148
+ console.info(`Copying the relevant binary for your platform ${process.platform}`);
149
+ let src = `./dist/ccmd-${PLATFORM_MAPPING[process.platform]}-${ARCH_MAPPING[process.arch]}_${PLATFORM_MAPPING[process.platform]}_${ARCH_MAPPING[process.arch]}/${opts.binName}`;
150
+
151
+ if (process.arch === "ia32" && process.platform === "win32") {
152
+ src = `./dist/ccmd-${PLATFORM_MAPPING[process.platform]}-amd64_${PLATFORM_MAPPING[process.platform]}_amd64/${opts.binName}`;
153
+ }
154
+
155
+ if (PLATFORM_MAPPING[process.platform] === "windows") {
156
+ let cmd = `copy ${src} ${opts.binPath}/${opts.binName}`
157
+ cmd = cmd.replace(/\//g, "\\")
158
+ await execShellCommand(cmd);
159
+ } else {
160
+ await execShellCommand(`cp ${src} ${opts.binPath}/${opts.binName}`);
161
+ }
162
+
163
+ await verifyAndPlaceBinary(opts.binName, opts.binPath, callback);
164
+ console.log("\x1b[32m", "ccmd installed, run 'ccmd --help' for details\n\n")
165
+ }
166
+
167
+ async function uninstall(callback) {
168
+ var opts = parsePackageJson();
169
+ try {
170
+ const installationPath = await getInstallationPath();
171
+ fs.unlink(path.join(installationPath, opts.binName), (err) => {
172
+ if (err) {
173
+ return callback(err);
174
+ }
175
+ });
176
+ } catch (ex) {
177
+ // Ignore errors when deleting the file.
178
+ }
179
+ console.info("Uninstalled cli successfully");
180
+ return callback(null);
181
+ }
182
+
183
+ // Parse command line arguments and call the right method
184
+ var actions = {
185
+ "install": install,
186
+ "uninstall": uninstall
187
+ };
188
+ /**
189
+ * Executes a shell command and return it as a Promise.
190
+ * @param cmd {string}
191
+ * @return {Promise<string>}
192
+ */
193
+ function execShellCommand(cmd) {
194
+ const exec = require('child_process').exec;
195
+ return new Promise((resolve, reject) => {
196
+ exec(cmd, (error, stdout, stderr) => {
197
+ if (error) {
198
+ console.warn(error);
199
+ }
200
+ resolve(stdout ? stdout : stderr);
201
+ });
202
+ });
203
+ }
204
+
205
+ var argv = process.argv;
206
+ if (argv && argv.length > 2) {
207
+ var cmd = process.argv[2];
208
+ if (!actions[cmd]) {
209
+ console.log("Invalid command. `install` and `uninstall` are the only supported commands");
210
+ process.exit(1);
211
+ }
212
+
213
+ actions[cmd](function (err) {
214
+ if (err) {
215
+ console.error(err);
216
+ process.exit(1);
217
+ } else {
218
+ process.exit(0);
219
+ }
220
+ });
221
+ }