@curdx/flow 2.2.3 → 2.2.4

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/cli/uninstall.js CHANGED
@@ -2,48 +2,22 @@
2
2
  * uninstall command — remove curdx-flow plugin (and optionally recommended plugins / artifacts).
3
3
  */
4
4
 
5
- import { existsSync, lstatSync, unlinkSync, rmSync, readlinkSync } from "node:fs";
6
- import { join } from "node:path";
7
- import { homedir } from "node:os";
8
-
9
- import {
10
- color,
11
- log,
12
- resultLastLine,
13
- resultOutput,
14
- confirm,
15
- listPlugins,
16
- } from "./utils.js";
17
- import { REQUIRED_PLUGINS, RECOMMENDED_PLUGINS, BUNDLED_MCPS } from "./registry.js";
18
- import {
19
- removeMcp,
20
- removePluginMarketplace,
21
- uninstallPlugin,
22
- } from "./lib/claude-ops.js";
5
+ import { log } from "./utils.js";
23
6
  import {
24
7
  createUninstallContext,
25
8
  ensureClaudeCliAvailableForUninstall,
26
- getInstalledTargets,
27
- getManagedMarketplaceIds,
28
9
  printUninstallSummary,
29
10
  removeProtocolsStep,
30
- selectRecommendedPluginsToRemove,
31
- shouldKeepBundledMcps,
32
- shouldKeepRequiredPlugins,
33
- UNINSTALL_STEP_COUNT,
34
11
  confirmUninstallStep,
35
12
  } from "./uninstall-workflow.js";
36
-
37
- const HOME = homedir();
38
-
39
- const RECOMMENDED = RECOMMENDED_PLUGINS.map(toUninstallTarget);
40
- const REQUIRED = REQUIRED_PLUGINS.map(toUninstallTarget);
41
-
42
- // Symlinks created by install.js (only cleaned with --purge)
43
- const MANAGED_SYMLINKS = [
44
- join(HOME, ".local", "bin", "bun"),
45
- join(HOME, ".local", "bin", "uv"),
46
- ];
13
+ import {
14
+ maybePurgeRuntimeArtifacts,
15
+ maybeRemoveBundledMcps,
16
+ maybeRemoveProjectState,
17
+ maybeUninstallRecommendedPlugins,
18
+ maybeUninstallRequiredPlugins,
19
+ uninstallCurdxFlowPlugin,
20
+ } from "./uninstall-actions.js";
47
21
 
