@curdx/flow 2.0.0-beta.8 → 2.0.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +4 -20
- package/CHANGELOG.md +101 -0
- package/README.md +6 -3
- package/README.zh.md +18 -18
- package/bin/curdx-flow.js +30 -1
- package/cli/README.md +8 -6
- package/cli/doctor.js +95 -23
- package/cli/install.js +433 -59
- package/cli/protocols-body.md +21 -0
- package/cli/protocols.js +74 -36
- package/cli/registry.js +123 -0
- package/cli/uninstall.js +105 -14
- package/cli/upgrade.js +7 -11
- package/cli/utils.js +321 -61
- package/commands/implement.md +3 -3
- package/commands/init.md +14 -3
- package/commands/spec.md +1 -1
- package/commands/start.md +47 -17
- package/hooks/hooks.json +2 -14
- package/hooks/scripts/inject-karpathy.sh +8 -5
- package/hooks/scripts/quick-mode-guard.sh +12 -9
- package/hooks/scripts/session-start.sh +1 -1
- package/hooks/scripts/stop-watcher.sh +25 -15
- package/package.json +9 -4
- package/skills/brownfield-index/SKILL.md +62 -0
- package/skills/browser-qa/SKILL.md +50 -0
- package/skills/epic/SKILL.md +68 -0
- package/skills/security-audit/SKILL.md +50 -0
- package/skills/ui-sketch/SKILL.md +49 -0
- package/hooks/scripts/fail-tracker.sh +0 -31
package/cli/protocols.js
CHANGED
|
@@ -5,43 +5,41 @@
|
|
|
5
5
|
* and reversible (uninstall removes it cleanly without touching user content).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
readFileSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
existsSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
renameSync,
|
|
14
|
+
unlinkSync,
|
|
15
|
+
} from "node:fs";
|
|
9
16
|
import { join, dirname } from "node:path";
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
|
|
20
|
+
// Use os.homedir() instead of process.env.HOME — HOME can be empty inside
|
|
21
|
+
// non-login shells (CI containers, some spawned child envs), which would
|
|
22
|
+
// resolve GLOBAL_CLAUDE_MD to "/.claude/CLAUDE.md" (filesystem root) and
|
|
23
|
+
// cause mkdir/writeFileSync to fail with EACCES. homedir() falls back to
|
|
24
|
+
// the effective user's passwd entry on POSIX and USERPROFILE on Windows.
|
|
25
|
+
const HOME = homedir();
|
|
12
26
|
export const GLOBAL_CLAUDE_MD = join(HOME, ".claude", "CLAUDE.md");
|
|
13
27
|
|
|
14
28
|
const SENTINEL_BEGIN =
|
|
15
29
|
"<!-- BEGIN curdx-flow protocols (auto-managed; do not edit between sentinels) -->";
|
|
16
30
|
const SENTINEL_END = "<!-- END curdx-flow protocols -->";
|
|
17
31
|
|
|
18
|
-
// Protocol
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- **Conversational layer = Simplified Chinese**: chat replies, explanations and reasoning shown directly to the human in a conversation interface (e.g. Claude Code chat).
|
|
28
|
-
|
|
29
|
-
Rationale: English in the persistence/tool layer aligns with developer-tool industry norms (npm/git/cargo are all English) and keeps the codebase internationally collaborable. Chinese in the conversational layer matches the user's language preference. Mixing the two (e.g. Chinese commit messages, Chinese CLI log output) is a violation.
|
|
30
|
-
|
|
31
|
-
### Discovery & reasoning
|
|
32
|
-
- **Library / framework / API questions**: query \`context7\` MCP first. Do not rely on training memory.
|
|
33
|
-
- **Planning / design / architecture review / epic decomposition**: use \`sequential-thinking\` MCP with at least 5 thoughts.
|
|
34
|
-
- **Cross-session memory**: query \`claude-mem\` MCP at task start when available.
|
|
35
|
-
|
|
36
|
-
### Three red lines (inherited from pua)
|
|
37
|
-
1. **Closed loop**: claiming "done"? Provide evidence (build output / passing tests / curl result).
|
|
38
|
-
2. **Fact-driven**: before saying "probably an env issue", verify it. Unverified attribution = blame-shifting.
|
|
39
|
-
3. **Exhaust everything**: before saying "I cannot", complete the systematic 4-stage debugging.
|
|
40
|
-
|
|
41
|
-
> Source: curdx-flow CLI installer (\`curdx-flow install\`). Remove with: \`curdx-flow uninstall --purge\`.
|
|
42
|
-
`;
|
|
32
|
+
// Protocol body lives in a sibling markdown file so it keeps markdown tooling
|
|
33
|
+
// (preview, lint, prettier) and avoids backtick-escaping noise inside a JS
|
|
34
|
+
// template literal. The body itself is English — it's instructions for the
|
|
35
|
+
// model, not user-facing prose.
|
|
36
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
const PROTOCOL_BODY = readFileSync(
|
|
38
|
+
join(__dirname, "protocols-body.md"),
|
|
39
|
+
"utf-8"
|
|
40
|
+
).trim();
|
|
43
41
|
|
|
44
|
-
const FULL_BLOCK = `${SENTINEL_BEGIN}\n${PROTOCOL_BODY
|
|
42
|
+
const FULL_BLOCK = `${SENTINEL_BEGIN}\n${PROTOCOL_BODY}\n${SENTINEL_END}`;
|
|
45
43
|
|
|
46
44
|
/**
|
|
47
45
|
* Read existing CLAUDE.md content; return "" if missing.
|
|
@@ -53,16 +51,56 @@ function readGlobalMd() {
|
|
|
53
51
|
|
|
54
52
|
/**
|
|
55
53
|
* Locate the sentinel block in the content.
|
|
56
|
-
* Returns { start, end } indices into content,
|
|
54
|
+
* Returns { start, end } indices into content, `null` if neither sentinel is
|
|
55
|
+
* present, or throws if the block is corrupted (begin without matching end).
|
|
56
|
+
* The throw is intentional — previously the corrupted case silently returned
|
|
57
|
+
* null, so the next run would append a SECOND block, producing drift.
|
|
57
58
|
*/
|
|
58
59
|
function findBlock(content) {
|
|
59
60
|
const start = content.indexOf(SENTINEL_BEGIN);
|
|
60
|
-
if (start === -1)
|
|
61
|
+
if (start === -1) {
|
|
62
|
+
// Also check for a dangling END without BEGIN — that is also corrupted.
|
|
63
|
+
if (content.indexOf(SENTINEL_END) !== -1) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Corrupted protocol block in ${GLOBAL_CLAUDE_MD}: END sentinel found without BEGIN. ` +
|
|
66
|
+
`Manually inspect the file and remove the dangling END line, then re-run.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
61
71
|
const endIdx = content.indexOf(SENTINEL_END, start);
|
|
62
|
-
if (endIdx === -1)
|
|
72
|
+
if (endIdx === -1) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Corrupted protocol block in ${GLOBAL_CLAUDE_MD}: BEGIN sentinel found without END. ` +
|
|
75
|
+
`Manually remove the orphan BEGIN line (or restore the END), then re-run.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
63
78
|
return { start, end: endIdx + SENTINEL_END.length };
|
|
64
79
|
}
|
|
65
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Write `content` to `path` atomically: write to a sibling temp file first,
|
|
83
|
+
* then rename. This prevents a half-written CLAUDE.md if the process is
|
|
84
|
+
* interrupted mid-write, and avoids races between concurrent install /
|
|
85
|
+
* uninstall invocations.
|
|
86
|
+
*/
|
|
87
|
+
function atomicWrite(path, content) {
|
|
88
|
+
const tmp = `${path}.curdx-flow.tmp.${process.pid}`;
|
|
89
|
+
try {
|
|
90
|
+
writeFileSync(tmp, content, "utf-8");
|
|
91
|
+
renameSync(tmp, path);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
// Best-effort cleanup of the temp file; swallow errors here since we
|
|
94
|
+
// are already re-throwing the real failure.
|
|
95
|
+
try {
|
|
96
|
+
if (existsSync(tmp)) unlinkSync(tmp);
|
|
97
|
+
} catch {
|
|
98
|
+
// ignore
|
|
99
|
+
}
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
66
104
|
/**
|
|
67
105
|
* Inject (or upgrade) the protocol block in ~/.claude/CLAUDE.md.
|
|
68
106
|
* @returns {{action:"created"|"upgraded"|"unchanged", path:string}}
|
|
@@ -81,8 +119,8 @@ export function injectGlobalProtocols() {
|
|
|
81
119
|
? (existing.endsWith("\n") ? "\n" : "\n\n")
|
|
82
120
|
: "";
|
|
83
121
|
const next = existing + sep + FULL_BLOCK + "\n";
|
|
84
|
-
|
|
85
|
-
return { action: existing.length === 0 ? "created" : "
|
|
122
|
+
atomicWrite(path, next);
|
|
123
|
+
return { action: existing.length === 0 ? "created" : "appended", path };
|
|
86
124
|
}
|
|
87
125
|
|
|
88
126
|
// Replace existing block (handle upgrade-in-place)
|
|
@@ -92,7 +130,7 @@ export function injectGlobalProtocols() {
|
|
|
92
130
|
}
|
|
93
131
|
const next =
|
|
94
132
|
existing.slice(0, block.start) + FULL_BLOCK + existing.slice(block.end);
|
|
95
|
-
|
|
133
|
+
atomicWrite(path, next);
|
|
96
134
|
return { action: "upgraded", path };
|
|
97
135
|
}
|
|
98
136
|
|
|
@@ -119,6 +157,6 @@ export function removeGlobalProtocols() {
|
|
|
119
157
|
}
|
|
120
158
|
|
|
121
159
|
const next = existing.slice(0, start) + existing.slice(end);
|
|
122
|
-
|
|
160
|
+
atomicWrite(path, next);
|
|
123
161
|
return { action: "removed", path };
|
|
124
162
|
}
|
package/cli/registry.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for recommended companion plugins.
|
|
3
|
+
*
|
|
4
|
+
* Background: before this file existed, the list of recommended plugins lived
|
|
5
|
+
* in FOUR independent places (install.js, uninstall.js, upgrade.js,
|
|
6
|
+
* doctor.js). They drifted — chrome-devtools-mcp was added to install.js
|
|
7
|
+
* during the beta.8 MCP decoupling but forgotten in the other three,
|
|
8
|
+
* making it installable but uninstallable. This registry exists so adding
|
|
9
|
+
* or removing a plugin is a one-file change.
|
|
10
|
+
*
|
|
11
|
+
* Every consumer pulls what it needs via property access:
|
|
12
|
+
* - install.js → marketplaceSource + installSpec + hint (+ optional postInstall)
|
|
13
|
+
* - uninstall.js → uninstallSpec + uninstallArgs + marketplaceId
|
|
14
|
+
* - upgrade.js → updateSpec + marketplaceId
|
|
15
|
+
* - doctor.js → id + installSpec (for health checks and recovery hints)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const RECOMMENDED_PLUGINS = [
|
|
19
|
+
{
|
|
20
|
+
name: "pua",
|
|
21
|
+
id: "pua@pua-skills",
|
|
22
|
+
marketplaceSource: "tanweai/pua",
|
|
23
|
+
marketplaceId: "pua-skills",
|
|
24
|
+
installSpec: "pua@pua-skills",
|
|
25
|
+
uninstallSpec: "pua@pua-skills",
|
|
26
|
+
updateSpec: "pua@pua-skills",
|
|
27
|
+
scope: "user",
|
|
28
|
+
hint: "no-give-up + three red lines",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "claude-mem",
|
|
32
|
+
id: "claude-mem@thedotmack",
|
|
33
|
+
marketplaceSource: "thedotmack/claude-mem",
|
|
34
|
+
marketplaceId: "thedotmack",
|
|
35
|
+
installSpec: "claude-mem@thedotmack",
|
|
36
|
+
uninstallSpec: "claude-mem@thedotmack",
|
|
37
|
+
updateSpec: "claude-mem@thedotmack",
|
|
38
|
+
uninstallArgs: ["--keep-data"],
|
|
39
|
+
scope: "user",
|
|
40
|
+
hint: "automatic cross-session memory",
|
|
41
|
+
postInstall: "claude-mem-runtimes",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "frontend-design",
|
|
45
|
+
id: "frontend-design@claude-plugins-official",
|
|
46
|
+
marketplaceSource: "anthropics/claude-plugins-official",
|
|
47
|
+
marketplaceId: "claude-plugins-official",
|
|
48
|
+
installSpec: "frontend-design@claude-plugins-official",
|
|
49
|
+
uninstallSpec: "frontend-design@claude-plugins-official",
|
|
50
|
+
updateSpec: "frontend-design@claude-plugins-official",
|
|
51
|
+
scope: "user",
|
|
52
|
+
hint: "Anthropic official UI skill",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "chrome-devtools-mcp",
|
|
56
|
+
id: "chrome-devtools-mcp@chrome-devtools-plugins",
|
|
57
|
+
marketplaceSource: "ChromeDevTools/chrome-devtools-mcp",
|
|
58
|
+
marketplaceId: "chrome-devtools-plugins",
|
|
59
|
+
installSpec: "chrome-devtools-mcp@chrome-devtools-plugins",
|
|
60
|
+
uninstallSpec: "chrome-devtools-mcp@chrome-devtools-plugins",
|
|
61
|
+
updateSpec: "chrome-devtools-mcp@chrome-devtools-plugins",
|
|
62
|
+
scope: "user",
|
|
63
|
+
hint: "Chrome DevTools + Puppeteer (Google official)",
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
export const REQUIRED_PLUGINS = [
|
|
68
|
+
{
|
|
69
|
+
name: "context7-plugin",
|
|
70
|
+
id: "context7-plugin@context7-marketplace",
|
|
71
|
+
marketplaceSource: "upstash/context7",
|
|
72
|
+
marketplaceId: "context7-marketplace",
|
|
73
|
+
installSpec: "context7-plugin@context7-marketplace",
|
|
74
|
+
uninstallSpec: "context7-plugin@context7-marketplace",
|
|
75
|
+
updateSpec: "context7-plugin@context7-marketplace",
|
|
76
|
+
scope: "user",
|
|
77
|
+
hint: "official Context7 plugin (MCP + skill + docs-researcher agent + /context7:docs)",
|
|
78
|
+
requiresConfig: true,
|
|
79
|
+
configType: "apiKey",
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* MCP servers that curdx-flow depends on for its core discipline rules and
|
|
85
|
+
* still registers directly. Starting beta.12 these are registered at
|
|
86
|
+
* USER-LEVEL via `claude mcp add` instead of plugin.json bundling, so:
|
|
87
|
+
*
|
|
88
|
+
* - Tool names stay standard (mcp__sequential-thinking__*)
|
|
89
|
+
* — matching every agent's and knowledge doc's hardcoded references.
|
|
90
|
+
*
|
|
91
|
+
* Context7 is installed via Upstash's official Claude Code plugin instead
|
|
92
|
+
* of direct `claude mcp add`: context7-plugin@context7-marketplace includes
|
|
93
|
+
* the MCP server, skill, docs-researcher agent, and /context7:docs command.
|
|
94
|
+
*/
|
|
95
|
+
export const BUNDLED_MCPS = [
|
|
96
|
+
{
|
|
97
|
+
name: "sequential-thinking",
|
|
98
|
+
command: "npx",
|
|
99
|
+
args: ["-y", "@modelcontextprotocol/server-sequential-thinking"],
|
|
100
|
+
purpose: "structured reasoning for design / review (L2 Mandatory Tool)",
|
|
101
|
+
preserveExisting: true,
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Marketplaces to refresh during `upgrade`. Derived from RECOMMENDED_PLUGINS
|
|
107
|
+
* plus the curdx-flow marketplace itself.
|
|
108
|
+
*/
|
|
109
|
+
export const MARKETPLACES_TO_REFRESH = [
|
|
110
|
+
"curdx-flow-marketplace",
|
|
111
|
+
...REQUIRED_PLUGINS.map((p) => p.marketplaceId),
|
|
112
|
+
...RECOMMENDED_PLUGINS.map((p) => p.marketplaceId),
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Plugin install specs to update during `upgrade` — includes curdx-flow
|
|
117
|
+
* itself plus every recommended plugin.
|
|
118
|
+
*/
|
|
119
|
+
export const PLUGINS_TO_UPDATE = [
|
|
120
|
+
"curdx-flow@curdx-flow-marketplace",
|
|
121
|
+
...REQUIRED_PLUGINS.map((p) => p.updateSpec),
|
|
122
|
+
...RECOMMENDED_PLUGINS.map((p) => p.updateSpec),
|
|
123
|
+
];
|
package/cli/uninstall.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { existsSync, lstatSync, unlinkSync, rmSync, readlinkSync } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
7
8
|
|
|
8
9
|
import {
|
|
9
10
|
color,
|
|
@@ -15,18 +16,25 @@ import {
|
|
|
15
16
|
listPlugins,
|
|
16
17
|
} from "./utils.js";
|
|
17
18
|
import { removeGlobalProtocols, GLOBAL_CLAUDE_MD } from "./protocols.js";
|
|
19
|
+
import { REQUIRED_PLUGINS, RECOMMENDED_PLUGINS, BUNDLED_MCPS } from "./registry.js";
|
|
18
20
|
|
|
19
|
-
const HOME =
|
|
21
|
+
const HOME = homedir();
|
|
20
22
|
|
|
21
|
-
//
|
|
22
|
-
const RECOMMENDED =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
// Pull uninstall-relevant subset from the single registry. See registry.js.
|
|
24
|
+
const RECOMMENDED = RECOMMENDED_PLUGINS.map(({ name, uninstallSpec, uninstallArgs, marketplaceId, scope }) => ({
|
|
25
|
+
name,
|
|
26
|
+
uninstallSpec,
|
|
27
|
+
uninstallArgs: uninstallArgs || [],
|
|
28
|
+
marketplaceId,
|
|
29
|
+
scope,
|
|
30
|
+
}));
|
|
31
|
+
const REQUIRED = REQUIRED_PLUGINS.map(({ name, uninstallSpec, uninstallArgs, marketplaceId, scope }) => ({
|
|
32
|
+
name,
|
|
33
|
+
uninstallSpec,
|
|
34
|
+
uninstallArgs: uninstallArgs || [],
|
|
35
|
+
marketplaceId,
|
|
36
|
+
scope,
|
|
37
|
+
}));
|
|
30
38
|
|
|
31
39
|
// Symlinks created by install.js (only cleaned with --purge)
|
|
32
40
|
const MANAGED_SYMLINKS = [
|
|
@@ -70,7 +78,7 @@ export async function uninstall(args = []) {
|
|
|
70
78
|
} else {
|
|
71
79
|
const r = await run(
|
|
72
80
|
"claude",
|
|
73
|
-
["plugin", "uninstall", "curdx-flow@curdx-flow-marketplace"],
|
|
81
|
+
["plugin", "uninstall", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
|
|
74
82
|
{ silent: true }
|
|
75
83
|
);
|
|
76
84
|
if (r.code === 0) {
|
|
@@ -116,10 +124,10 @@ export async function uninstall(args = []) {
|
|
|
116
124
|
for (const name of toRemove) {
|
|
117
125
|
const rec = presentRecs.find((r) => r.name === name);
|
|
118
126
|
log.blank();
|
|
119
|
-
console.log(` ${color.cyan("
|
|
127
|
+
console.log(` ${color.cyan("▸")} Uninstalling ${color.bold(rec.name)}...`);
|
|
120
128
|
const r = await run(
|
|
121
129
|
"claude",
|
|
122
|
-
["plugin", "uninstall", rec.uninstallSpec],
|
|
130
|
+
["plugin", "uninstall", "--scope", rec.scope, ...rec.uninstallArgs, rec.uninstallSpec],
|
|
123
131
|
{ silent: true }
|
|
124
132
|
);
|
|
125
133
|
if (r.code === 0) {
|
|
@@ -133,9 +141,71 @@ export async function uninstall(args = []) {
|
|
|
133
141
|
}
|
|
134
142
|
}
|
|
135
143
|
|
|
144
|
+
// ---------- Step 4.5: optionally remove user-level MCPs ----------
|
|
145
|
+
// Starting beta.12, the install command registers context7 +
|
|
146
|
+
// sequential-thinking at user-level (not plugin-bundled). Ask before
|
|
147
|
+
// removing because the user may have customised args (e.g. --api-key)
|
|
148
|
+
// or still be using these MCPs outside curdx-flow.
|
|
149
|
+
log.blank();
|
|
150
|
+
log.info("Required MCP servers (context7, sequential-thinking)");
|
|
151
|
+
if (keepRecommended || yes) {
|
|
152
|
+
log.info(
|
|
153
|
+
color.dim("--yes or --keep-recommended: keeping user-level MCPs (remove manually with `claude mcp remove <name>`)")
|
|
154
|
+
);
|
|
155
|
+
} else {
|
|
156
|
+
const removeMcps = await confirm(
|
|
157
|
+
`Remove user-level MCPs registered by install (${BUNDLED_MCPS.map((m) => m.name).join(", ")})? ${color.dim("(keeps them if other tools depend on them)")}`,
|
|
158
|
+
false
|
|
159
|
+
);
|
|
160
|
+
if (removeMcps) {
|
|
161
|
+
for (const mcp of BUNDLED_MCPS) {
|
|
162
|
+
const r = await run("claude", ["mcp", "remove", mcp.name], {
|
|
163
|
+
silent: true,
|
|
164
|
+
});
|
|
165
|
+
if (r.code === 0) {
|
|
166
|
+
log.ok(` ${mcp.name.padEnd(22)} removed`);
|
|
167
|
+
} else {
|
|
168
|
+
log.info(` ${mcp.name.padEnd(22)} ${color.dim("not present or already removed")}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
log.info("Keeping user-level MCPs");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ---------- Step 4.75: uninstall required companion plugins ----------
|
|
177
|
+
log.blank();
|
|
178
|
+
log.info("Required companion plugins");
|
|
179
|
+
if (yes) {
|
|
180
|
+
log.info(
|
|
181
|
+
color.dim("--yes mode: keeping required companion plugins (use --purge to remove them)")
|
|
182
|
+
);
|
|
183
|
+
} else {
|
|
184
|
+
const removeRequired = await confirm(
|
|
185
|
+
`Remove required companion plugins (${REQUIRED.map((p) => p.name).join(", ")})? ${color.dim("(keeps shared tools available if other workflows depend on them)")}`,
|
|
186
|
+
false
|
|
187
|
+
);
|
|
188
|
+
if (removeRequired) {
|
|
189
|
+
for (const plugin of REQUIRED) {
|
|
190
|
+
const r = await run(
|
|
191
|
+
"claude",
|
|
192
|
+
["plugin", "uninstall", "--scope", plugin.scope, ...plugin.uninstallArgs, plugin.uninstallSpec],
|
|
193
|
+
{ silent: true }
|
|
194
|
+
);
|
|
195
|
+
if (r.code === 0) {
|
|
196
|
+
log.ok(` ${plugin.name.padEnd(22)} uninstalled`);
|
|
197
|
+
} else {
|
|
198
|
+
log.info(` ${plugin.name.padEnd(22)} ${color.dim("not present or already removed")}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
log.info("Keeping required companion plugins");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
136
206
|
// ---------- Step 5: cleanup symlinks (only with --purge) ----------
|
|
137
207
|
log.blank();
|
|
138
|
-
log.step(3, 4, "Runtime symlinks");
|
|
208
|
+
log.step(3, 4, "Runtime symlinks and marketplaces");
|
|
139
209
|
if (!purge) {
|
|
140
210
|
log.info(
|
|
141
211
|
color.dim("Keeping ~/.local/bin/bun, ~/.local/bin/uv (use --purge to remove)")
|
|
@@ -144,6 +214,27 @@ export async function uninstall(args = []) {
|
|
|
144
214
|
color.dim("Reason: these bun/uv binaries may be used by other tools — confirm before deleting")
|
|
145
215
|
);
|
|
146
216
|
} else {
|
|
217
|
+
const marketplaceIds = [
|
|
218
|
+
...new Set(
|
|
219
|
+
RECOMMENDED
|
|
220
|
+
.concat(REQUIRED)
|
|
221
|
+
.map((r) => r.marketplaceId)
|
|
222
|
+
.filter((id) => id && id !== "claude-plugins-official")
|
|
223
|
+
),
|
|
224
|
+
];
|
|
225
|
+
for (const marketplaceId of marketplaceIds) {
|
|
226
|
+
const r = await run(
|
|
227
|
+
"claude",
|
|
228
|
+
["plugin", "marketplace", "remove", marketplaceId],
|
|
229
|
+
{ silent: true }
|
|
230
|
+
);
|
|
231
|
+
if (r.code === 0) {
|
|
232
|
+
log.ok(`Removed marketplace ${marketplaceId}`);
|
|
233
|
+
} else if (!r.stderr.includes("not found")) {
|
|
234
|
+
log.warn(`Failed to remove marketplace ${marketplaceId}: ${r.stderr.trim().split("\n").pop()}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
147
238
|
for (const link of MANAGED_SYMLINKS) {
|
|
148
239
|
if (!existsSync(link) && !isBrokenSymlink(link)) {
|
|
149
240
|
continue;
|
package/cli/upgrade.js
CHANGED
|
@@ -3,13 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { color, log, run, listPlugins, claudeVersion } from "./utils.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"claude-mem@thedotmack",
|
|
11
|
-
"frontend-design@claude-plugins-official",
|
|
12
|
-
];
|
|
6
|
+
import {
|
|
7
|
+
PLUGINS_TO_UPDATE,
|
|
8
|
+
MARKETPLACES_TO_REFRESH,
|
|
9
|
+
} from "./registry.js";
|
|
13
10
|
|
|
14
11
|
export async function upgrade(args = []) {
|
|
15
12
|
log.title("⬆️ CurDX-Flow upgrade");
|
|
@@ -19,10 +16,9 @@ export async function upgrade(args = []) {
|
|
|
19
16
|
process.exit(1);
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
// Refresh marketplaces first
|
|
19
|
+
// Refresh marketplaces first (derived from cli/registry.js)
|
|
23
20
|
log.step(1, 2, "Refreshing marketplaces...");
|
|
24
|
-
const
|
|
25
|
-
for (const mp of marketplaces) {
|
|
21
|
+
for (const mp of MARKETPLACES_TO_REFRESH) {
|
|
26
22
|
const r = await run(
|
|
27
23
|
"claude",
|
|
28
24
|
["plugin", "marketplace", "update", mp],
|
|
@@ -51,7 +47,7 @@ export async function upgrade(args = []) {
|
|
|
51
47
|
continue;
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
const r = await run("claude", ["plugin", "update", spec], { silent: true });
|
|
50
|
+
const r = await run("claude", ["plugin", "update", "--scope", "user", spec], { silent: true });
|
|
55
51
|
if (r.code === 0) {
|
|
56
52
|
const updated = r.stdout.includes("updated from");
|
|
57
53
|
if (updated) {
|