@curdx/flow 3.3.0 → 3.3.2

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/index.mjs +245 -178
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to `@curdx/flow` are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/) and the project follows [Semantic Versioning](https://semver.org/).
4
4
 
5
+ ## 3.3.2 — 2026-04-27
6
+
7
+ ### Fixed
8
+
9
+ - **CLAUDE.md sync no longer skipped on the "nothing selected" path** — when a user upgraded flow with all tools already installed and ran `install`, the multiselect would show nothing pre-checked; pressing enter without a selection caused the flow to early-return before reaching the sync step, so the managed block was never added to CLAUDE.md. Each of `install` / `update` / `uninstall` now wraps its body in `try / finally` and runs the sync at the end of any non-cancelled exit (including "nothing to do" paths). User-cancelled flows (Ctrl+C, multiselect cancel, uninstall confirm "no") still skip the sync to respect intent.
10
+
11
+ ## 3.3.1 — 2026-04-27
12
+
13
+ ### Fixed
14
+
15
+ - **Silent stalls between phases** — added spinners to the previously-silent windows where flow shells out to `claude plugin list --json` and `claude mcp list` (the latter performs an MCP server health check and can take 5-15 seconds). Affected sites: `install` (state-derivation between marketplace refresh and the multiselect), `update` and `uninstall` (state-derivation at flow entry), and the post-flow CLAUDE.md sync (after install/update/uninstall busts the cache, sync re-queries state). Each now shows `Checking installed state… (claude plugin list / mcp list)` with a result line so the run no longer feels frozen.
16
+ - **CLAUDE.md sync feedback** — replaced the post-hoc `p.log.info` line with a live spinner that converts to a final status line on completion, matching the marketplace-refresh and per-item install UX.
17
+
5
18
  ## 3.3.0 — 2026-04-27
6
19
 
7
20
  ### Added
package/dist/index.mjs CHANGED
@@ -67,6 +67,9 @@ var messages = {
67
67
  "chrome.prereqChrome": "\u9700\u8981\u672C\u673A\u5DF2\u5B89\u88C5 Chrome\uFF08chrome-devtools-mcp \u4F1A\u8C03\u7528\u672C\u5730\u6D4F\u89C8\u5668\uFF09",
68
68
  "reinstall.uninstalling": "\u5148\u5378\u8F7D\u65E7\u7248\u672C\u2026",
69
69
  "reinstall.installing": "\u5B89\u88C5\u65B0\u7248\u672C\u2026",
70
+ "state.checking": "\u68C0\u67E5\u5DF2\u5B89\u88C5\u72B6\u6001\u2026\uFF08claude plugin list / mcp list\uFF09",
71
+ "state.checked": "\u5DF2\u68C0\u67E5 {count} \u9879",
72
+ "claudeMd.syncing": "\u540C\u6B65 ~/.claude/CLAUDE.md \u2026",
70
73
  "claudeMd.synced": "CLAUDE.md \u5DF2\u66F4\u65B0\uFF08{path}\uFF09",
71
74
  "claudeMd.unchanged": "CLAUDE.md \u5DF2\u662F\u6700\u65B0",
72
75
  "claudeMd.removed": "\u5DF2\u4ECE CLAUDE.md \u79FB\u9664 @curdx/flow \u533A\u5757",
@@ -135,6 +138,9 @@ var messages2 = {
135
138
  "chrome.prereqChrome": "Requires Chrome installed locally (chrome-devtools-mcp drives the local browser)",
136
139
  "reinstall.uninstalling": "Uninstalling old version\u2026",
137
140
  "reinstall.installing": "Installing new version\u2026",
141
+ "state.checking": "Checking installed state\u2026 (claude plugin list / mcp list)",
142
+ "state.checked": "Checked {count} item(s)",
143
+ "claudeMd.syncing": "Syncing ~/.claude/CLAUDE.md\u2026",
138
144
  "claudeMd.synced": "CLAUDE.md updated ({path})",
139
145
  "claudeMd.unchanged": "CLAUDE.md already up to date",
140
146
  "claudeMd.removed": "Removed @curdx/flow block from CLAUDE.md",
@@ -769,23 +775,29 @@ async function syncClaudeMd(opts) {
769
775
  }
770
776
  }
771
777
  async function syncFromState(opts) {
772
- const r = await syncClaudeMd(opts);
778
+ if (opts?.skip) {
779
+ p3.log.info(t("claudeMd.skipped"));
780
+ return;
781
+ }
782
+ const sp = p3.spinner();
783
+ sp.start(t("claudeMd.syncing"));
784
+ const r = await syncClaudeMd();
773
785
  switch (r.status) {
774
786
  case "skipped":
775
- p3.log.info(t("claudeMd.skipped"));
787
+ sp.stop(t("claudeMd.skipped"));
776
788
  return;
777
789
  case "unchanged":
778
- p3.log.info(t("claudeMd.unchanged"));
790
+ sp.stop(t("claudeMd.unchanged"));
779
791
  return;
780
792
  case "created":
781
793
  case "updated":
782
- p3.log.info(t("claudeMd.synced", { path: r.path }));
794
+ sp.stop(t("claudeMd.synced", { path: r.path }));
783
795
  return;
784
796
  case "removed":
785
- p3.log.info(t("claudeMd.removed"));
797
+ sp.stop(t("claudeMd.removed"));
786
798
  return;
787
799
  case "failed":
788
- p3.log.warn(t("claudeMd.failed", { error: r.error ?? "unknown" }));
800
+ sp.stop(t("claudeMd.failed", { error: r.error ?? "unknown" }));
789
801
  return;
790
802
  }
791
803
  }
@@ -940,41 +952,55 @@ async function maybeRefreshMarketplaces(opts) {
940
952
  );
941
953
  }
942
954
  async function installFlow(opts = {}) {
943
- await maybeRefreshMarketplaces(opts);
944
- const explicit = opts.all || opts.ids && opts.ids.length > 0;
945
- const candidates = explicit ? selectFromIds(opts) : [...PKGS];
946
- if (candidates.length === 0) {
947
- p4.log.info(t("install.nothingSelected"));
948
- return;
949
- }
950
- const stateMap = /* @__PURE__ */ new Map();
951
- await Promise.all(
952
- candidates.map(async (pkg) => {
953
- stateMap.set(pkg.id, await deriveState(pkg));
954
- })
955
- );
956
- let targets;
957
- if (explicit) {
958
- targets = candidates;
959
- } else {
960
- const picked = await selectInteractive(stateMap);
961
- if (picked === null) {
962
- p4.cancel(t("app.cancelled"));
955
+ let userCancelled = false;
956
+ try {
957
+ await maybeRefreshMarketplaces(opts);
958
+ const explicit = opts.all || opts.ids && opts.ids.length > 0;
959
+ const candidates = explicit ? selectFromIds(opts) : [...PKGS];
960
+ if (candidates.length === 0) {
961
+ p4.log.info(t("install.nothingSelected"));
963
962
  return;
964
963
  }
965
- targets = picked;
966
- }
967
- if (targets.length === 0) {
968
- p4.log.info(t("install.nothingSelected"));
969
- return;
970
- }
971
- const results = [];
972
- for (const pkg of targets) {
973
- const state = stateMap.get(pkg.id) ?? { kind: "not_installed" };
974
- results.push(await runOne(pkg, state, opts));
964
+ const stateMap = /* @__PURE__ */ new Map();
965
+ const sp = p4.spinner();
966
+ sp.start(t("state.checking"));
967
+ try {
968
+ await Promise.all([listPlugins(), listMcp()]);
969
+ await Promise.all(
970
+ candidates.map(async (pkg) => {
971
+ stateMap.set(pkg.id, await deriveState(pkg));
972
+ })
973
+ );
974
+ } finally {
975
+ sp.stop(t("state.checked", { count: candidates.length }));
976
+ }
977
+ let targets;
978
+ if (explicit) {
979
+ targets = candidates;
980
+ } else {
981
+ const picked = await selectInteractive(stateMap);
982
+ if (picked === null) {
983
+ userCancelled = true;
984
+ p4.cancel(t("app.cancelled"));
985
+ return;
986
+ }
987
+ targets = picked;
988
+ }
989
+ if (targets.length === 0) {
990
+ p4.log.info(t("install.nothingSelected"));
991
+ return;
992
+ }
993
+ const results = [];
994
+ for (const pkg of targets) {
995
+ const state = stateMap.get(pkg.id) ?? { kind: "not_installed" };
996
+ results.push(await runOne(pkg, state, opts));
997
+ }
998
+ summarize(results);
999
+ } finally {
1000
+ if (!userCancelled) {
1001
+ await syncFromState({ skip: opts.noClaudeMd });
1002
+ }
975
1003
  }
976
- summarize(results);
977
- await syncFromState({ skip: opts.noClaudeMd });
978
1004
  }
979
1005
 
980
1006
  // src/flows/uninstall.ts
@@ -984,81 +1010,102 @@ async function getInstalled() {
984
1010
  const states = await Promise.all(PKGS.map(async (pkg) => ({ pkg, installed: await pkg.isInstalled() })));
985
1011
  return states.filter((s) => s.installed).map((s) => s.pkg);
986
1012
  }
1013
+ async function probeInstalled() {
1014
+ const sp = p5.spinner();
1015
+ sp.start(t("state.checking"));
1016
+ try {
1017
+ await Promise.all([listPlugins(), listMcp()]);
1018
+ const installed = await getInstalled();
1019
+ sp.stop(t("state.checked", { count: installed.length }));
1020
+ return installed;
1021
+ } catch (err) {
1022
+ sp.stop(t("state.checked", { count: 0 }));
1023
+ throw err;
1024
+ }
1025
+ }
987
1026
  async function uninstallFlow(opts = {}) {
988
- const installed = await getInstalled();
989
- let targets;
990
- if (opts.ids && opts.ids.length > 0) {
991
- targets = [];
992
- for (const id of opts.ids) {
993
- const pkg = findPkg(id);
994
- if (!pkg) {
995
- p5.log.warn(`Unknown id: ${id}`);
996
- continue;
1027
+ let userCancelled = false;
1028
+ try {
1029
+ const installed = await probeInstalled();
1030
+ let targets;
1031
+ if (opts.ids && opts.ids.length > 0) {
1032
+ targets = [];
1033
+ for (const id of opts.ids) {
1034
+ const pkg = findPkg(id);
1035
+ if (!pkg) {
1036
+ p5.log.warn(`Unknown id: ${id}`);
1037
+ continue;
1038
+ }
1039
+ if (!installed.some((x2) => x2.id === pkg.id)) {
1040
+ p5.log.warn(`${pkg.name}: ${t("pkg.notInstalled")}`);
1041
+ continue;
1042
+ }
1043
+ targets.push(pkg);
997
1044
  }
998
- if (!installed.some((x2) => x2.id === pkg.id)) {
999
- p5.log.warn(`${pkg.name}: ${t("pkg.notInstalled")}`);
1000
- continue;
1045
+ } else {
1046
+ if (installed.length === 0) {
1047
+ p5.log.info(t("uninstall.noneInstalled"));
1048
+ return;
1001
1049
  }
1002
- targets.push(pkg);
1003
- }
1004
- } else {
1005
- if (installed.length === 0) {
1006
- p5.log.info(t("uninstall.noneInstalled"));
1007
- return;
1050
+ const picked = await p5.multiselect({
1051
+ message: t("uninstall.selectPrompt"),
1052
+ options: installed.map((pkg) => ({
1053
+ value: pkg.id,
1054
+ label: `${pkg.name} ${pc2.dim(`(${pkg.type})`)}`,
1055
+ hint: pkg.description
1056
+ })),
1057
+ required: false
1058
+ });
1059
+ if (p5.isCancel(picked)) {
1060
+ userCancelled = true;
1061
+ p5.cancel(t("app.cancelled"));
1062
+ return;
1063
+ }
1064
+ targets = picked.map((id) => findPkg(id)).filter((x2) => Boolean(x2));
1008
1065
  }
1009
- const picked = await p5.multiselect({
1010
- message: t("uninstall.selectPrompt"),
1011
- options: installed.map((pkg) => ({
1012
- value: pkg.id,
1013
- label: `${pkg.name} ${pc2.dim(`(${pkg.type})`)}`,
1014
- hint: pkg.description
1015
- })),
1016
- required: false
1017
- });
1018
- if (p5.isCancel(picked)) {
1019
- p5.cancel(t("app.cancelled"));
1066
+ if (targets.length === 0) {
1067
+ p5.log.info(t("install.nothingSelected"));
1020
1068
  return;
1021
1069
  }
1022
- targets = picked.map((id) => findPkg(id)).filter((x2) => Boolean(x2));
1023
- }
1024
- if (targets.length === 0) {
1025
- p5.log.info(t("install.nothingSelected"));
1026
- return;
1027
- }
1028
- if (!opts.yes) {
1029
- const ok2 = await p5.confirm({
1030
- message: t("uninstall.confirm", { count: targets.length }),
1031
- initialValue: false
1032
- });
1033
- if (p5.isCancel(ok2) || ok2 === false) {
1034
- p5.cancel(t("app.cancelled"));
1035
- return;
1070
+ if (!opts.yes) {
1071
+ const ok2 = await p5.confirm({
1072
+ message: t("uninstall.confirm", { count: targets.length }),
1073
+ initialValue: false
1074
+ });
1075
+ if (p5.isCancel(ok2) || ok2 === false) {
1076
+ userCancelled = true;
1077
+ p5.cancel(t("app.cancelled"));
1078
+ return;
1079
+ }
1036
1080
  }
1037
- }
1038
- const results = [];
1039
- for (const pkg of targets) {
1040
- const log5 = p5.taskLog({ title: t("uninstall.starting", { name: pkg.name }) });
1041
- try {
1042
- await pkg.uninstall({ log: log5, config: {}, t });
1043
- log5.success(t("uninstall.success", { name: pkg.name }));
1044
- results.push({ id: pkg.id, status: "ok" });
1045
- } catch (err) {
1046
- const msg = err instanceof Error ? err.message : String(err);
1047
- log5.error(`${t("uninstall.failed", { name: pkg.name })}
1081
+ const results = [];
1082
+ for (const pkg of targets) {
1083
+ const log5 = p5.taskLog({ title: t("uninstall.starting", { name: pkg.name }) });
1084
+ try {
1085
+ await pkg.uninstall({ log: log5, config: {}, t });
1086
+ log5.success(t("uninstall.success", { name: pkg.name }));
1087
+ results.push({ id: pkg.id, status: "ok" });
1088
+ } catch (err) {
1089
+ const msg = err instanceof Error ? err.message : String(err);
1090
+ log5.error(`${t("uninstall.failed", { name: pkg.name })}
1048
1091
  ${msg}`);
1049
- results.push({ id: pkg.id, status: "fail", message: msg });
1092
+ results.push({ id: pkg.id, status: "fail", message: msg });
1093
+ }
1094
+ }
1095
+ const ok = results.filter((r) => r.status === "ok").length;
1096
+ const fail = results.filter((r) => r.status === "fail").length;
1097
+ p5.note(
1098
+ [
1099
+ pc2.green(t("install.summaryOk", { count: ok })),
1100
+ pc2.red(t("install.summaryFail", { count: fail }))
1101
+ ].join("\n"),
1102
+ t("install.summaryTitle")
1103
+ );
1104
+ } finally {
1105
+ if (!userCancelled) {
1106
+ await syncFromState({ skip: opts.noClaudeMd });
1050
1107
  }
1051
1108
  }
1052
- const ok = results.filter((r) => r.status === "ok").length;
1053
- const fail = results.filter((r) => r.status === "fail").length;
1054
- p5.note(
1055
- [
1056
- pc2.green(t("install.summaryOk", { count: ok })),
1057
- pc2.red(t("install.summaryFail", { count: fail }))
1058
- ].join("\n"),
1059
- t("install.summaryTitle")
1060
- );
1061
- await syncFromState({ skip: opts.noClaudeMd });
1062
1109
  }
1063
1110
 
1064
1111
  // src/flows/update.ts
@@ -1068,90 +1115,110 @@ async function getInstalled2() {
1068
1115
  const states = await Promise.all(PKGS.map(async (pkg) => ({ pkg, installed: await pkg.isInstalled() })));
1069
1116
  return states.filter((s) => s.installed).map((s) => s.pkg);
1070
1117
  }
1071
- async function updateFlow(opts = {}) {
1072
- const installed = await getInstalled2();
1073
- if (installed.length === 0) {
1074
- p6.log.info(t("update.noneInstalled"));
1075
- return;
1118
+ async function probeInstalled2() {
1119
+ const sp = p6.spinner();
1120
+ sp.start(t("state.checking"));
1121
+ try {
1122
+ await Promise.all([listPlugins(), listMcp()]);
1123
+ const installed = await getInstalled2();
1124
+ sp.stop(t("state.checked", { count: installed.length }));
1125
+ return installed;
1126
+ } catch (err) {
1127
+ sp.stop(t("state.checked", { count: 0 }));
1128
+ throw err;
1076
1129
  }
1077
- let targets;
1078
- if (opts.all) {
1079
- targets = installed;
1080
- } else if (opts.ids && opts.ids.length > 0) {
1081
- targets = [];
1082
- for (const id of opts.ids) {
1083
- const pkg = findPkg(id);
1084
- if (!pkg) {
1085
- p6.log.warn(`Unknown id: ${id}`);
1086
- continue;
1130
+ }
1131
+ async function updateFlow(opts = {}) {
1132
+ let userCancelled = false;
1133
+ try {
1134
+ const installed = await probeInstalled2();
1135
+ if (installed.length === 0) {
1136
+ p6.log.info(t("update.noneInstalled"));
1137
+ return;
1138
+ }
1139
+ let targets;
1140
+ if (opts.all) {
1141
+ targets = installed;
1142
+ } else if (opts.ids && opts.ids.length > 0) {
1143
+ targets = [];
1144
+ for (const id of opts.ids) {
1145
+ const pkg = findPkg(id);
1146
+ if (!pkg) {
1147
+ p6.log.warn(`Unknown id: ${id}`);
1148
+ continue;
1149
+ }
1150
+ if (!installed.some((x2) => x2.id === pkg.id)) {
1151
+ p6.log.warn(`${pkg.name}: ${t("pkg.notInstalled")}`);
1152
+ continue;
1153
+ }
1154
+ targets.push(pkg);
1087
1155
  }
1088
- if (!installed.some((x2) => x2.id === pkg.id)) {
1089
- p6.log.warn(`${pkg.name}: ${t("pkg.notInstalled")}`);
1090
- continue;
1156
+ } else {
1157
+ const picked = await p6.multiselect({
1158
+ message: t("update.selectPrompt"),
1159
+ options: installed.map((pkg) => ({
1160
+ value: pkg.id,
1161
+ label: `${pkg.name} ${pc3.dim(`(${pkg.type})`)}`,
1162
+ hint: pkg.description
1163
+ })),
1164
+ required: false
1165
+ });
1166
+ if (p6.isCancel(picked)) {
1167
+ userCancelled = true;
1168
+ p6.cancel(t("app.cancelled"));
1169
+ return;
1091
1170
  }
1092
- targets.push(pkg);
1171
+ targets = picked.map((id) => findPkg(id)).filter((x2) => Boolean(x2));
1093
1172
  }
1094
- } else {
1095
- const picked = await p6.multiselect({
1096
- message: t("update.selectPrompt"),
1097
- options: installed.map((pkg) => ({
1098
- value: pkg.id,
1099
- label: `${pkg.name} ${pc3.dim(`(${pkg.type})`)}`,
1100
- hint: pkg.description
1101
- })),
1102
- required: false
1103
- });
1104
- if (p6.isCancel(picked)) {
1105
- p6.cancel(t("app.cancelled"));
1173
+ if (targets.length === 0) {
1174
+ p6.log.info(t("install.nothingSelected"));
1106
1175
  return;
1107
1176
  }
1108
- targets = picked.map((id) => findPkg(id)).filter((x2) => Boolean(x2));
1109
- }
1110
- if (targets.length === 0) {
1111
- p6.log.info(t("install.nothingSelected"));
1112
- return;
1113
- }
1114
- const results = [];
1115
- for (const pkg of targets) {
1116
- if (pkg.id === "sequential-thinking") {
1117
- p6.log.info(t("update.mcpAutoNote", { name: pkg.name }));
1118
- results.push({ id: pkg.id, status: "noop" });
1119
- continue;
1120
- }
1121
- if (pkg.id === "context7") {
1122
- p6.log.info(t("update.context7Note"));
1123
- results.push({ id: pkg.id, status: "noop" });
1124
- continue;
1125
- }
1126
- const log5 = p6.taskLog({ title: t("update.starting", { name: pkg.name }) });
1127
- try {
1128
- if (pkg.update) {
1129
- await pkg.update({ log: log5, config: {}, t });
1130
- } else {
1131
- await pkg.uninstall({ log: log5, config: {}, t });
1132
- await pkg.install({ log: log5, config: {}, t });
1177
+ const results = [];
1178
+ for (const pkg of targets) {
1179
+ if (pkg.id === "sequential-thinking") {
1180
+ p6.log.info(t("update.mcpAutoNote", { name: pkg.name }));
1181
+ results.push({ id: pkg.id, status: "noop" });
1182
+ continue;
1133
1183
  }
1134
- log5.success(t("update.success", { name: pkg.name }));
1135
- results.push({ id: pkg.id, status: "ok" });
1136
- } catch (err) {
1137
- const msg = err instanceof Error ? err.message : String(err);
1138
- log5.error(`${t("update.failed", { name: pkg.name })}
1184
+ if (pkg.id === "context7") {
1185
+ p6.log.info(t("update.context7Note"));
1186
+ results.push({ id: pkg.id, status: "noop" });
1187
+ continue;
1188
+ }
1189
+ const log5 = p6.taskLog({ title: t("update.starting", { name: pkg.name }) });
1190
+ try {
1191
+ if (pkg.update) {
1192
+ await pkg.update({ log: log5, config: {}, t });
1193
+ } else {
1194
+ await pkg.uninstall({ log: log5, config: {}, t });
1195
+ await pkg.install({ log: log5, config: {}, t });
1196
+ }
1197
+ log5.success(t("update.success", { name: pkg.name }));
1198
+ results.push({ id: pkg.id, status: "ok" });
1199
+ } catch (err) {
1200
+ const msg = err instanceof Error ? err.message : String(err);
1201
+ log5.error(`${t("update.failed", { name: pkg.name })}
1139
1202
  ${msg}`);
1140
- results.push({ id: pkg.id, status: "fail", message: msg });
1203
+ results.push({ id: pkg.id, status: "fail", message: msg });
1204
+ }
1205
+ }
1206
+ const ok = results.filter((r) => r.status === "ok").length;
1207
+ const fail = results.filter((r) => r.status === "fail").length;
1208
+ const noop = results.filter((r) => r.status === "noop").length;
1209
+ p6.note(
1210
+ [
1211
+ pc3.green(t("install.summaryOk", { count: ok })),
1212
+ pc3.red(t("install.summaryFail", { count: fail })),
1213
+ pc3.dim(`noop: ${noop}`)
1214
+ ].join("\n"),
1215
+ t("install.summaryTitle")
1216
+ );
1217
+ } finally {
1218
+ if (!userCancelled) {
1219
+ await syncFromState({ skip: opts.noClaudeMd });
1141
1220
  }
1142
1221
  }
1143
- const ok = results.filter((r) => r.status === "ok").length;
1144
- const fail = results.filter((r) => r.status === "fail").length;
1145
- const noop = results.filter((r) => r.status === "noop").length;
1146
- p6.note(
1147
- [
1148
- pc3.green(t("install.summaryOk", { count: ok })),
1149
- pc3.red(t("install.summaryFail", { count: fail })),
1150
- pc3.dim(`noop: ${noop}`)
1151
- ].join("\n"),
1152
- t("install.summaryTitle")
1153
- );
1154
- await syncFromState({ skip: opts.noClaudeMd });
1155
1222
  }
1156
1223
 
1157
1224
  // src/flows/status.ts
@@ -1316,7 +1383,7 @@ var SUBCOMMANDS = /* @__PURE__ */ new Set(["install", "uninstall", "update", "st
1316
1383
  var root = defineCommand({
1317
1384
  meta: {
1318
1385
  name: "@curdx/flow",
1319
- version: "3.3.0",
1386
+ version: "3.3.2",
1320
1387
  description: "Interactive installer for Claude Code plugins and MCP servers"
1321
1388
  },
1322
1389
  args: sharedArgs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "3.3.0",
3
+ "version": "3.3.2",
4
4
  "description": "Interactive installer for Claude Code plugins and MCP servers",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.mjs",