crafters 0.1.0 → 0.2.0
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/CHANGELOG.md +15 -0
- package/CLAUDE.md +42 -0
- package/README.md +37 -6
- package/package.json +2 -1
- package/src/commands/claude.ts +184 -0
- package/src/index.ts +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2025-01-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Claude Code Integration**
|
|
13
|
+
- `crafters claude install` - Install commands and agents from claude-dx repo
|
|
14
|
+
- `crafters claude update` - Update all commands and agents (overwrites existing)
|
|
15
|
+
|
|
16
|
+
- **Features**
|
|
17
|
+
- Clones/updates `crafter-station/claude-dx` to local machine
|
|
18
|
+
- Copies commands to `~/.claude/commands/`
|
|
19
|
+
- Copies agents to `~/.claude/agents/`
|
|
20
|
+
- Intelligent merge of `settings.json` (preserves existing config, adds new permissions)
|
|
21
|
+
- Skip existing commands by default, use `--force` or `update` to overwrite
|
|
22
|
+
|
|
8
23
|
## [0.1.0] - 2024-12-20
|
|
9
24
|
|
|
10
25
|
### Added
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Run the CLI in development
|
|
9
|
+
bun run src/index.ts
|
|
10
|
+
|
|
11
|
+
# Build standalone executable
|
|
12
|
+
bun build src/index.ts --compile --outfile crafters
|
|
13
|
+
|
|
14
|
+
# Run with specific command
|
|
15
|
+
bun run src/index.ts claude install
|
|
16
|
+
bun run src/index.ts claude update
|
|
17
|
+
bun run src/index.ts domain add myapp -p my-vercel-project
|
|
18
|
+
bun run src/index.ts login --spaceshipKey="..." --spaceshipSecret="..." --vercelToken="..."
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Architecture
|
|
22
|
+
|
|
23
|
+
This is a CLI tool (`crafters`) for managing domains on Vercel projects with Spaceship DNS. Built with Bun and the `citty` CLI framework.
|
|
24
|
+
|
|
25
|
+
### Structure
|
|
26
|
+
|
|
27
|
+
- `src/index.ts` - Entry point, defines main command and subcommands using citty's `defineCommand`
|
|
28
|
+
- `src/commands/` - Command implementations
|
|
29
|
+
- `claude.ts` - `claude install/update` for syncing Claude Code config from claude-dx
|
|
30
|
+
- `domain.ts` - `domain add/remove/list` commands (core functionality)
|
|
31
|
+
- `login.ts` - `login/logout/whoami` credential management
|
|
32
|
+
- `src/lib/` - API clients and utilities
|
|
33
|
+
- `spaceship.ts` - Spaceship DNS API client (CNAME management)
|
|
34
|
+
- `vercel.ts` - Vercel SDK wrapper for project domain operations
|
|
35
|
+
- `config.ts` - Config file management (`~/.crafters/config.json`)
|
|
36
|
+
|
|
37
|
+
### Key Patterns
|
|
38
|
+
|
|
39
|
+
- Commands use citty's `defineCommand` with `meta`, `args`, and `run` properties
|
|
40
|
+
- API clients are factory functions (`createSpaceshipClient`, `createVercelClient`) returning method objects
|
|
41
|
+
- Configuration supports both file-based (`~/.crafters/config.json`) and environment variable fallbacks
|
|
42
|
+
- Domain operations coordinate both Vercel (add domain to project) and Spaceship (create CNAME record)
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# crafters
|
|
2
2
|
|
|
3
|
-
CLI for
|
|
3
|
+
Crafter Station CLI for domain management and Claude Code configuration.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,19 +8,41 @@ CLI for managing domains on Vercel projects with Spaceship DNS.
|
|
|
8
8
|
bun install -g crafters
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
### Claude Code
|
|
14
|
+
|
|
15
|
+
Sync Claude Code commands and agents from `crafter-station/claude-dx`.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# First-time install (skips existing commands)
|
|
19
|
+
crafters claude install
|
|
20
|
+
|
|
21
|
+
# Update all commands (overwrites existing)
|
|
22
|
+
crafters claude update
|
|
23
|
+
|
|
24
|
+
# Force overwrite on install
|
|
25
|
+
crafters claude install --force
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**What it does:**
|
|
29
|
+
- Clones/pulls `claude-dx` to `~/Programming/crafter-station/`
|
|
30
|
+
- Copies commands to `~/.claude/commands/`
|
|
31
|
+
- Copies agents to `~/.claude/agents/`
|
|
32
|
+
- Merges `settings.json` without overwriting existing config
|
|
33
|
+
|
|
34
|
+
### Domain Management
|
|
35
|
+
|
|
36
|
+
Manage Vercel project domains with Spaceship DNS.
|
|
12
37
|
|
|
13
38
|
```bash
|
|
39
|
+
# Setup credentials
|
|
14
40
|
crafters login \
|
|
15
41
|
--spaceshipKey="YOUR_KEY" \
|
|
16
42
|
--spaceshipSecret="YOUR_SECRET" \
|
|
17
43
|
--vercelToken="YOUR_TOKEN" \
|
|
18
44
|
--vercelTeamId="YOUR_TEAM_ID"
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Usage
|
|
22
45
|
|
|
23
|
-
```bash
|
|
24
46
|
# Add subdomain to project
|
|
25
47
|
crafters domain add myapp -p my-vercel-project
|
|
26
48
|
|
|
@@ -32,8 +54,17 @@ crafters domain list
|
|
|
32
54
|
|
|
33
55
|
# Check current config
|
|
34
56
|
crafters whoami
|
|
57
|
+
|
|
58
|
+
# Clear credentials
|
|
59
|
+
crafters logout
|
|
35
60
|
```
|
|
36
61
|
|
|
62
|
+
## Requirements
|
|
63
|
+
|
|
64
|
+
- [Bun](https://bun.sh)
|
|
65
|
+
- [GitHub CLI](https://cli.github.com) (for claude commands)
|
|
66
|
+
- Access to `crafter-station/claude-dx` repo
|
|
67
|
+
|
|
37
68
|
## License
|
|
38
69
|
|
|
39
70
|
MIT
|
package/package.json
CHANGED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
readdir,
|
|
7
|
+
copyFile,
|
|
8
|
+
mkdir,
|
|
9
|
+
readFile,
|
|
10
|
+
writeFile,
|
|
11
|
+
stat,
|
|
12
|
+
cp,
|
|
13
|
+
} from "node:fs/promises";
|
|
14
|
+
|
|
15
|
+
const CLAUDE_DX_REPO = "crafter-station/claude-dx";
|
|
16
|
+
const CLAUDE_DX_PATH = join(homedir(), "Programming/crafter-station/claude-dx");
|
|
17
|
+
const CLAUDE_CONFIG_PATH = join(homedir(), ".claude");
|
|
18
|
+
|
|
19
|
+
async function exists(path: string): Promise<boolean> {
|
|
20
|
+
try {
|
|
21
|
+
await stat(path);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function cloneOrPullRepo(): Promise<void> {
|
|
29
|
+
if (await exists(CLAUDE_DX_PATH)) {
|
|
30
|
+
console.log("Updating claude-dx...");
|
|
31
|
+
execSync("git pull", { cwd: CLAUDE_DX_PATH, stdio: "inherit" });
|
|
32
|
+
} else {
|
|
33
|
+
console.log("Cloning claude-dx...");
|
|
34
|
+
execSync(`gh repo clone ${CLAUDE_DX_REPO} ${CLAUDE_DX_PATH}`, {
|
|
35
|
+
stdio: "inherit",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function copyCommands(
|
|
41
|
+
force: boolean,
|
|
42
|
+
): Promise<{ copied: number; skipped: number }> {
|
|
43
|
+
const srcDir = join(CLAUDE_DX_PATH, ".claude/commands");
|
|
44
|
+
const destDir = join(CLAUDE_CONFIG_PATH, "commands");
|
|
45
|
+
|
|
46
|
+
await mkdir(destDir, { recursive: true });
|
|
47
|
+
|
|
48
|
+
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
49
|
+
let copied = 0;
|
|
50
|
+
let skipped = 0;
|
|
51
|
+
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
const srcPath = join(srcDir, entry.name);
|
|
54
|
+
const destPath = join(destDir, entry.name);
|
|
55
|
+
|
|
56
|
+
if ((await exists(destPath)) && !force) {
|
|
57
|
+
console.log(` Skip: ${entry.name} (exists)`);
|
|
58
|
+
skipped++;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (entry.isDirectory()) {
|
|
63
|
+
await cp(srcPath, destPath, { recursive: true });
|
|
64
|
+
} else {
|
|
65
|
+
await copyFile(srcPath, destPath);
|
|
66
|
+
}
|
|
67
|
+
console.log(` Copy: ${entry.name}`);
|
|
68
|
+
copied++;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { copied, skipped };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function copyAgents(): Promise<number> {
|
|
75
|
+
const srcDir = join(CLAUDE_DX_PATH, ".claude/agents");
|
|
76
|
+
const destDir = join(CLAUDE_CONFIG_PATH, "agents");
|
|
77
|
+
|
|
78
|
+
if (!(await exists(srcDir))) return 0;
|
|
79
|
+
|
|
80
|
+
await mkdir(destDir, { recursive: true });
|
|
81
|
+
|
|
82
|
+
const files = await readdir(srcDir);
|
|
83
|
+
for (const file of files) {
|
|
84
|
+
await copyFile(join(srcDir, file), join(destDir, file));
|
|
85
|
+
console.log(` Copy: ${file}`);
|
|
86
|
+
}
|
|
87
|
+
return files.length;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function mergeSettings(): Promise<void> {
|
|
91
|
+
const srcPath = join(CLAUDE_DX_PATH, ".claude/settings.json");
|
|
92
|
+
const destPath = join(CLAUDE_CONFIG_PATH, "settings.json");
|
|
93
|
+
|
|
94
|
+
if (!(await exists(srcPath))) return;
|
|
95
|
+
|
|
96
|
+
const srcSettings = JSON.parse(await readFile(srcPath, "utf-8"));
|
|
97
|
+
let destSettings: Record<string, unknown> = {};
|
|
98
|
+
|
|
99
|
+
if (await exists(destPath)) {
|
|
100
|
+
destSettings = JSON.parse(await readFile(destPath, "utf-8"));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const merged: Record<string, unknown> = { ...destSettings };
|
|
104
|
+
|
|
105
|
+
for (const [key, value] of Object.entries(srcSettings)) {
|
|
106
|
+
if (key === "permissions" && typeof value === "object" && value !== null) {
|
|
107
|
+
merged.permissions = merged.permissions || {};
|
|
108
|
+
const srcPerms = value as Record<string, string[]>;
|
|
109
|
+
const destPerms = (merged.permissions || {}) as Record<string, string[]>;
|
|
110
|
+
|
|
111
|
+
for (const [permKey, permValue] of Object.entries(srcPerms)) {
|
|
112
|
+
const existing = destPerms[permKey] || [];
|
|
113
|
+
(merged.permissions as Record<string, string[]>)[permKey] = [
|
|
114
|
+
...new Set([...existing, ...permValue]),
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
} else if (!(key in merged)) {
|
|
118
|
+
merged[key] = value;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await writeFile(destPath, JSON.stringify(merged, null, 2));
|
|
123
|
+
console.log(" Merged settings.json");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function runInstall(force: boolean): Promise<void> {
|
|
127
|
+
console.log("\n1. Syncing claude-dx repo...");
|
|
128
|
+
await cloneOrPullRepo();
|
|
129
|
+
|
|
130
|
+
console.log("\n2. Installing commands...");
|
|
131
|
+
const { copied, skipped } = await copyCommands(force);
|
|
132
|
+
|
|
133
|
+
console.log("\n3. Installing agents...");
|
|
134
|
+
const agentCount = await copyAgents();
|
|
135
|
+
|
|
136
|
+
console.log("\n4. Merging settings...");
|
|
137
|
+
await mergeSettings();
|
|
138
|
+
|
|
139
|
+
console.log(
|
|
140
|
+
`\nDone! ${copied} commands installed, ${skipped} skipped, ${agentCount} agents installed.`,
|
|
141
|
+
);
|
|
142
|
+
if (skipped > 0 && !force) {
|
|
143
|
+
console.log("Use 'crafters claude update' to overwrite existing commands.\n");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const claudeInstall = defineCommand({
|
|
148
|
+
meta: {
|
|
149
|
+
name: "install",
|
|
150
|
+
description: "Install Claude Code commands and agents from claude-dx",
|
|
151
|
+
},
|
|
152
|
+
args: {
|
|
153
|
+
force: {
|
|
154
|
+
type: "boolean",
|
|
155
|
+
alias: "f",
|
|
156
|
+
description: "Overwrite existing commands",
|
|
157
|
+
default: false,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
async run({ args }) {
|
|
161
|
+
await runInstall(args.force);
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
export const claudeUpdate = defineCommand({
|
|
166
|
+
meta: {
|
|
167
|
+
name: "update",
|
|
168
|
+
description: "Update Claude Code commands and agents from claude-dx",
|
|
169
|
+
},
|
|
170
|
+
async run() {
|
|
171
|
+
await runInstall(true);
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
export const claude = defineCommand({
|
|
176
|
+
meta: {
|
|
177
|
+
name: "claude",
|
|
178
|
+
description: "Manage Claude Code configuration",
|
|
179
|
+
},
|
|
180
|
+
subCommands: {
|
|
181
|
+
install: claudeInstall,
|
|
182
|
+
update: claudeUpdate,
|
|
183
|
+
},
|
|
184
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { defineCommand, runMain } from "citty";
|
|
3
|
+
import { claude } from "./commands/claude";
|
|
3
4
|
import { domain } from "./commands/domain";
|
|
4
5
|
import { login, logout, whoami } from "./commands/login";
|
|
5
6
|
|
|
6
7
|
const main = defineCommand({
|
|
7
8
|
meta: {
|
|
8
9
|
name: "crafters",
|
|
9
|
-
version: "0.0
|
|
10
|
-
description: "Crafter Station CLI - Domain management
|
|
10
|
+
version: "0.2.0",
|
|
11
|
+
description: "Crafter Station CLI - Domain management and Claude Code configuration",
|
|
11
12
|
},
|
|
12
13
|
subCommands: {
|
|
14
|
+
claude,
|
|
13
15
|
domain,
|
|
14
16
|
login,
|
|
15
17
|
logout,
|