48
22
  export async function uninstall(args = []) {
49
23
  const context = createUninstallContext(args);
@@ -66,221 +40,3 @@ export async function uninstall(args = []) {
66
40
 
67
41
  printUninstallSummary(context);
68
42
  }
69
-
70
- async function uninstallCurdxFlowPlugin() {
71
- log.blank();
72
- log.step(1, UNINSTALL_STEP_COUNT, "Uninstalling curdx-flow plugin...");
73
- const curdx = listPlugins().find((plugin) => plugin.name === "curdx-flow");
74
- if (!curdx) {
75
- log.info("curdx-flow not installed, skipping");
76
- return;
77
- }
78
-
79
- const result = await uninstallPlugin({
80
- scope: "user",
81
- uninstallSpec: "curdx-flow@curdx-flow-marketplace",
82
- });
83
- if (result.code === 0) {
84
- log.ok("curdx-flow uninstalled");
85
- return;
86
- }
87
-
88
- log.err(`Uninstall failed: ${resultOutput(result)}`);
89
- }
90
-
91
- async function maybeUninstallRecommendedPlugins({ yes, keepRecommended }) {
92
- log.blank();
93
- log.step(2, UNINSTALL_STEP_COUNT, "Recommended plugins");
94
- if (keepRecommended) {
95
- log.info("Keeping recommended plugins (--keep-recommended)");
96
- return;
97
- }
98
-
99
- const present = getInstalledTargets(RECOMMENDED);
100
- if (present.length === 0) {
101
- log.info("No installed recommended plugins");
102
- return;
103
- }
104
-
105
- const selected = await selectRecommendedPluginsToRemove({ yes, present });
106
- for (const name of selected) {
107
- const entry = present.find((plugin) => plugin.name === name);
108
- if (!entry) continue;
109
- await uninstallNamedPlugin(entry);
110
- }
111
- }
112
-
113
- async function uninstallNamedPlugin(entry) {
114
- log.blank();
115
- console.log(` ${color.cyan("▸")} Uninstalling ${color.bold(entry.name)}...`);
116
- const result = await uninstallPlugin(entry);
117
- if (result.code === 0) {
118
- console.log(` ${color.green("✓")} ${entry.name} uninstalled`);
119
- return;
120
- }
121
-
122
- console.log(
123
- ` ${color.red("✗")} ${entry.name} uninstall failed: ${resultLastLine(result)}`
124
- );
125
- }
126
-
127
- async function maybeRemoveBundledMcps({ yes, keepRecommended }) {
128
- log.blank();
129
- log.info("Required MCP servers (context7, sequential-thinking)");
130
- if (shouldKeepBundledMcps({ yes, keepRecommended })) {
131
- log.info(
132
- color.dim("--yes or --keep-recommended: keeping user-level MCPs (remove manually with `claude mcp remove <name>`)")
133
- );
134
- return;
135
- }
136
-
137
- const removeMcps = await confirm(
138
- `Remove user-level MCPs registered by install (${BUNDLED_MCPS.map((mcp) => mcp.name).join(", ")})? ${color.dim("(keeps them if other tools depend on them)")}`,
139
- false
140
- );
141
- if (!removeMcps) {
142
- log.info("Keeping user-level MCPs");
143
- return;
144
- }
145
-
146
- for (const mcp of BUNDLED_MCPS) {
147
- const result = await removeMcp({ name: mcp.name });
148
- if (result.code === 0) {
149
- log.ok(` ${mcp.name.padEnd(22)} removed`);
150
- } else {
151
- log.info(` ${mcp.name.padEnd(22)} ${color.dim("not present or already removed")}`);
152
- }
153
- }
154
- }
155
-
156
- async function maybeUninstallRequiredPlugins({ yes }) {
157
- log.blank();
158
- log.info("Required companion plugins");
159
- if (shouldKeepRequiredPlugins({ yes })) {
160
- log.info(
161
- color.dim("--yes mode: keeping required companion plugins (use --purge to remove them)")
162
- );
163
- return;
164
- }
165
-
166
- const removeRequired = await confirm(
167
- `Remove required companion plugins (${REQUIRED.map((plugin) => plugin.name).join(", ")})? ${color.dim("(keeps shared tools available if other workflows depend on them)")}`,
168
- false
169
- );
170
- if (!removeRequired) {
171
- log.info("Keeping required companion plugins");
172
- return;
173
- }
174
-
175
- for (const plugin of REQUIRED) {
176
- const result = await uninstallPlugin(plugin);
177
- if (result.code === 0) {
178
- log.ok(` ${plugin.name.padEnd(22)} uninstalled`);
179
- } else {
180
- log.info(` ${plugin.name.padEnd(22)} ${color.dim("not present or already removed")}`);
181
- }
182
- }
183
- }
184
-
185
- async function maybePurgeRuntimeArtifacts({ purge }) {
186
- log.blank();
187
- log.step(3, UNINSTALL_STEP_COUNT, "Runtime symlinks and marketplaces");
188
- if (!purge) {
189
- log.info(
190
- color.dim("Keeping ~/.local/bin/bun, ~/.local/bin/uv (use --purge to remove)")
191
- );
192
- log.info(
193
- color.dim("Reason: these bun/uv binaries may be used by other tools — confirm before deleting")
194
- );
195
- return;
196
- }
197
-
198
- await purgeManagedMarketplaces();
199
- removeManagedSymlinks();
200
- }
201
-
202
- async function purgeManagedMarketplaces() {
203
- const marketplaceIds = getManagedMarketplaceIds(RECOMMENDED.concat(REQUIRED));
204
-
205
- for (const marketplaceId of marketplaceIds) {
206
- const result = await removePluginMarketplace(marketplaceId);
207
- if (result.code === 0) {
208
- log.ok(`Removed marketplace ${marketplaceId}`);
209
- } else if (!result.stderr.includes("not found")) {
210
- log.warn(`Failed to remove marketplace ${marketplaceId}: ${resultLastLine(result)}`);
211
- }
212
- }
213
- }
214
-
215
- function removeManagedSymlinks() {
216
- for (const link of MANAGED_SYMLINKS) {
217
- if (!existsSync(link) && !isBrokenSymlink(link)) {
218
- continue;
219
- }
220
- try {
221
- const stat = lstatSync(link);
222
- if (!stat.isSymbolicLink()) {
223
- log.warn(
224
- `${link} is not a symlink (likely a real file placed by the user), skipping`
225
- );
226
- continue;
227
- }
228
- const target = readlinkSync(link);
229
- unlinkSync(link);
230
- log.ok(`Removed symlink ${link} ${color.dim(`(was → ${target})`)}`);
231
- } catch (err) {
232
- log.warn(`Failed to remove ${link}: ${err.message}`);
233
- }
234
- }
235
- }
236
-
237
- async function maybeRemoveProjectState({ yes }) {
238
- log.blank();
239
- log.step(4, UNINSTALL_STEP_COUNT, "Project state directory");
240
- const flowDir = join(process.cwd(), ".flow");
241
- if (!existsSync(flowDir)) {
242
- log.info(".flow/ does not exist, skipping");
243
- return;
244
- }
245
-
246
- if (yes) {
247
- log.info(
248
- color.dim("--yes mode: keeping .flow/ (contains specs & decisions — confirm by hand before deleting)")
249
- );
250
- return;
251
- }
252
-
253
- const ok = await confirm(
254
- `${color.red("DANGER:")} delete the ${color.bold(".flow/")} directory of the current project? ${color.dim("(includes all specs / decisions, not recoverable)")}`,
255
- false
256
- );
257
- if (!ok) {
258
- log.info("Keeping .flow/");
259
- return;
260
- }
261
-
262
- try {
263
- rmSync(flowDir, { recursive: true, force: true });
264
- log.ok(`Removed ${flowDir}`);
265
- } catch (err) {
266
- log.err(`Removal failed: ${err.message}`);
267
- }
268
- }
269
-
270
- function toUninstallTarget(entry) {
271
- return {
272
- name: entry.name,
273
- uninstallSpec: entry.uninstallSpec,
274
- uninstallArgs: entry.uninstallArgs || [],
275
- marketplaceId: entry.marketplaceId,
276
- scope: entry.scope,
277
- };
278
- }
279
-
280
- function isBrokenSymlink(pathname) {
281
- try {
282
- return lstatSync(pathname).isSymbolicLink();
283
- } catch {
284
- return false;
285
- }
286
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "2.2.3",
3
+ "version": "2.2.4",
4
4
  "description": "CLI installer for CurdX-Flow — AI engineering workflow meta-framework for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {