@curdx/flow 3.3.1 → 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.
- package/CHANGELOG.md +6 -0
- package/dist/index.mjs +200 -178
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
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
|
+
|
|
5
11
|
## 3.3.1 — 2026-04-27
|
|
6
12
|
|
|
7
13
|
### Fixed
|
package/dist/index.mjs
CHANGED
|
@@ -952,48 +952,55 @@ async function maybeRefreshMarketplaces(opts) {
|
|
|
952
952
|
);
|
|
953
953
|
}
|
|
954
954
|
async function installFlow(opts = {}) {
|
|
955
|
-
|
|
956
|
-
const explicit = opts.all || opts.ids && opts.ids.length > 0;
|
|
957
|
-
const candidates = explicit ? selectFromIds(opts) : [...PKGS];
|
|
958
|
-
if (candidates.length === 0) {
|
|
959
|
-
p4.log.info(t("install.nothingSelected"));
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
const stateMap = /* @__PURE__ */ new Map();
|
|
963
|
-
const sp = p4.spinner();
|
|
964
|
-
sp.start(t("state.checking"));
|
|
955
|
+
let userCancelled = false;
|
|
965
956
|
try {
|
|
966
|
-
await
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
);
|
|
972
|
-
} finally {
|
|
973
|
-
sp.stop(t("state.checked", { count: candidates.length }));
|
|
974
|
-
}
|
|
975
|
-
let targets;
|
|
976
|
-
if (explicit) {
|
|
977
|
-
targets = candidates;
|
|
978
|
-
} else {
|
|
979
|
-
const picked = await selectInteractive(stateMap);
|
|
980
|
-
if (picked === null) {
|
|
981
|
-
p4.cancel(t("app.cancelled"));
|
|
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"));
|
|
982
962
|
return;
|
|
983
963
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
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
|
+
}
|
|
994
1003
|
}
|
|
995
|
-
summarize(results);
|
|
996
|
-
await syncFromState({ skip: opts.noClaudeMd });
|
|
997
1004
|
}
|
|
998
1005
|
|
|
999
1006
|
// src/flows/uninstall.ts
|
|
@@ -1017,80 +1024,88 @@ async function probeInstalled() {
|
|
|
1017
1024
|
}
|
|
1018
1025
|
}
|
|
1019
1026
|
async function uninstallFlow(opts = {}) {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
targets
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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);
|
|
1029
1044
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1045
|
+
} else {
|
|
1046
|
+
if (installed.length === 0) {
|
|
1047
|
+
p5.log.info(t("uninstall.noneInstalled"));
|
|
1048
|
+
return;
|
|
1033
1049
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
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));
|
|
1040
1065
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
options: installed.map((pkg) => ({
|
|
1044
|
-
value: pkg.id,
|
|
1045
|
-
label: `${pkg.name} ${pc2.dim(`(${pkg.type})`)}`,
|
|
1046
|
-
hint: pkg.description
|
|
1047
|
-
})),
|
|
1048
|
-
required: false
|
|
1049
|
-
});
|
|
1050
|
-
if (p5.isCancel(picked)) {
|
|
1051
|
-
p5.cancel(t("app.cancelled"));
|
|
1066
|
+
if (targets.length === 0) {
|
|
1067
|
+
p5.log.info(t("install.nothingSelected"));
|
|
1052
1068
|
return;
|
|
1053
1069
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
});
|
|
1065
|
-
if (p5.isCancel(ok2) || ok2 === false) {
|
|
1066
|
-
p5.cancel(t("app.cancelled"));
|
|
1067
|
-
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
|
+
}
|
|
1068
1080
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
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 })}
|
|
1080
1091
|
${msg}`);
|
|
1081
|
-
|
|
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 });
|
|
1082
1107
|
}
|
|
1083
1108
|
}
|
|
1084
|
-
const ok = results.filter((r) => r.status === "ok").length;
|
|
1085
|
-
const fail = results.filter((r) => r.status === "fail").length;
|
|
1086
|
-
p5.note(
|
|
1087
|
-
[
|
|
1088
|
-
pc2.green(t("install.summaryOk", { count: ok })),
|
|
1089
|
-
pc2.red(t("install.summaryFail", { count: fail }))
|
|
1090
|
-
].join("\n"),
|
|
1091
|
-
t("install.summaryTitle")
|
|
1092
|
-
);
|
|
1093
|
-
await syncFromState({ skip: opts.noClaudeMd });
|
|
1094
1109
|
}
|
|
1095
1110
|
|
|
1096
1111
|
// src/flows/update.ts
|
|
@@ -1114,89 +1129,96 @@ async function probeInstalled2() {
|
|
|
1114
1129
|
}
|
|
1115
1130
|
}
|
|
1116
1131
|
async function updateFlow(opts = {}) {
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
targets
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
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);
|
|
1132
1155
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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;
|
|
1136
1170
|
}
|
|
1137
|
-
targets.
|
|
1171
|
+
targets = picked.map((id) => findPkg(id)).filter((x2) => Boolean(x2));
|
|
1138
1172
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
message: t("update.selectPrompt"),
|
|
1142
|
-
options: installed.map((pkg) => ({
|
|
1143
|
-
value: pkg.id,
|
|
1144
|
-
label: `${pkg.name} ${pc3.dim(`(${pkg.type})`)}`,
|
|
1145
|
-
hint: pkg.description
|
|
1146
|
-
})),
|
|
1147
|
-
required: false
|
|
1148
|
-
});
|
|
1149
|
-
if (p6.isCancel(picked)) {
|
|
1150
|
-
p6.cancel(t("app.cancelled"));
|
|
1173
|
+
if (targets.length === 0) {
|
|
1174
|
+
p6.log.info(t("install.nothingSelected"));
|
|
1151
1175
|
return;
|
|
1152
1176
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
const results = [];
|
|
1160
|
-
for (const pkg of targets) {
|
|
1161
|
-
if (pkg.id === "sequential-thinking") {
|
|
1162
|
-
p6.log.info(t("update.mcpAutoNote", { name: pkg.name }));
|
|
1163
|
-
results.push({ id: pkg.id, status: "noop" });
|
|
1164
|
-
continue;
|
|
1165
|
-
}
|
|
1166
|
-
if (pkg.id === "context7") {
|
|
1167
|
-
p6.log.info(t("update.context7Note"));
|
|
1168
|
-
results.push({ id: pkg.id, status: "noop" });
|
|
1169
|
-
continue;
|
|
1170
|
-
}
|
|
1171
|
-
const log5 = p6.taskLog({ title: t("update.starting", { name: pkg.name }) });
|
|
1172
|
-
try {
|
|
1173
|
-
if (pkg.update) {
|
|
1174
|
-
await pkg.update({ log: log5, config: {}, t });
|
|
1175
|
-
} else {
|
|
1176
|
-
await pkg.uninstall({ log: log5, config: {}, t });
|
|
1177
|
-
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;
|
|
1178
1183
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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 })}
|
|
1184
1202
|
${msg}`);
|
|
1185
|
-
|
|
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 });
|
|
1186
1220
|
}
|
|
1187
1221
|
}
|
|
1188
|
-
const ok = results.filter((r) => r.status === "ok").length;
|
|
1189
|
-
const fail = results.filter((r) => r.status === "fail").length;
|
|
1190
|
-
const noop = results.filter((r) => r.status === "noop").length;
|
|
1191
|
-
p6.note(
|
|
1192
|
-
[
|
|
1193
|
-
pc3.green(t("install.summaryOk", { count: ok })),
|
|
1194
|
-
pc3.red(t("install.summaryFail", { count: fail })),
|
|
1195
|
-
pc3.dim(`noop: ${noop}`)
|
|
1196
|
-
].join("\n"),
|
|
1197
|
-
t("install.summaryTitle")
|
|
1198
|
-
);
|
|
1199
|
-
await syncFromState({ skip: opts.noClaudeMd });
|
|
1200
1222
|
}
|
|
1201
1223
|
|
|
1202
1224
|
// src/flows/status.ts
|
|
@@ -1361,7 +1383,7 @@ var SUBCOMMANDS = /* @__PURE__ */ new Set(["install", "uninstall", "update", "st
|
|
|
1361
1383
|
var root = defineCommand({
|
|
1362
1384
|
meta: {
|
|
1363
1385
|
name: "@curdx/flow",
|
|
1364
|
-
version: "3.3.
|
|
1386
|
+
version: "3.3.2",
|
|
1365
1387
|
description: "Interactive installer for Claude Code plugins and MCP servers"
|
|
1366
1388
|
},
|
|
1367
1389
|
args: sharedArgs,
|