great-cto 2.15.0 → 2.17.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/companion.js +142 -0
- package/dist/detect.js +1 -0
- package/dist/jurisdictions.js +1 -1
- package/dist/main.js +23 -0
- package/dist/settings.js +10 -2
- package/package.json +1 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Companion plugin installer — superpowers + beads.
|
|
3
|
+
*
|
|
4
|
+
* great_cto requires these two plugins to be present. This module installs
|
|
5
|
+
* them automatically during `great-cto init/install` so users get a working
|
|
6
|
+
* setup in one command without reading the "Requires:" line in the README.
|
|
7
|
+
*
|
|
8
|
+
* Install strategy:
|
|
9
|
+
* - Clone (depth=1) the latest semver tag from GitHub into
|
|
10
|
+
* ~/.claude/plugins/cache/local/<name>/<version>/
|
|
11
|
+
* - Enable <name>@local in ~/.claude/settings.json
|
|
12
|
+
* - Idempotent — skips if any version is already present in the cache dir
|
|
13
|
+
* - Best-effort — never fails the parent install; logs a human-friendly
|
|
14
|
+
* hint if git is unavailable or clone fails
|
|
15
|
+
*/
|
|
16
|
+
import { spawnSync, execFileSync } from "node:child_process";
|
|
17
|
+
import { existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
18
|
+
import { homedir } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { dim, success, log, warn } from "./ui.js";
|
|
21
|
+
import { enablePlugin } from "./settings.js";
|
|
22
|
+
export const COMPANION_PLUGINS = [
|
|
23
|
+
{
|
|
24
|
+
name: "superpowers",
|
|
25
|
+
pluginKey: "superpowers@local",
|
|
26
|
+
repoUrl: "https://github.com/obra/superpowers.git",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "beads",
|
|
30
|
+
pluginKey: "beads@local",
|
|
31
|
+
repoUrl: "https://github.com/steveyegge/beads.git",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
function getPluginCacheDir(name) {
|
|
35
|
+
return join(homedir(), ".claude", "plugins", "cache", "local", name);
|
|
36
|
+
}
|
|
37
|
+
/** Returns true if any version folder already exists in the cache dir. */
|
|
38
|
+
function isAlreadyInstalled(name) {
|
|
39
|
+
const base = getPluginCacheDir(name);
|
|
40
|
+
if (!existsSync(base))
|
|
41
|
+
return null;
|
|
42
|
+
try {
|
|
43
|
+
const versions = readdirSync(base).filter((v) => /\S/.test(v));
|
|
44
|
+
return versions.length > 0 ? versions[0] : null;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Detect the latest semver tag from a remote repo without cloning. */
|
|
51
|
+
function detectLatestTag(repoUrl) {
|
|
52
|
+
try {
|
|
53
|
+
const out = execFileSync("git", ["ls-remote", "--tags", repoUrl], {
|
|
54
|
+
encoding: "utf-8",
|
|
55
|
+
timeout: 15_000,
|
|
56
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
57
|
+
});
|
|
58
|
+
const tags = out
|
|
59
|
+
.split("\n")
|
|
60
|
+
.map((line) => line.match(/refs\/tags\/v?([0-9]+\.[0-9]+\.[0-9]+)(?!\^)/)?.[1])
|
|
61
|
+
.filter((t) => !!t)
|
|
62
|
+
.sort((a, b) => {
|
|
63
|
+
const pa = a.split(".").map(Number);
|
|
64
|
+
const pb = b.split(".").map(Number);
|
|
65
|
+
for (let i = 0; i < 3; i++) {
|
|
66
|
+
const d = (pb[i] ?? 0) - (pa[i] ?? 0);
|
|
67
|
+
if (d !== 0)
|
|
68
|
+
return d;
|
|
69
|
+
}
|
|
70
|
+
return 0;
|
|
71
|
+
});
|
|
72
|
+
return tags[0] ?? null;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Install a single companion plugin.
|
|
80
|
+
* Silent no-op if already present. Best-effort on failure.
|
|
81
|
+
*/
|
|
82
|
+
export function installCompanionPlugin(plugin) {
|
|
83
|
+
const { name, pluginKey, repoUrl } = plugin;
|
|
84
|
+
// ── already installed? ─────────────────────────────────────────────────
|
|
85
|
+
const existing = isAlreadyInstalled(name);
|
|
86
|
+
if (existing) {
|
|
87
|
+
// Make sure it's enabled in settings even if it was manually placed
|
|
88
|
+
enablePlugin(pluginKey);
|
|
89
|
+
return { name, status: "already_present", version: existing };
|
|
90
|
+
}
|
|
91
|
+
// ── git available? ─────────────────────────────────────────────────────
|
|
92
|
+
try {
|
|
93
|
+
execFileSync("git", ["--version"], { stdio: "ignore", timeout: 5_000 });
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
const msg = `git not found — install git, then run: npx great-cto install`;
|
|
97
|
+
warn(`${name}: ${msg}`);
|
|
98
|
+
return { name, status: "skipped", version: "—", reason: msg };
|
|
99
|
+
}
|
|
100
|
+
// ── resolve version ────────────────────────────────────────────────────
|
|
101
|
+
const tag = detectLatestTag(repoUrl);
|
|
102
|
+
const version = tag ?? "main";
|
|
103
|
+
const ref = tag ? `v${tag}` : "main";
|
|
104
|
+
const destDir = join(getPluginCacheDir(name), version);
|
|
105
|
+
mkdirSync(getPluginCacheDir(name), { recursive: true });
|
|
106
|
+
log(dim(` installing ${name} ${version}…`));
|
|
107
|
+
// ── clone ──────────────────────────────────────────────────────────────
|
|
108
|
+
const cloneArgs = tag
|
|
109
|
+
? ["clone", "--depth=1", "--branch", ref, repoUrl, destDir]
|
|
110
|
+
: ["clone", "--depth=1", repoUrl, destDir];
|
|
111
|
+
const result = spawnSync("git", cloneArgs, {
|
|
112
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
113
|
+
timeout: 60_000,
|
|
114
|
+
});
|
|
115
|
+
if (result.status !== 0) {
|
|
116
|
+
const stderr = (result.stderr?.toString() ?? "").slice(0, 200);
|
|
117
|
+
const msg = `clone failed: ${stderr}`;
|
|
118
|
+
warn(`${name}: ${msg}`);
|
|
119
|
+
warn(` install manually: claude plugin install github.com/${repoUrl.replace("https://github.com/", "").replace(".git", "")}`);
|
|
120
|
+
return { name, status: "skipped", version: "—", reason: msg };
|
|
121
|
+
}
|
|
122
|
+
// ── enable in settings ─────────────────────────────────────────────────
|
|
123
|
+
enablePlugin(pluginKey);
|
|
124
|
+
success(`${name} ${version} installed`);
|
|
125
|
+
return { name, status: "installed", version };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Install all companion plugins declared in COMPANION_PLUGINS.
|
|
129
|
+
* Returns a summary array. Never throws — best-effort for all.
|
|
130
|
+
*/
|
|
131
|
+
export function installAllCompanions() {
|
|
132
|
+
return COMPANION_PLUGINS.map((plugin) => {
|
|
133
|
+
try {
|
|
134
|
+
return installCompanionPlugin(plugin);
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
138
|
+
warn(`${plugin.name}: unexpected error — ${msg}`);
|
|
139
|
+
return { name: plugin.name, status: "skipped", version: "—", reason: msg };
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
package/dist/detect.js
CHANGED
|
@@ -1185,6 +1185,7 @@ function mineReadmeKeywords(dir) {
|
|
|
1185
1185
|
"ccpa", "cpra", "california consumer privacy",
|
|
1186
1186
|
"california privacy rights", "cppa", "california residents",
|
|
1187
1187
|
"california users", "do not sell", "opt-out of sale",
|
|
1188
|
+
"data subject rights california", "dsr california",
|
|
1188
1189
|
// UK
|
|
1189
1190
|
"uk gdpr", "information commissioner", "dpa 2018",
|
|
1190
1191
|
"uk users", "uk customers", "united kingdom", "british users",
|
package/dist/jurisdictions.js
CHANGED
|
@@ -44,7 +44,7 @@ export const JURISDICTION_SIGNALS = {
|
|
|
44
44
|
"eu": {
|
|
45
45
|
keywords: [
|
|
46
46
|
// Legal / regulatory terms
|
|
47
|
-
"gdpr", "dsgvo", "rgpd", "data protection officer",
|
|
47
|
+
"gdpr", "dsgvo", "rgpd", "data protection officer", "dpo",
|
|
48
48
|
"right to erasure", "right to be forgotten", "data subject request",
|
|
49
49
|
"article 6", "article 9", "legitimate interest", "lawful basis",
|
|
50
50
|
"privacy by design", "privacy notice", "cookie consent",
|
package/dist/main.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// 4. confirm with user (unless -y)
|
|
8
8
|
// 5. install plugin (git clone)
|
|
9
9
|
// 6. enable in ~/.claude/settings.json
|
|
10
|
+
// 6b. install companion plugins (superpowers + beads)
|
|
10
11
|
// 7. bootstrap .great_cto/PROJECT.md
|
|
11
12
|
// 8. print next steps
|
|
12
13
|
import { resolve } from "node:path";
|
|
@@ -15,6 +16,7 @@ import { detect } from "./detect.js";
|
|
|
15
16
|
import { pickArchetype, suggestCompliance } from "./archetypes.js";
|
|
16
17
|
import { install, findInstalledVersions } from "./installer.js";
|
|
17
18
|
import { enableGreatCto } from "./settings.js";
|
|
19
|
+
import { installAllCompanions } from "./companion.js";
|
|
18
20
|
import { bootstrap } from "./bootstrap.js";
|
|
19
21
|
import { shouldUseLlmFallback, suggestArchetypeFromLlm } from "./llm-fallback.js";
|
|
20
22
|
import { readFileSync, copyFileSync, chmodSync, existsSync as fsExistsSync } from "node:fs";
|
|
@@ -659,6 +661,27 @@ async function runInit(args) {
|
|
|
659
661
|
}
|
|
660
662
|
catch { /* best-effort — don't block install */ }
|
|
661
663
|
}
|
|
664
|
+
// ── 4b-companion. install superpowers + beads ────────────
|
|
665
|
+
// These are required companion plugins. Auto-install so users don't need
|
|
666
|
+
// a separate manual step after reading "Requires:" in the README.
|
|
667
|
+
// Idempotent — silently skips if already present.
|
|
668
|
+
{
|
|
669
|
+
log("");
|
|
670
|
+
step(4, 5, "installing companion plugins (superpowers + beads)");
|
|
671
|
+
const companions = installAllCompanions();
|
|
672
|
+
for (const r of companions) {
|
|
673
|
+
if (r.status === "installed") {
|
|
674
|
+
log(` ${dim(`${r.name} ${r.version} installed`)}`);
|
|
675
|
+
}
|
|
676
|
+
else if (r.status === "already_present") {
|
|
677
|
+
log(` ${dim(`${r.name} ${r.version} already installed`)}`);
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
warn(` ${r.name} skipped — ${r.reason ?? "unknown reason"}`);
|
|
681
|
+
warn(` install manually: claude plugin install github.com/obra/${r.name === "superpowers" ? "superpowers" : ""} or github.com/steveyegge/beads`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
662
685
|
// ── 4c. bootstrap skills catalog (v1.0.140+) ─────────────
|
|
663
686
|
// Clone external skill repos + run skill-discover.sh so agents have
|
|
664
687
|
// the catalog locally from session 1, not after first SessionStart hook.
|
package/dist/settings.js
CHANGED
|
@@ -7,9 +7,13 @@ import { dim, success, warn } from "./ui.js";
|
|
|
7
7
|
export function getSettingsPath() {
|
|
8
8
|
return join(homedir(), ".claude", "settings.json");
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Enable a plugin key in ~/.claude/settings.json.
|
|
12
|
+
* Idempotent — no-op if the key is already present.
|
|
13
|
+
* Takes an optional backup of the existing file before writing.
|
|
14
|
+
*/
|
|
15
|
+
export function enablePlugin(pluginKey) {
|
|
11
16
|
const path = getSettingsPath();
|
|
12
|
-
const pluginKey = "great_cto@local";
|
|
13
17
|
const backupPath = existsSync(path) ? `${path}.bak-${Date.now()}` : null;
|
|
14
18
|
mkdirSync(dirname(path), { recursive: true });
|
|
15
19
|
// Read existing
|
|
@@ -53,3 +57,7 @@ export function enableGreatCto() {
|
|
|
53
57
|
}
|
|
54
58
|
return { settingsPath: path, enabled: true, alreadyEnabled: false, backupPath };
|
|
55
59
|
}
|
|
60
|
+
/** Convenience alias — kept for backward compatibility with existing callers. */
|
|
61
|
+
export function enableGreatCto() {
|
|
62
|
+
return enablePlugin("great_cto@local");
|
|
63
|
+
}
|
package/package.json
CHANGED