safeword 0.1.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/dist/check-J6DFVBCE.js +129 -0
- package/dist/check-J6DFVBCE.js.map +1 -0
- package/dist/chunk-24OB57NJ.js +78 -0
- package/dist/chunk-24OB57NJ.js.map +1 -0
- package/dist/chunk-DB4CMUFD.js +157 -0
- package/dist/chunk-DB4CMUFD.js.map +1 -0
- package/dist/chunk-UQMQ64CB.js +107 -0
- package/dist/chunk-UQMQ64CB.js.map +1 -0
- package/dist/chunk-W66Z3C5H.js +21 -0
- package/dist/chunk-W66Z3C5H.js.map +1 -0
- package/dist/chunk-WWQ4YRZN.js +7 -0
- package/dist/chunk-WWQ4YRZN.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +34 -0
- package/dist/cli.js.map +1 -0
- package/dist/diff-U4IELWRL.js +163 -0
- package/dist/diff-U4IELWRL.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/reset-XETOHTCK.js +126 -0
- package/dist/reset-XETOHTCK.js.map +1 -0
- package/dist/setup-CLDCHROZ.js +237 -0
- package/dist/setup-CLDCHROZ.js.map +1 -0
- package/dist/upgrade-DOKWRK7J.js +146 -0
- package/dist/upgrade-DOKWRK7J.js.map +1 -0
- package/package.json +50 -0
- package/templates/doc-templates/ticket-template.md +82 -0
- package/templates/hooks/inject-timestamp.sh +7 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isNewerVersion
|
|
3
|
+
} from "./chunk-W66Z3C5H.js";
|
|
4
|
+
import {
|
|
5
|
+
VERSION
|
|
6
|
+
} from "./chunk-WWQ4YRZN.js";
|
|
7
|
+
import {
|
|
8
|
+
exists,
|
|
9
|
+
header,
|
|
10
|
+
info,
|
|
11
|
+
keyValue,
|
|
12
|
+
readFile,
|
|
13
|
+
readFileSafe,
|
|
14
|
+
success,
|
|
15
|
+
warn
|
|
16
|
+
} from "./chunk-UQMQ64CB.js";
|
|
17
|
+
|
|
18
|
+
// src/commands/check.ts
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
async function checkLatestVersion(timeout = 3e3) {
|
|
21
|
+
try {
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
24
|
+
const response = await fetch("https://registry.npmjs.org/safeword/latest", {
|
|
25
|
+
signal: controller.signal
|
|
26
|
+
});
|
|
27
|
+
clearTimeout(timeoutId);
|
|
28
|
+
if (!response.ok) return null;
|
|
29
|
+
const data = await response.json();
|
|
30
|
+
return data.version ?? null;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function checkHealth(cwd) {
|
|
36
|
+
const safewordDir = join(cwd, ".safeword");
|
|
37
|
+
const issues = [];
|
|
38
|
+
if (!exists(safewordDir)) {
|
|
39
|
+
return {
|
|
40
|
+
configured: false,
|
|
41
|
+
projectVersion: null,
|
|
42
|
+
cliVersion: VERSION,
|
|
43
|
+
updateAvailable: false,
|
|
44
|
+
latestVersion: null,
|
|
45
|
+
issues: []
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const versionPath = join(safewordDir, "version");
|
|
49
|
+
const projectVersion = readFileSafe(versionPath)?.trim() ?? null;
|
|
50
|
+
const requiredFiles = ["SAFEWORD.md", "version", "hooks/agents-md-check.sh"];
|
|
51
|
+
for (const file of requiredFiles) {
|
|
52
|
+
if (!exists(join(safewordDir, file))) {
|
|
53
|
+
issues.push(`Missing: .safeword/${file}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const agentsMdPath = join(cwd, "AGENTS.md");
|
|
57
|
+
if (exists(agentsMdPath)) {
|
|
58
|
+
const content = readFile(agentsMdPath);
|
|
59
|
+
if (!content.includes("@./.safeword/SAFEWORD.md")) {
|
|
60
|
+
issues.push("AGENTS.md missing safeword link");
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
issues.push("AGENTS.md file missing");
|
|
64
|
+
}
|
|
65
|
+
const settingsPath = join(cwd, ".claude", "settings.json");
|
|
66
|
+
if (!exists(settingsPath)) {
|
|
67
|
+
issues.push("Missing: .claude/settings.json");
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
configured: true,
|
|
71
|
+
projectVersion,
|
|
72
|
+
cliVersion: VERSION,
|
|
73
|
+
updateAvailable: false,
|
|
74
|
+
latestVersion: null,
|
|
75
|
+
issues
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function check(options) {
|
|
79
|
+
const cwd = process.cwd();
|
|
80
|
+
header("Safeword Health Check");
|
|
81
|
+
const health = checkHealth(cwd);
|
|
82
|
+
if (!health.configured) {
|
|
83
|
+
info("Not configured. Run `safeword setup` to initialize.");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
keyValue("Safeword CLI", `v${health.cliVersion}`);
|
|
87
|
+
keyValue("Project config", health.projectVersion ? `v${health.projectVersion}` : "unknown");
|
|
88
|
+
if (!options.offline) {
|
|
89
|
+
info("\nChecking for updates...");
|
|
90
|
+
const latestVersion = await checkLatestVersion();
|
|
91
|
+
if (latestVersion) {
|
|
92
|
+
health.latestVersion = latestVersion;
|
|
93
|
+
health.updateAvailable = isNewerVersion(health.cliVersion, latestVersion);
|
|
94
|
+
if (health.updateAvailable) {
|
|
95
|
+
warn(`Update available: v${latestVersion}`);
|
|
96
|
+
info("Run `npm install -g safeword` to upgrade");
|
|
97
|
+
} else {
|
|
98
|
+
success("CLI is up to date");
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
warn("Couldn't check for updates (offline?)");
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
info("\nSkipped update check (offline mode)");
|
|
105
|
+
}
|
|
106
|
+
if (health.projectVersion && isNewerVersion(health.cliVersion, health.projectVersion)) {
|
|
107
|
+
warn(`Project config (v${health.projectVersion}) is newer than CLI (v${health.cliVersion})`);
|
|
108
|
+
info("Consider upgrading the CLI");
|
|
109
|
+
} else if (health.projectVersion && isNewerVersion(health.projectVersion, health.cliVersion)) {
|
|
110
|
+
info(`
|
|
111
|
+
Upgrade available for project config`);
|
|
112
|
+
info(
|
|
113
|
+
`Run \`safeword upgrade\` to update from v${health.projectVersion} to v${health.cliVersion}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (health.issues.length > 0) {
|
|
117
|
+
header("Issues Found");
|
|
118
|
+
for (const issue of health.issues) {
|
|
119
|
+
warn(issue);
|
|
120
|
+
}
|
|
121
|
+
info("\nRun `safeword upgrade` to repair configuration");
|
|
122
|
+
} else {
|
|
123
|
+
success("\nConfiguration is healthy");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export {
|
|
127
|
+
check
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=check-J6DFVBCE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["/**\n * Check command - Verify project health and configuration\n */\n\nimport { join } from 'node:path';\nimport { VERSION } from '../version.js';\nimport { exists, readFile, readFileSafe } from '../utils/fs.js';\nimport { info, success, warn, error, header, keyValue } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\n\nexport interface CheckOptions {\n offline?: boolean;\n}\n\ninterface HealthStatus {\n configured: boolean;\n projectVersion: string | null;\n cliVersion: string;\n updateAvailable: boolean;\n latestVersion: string | null;\n issues: string[];\n}\n\n/**\n * Check for latest version from npm (with timeout)\n */\nasync function checkLatestVersion(timeout = 3000): Promise<string | null> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const response = await fetch('https://registry.npmjs.org/safeword/latest', {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) return null;\n\n const data = (await response.json()) as { version?: string };\n return data.version ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Check project configuration health\n */\nfunction checkHealth(cwd: string): HealthStatus {\n const safewordDir = join(cwd, '.safeword');\n const issues: string[] = [];\n\n // Check if configured\n if (!exists(safewordDir)) {\n return {\n configured: false,\n projectVersion: null,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: null,\n issues: [],\n };\n }\n\n // Read project version\n const versionPath = join(safewordDir, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? null;\n\n // Check for required files\n const requiredFiles = ['SAFEWORD.md', 'version', 'hooks/agents-md-check.sh'];\n\n for (const file of requiredFiles) {\n if (!exists(join(safewordDir, file))) {\n issues.push(`Missing: .safeword/${file}`);\n }\n }\n\n // Check AGENTS.md link\n const agentsMdPath = join(cwd, 'AGENTS.md');\n if (exists(agentsMdPath)) {\n const content = readFile(agentsMdPath);\n if (!content.includes('@./.safeword/SAFEWORD.md')) {\n issues.push('AGENTS.md missing safeword link');\n }\n } else {\n issues.push('AGENTS.md file missing');\n }\n\n // Check .claude/settings.json\n const settingsPath = join(cwd, '.claude', 'settings.json');\n if (!exists(settingsPath)) {\n issues.push('Missing: .claude/settings.json');\n }\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: null,\n issues,\n };\n}\n\nexport async function check(options: CheckOptions): Promise<void> {\n const cwd = process.cwd();\n\n header('Safeword Health Check');\n\n const health = checkHealth(cwd);\n\n // Not configured\n if (!health.configured) {\n info('Not configured. Run `safeword setup` to initialize.');\n return;\n }\n\n // Show versions\n keyValue('Safeword CLI', `v${health.cliVersion}`);\n keyValue('Project config', health.projectVersion ? `v${health.projectVersion}` : 'unknown');\n\n // Check for updates (unless offline)\n if (!options.offline) {\n info('\\nChecking for updates...');\n const latestVersion = await checkLatestVersion();\n\n if (latestVersion) {\n health.latestVersion = latestVersion;\n health.updateAvailable = isNewerVersion(health.cliVersion, latestVersion);\n\n if (health.updateAvailable) {\n warn(`Update available: v${latestVersion}`);\n info('Run `npm install -g safeword` to upgrade');\n } else {\n success('CLI is up to date');\n }\n } else {\n warn(\"Couldn't check for updates (offline?)\");\n }\n } else {\n info('\\nSkipped update check (offline mode)');\n }\n\n // Check project version vs CLI version\n if (health.projectVersion && isNewerVersion(health.cliVersion, health.projectVersion)) {\n warn(`Project config (v${health.projectVersion}) is newer than CLI (v${health.cliVersion})`);\n info('Consider upgrading the CLI');\n } else if (health.projectVersion && isNewerVersion(health.projectVersion, health.cliVersion)) {\n info(`\\nUpgrade available for project config`);\n info(\n `Run \\`safeword upgrade\\` to update from v${health.projectVersion} to v${health.cliVersion}`,\n );\n }\n\n // Show issues\n if (health.issues.length > 0) {\n header('Issues Found');\n for (const issue of health.issues) {\n warn(issue);\n }\n info('\\nRun `safeword upgrade` to repair configuration');\n } else {\n success('\\nConfiguration is healthy');\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureDir,
|
|
3
|
+
exists,
|
|
4
|
+
makeExecutable,
|
|
5
|
+
readFile,
|
|
6
|
+
writeFile
|
|
7
|
+
} from "./chunk-UQMQ64CB.js";
|
|
8
|
+
|
|
9
|
+
// src/utils/git.ts
|
|
10
|
+
import { execSync } from "child_process";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
var MARKER_START = "# SAFEWORD_ARCH_CHECK_START";
|
|
13
|
+
var MARKER_END = "# SAFEWORD_ARCH_CHECK_END";
|
|
14
|
+
function isGitRepo(cwd) {
|
|
15
|
+
return exists(join(cwd, ".git"));
|
|
16
|
+
}
|
|
17
|
+
function getHookContent() {
|
|
18
|
+
return `
|
|
19
|
+
${MARKER_START}
|
|
20
|
+
# Safeword architecture check
|
|
21
|
+
# This section is managed by safeword - do not edit manually
|
|
22
|
+
if [ -f ".safeword/hooks/pre-commit.sh" ]; then
|
|
23
|
+
bash .safeword/hooks/pre-commit.sh
|
|
24
|
+
fi
|
|
25
|
+
${MARKER_END}
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
function installGitHook(cwd) {
|
|
29
|
+
const hooksDir = join(cwd, ".git", "hooks");
|
|
30
|
+
const hookPath = join(hooksDir, "pre-commit");
|
|
31
|
+
ensureDir(hooksDir);
|
|
32
|
+
let content = "";
|
|
33
|
+
if (exists(hookPath)) {
|
|
34
|
+
content = readFile(hookPath);
|
|
35
|
+
if (content.includes(MARKER_START)) {
|
|
36
|
+
content = removeMarkerSection(content);
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
content = "#!/bin/bash\n";
|
|
40
|
+
}
|
|
41
|
+
content = content.trimEnd() + "\n" + getHookContent();
|
|
42
|
+
writeFile(hookPath, content);
|
|
43
|
+
makeExecutable(hookPath);
|
|
44
|
+
}
|
|
45
|
+
function removeGitHook(cwd) {
|
|
46
|
+
const hookPath = join(cwd, ".git", "hooks", "pre-commit");
|
|
47
|
+
if (!exists(hookPath)) return;
|
|
48
|
+
let content = readFile(hookPath);
|
|
49
|
+
if (!content.includes(MARKER_START)) return;
|
|
50
|
+
content = removeMarkerSection(content);
|
|
51
|
+
writeFile(hookPath, content);
|
|
52
|
+
}
|
|
53
|
+
function removeMarkerSection(content) {
|
|
54
|
+
const lines = content.split("\n");
|
|
55
|
+
const result = [];
|
|
56
|
+
let inMarkerSection = false;
|
|
57
|
+
for (const line of lines) {
|
|
58
|
+
if (line.includes(MARKER_START)) {
|
|
59
|
+
inMarkerSection = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (line.includes(MARKER_END)) {
|
|
63
|
+
inMarkerSection = false;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (!inMarkerSection) {
|
|
67
|
+
result.push(line);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return result.join("\n").trim() + "\n";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
isGitRepo,
|
|
75
|
+
installGitHook,
|
|
76
|
+
removeGitHook
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=chunk-24OB57NJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/git.ts"],"sourcesContent":["/**\n * Git utilities for CLI operations\n */\n\nimport { execSync } from 'node:child_process';\nimport { join } from 'node:path';\nimport { exists, readFile, writeFile, ensureDir, makeExecutable } from './fs.js';\n\nconst MARKER_START = '# SAFEWORD_ARCH_CHECK_START';\nconst MARKER_END = '# SAFEWORD_ARCH_CHECK_END';\n\n/**\n * Check if directory is a git repository\n */\nexport function isGitRepo(cwd: string): boolean {\n return exists(join(cwd, '.git'));\n}\n\n/**\n * Initialize a git repository\n */\nexport function initGitRepo(cwd: string): void {\n execSync('git init', { cwd, stdio: 'pipe' });\n}\n\n/**\n * Get the pre-commit hook content to add\n */\nfunction getHookContent(): string {\n return `\n${MARKER_START}\n# Safeword architecture check\n# This section is managed by safeword - do not edit manually\nif [ -f \".safeword/hooks/pre-commit.sh\" ]; then\n bash .safeword/hooks/pre-commit.sh\nfi\n${MARKER_END}\n`;\n}\n\n/**\n * Install safeword markers into pre-commit hook\n */\nexport function installGitHook(cwd: string): void {\n const hooksDir = join(cwd, '.git', 'hooks');\n const hookPath = join(hooksDir, 'pre-commit');\n\n ensureDir(hooksDir);\n\n let content = '';\n\n if (exists(hookPath)) {\n content = readFile(hookPath);\n\n // Check if already has safeword markers\n if (content.includes(MARKER_START)) {\n // Remove existing safeword section and re-add (update)\n content = removeMarkerSection(content);\n }\n } else {\n // Create new hook file with shebang\n content = '#!/bin/bash\\n';\n }\n\n // Add safeword section\n content = content.trimEnd() + '\\n' + getHookContent();\n\n writeFile(hookPath, content);\n makeExecutable(hookPath);\n}\n\n/**\n * Remove safeword markers from pre-commit hook\n */\nexport function removeGitHook(cwd: string): void {\n const hookPath = join(cwd, '.git', 'hooks', 'pre-commit');\n\n if (!exists(hookPath)) return;\n\n let content = readFile(hookPath);\n\n if (!content.includes(MARKER_START)) return;\n\n content = removeMarkerSection(content);\n\n // If only shebang remains, we could delete the file\n // but safer to leave it\n writeFile(hookPath, content);\n}\n\n/**\n * Remove the section between markers (inclusive)\n */\nfunction removeMarkerSection(content: string): string {\n const lines = content.split('\\n');\n const result: string[] = [];\n let inMarkerSection = false;\n\n for (const line of lines) {\n if (line.includes(MARKER_START)) {\n inMarkerSection = true;\n continue;\n }\n if (line.includes(MARKER_END)) {\n inMarkerSection = false;\n continue;\n }\n if (!inMarkerSection) {\n result.push(line);\n }\n }\n\n return result.join('\\n').trim() + '\\n';\n}\n\n/**\n * Check if git hooks have safeword markers\n */\nexport function hasGitHook(cwd: string): boolean {\n const hookPath = join(cwd, '.git', 'hooks', 'pre-commit');\n if (!exists(hookPath)) return false;\n const content = readFile(hookPath);\n return content.includes(MARKER_START);\n}\n"],"mappings":";;;;;;;;;AAIA,SAAS,gBAAgB;AACzB,SAAS,YAAY;AAGrB,IAAM,eAAe;AACrB,IAAM,aAAa;AAKZ,SAAS,UAAU,KAAsB;AAC9C,SAAO,OAAO,KAAK,KAAK,MAAM,CAAC;AACjC;AAYA,SAAS,iBAAyB;AAChC,SAAO;AAAA,EACP,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMZ,UAAU;AAAA;AAEZ;AAKO,SAAS,eAAe,KAAmB;AAChD,QAAM,WAAW,KAAK,KAAK,QAAQ,OAAO;AAC1C,QAAM,WAAW,KAAK,UAAU,YAAY;AAE5C,YAAU,QAAQ;AAElB,MAAI,UAAU;AAEd,MAAI,OAAO,QAAQ,GAAG;AACpB,cAAU,SAAS,QAAQ;AAG3B,QAAI,QAAQ,SAAS,YAAY,GAAG;AAElC,gBAAU,oBAAoB,OAAO;AAAA,IACvC;AAAA,EACF,OAAO;AAEL,cAAU;AAAA,EACZ;AAGA,YAAU,QAAQ,QAAQ,IAAI,OAAO,eAAe;AAEpD,YAAU,UAAU,OAAO;AAC3B,iBAAe,QAAQ;AACzB;AAKO,SAAS,cAAc,KAAmB;AAC/C,QAAM,WAAW,KAAK,KAAK,QAAQ,SAAS,YAAY;AAExD,MAAI,CAAC,OAAO,QAAQ,EAAG;AAEvB,MAAI,UAAU,SAAS,QAAQ;AAE/B,MAAI,CAAC,QAAQ,SAAS,YAAY,EAAG;AAErC,YAAU,oBAAoB,OAAO;AAIrC,YAAU,UAAU,OAAO;AAC7B;AAKA,SAAS,oBAAoB,SAAyB;AACpD,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,QAAM,SAAmB,CAAC;AAC1B,MAAI,kBAAkB;AAEtB,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,SAAS,YAAY,GAAG;AAC/B,wBAAkB;AAClB;AAAA,IACF;AACA,QAAI,KAAK,SAAS,UAAU,GAAG;AAC7B,wBAAkB;AAClB;AAAA,IACF;AACA,QAAI,CAAC,iBAAiB;AACpB,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,EACF;AAEA,SAAO,OAAO,KAAK,IAAI,EAAE,KAAK,IAAI;AACpC;","names":[]}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// src/templates/content.ts
|
|
2
|
+
var SAFEWORD_MD = `# SAFEWORD Configuration
|
|
3
|
+
|
|
4
|
+
This directory contains safeword configuration for AI coding agents.
|
|
5
|
+
|
|
6
|
+
## Structure
|
|
7
|
+
|
|
8
|
+
- \`SAFEWORD.md\` - This file (main configuration)
|
|
9
|
+
- \`guides/\` - Reference documentation
|
|
10
|
+
- \`templates/\` - Document templates
|
|
11
|
+
- \`hooks/\` - Claude Code hook scripts
|
|
12
|
+
- \`version\` - Installed safeword version
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
The AGENTS.md file in your project root should reference this configuration:
|
|
17
|
+
|
|
18
|
+
\`\`\`markdown
|
|
19
|
+
**\u26A0\uFE0F ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**
|
|
20
|
+
\`\`\`
|
|
21
|
+
|
|
22
|
+
This ensures AI agents read the safeword configuration before any other context.
|
|
23
|
+
|
|
24
|
+
## Customization
|
|
25
|
+
|
|
26
|
+
You can customize the guides and templates, but note that running \`safeword upgrade\`
|
|
27
|
+
will overwrite changes. Keep customizations in separate files if needed.
|
|
28
|
+
|
|
29
|
+
## Commands
|
|
30
|
+
|
|
31
|
+
- \`safeword check\` - Verify configuration health
|
|
32
|
+
- \`safeword upgrade\` - Update to latest templates
|
|
33
|
+
- \`safeword diff\` - Preview upgrade changes
|
|
34
|
+
- \`safeword reset\` - Remove safeword configuration
|
|
35
|
+
`;
|
|
36
|
+
var AGENTS_MD_LINK = "**\u26A0\uFE0F ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**";
|
|
37
|
+
var PRETTIERRC = `{
|
|
38
|
+
"semi": true,
|
|
39
|
+
"singleQuote": true,
|
|
40
|
+
"tabWidth": 2,
|
|
41
|
+
"trailingComma": "es5",
|
|
42
|
+
"printWidth": 100
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
var HOOK_AGENTS_CHECK = `#!/bin/bash
|
|
46
|
+
# Safeword AGENTS.md self-healing hook
|
|
47
|
+
# Ensures the AGENTS.md link is always present
|
|
48
|
+
|
|
49
|
+
LINK='**\u26A0\uFE0F ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**'
|
|
50
|
+
|
|
51
|
+
if [ ! -d ".safeword" ]; then
|
|
52
|
+
# Not a safeword project, skip
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
if [ ! -f "AGENTS.md" ]; then
|
|
57
|
+
# AGENTS.md doesn't exist, create it
|
|
58
|
+
echo "$LINK" > AGENTS.md
|
|
59
|
+
echo "SAFEWORD: Created AGENTS.md with safeword link"
|
|
60
|
+
exit 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Check if link is present
|
|
64
|
+
if ! grep -q "@./.safeword/SAFEWORD.md" AGENTS.md; then
|
|
65
|
+
# Link missing, prepend it
|
|
66
|
+
CONTENT=$(cat AGENTS.md)
|
|
67
|
+
echo -e "$LINK\\n\\n$CONTENT" > AGENTS.md
|
|
68
|
+
echo "SAFEWORD: Restored AGENTS.md link (was removed)"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
exit 0
|
|
72
|
+
`;
|
|
73
|
+
var HOOK_PRE_COMMIT = `#!/bin/bash
|
|
74
|
+
# Safeword pre-commit hook
|
|
75
|
+
# Runs architecture checks before commit
|
|
76
|
+
|
|
77
|
+
# Run linting if available
|
|
78
|
+
if [ -f "package.json" ] && grep -q '"lint"' package.json; then
|
|
79
|
+
npm run lint --silent || exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
exit 0
|
|
83
|
+
`;
|
|
84
|
+
var HOOK_POST_TOOL = `#!/bin/bash
|
|
85
|
+
# Safeword post-tool hook
|
|
86
|
+
# Placeholder for post-tool validations
|
|
87
|
+
exit 0
|
|
88
|
+
`;
|
|
89
|
+
var SKILL_QUALITY_REVIEWER = `# Quality Reviewer Skill
|
|
90
|
+
|
|
91
|
+
This skill provides deep code quality review with web research capabilities.
|
|
92
|
+
|
|
93
|
+
## Usage
|
|
94
|
+
|
|
95
|
+
Use when user explicitly requests verification against latest docs or needs deeper analysis.
|
|
96
|
+
|
|
97
|
+
## Capabilities
|
|
98
|
+
|
|
99
|
+
- Fetches current documentation (WebFetch)
|
|
100
|
+
- Checks latest versions (WebSearch)
|
|
101
|
+
- Provides deep analysis (performance, security, alternatives)
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
// src/templates/config.ts
|
|
105
|
+
function getEslintConfig(options) {
|
|
106
|
+
const imports = ['import js from "@eslint/js";'];
|
|
107
|
+
const configs = ["js.configs.recommended"];
|
|
108
|
+
if (options.typescript) {
|
|
109
|
+
imports.push('import tseslint from "typescript-eslint";');
|
|
110
|
+
configs.push("...tseslint.configs.recommended");
|
|
111
|
+
}
|
|
112
|
+
if (options.react || options.nextjs) {
|
|
113
|
+
imports.push('import react from "eslint-plugin-react";');
|
|
114
|
+
imports.push('import reactHooks from "eslint-plugin-react-hooks";');
|
|
115
|
+
configs.push("react.configs.flat.recommended");
|
|
116
|
+
configs.push('react.configs.flat["jsx-runtime"]');
|
|
117
|
+
configs.push(
|
|
118
|
+
'{ plugins: { "react-hooks": reactHooks }, rules: reactHooks.configs.recommended.rules }'
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return `${imports.join("\n")}
|
|
122
|
+
|
|
123
|
+
export default [
|
|
124
|
+
${configs.join(",\n ")},
|
|
125
|
+
{
|
|
126
|
+
ignores: ["node_modules/", "dist/", ".next/", "build/"],
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
var SETTINGS_HOOKS = {
|
|
132
|
+
SessionStart: [
|
|
133
|
+
{
|
|
134
|
+
command: "bash .safeword/hooks/agents-md-check.sh",
|
|
135
|
+
description: "Safeword: Verify AGENTS.md link"
|
|
136
|
+
}
|
|
137
|
+
],
|
|
138
|
+
PostToolUse: [
|
|
139
|
+
{
|
|
140
|
+
command: "bash .safeword/hooks/post-tool.sh 2>/dev/null || true",
|
|
141
|
+
description: "Safeword: Post-tool validation"
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export {
|
|
147
|
+
SAFEWORD_MD,
|
|
148
|
+
AGENTS_MD_LINK,
|
|
149
|
+
PRETTIERRC,
|
|
150
|
+
HOOK_AGENTS_CHECK,
|
|
151
|
+
HOOK_PRE_COMMIT,
|
|
152
|
+
HOOK_POST_TOOL,
|
|
153
|
+
SKILL_QUALITY_REVIEWER,
|
|
154
|
+
getEslintConfig,
|
|
155
|
+
SETTINGS_HOOKS
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=chunk-DB4CMUFD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/templates/content.ts","../src/templates/config.ts"],"sourcesContent":["/**\n * Content templates - markdown, scripts, and static content\n */\n\nexport const SAFEWORD_MD = `# SAFEWORD Configuration\n\nThis directory contains safeword configuration for AI coding agents.\n\n## Structure\n\n- \\`SAFEWORD.md\\` - This file (main configuration)\n- \\`guides/\\` - Reference documentation\n- \\`templates/\\` - Document templates\n- \\`hooks/\\` - Claude Code hook scripts\n- \\`version\\` - Installed safeword version\n\n## Usage\n\nThe AGENTS.md file in your project root should reference this configuration:\n\n\\`\\`\\`markdown\n**⚠️ ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**\n\\`\\`\\`\n\nThis ensures AI agents read the safeword configuration before any other context.\n\n## Customization\n\nYou can customize the guides and templates, but note that running \\`safeword upgrade\\`\nwill overwrite changes. Keep customizations in separate files if needed.\n\n## Commands\n\n- \\`safeword check\\` - Verify configuration health\n- \\`safeword upgrade\\` - Update to latest templates\n- \\`safeword diff\\` - Preview upgrade changes\n- \\`safeword reset\\` - Remove safeword configuration\n`;\n\nexport const AGENTS_MD_LINK = '**⚠️ ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**';\n\nexport const PRETTIERRC = `{\n \"semi\": true,\n \"singleQuote\": true,\n \"tabWidth\": 2,\n \"trailingComma\": \"es5\",\n \"printWidth\": 100\n}\n`;\n\nexport const HOOK_AGENTS_CHECK = `#!/bin/bash\n# Safeword AGENTS.md self-healing hook\n# Ensures the AGENTS.md link is always present\n\nLINK='**⚠️ ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**'\n\nif [ ! -d \".safeword\" ]; then\n # Not a safeword project, skip\n exit 0\nfi\n\nif [ ! -f \"AGENTS.md\" ]; then\n # AGENTS.md doesn't exist, create it\n echo \"$LINK\" > AGENTS.md\n echo \"SAFEWORD: Created AGENTS.md with safeword link\"\n exit 0\nfi\n\n# Check if link is present\nif ! grep -q \"@./.safeword/SAFEWORD.md\" AGENTS.md; then\n # Link missing, prepend it\n CONTENT=$(cat AGENTS.md)\n echo -e \"$LINK\\\\n\\\\n$CONTENT\" > AGENTS.md\n echo \"SAFEWORD: Restored AGENTS.md link (was removed)\"\nfi\n\nexit 0\n`;\n\nexport const HOOK_PRE_COMMIT = `#!/bin/bash\n# Safeword pre-commit hook\n# Runs architecture checks before commit\n\n# Run linting if available\nif [ -f \"package.json\" ] && grep -q '\"lint\"' package.json; then\n npm run lint --silent || exit 1\nfi\n\nexit 0\n`;\n\nexport const HOOK_POST_TOOL = `#!/bin/bash\n# Safeword post-tool hook\n# Placeholder for post-tool validations\nexit 0\n`;\n\nexport const SKILL_QUALITY_REVIEWER = `# Quality Reviewer Skill\n\nThis skill provides deep code quality review with web research capabilities.\n\n## Usage\n\nUse when user explicitly requests verification against latest docs or needs deeper analysis.\n\n## Capabilities\n\n- Fetches current documentation (WebFetch)\n- Checks latest versions (WebSearch)\n- Provides deep analysis (performance, security, alternatives)\n`;\n","/**\n * Configuration templates - ESLint config generation and hook settings\n */\n\nexport function getEslintConfig(options: {\n typescript?: boolean;\n react?: boolean;\n nextjs?: boolean;\n}): string {\n const imports: string[] = ['import js from \"@eslint/js\";'];\n const configs: string[] = ['js.configs.recommended'];\n\n if (options.typescript) {\n imports.push('import tseslint from \"typescript-eslint\";');\n configs.push('...tseslint.configs.recommended');\n }\n\n if (options.react || options.nextjs) {\n imports.push('import react from \"eslint-plugin-react\";');\n imports.push('import reactHooks from \"eslint-plugin-react-hooks\";');\n configs.push('react.configs.flat.recommended');\n configs.push('react.configs.flat[\"jsx-runtime\"]');\n configs.push(\n '{ plugins: { \"react-hooks\": reactHooks }, rules: reactHooks.configs.recommended.rules }',\n );\n }\n\n return `${imports.join('\\n')}\n\nexport default [\n ${configs.join(',\\n ')},\n {\n ignores: [\"node_modules/\", \"dist/\", \".next/\", \"build/\"],\n },\n];\n`;\n}\n\nexport const SETTINGS_HOOKS = {\n SessionStart: [\n {\n command: 'bash .safeword/hooks/agents-md-check.sh',\n description: 'Safeword: Verify AGENTS.md link',\n },\n ],\n PostToolUse: [\n {\n command: 'bash .safeword/hooks/post-tool.sh 2>/dev/null || true',\n description: 'Safeword: Post-tool validation',\n },\n ],\n};\n"],"mappings":";AAIO,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmCpB,IAAM,iBAAiB;AAEvB,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASnB,IAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6B1B,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYxB,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAMvB,IAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC7F/B,SAAS,gBAAgB,SAIrB;AACT,QAAM,UAAoB,CAAC,8BAA8B;AACzD,QAAM,UAAoB,CAAC,wBAAwB;AAEnD,MAAI,QAAQ,YAAY;AACtB,YAAQ,KAAK,2CAA2C;AACxD,YAAQ,KAAK,iCAAiC;AAAA,EAChD;AAEA,MAAI,QAAQ,SAAS,QAAQ,QAAQ;AACnC,YAAQ,KAAK,0CAA0C;AACvD,YAAQ,KAAK,qDAAqD;AAClE,YAAQ,KAAK,gCAAgC;AAC7C,YAAQ,KAAK,mCAAmC;AAChD,YAAQ;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAEA,SAAO,GAAG,QAAQ,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA,IAG1B,QAAQ,KAAK,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAMzB;AAEO,IAAM,iBAAiB;AAAA,EAC5B,cAAc;AAAA,IACZ;AAAA,MACE,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX;AAAA,MACE,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// src/utils/fs.ts
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
statSync,
|
|
10
|
+
chmodSync
|
|
11
|
+
} from "fs";
|
|
12
|
+
import { join, dirname } from "path";
|
|
13
|
+
function exists(path) {
|
|
14
|
+
return existsSync(path);
|
|
15
|
+
}
|
|
16
|
+
function ensureDir(path) {
|
|
17
|
+
if (!existsSync(path)) {
|
|
18
|
+
mkdirSync(path, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function readFile(path) {
|
|
22
|
+
return readFileSync(path, "utf-8");
|
|
23
|
+
}
|
|
24
|
+
function readFileSafe(path) {
|
|
25
|
+
if (!existsSync(path)) return null;
|
|
26
|
+
return readFileSync(path, "utf-8");
|
|
27
|
+
}
|
|
28
|
+
function writeFile(path, content) {
|
|
29
|
+
ensureDir(dirname(path));
|
|
30
|
+
writeFileSync(path, content);
|
|
31
|
+
}
|
|
32
|
+
function remove(path) {
|
|
33
|
+
if (existsSync(path)) {
|
|
34
|
+
rmSync(path, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function listDir(path) {
|
|
38
|
+
if (!existsSync(path)) return [];
|
|
39
|
+
return readdirSync(path);
|
|
40
|
+
}
|
|
41
|
+
function makeExecutable(path) {
|
|
42
|
+
chmodSync(path, 493);
|
|
43
|
+
}
|
|
44
|
+
function readJson(path) {
|
|
45
|
+
const content = readFileSafe(path);
|
|
46
|
+
if (!content) return null;
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(content);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function writeJson(path, data) {
|
|
54
|
+
writeFile(path, JSON.stringify(data, null, 2) + "\n");
|
|
55
|
+
}
|
|
56
|
+
function updateJson(path, updater) {
|
|
57
|
+
const existing = readJson(path);
|
|
58
|
+
const updated = updater(existing);
|
|
59
|
+
writeJson(path, updated);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/utils/output.ts
|
|
63
|
+
function info(message) {
|
|
64
|
+
console.log(message);
|
|
65
|
+
}
|
|
66
|
+
function success(message) {
|
|
67
|
+
console.log(`\u2713 ${message}`);
|
|
68
|
+
}
|
|
69
|
+
function warn(message) {
|
|
70
|
+
console.warn(`\u26A0 ${message}`);
|
|
71
|
+
}
|
|
72
|
+
function error(message) {
|
|
73
|
+
console.error(`\u2717 ${message}`);
|
|
74
|
+
}
|
|
75
|
+
function header(title) {
|
|
76
|
+
console.log(`
|
|
77
|
+
${title}`);
|
|
78
|
+
console.log("\u2500".repeat(title.length));
|
|
79
|
+
}
|
|
80
|
+
function listItem(item, indent = 2) {
|
|
81
|
+
console.log(`${" ".repeat(indent)}\u2022 ${item}`);
|
|
82
|
+
}
|
|
83
|
+
function keyValue(key, value) {
|
|
84
|
+
console.log(` ${key}: ${value}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
exists,
|
|
89
|
+
ensureDir,
|
|
90
|
+
readFile,
|
|
91
|
+
readFileSafe,
|
|
92
|
+
writeFile,
|
|
93
|
+
remove,
|
|
94
|
+
listDir,
|
|
95
|
+
makeExecutable,
|
|
96
|
+
readJson,
|
|
97
|
+
writeJson,
|
|
98
|
+
updateJson,
|
|
99
|
+
info,
|
|
100
|
+
success,
|
|
101
|
+
warn,
|
|
102
|
+
error,
|
|
103
|
+
header,
|
|
104
|
+
listItem,
|
|
105
|
+
keyValue
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=chunk-UQMQ64CB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/fs.ts","../src/utils/output.ts"],"sourcesContent":["/**\n * File system utilities for CLI operations\n */\n\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n rmSync,\n readdirSync,\n statSync,\n chmodSync,\n} from 'node:fs';\nimport { join, dirname } from 'node:path';\n\n/**\n * Check if a path exists\n */\nexport function exists(path: string): boolean {\n return existsSync(path);\n}\n\n/**\n * Check if path is a directory\n */\nexport function isDirectory(path: string): boolean {\n return existsSync(path) && statSync(path).isDirectory();\n}\n\n/**\n * Create directory recursively\n */\nexport function ensureDir(path: string): void {\n if (!existsSync(path)) {\n mkdirSync(path, { recursive: true });\n }\n}\n\n/**\n * Read file as string\n */\nexport function readFile(path: string): string {\n return readFileSync(path, 'utf-8');\n}\n\n/**\n * Read file as string, return null if not exists\n */\nexport function readFileSafe(path: string): string | null {\n if (!existsSync(path)) return null;\n return readFileSync(path, 'utf-8');\n}\n\n/**\n * Write file, creating parent directories if needed\n */\nexport function writeFile(path: string, content: string): void {\n ensureDir(dirname(path));\n writeFileSync(path, content);\n}\n\n/**\n * Remove file or directory recursively\n */\nexport function remove(path: string): void {\n if (existsSync(path)) {\n rmSync(path, { recursive: true, force: true });\n }\n}\n\n/**\n * List files in directory\n */\nexport function listDir(path: string): string[] {\n if (!existsSync(path)) return [];\n return readdirSync(path);\n}\n\n/**\n * Copy directory recursively\n */\nexport function copyDir(src: string, dest: string): void {\n ensureDir(dest);\n const entries = readdirSync(src, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = join(src, entry.name);\n const destPath = join(dest, entry.name);\n\n if (entry.isDirectory()) {\n copyDir(srcPath, destPath);\n } else {\n writeFileSync(destPath, readFileSync(srcPath));\n }\n }\n}\n\n/**\n * Make file executable\n */\nexport function makeExecutable(path: string): void {\n chmodSync(path, 0o755);\n}\n\n/**\n * Read JSON file\n */\nexport function readJson<T = unknown>(path: string): T | null {\n const content = readFileSafe(path);\n if (!content) return null;\n try {\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Write JSON file with formatting\n */\nexport function writeJson(path: string, data: unknown): void {\n writeFile(path, JSON.stringify(data, null, 2) + '\\n');\n}\n\n/**\n * Update JSON file, merging with existing content\n */\nexport function updateJson<T extends Record<string, unknown>>(\n path: string,\n updater: (existing: T | null) => T,\n): void {\n const existing = readJson<T>(path);\n const updated = updater(existing);\n writeJson(path, updated);\n}\n","/**\n * Console output utilities for consistent CLI messaging\n */\n\n/**\n * Print info message\n */\nexport function info(message: string): void {\n console.log(message);\n}\n\n/**\n * Print success message\n */\nexport function success(message: string): void {\n console.log(`✓ ${message}`);\n}\n\n/**\n * Print warning message\n */\nexport function warn(message: string): void {\n console.warn(`⚠ ${message}`);\n}\n\n/**\n * Print error message to stderr\n */\nexport function error(message: string): void {\n console.error(`✗ ${message}`);\n}\n\n/**\n * Print a blank line\n */\nexport function blank(): void {\n console.log('');\n}\n\n/**\n * Print a section header\n */\nexport function header(title: string): void {\n console.log(`\\n${title}`);\n console.log('─'.repeat(title.length));\n}\n\n/**\n * Print a list item\n */\nexport function listItem(item: string, indent = 2): void {\n console.log(`${' '.repeat(indent)}• ${item}`);\n}\n\n/**\n * Print key-value pair\n */\nexport function keyValue(key: string, value: string): void {\n console.log(` ${key}: ${value}`);\n}\n"],"mappings":";AAIA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,MAAM,eAAe;AAKvB,SAAS,OAAO,MAAuB;AAC5C,SAAO,WAAW,IAAI;AACxB;AAYO,SAAS,UAAU,MAAoB;AAC5C,MAAI,CAAC,WAAW,IAAI,GAAG;AACrB,cAAU,MAAM,EAAE,WAAW,KAAK,CAAC;AAAA,EACrC;AACF;AAKO,SAAS,SAAS,MAAsB;AAC7C,SAAO,aAAa,MAAM,OAAO;AACnC;AAKO,SAAS,aAAa,MAA6B;AACxD,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,SAAO,aAAa,MAAM,OAAO;AACnC;AAKO,SAAS,UAAU,MAAc,SAAuB;AAC7D,YAAU,QAAQ,IAAI,CAAC;AACvB,gBAAc,MAAM,OAAO;AAC7B;AAKO,SAAS,OAAO,MAAoB;AACzC,MAAI,WAAW,IAAI,GAAG;AACpB,WAAO,MAAM,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC/C;AACF;AAKO,SAAS,QAAQ,MAAwB;AAC9C,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO,CAAC;AAC/B,SAAO,YAAY,IAAI;AACzB;AAwBO,SAAS,eAAe,MAAoB;AACjD,YAAU,MAAM,GAAK;AACvB;AAKO,SAAS,SAAsB,MAAwB;AAC5D,QAAM,UAAU,aAAa,IAAI;AACjC,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACF,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,UAAU,MAAc,MAAqB;AAC3D,YAAU,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,IAAI;AACtD;AAKO,SAAS,WACd,MACA,SACM;AACN,QAAM,WAAW,SAAY,IAAI;AACjC,QAAM,UAAU,QAAQ,QAAQ;AAChC,YAAU,MAAM,OAAO;AACzB;;;AChIO,SAAS,KAAK,SAAuB;AAC1C,UAAQ,IAAI,OAAO;AACrB;AAKO,SAAS,QAAQ,SAAuB;AAC7C,UAAQ,IAAI,UAAK,OAAO,EAAE;AAC5B;AAKO,SAAS,KAAK,SAAuB;AAC1C,UAAQ,KAAK,UAAK,OAAO,EAAE;AAC7B;AAKO,SAAS,MAAM,SAAuB;AAC3C,UAAQ,MAAM,UAAK,OAAO,EAAE;AAC9B;AAYO,SAAS,OAAO,OAAqB;AAC1C,UAAQ,IAAI;AAAA,EAAK,KAAK,EAAE;AACxB,UAAQ,IAAI,SAAI,OAAO,MAAM,MAAM,CAAC;AACtC;AAKO,SAAS,SAAS,MAAc,SAAS,GAAS;AACvD,UAAQ,IAAI,GAAG,IAAI,OAAO,MAAM,CAAC,UAAK,IAAI,EAAE;AAC9C;AAKO,SAAS,SAAS,KAAa,OAAqB;AACzD,UAAQ,IAAI,KAAK,GAAG,KAAK,KAAK,EAAE;AAClC;","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/utils/version.ts
|
|
2
|
+
function compareVersions(a, b) {
|
|
3
|
+
const aParts = a.split(".").map(Number);
|
|
4
|
+
const bParts = b.split(".").map(Number);
|
|
5
|
+
for (let i = 0; i < 3; i++) {
|
|
6
|
+
const aVal = aParts[i] ?? 0;
|
|
7
|
+
const bVal = bParts[i] ?? 0;
|
|
8
|
+
if (aVal < bVal) return -1;
|
|
9
|
+
if (aVal > bVal) return 1;
|
|
10
|
+
}
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
function isNewerVersion(current, latest) {
|
|
14
|
+
return compareVersions(current, latest) === -1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
compareVersions,
|
|
19
|
+
isNewerVersion
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=chunk-W66Z3C5H.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/version.ts"],"sourcesContent":["/**\n * Version comparison utilities\n */\n\n/**\n * Compare two semver versions\n * @returns -1 if a < b, 0 if a == b, 1 if a > b\n */\nexport function compareVersions(a: string, b: string): -1 | 0 | 1 {\n const aParts = a.split('.').map(Number);\n const bParts = b.split('.').map(Number);\n\n for (let i = 0; i < 3; i++) {\n const aVal = aParts[i] ?? 0;\n const bVal = bParts[i] ?? 0;\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n }\n\n return 0;\n}\n\n/**\n * Check if latest version is newer than current\n */\nexport function isNewerVersion(current: string, latest: string): boolean {\n return compareVersions(current, latest) === -1;\n}\n"],"mappings":";AAQO,SAAS,gBAAgB,GAAW,GAAuB;AAChE,QAAM,SAAS,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;AACtC,QAAM,SAAS,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;AAEtC,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,OAAO,OAAO,CAAC,KAAK;AAC1B,UAAM,OAAO,OAAO,CAAC,KAAK;AAC1B,QAAI,OAAO,KAAM,QAAO;AACxB,QAAI,OAAO,KAAM,QAAO;AAAA,EAC1B;AAEA,SAAO;AACT;AAKO,SAAS,eAAe,SAAiB,QAAyB;AACvE,SAAO,gBAAgB,SAAS,MAAM,MAAM;AAC9C;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// Version is read from package.json at build time\n// This will be replaced by tsup or read dynamically\nexport const VERSION = '0.1.0';\n"],"mappings":";AAEO,IAAM,UAAU;","names":[]}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
VERSION
|
|
4
|
+
} from "./chunk-WWQ4YRZN.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
var program = new Command();
|
|
9
|
+
program.name("safeword").description("CLI for setting up and managing safeword development environments").version(VERSION);
|
|
10
|
+
program.command("setup").description("Set up safeword in the current project").option("-y, --yes", "Accept all defaults (non-interactive mode)").action(async (options) => {
|
|
11
|
+
const { setup } = await import("./setup-CLDCHROZ.js");
|
|
12
|
+
await setup(options);
|
|
13
|
+
});
|
|
14
|
+
program.command("check").description("Check project health and versions").option("--offline", "Skip remote version check").action(async (options) => {
|
|
15
|
+
const { check } = await import("./check-J6DFVBCE.js");
|
|
16
|
+
await check(options);
|
|
17
|
+
});
|
|
18
|
+
program.command("upgrade").description("Upgrade safeword configuration to latest version").action(async () => {
|
|
19
|
+
const { upgrade } = await import("./upgrade-DOKWRK7J.js");
|
|
20
|
+
await upgrade();
|
|
21
|
+
});
|
|
22
|
+
program.command("diff").description("Preview changes that would be made by upgrade").option("-v, --verbose", "Show full diff output").action(async (options) => {
|
|
23
|
+
const { diff } = await import("./diff-U4IELWRL.js");
|
|
24
|
+
await diff(options);
|
|
25
|
+
});
|
|
26
|
+
program.command("reset").description("Remove safeword configuration from project").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
|
|
27
|
+
const { reset } = await import("./reset-XETOHTCK.js");
|
|
28
|
+
await reset(options);
|
|
29
|
+
});
|
|
30
|
+
if (process.argv.length === 2) {
|
|
31
|
+
program.help();
|
|
32
|
+
}
|
|
33
|
+
program.parse();
|
|
34
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport { VERSION } from './version.js';\n\nconst program = new Command();\n\nprogram\n .name('safeword')\n .description('CLI for setting up and managing safeword development environments')\n .version(VERSION);\n\nprogram\n .command('setup')\n .description('Set up safeword in the current project')\n .option('-y, --yes', 'Accept all defaults (non-interactive mode)')\n .action(async options => {\n const { setup } = await import('./commands/setup.js');\n await setup(options);\n });\n\nprogram\n .command('check')\n .description('Check project health and versions')\n .option('--offline', 'Skip remote version check')\n .action(async options => {\n const { check } = await import('./commands/check.js');\n await check(options);\n });\n\nprogram\n .command('upgrade')\n .description('Upgrade safeword configuration to latest version')\n .action(async () => {\n const { upgrade } = await import('./commands/upgrade.js');\n await upgrade();\n });\n\nprogram\n .command('diff')\n .description('Preview changes that would be made by upgrade')\n .option('-v, --verbose', 'Show full diff output')\n .action(async options => {\n const { diff } = await import('./commands/diff.js');\n await diff(options);\n });\n\nprogram\n .command('reset')\n .description('Remove safeword configuration from project')\n .option('-y, --yes', 'Skip confirmation prompt')\n .action(async options => {\n const { reset } = await import('./commands/reset.js');\n await reset(options);\n });\n\n// Show help if no arguments provided\nif (process.argv.length === 2) {\n program.help();\n}\n\n// Parse arguments\nprogram.parse();\n"],"mappings":";;;;;;AAEA,SAAS,eAAe;AAGxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,UAAU,EACf,YAAY,mEAAmE,EAC/E,QAAQ,OAAO;AAElB,QACG,QAAQ,OAAO,EACf,YAAY,wCAAwC,EACpD,OAAO,aAAa,4CAA4C,EAChE,OAAO,OAAM,YAAW;AACvB,QAAM,EAAE,MAAM,IAAI,MAAM,OAAO,qBAAqB;AACpD,QAAM,MAAM,OAAO;AACrB,CAAC;AAEH,QACG,QAAQ,OAAO,EACf,YAAY,mCAAmC,EAC/C,OAAO,aAAa,2BAA2B,EAC/C,OAAO,OAAM,YAAW;AACvB,QAAM,EAAE,MAAM,IAAI,MAAM,OAAO,qBAAqB;AACpD,QAAM,MAAM,OAAO;AACrB,CAAC;AAEH,QACG,QAAQ,SAAS,EACjB,YAAY,kDAAkD,EAC9D,OAAO,YAAY;AAClB,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,uBAAuB;AACxD,QAAM,QAAQ;AAChB,CAAC;AAEH,QACG,QAAQ,MAAM,EACd,YAAY,+CAA+C,EAC3D,OAAO,iBAAiB,uBAAuB,EAC/C,OAAO,OAAM,YAAW;AACvB,QAAM,EAAE,KAAK,IAAI,MAAM,OAAO,oBAAoB;AAClD,QAAM,KAAK,OAAO;AACpB,CAAC;AAEH,QACG,QAAQ,OAAO,EACf,YAAY,4CAA4C,EACxD,OAAO,aAAa,0BAA0B,EAC9C,OAAO,OAAM,YAAW;AACvB,QAAM,EAAE,MAAM,IAAI,MAAM,OAAO,qBAAqB;AACpD,QAAM,MAAM,OAAO;AACrB,CAAC;AAGH,IAAI,QAAQ,KAAK,WAAW,GAAG;AAC7B,UAAQ,KAAK;AACf;AAGA,QAAQ,MAAM;","names":[]}
|