codeguilds 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/README.md +70 -0
- package/dist/auth-B7TRPIVY.js +13 -0
- package/dist/chunk-3T3YVJPG.js +52 -0
- package/dist/index.js +557 -0
- package/package.json +30 -0
- package/src/commands/info.ts +37 -0
- package/src/commands/install.ts +55 -0
- package/src/commands/list.ts +31 -0
- package/src/commands/login.ts +116 -0
- package/src/commands/search.ts +45 -0
- package/src/commands/uninstall.ts +33 -0
- package/src/index.ts +56 -0
- package/src/lib/api.ts +53 -0
- package/src/lib/auth.ts +47 -0
- package/src/lib/claude-settings.ts +107 -0
- package/src/lib/format.ts +5 -0
- package/src/lib/installers.ts +165 -0
- package/src/lib/lock.ts +35 -0
- package/src/lib/types.ts +65 -0
- package/tsconfig.json +13 -0
- package/tsup.config.ts +12 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { writeFileSync, existsSync, readFileSync, mkdirSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { addMcpServer, addHook, removeMcpServer, removeHook } from "./claude-settings.js";
|
|
5
|
+
import type {
|
|
6
|
+
RegistryPackage,
|
|
7
|
+
InstallConfigMcp,
|
|
8
|
+
InstallConfigHook,
|
|
9
|
+
InstallConfigFile,
|
|
10
|
+
InstallConfigTemplate,
|
|
11
|
+
} from "./types.js";
|
|
12
|
+
|
|
13
|
+
export type Scope = "global" | "project";
|
|
14
|
+
|
|
15
|
+
export interface InstallResult {
|
|
16
|
+
destination: string;
|
|
17
|
+
note?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function installPackage(
|
|
21
|
+
pkg: RegistryPackage,
|
|
22
|
+
scope: Scope,
|
|
23
|
+
strategyOverride?: "append" | "prepend" | "replace",
|
|
24
|
+
): Promise<InstallResult> {
|
|
25
|
+
switch (pkg.package_type) {
|
|
26
|
+
case "mcp_server": {
|
|
27
|
+
if (!pkg.install_config) throw new Error(`Package '${pkg.slug}' has no install_config.`);
|
|
28
|
+
return installMcp(pkg, pkg.install_config as InstallConfigMcp, scope);
|
|
29
|
+
}
|
|
30
|
+
case "hook": {
|
|
31
|
+
if (!pkg.install_config) throw new Error(`Package '${pkg.slug}' has no install_config.`);
|
|
32
|
+
return installHook(pkg, pkg.install_config as InstallConfigHook, scope);
|
|
33
|
+
}
|
|
34
|
+
case "skill":
|
|
35
|
+
case "prompt": {
|
|
36
|
+
const fileConfig = pkg.install_config as InstallConfigFile | undefined;
|
|
37
|
+
const filename = fileConfig?.filename ?? `${pkg.slug}.md`;
|
|
38
|
+
return installFile(pkg, { filename }, "commands");
|
|
39
|
+
}
|
|
40
|
+
case "agent": {
|
|
41
|
+
const fileConfig = pkg.install_config as InstallConfigFile | undefined;
|
|
42
|
+
const filename = fileConfig?.filename ?? `${pkg.slug}.md`;
|
|
43
|
+
return installFile(pkg, { filename }, "agents");
|
|
44
|
+
}
|
|
45
|
+
case "claude_md_template": {
|
|
46
|
+
const tmplConfig = pkg.install_config as InstallConfigTemplate | undefined;
|
|
47
|
+
const strategy = strategyOverride ?? tmplConfig?.merge_strategy ?? "append";
|
|
48
|
+
return installTemplate(pkg, { filename: tmplConfig?.filename ?? "CLAUDE.md", merge_strategy: strategy }, scope);
|
|
49
|
+
}
|
|
50
|
+
default:
|
|
51
|
+
throw new Error(`Unknown package type: ${pkg.package_type}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function installMcp(
|
|
56
|
+
pkg: RegistryPackage,
|
|
57
|
+
config: InstallConfigMcp,
|
|
58
|
+
scope: Scope
|
|
59
|
+
): InstallResult {
|
|
60
|
+
addMcpServer(config.server_name, config.command, config.args ?? [], config.env ?? {}, scope);
|
|
61
|
+
const path = scope === "global" ? "~/.claude/settings.json" : ".claude/settings.json";
|
|
62
|
+
return {
|
|
63
|
+
destination: path,
|
|
64
|
+
note: `Added MCP server '${config.server_name}' — restart Claude Code to activate`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function installHook(
|
|
69
|
+
pkg: RegistryPackage,
|
|
70
|
+
config: InstallConfigHook,
|
|
71
|
+
scope: Scope
|
|
72
|
+
): InstallResult {
|
|
73
|
+
addHook(config.event, config.matcher, config.command, scope);
|
|
74
|
+
const path = scope === "global" ? "~/.claude/settings.json" : ".claude/settings.json";
|
|
75
|
+
return {
|
|
76
|
+
destination: path,
|
|
77
|
+
note: `Added hook for '${config.event}' event`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isSafeFilename(filename: string): boolean {
|
|
82
|
+
if (!filename || filename.includes("..") || filename.includes("/") || filename.startsWith("~")) return false;
|
|
83
|
+
return /^[\w\-. ]+$/.test(filename);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function installFile(
|
|
87
|
+
pkg: RegistryPackage,
|
|
88
|
+
config: InstallConfigFile,
|
|
89
|
+
subdir: "commands" | "agents"
|
|
90
|
+
): Promise<InstallResult> {
|
|
91
|
+
if (!pkg.readme_content) {
|
|
92
|
+
throw new Error(`No content to install for '${pkg.slug}'`);
|
|
93
|
+
}
|
|
94
|
+
if (!isSafeFilename(config.filename)) {
|
|
95
|
+
throw new Error(`Unsafe filename rejected: '${config.filename}'`);
|
|
96
|
+
}
|
|
97
|
+
const dir = resolve(homedir(), ".claude", subdir);
|
|
98
|
+
mkdirSync(dir, { recursive: true });
|
|
99
|
+
const dest = resolve(dir, config.filename);
|
|
100
|
+
writeFileSync(dest, pkg.readme_content);
|
|
101
|
+
return { destination: `~/.claude/${subdir}/${config.filename}` };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function installTemplate(
|
|
105
|
+
pkg: RegistryPackage,
|
|
106
|
+
config: InstallConfigTemplate,
|
|
107
|
+
scope: Scope
|
|
108
|
+
): Promise<InstallResult> {
|
|
109
|
+
if (!pkg.readme_content) {
|
|
110
|
+
throw new Error(`No content to install for '${pkg.slug}'`);
|
|
111
|
+
}
|
|
112
|
+
const dest =
|
|
113
|
+
scope === "project"
|
|
114
|
+
? resolve(process.cwd(), "CLAUDE.md")
|
|
115
|
+
: resolve(homedir(), ".claude", "CLAUDE.md");
|
|
116
|
+
|
|
117
|
+
const content = pkg.readme_content;
|
|
118
|
+
|
|
119
|
+
if (!existsSync(dest) || config.merge_strategy === "replace") {
|
|
120
|
+
writeFileSync(dest, content);
|
|
121
|
+
} else {
|
|
122
|
+
const existing = readFileSync(dest, "utf-8");
|
|
123
|
+
const merged =
|
|
124
|
+
config.merge_strategy === "prepend"
|
|
125
|
+
? `${content}\n\n${existing}`
|
|
126
|
+
: `${existing}\n\n${content}`;
|
|
127
|
+
writeFileSync(dest, merged);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const displayDest = scope === "project" ? "./CLAUDE.md" : "~/.claude/CLAUDE.md";
|
|
131
|
+
return { destination: displayDest, note: `Merged via '${config.merge_strategy}' strategy` };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function uninstallPackage(
|
|
135
|
+
pkg: RegistryPackage,
|
|
136
|
+
scope: Scope
|
|
137
|
+
): { success: boolean; message: string } {
|
|
138
|
+
if (!pkg.install_config) return { success: true, message: "Nothing to undo" };
|
|
139
|
+
|
|
140
|
+
switch (pkg.package_type) {
|
|
141
|
+
case "mcp_server": {
|
|
142
|
+
const config = pkg.install_config as InstallConfigMcp;
|
|
143
|
+
const removed = removeMcpServer(config.server_name, scope);
|
|
144
|
+
return {
|
|
145
|
+
success: true,
|
|
146
|
+
message: removed
|
|
147
|
+
? `Removed MCP server '${config.server_name}' from settings`
|
|
148
|
+
: `MCP server '${config.server_name}' was not in settings`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
case "hook": {
|
|
152
|
+
const config = pkg.install_config as InstallConfigHook;
|
|
153
|
+
const removed = removeHook(config.event, config.command, scope);
|
|
154
|
+
return {
|
|
155
|
+
success: true,
|
|
156
|
+
message: removed ? `Removed hook from '${config.event}'` : `Hook was not found in settings`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
default:
|
|
160
|
+
return {
|
|
161
|
+
success: true,
|
|
162
|
+
message: "File-based package — remove it manually from ~/.claude/commands/ or ~/.claude/agents/",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
package/src/lib/lock.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import type { LockFile, LockEntry } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export function getLockPath(projectDir?: string): string {
|
|
6
|
+
return resolve(projectDir ?? process.cwd(), ".codeguilds-lock.json");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function readLock(projectDir?: string): LockFile {
|
|
10
|
+
const path = getLockPath(projectDir);
|
|
11
|
+
if (!existsSync(path)) return { version: 1, packages: {} };
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(readFileSync(path, "utf-8")) as LockFile;
|
|
14
|
+
} catch {
|
|
15
|
+
return { version: 1, packages: {} };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function writeLock(lock: LockFile, projectDir?: string): void {
|
|
20
|
+
writeFileSync(getLockPath(projectDir), JSON.stringify(lock, null, 2) + "\n");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function addToLock(entry: LockEntry, projectDir?: string): void {
|
|
24
|
+
const lock = readLock(projectDir);
|
|
25
|
+
lock.packages[entry.slug] = entry;
|
|
26
|
+
writeLock(lock, projectDir);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function removeFromLock(slug: string, projectDir?: string): boolean {
|
|
30
|
+
const lock = readLock(projectDir);
|
|
31
|
+
if (!lock.packages[slug]) return false;
|
|
32
|
+
delete lock.packages[slug];
|
|
33
|
+
writeLock(lock, projectDir);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
package/src/lib/types.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export type PackageType =
|
|
2
|
+
| "mcp_server"
|
|
3
|
+
| "skill"
|
|
4
|
+
| "agent"
|
|
5
|
+
| "hook"
|
|
6
|
+
| "prompt"
|
|
7
|
+
| "claude_md_template";
|
|
8
|
+
|
|
9
|
+
export interface InstallConfigMcp {
|
|
10
|
+
server_name: string;
|
|
11
|
+
command: string;
|
|
12
|
+
args: string[];
|
|
13
|
+
env: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface InstallConfigHook {
|
|
17
|
+
event: string;
|
|
18
|
+
matcher: string;
|
|
19
|
+
command: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface InstallConfigFile {
|
|
23
|
+
filename: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface InstallConfigTemplate {
|
|
27
|
+
filename: string;
|
|
28
|
+
merge_strategy: "append" | "prepend" | "replace";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type InstallConfig =
|
|
32
|
+
| InstallConfigMcp
|
|
33
|
+
| InstallConfigHook
|
|
34
|
+
| InstallConfigFile
|
|
35
|
+
| InstallConfigTemplate;
|
|
36
|
+
|
|
37
|
+
export interface RegistryPackage {
|
|
38
|
+
id: string;
|
|
39
|
+
slug: string;
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
package_type: PackageType;
|
|
43
|
+
current_version: string;
|
|
44
|
+
download_count: number;
|
|
45
|
+
star_count: number;
|
|
46
|
+
install_config: InstallConfig | null;
|
|
47
|
+
source_url: string | null;
|
|
48
|
+
readme_content: string | null;
|
|
49
|
+
license: string | null;
|
|
50
|
+
published_at: string | null;
|
|
51
|
+
publisher: { username: string; display_name: string | null } | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface LockEntry {
|
|
55
|
+
slug: string;
|
|
56
|
+
name: string;
|
|
57
|
+
current_version: string;
|
|
58
|
+
package_type: PackageType;
|
|
59
|
+
installed_at: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface LockFile {
|
|
63
|
+
version: 1;
|
|
64
|
+
packages: Record<string, LockEntry>;
|
|
65
|
+
}
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ["src/index.ts"],
|
|
5
|
+
format: ["esm"],
|
|
6
|
+
target: "node18",
|
|
7
|
+
outExtension: () => ({ js: ".js" }),
|
|
8
|
+
banner: { js: "#!/usr/bin/env node" },
|
|
9
|
+
clean: true,
|
|
10
|
+
minify: false,
|
|
11
|
+
shims: true,
|
|
12
|
+
});
|