toolcraft 0.0.129 → 0.0.131
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/composition.json +2 -2
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +239 -71
- package/dist/composition.json +2 -2
- package/dist/design/help-formatter.d.ts +2 -2
- package/dist/design/help-formatter.js +1 -1
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/toolcraft-design/dist/components/help-formatter-plain.js +61 -25
- package/node_modules/toolcraft-design/dist/components/help-formatter.d.ts +17 -0
- package/node_modules/toolcraft-design/dist/components/help-formatter.js +128 -29
- package/node_modules/toolcraft-design/dist/components/index.d.ts +2 -2
- package/node_modules/toolcraft-design/dist/components/index.js +1 -1
- package/node_modules/toolcraft-design/dist/help-formatter.d.ts +2 -2
- package/node_modules/toolcraft-design/dist/help-formatter.js +1 -1
- package/node_modules/toolcraft-design/dist/index.d.ts +2 -2
- package/node_modules/toolcraft-design/dist/index.js +1 -1
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +2 -2
package/composition.json
CHANGED
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
},
|
|
109
109
|
{
|
|
110
110
|
"name": "toolcraft",
|
|
111
|
-
"version": "0.0.
|
|
111
|
+
"version": "0.0.131",
|
|
112
112
|
"license": "MIT"
|
|
113
113
|
},
|
|
114
114
|
{
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
"name": "toolcraft-schema",
|
|
121
|
-
"version": "0.0.
|
|
121
|
+
"version": "0.0.131",
|
|
122
122
|
"license": "MIT"
|
|
123
123
|
},
|
|
124
124
|
{
|
package/dist/cli.d.ts
CHANGED
|
@@ -7,8 +7,14 @@ export { renderErrorReport } from "./error-report.js";
|
|
|
7
7
|
export type { ErrorReportRenderContext, ErrorReportRenderResult } from "./error-report.js";
|
|
8
8
|
export { configureTheme };
|
|
9
9
|
type Casing = "kebab" | "snake";
|
|
10
|
+
export type CLIHelpDepth = "concise" | "extended";
|
|
10
11
|
export interface CLIControls {
|
|
11
12
|
debug?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Group `--help` depth. `extended` (default) lists every nested action under the
|
|
15
|
+
* help target; `concise` lists only direct children.
|
|
16
|
+
*/
|
|
17
|
+
help?: CLIHelpDepth;
|
|
12
18
|
logLevel?: boolean;
|
|
13
19
|
output?: boolean | CLIOutputControl;
|
|
14
20
|
verbose?: boolean;
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Command as CommanderCommand, CommanderError, InvalidArgumentError, Option } from "commander";
|
|
5
5
|
import { validate as validateSchema } from "toolcraft-schema";
|
|
6
|
-
import { cancel, configureTheme, confirm, createLogger, formatCommandList, formatOptionList, getTheme, helpFormatterPlain, isCancel, note, promptText, renderTable, resetOutputFormatCache, select, text } from "toolcraft-design";
|
|
6
|
+
import { cancel, configureTheme, confirm, createLogger, formatCommandList, formatOptionList, getTheme, helpFormatterPlain, isCancel, note, promptText, renderHelpTokens, renderTable, resetOutputFormatCache, select, text } from "toolcraft-design";
|
|
7
7
|
import { ApprovalDeclinedError, ToolcraftBugError, UserError, assertCommandRequirements, getCommandSourcePath, hasMcpProxyConfig, resolveCommandSecrets } from "./index.js";
|
|
8
8
|
import { hasOwnErrorCode } from "./error-codes.js";
|
|
9
9
|
import { writeErrorReport } from "./error-report.js";
|
|
@@ -663,6 +663,7 @@ function resolveCLIControls(controls) {
|
|
|
663
663
|
validateOutputFormats(outputFormats);
|
|
664
664
|
return {
|
|
665
665
|
debug: controls?.debug === true,
|
|
666
|
+
help: controls?.help === "concise" ? "concise" : "extended",
|
|
666
667
|
logLevel: controls?.logLevel === true,
|
|
667
668
|
output: controls?.output === true || typeof controls?.output === "object",
|
|
668
669
|
outputFormats,
|
|
@@ -843,12 +844,37 @@ function appendHelpMetadata(description, metadata) {
|
|
|
843
844
|
return description;
|
|
844
845
|
}
|
|
845
846
|
if (description.length === 0) {
|
|
846
|
-
return `(${
|
|
847
|
+
return metadata.map((entry) => `(${entry})`).join(" ");
|
|
847
848
|
}
|
|
848
|
-
return `${description}
|
|
849
|
+
return `${description} ${metadata.map((entry) => `(${entry})`).join(" ")}`;
|
|
850
|
+
}
|
|
851
|
+
function normalizeHelpEchoKey(value) {
|
|
852
|
+
let normalized = "";
|
|
853
|
+
for (const character of value.trim().toLowerCase()) {
|
|
854
|
+
if (character !== " " &&
|
|
855
|
+
character !== "\t" &&
|
|
856
|
+
character !== "\n" &&
|
|
857
|
+
character !== "\r" &&
|
|
858
|
+
character !== "_" &&
|
|
859
|
+
character !== "." &&
|
|
860
|
+
character !== "-") {
|
|
861
|
+
normalized += character;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return normalized;
|
|
865
|
+
}
|
|
866
|
+
function isEchoHelpDescription(description, name) {
|
|
867
|
+
if (description.length === 0) {
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
return normalizeHelpEchoKey(description) === normalizeHelpEchoKey(name);
|
|
871
|
+
}
|
|
872
|
+
function suppressEchoHelpDescription(description, name) {
|
|
873
|
+
return isEchoHelpDescription(description, name) ? "" : description;
|
|
849
874
|
}
|
|
850
875
|
function formatHelpFieldDescription(field) {
|
|
851
|
-
const
|
|
876
|
+
const rawDescription = field.description ?? field.displayPath;
|
|
877
|
+
const description = suppressEchoHelpDescription(rawDescription, field.displayPath);
|
|
852
878
|
const metadata = [];
|
|
853
879
|
if (field.schema.kind === "enum" && field.schema.values.length <= 8) {
|
|
854
880
|
const values = field.schema.values.map((value) => String(value)).join(", ");
|
|
@@ -1005,53 +1031,55 @@ function formatDynamicHelpMetadata(field) {
|
|
|
1005
1031
|
}
|
|
1006
1032
|
return metadata;
|
|
1007
1033
|
}
|
|
1034
|
+
function createHelpOptionRow(flags, description) {
|
|
1035
|
+
return {
|
|
1036
|
+
flags,
|
|
1037
|
+
flagTokens: tokenizeHelpFlags(flags),
|
|
1038
|
+
description
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1008
1041
|
function collectDynamicObjectHelpRows(schema, casing, optionPrefix, displayPrefix, metadata) {
|
|
1009
1042
|
const rows = [];
|
|
1010
1043
|
for (const [key, rawChildSchema] of Object.entries(schema.shape)) {
|
|
1011
1044
|
const childSchema = unwrapOptional(rawChildSchema);
|
|
1012
1045
|
const optionFlag = `${optionPrefix}.${formatSegment(key, casing)}`;
|
|
1013
1046
|
const displayPath = `${displayPrefix}.${key}`;
|
|
1014
|
-
const
|
|
1047
|
+
const rawDescription = childSchema.description ?? displayPath;
|
|
1048
|
+
const description = suppressEchoHelpDescription(rawDescription, displayPath);
|
|
1015
1049
|
if (childSchema.kind === "object") {
|
|
1016
1050
|
rows.push(...collectDynamicObjectHelpRows(childSchema, casing, optionFlag, displayPath, metadata));
|
|
1017
1051
|
continue;
|
|
1018
1052
|
}
|
|
1019
1053
|
if (childSchema.kind === "record") {
|
|
1020
|
-
rows.push({
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
})}>`,
|
|
1036
|
-
description: appendHelpMetadata(description, metadata)
|
|
1037
|
-
});
|
|
1054
|
+
rows.push(createHelpOptionRow(`${optionFlag}.<key> <${describeDynamicFieldType({
|
|
1055
|
+
...{
|
|
1056
|
+
id: displayPath,
|
|
1057
|
+
path: [],
|
|
1058
|
+
displayPath,
|
|
1059
|
+
optionPath: [],
|
|
1060
|
+
optionPathDisplay: `${displayPath}.<key>`,
|
|
1061
|
+
optionFlag: `${optionFlag}.<key>`,
|
|
1062
|
+
optional: false,
|
|
1063
|
+
hasDefault: false,
|
|
1064
|
+
defaultValue: undefined,
|
|
1065
|
+
requiredWhenActive: false,
|
|
1066
|
+
schema: childSchema
|
|
1067
|
+
}
|
|
1068
|
+
})}>`, appendHelpMetadata(description, metadata)));
|
|
1038
1069
|
continue;
|
|
1039
1070
|
}
|
|
1040
1071
|
if (childSchema.kind === "array" && unwrapOptional(childSchema.item).kind === "object") {
|
|
1041
1072
|
rows.push(...collectDynamicObjectHelpRows(unwrapOptional(childSchema.item), casing, `${optionFlag}.<index>`, `${displayPath}.<index>`, metadata));
|
|
1042
1073
|
continue;
|
|
1043
1074
|
}
|
|
1044
|
-
rows.push(
|
|
1045
|
-
|
|
1046
|
-
?
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
})}>`,
|
|
1053
|
-
description: appendHelpMetadata(description, metadata)
|
|
1054
|
-
});
|
|
1075
|
+
rows.push(createHelpOptionRow(childSchema.kind === "boolean"
|
|
1076
|
+
? childSchema.default === true
|
|
1077
|
+
? `--no-${optionFlag.slice(2)}`
|
|
1078
|
+
: optionFlag
|
|
1079
|
+
: `${optionFlag} <${describeHelpValueToken(childSchema, {
|
|
1080
|
+
displayPath,
|
|
1081
|
+
optionFlag
|
|
1082
|
+
})}>`, appendHelpMetadata(description, metadata)));
|
|
1055
1083
|
}
|
|
1056
1084
|
return rows;
|
|
1057
1085
|
}
|
|
@@ -1070,17 +1098,11 @@ function formatDynamicHelpFields(field, casing) {
|
|
|
1070
1098
|
}
|
|
1071
1099
|
}
|
|
1072
1100
|
return [
|
|
1073
|
-
{
|
|
1074
|
-
flags: `${field.optionFlag} <${describeDynamicFieldType(field)}>`,
|
|
1075
|
-
description: appendHelpMetadata(field.description ?? field.optionPathDisplay, metadata)
|
|
1076
|
-
}
|
|
1101
|
+
createHelpOptionRow(`${field.optionFlag} <${describeDynamicFieldType(field)}>`, appendHelpMetadata(suppressEchoHelpDescription(field.description ?? field.optionPathDisplay, field.optionPathDisplay), metadata))
|
|
1077
1102
|
];
|
|
1078
1103
|
}
|
|
1079
1104
|
function formatSecretRows(secrets) {
|
|
1080
|
-
return Object.values(secrets).map((secret) => (
|
|
1081
|
-
flags: secret.env,
|
|
1082
|
-
description: formatSecretDescription(secret)
|
|
1083
|
-
}));
|
|
1105
|
+
return Object.values(secrets).map((secret) => createHelpOptionRow(secret.env, formatSecretDescription(secret)));
|
|
1084
1106
|
}
|
|
1085
1107
|
function formatSecretDescription(secret) {
|
|
1086
1108
|
if (secret.description !== undefined && secret.description.length > 0) {
|
|
@@ -1109,34 +1131,142 @@ function formatExampleCommand(breadcrumb, rootUsageName, params) {
|
|
|
1109
1131
|
function formatExampleRows(examples, breadcrumb, rootUsageName) {
|
|
1110
1132
|
return examples.map((example) => `${example.title}\n ${formatExampleCommand(breadcrumb, rootUsageName, example.params)}`);
|
|
1111
1133
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1134
|
+
const MAX_INLINE_OPTIONAL_PARAMETER_TOKENS = 8;
|
|
1135
|
+
function tokenizeHelpFlags(flags) {
|
|
1136
|
+
const tokens = [];
|
|
1137
|
+
let index = 0;
|
|
1138
|
+
while (index < flags.length) {
|
|
1139
|
+
if (flags[index] === " ") {
|
|
1140
|
+
let end = index + 1;
|
|
1141
|
+
while (end < flags.length && flags[end] === " ") {
|
|
1142
|
+
end += 1;
|
|
1143
|
+
}
|
|
1144
|
+
tokens.push({ text: flags.slice(index, end), role: "literal" });
|
|
1145
|
+
index = end;
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
if (flags[index] === "[" || flags[index] === "]") {
|
|
1149
|
+
tokens.push({ text: flags[index], role: "dim" });
|
|
1150
|
+
index += 1;
|
|
1151
|
+
continue;
|
|
1152
|
+
}
|
|
1153
|
+
if (flags[index] === "<") {
|
|
1154
|
+
const close = flags.indexOf(">", index);
|
|
1155
|
+
if (close === -1) {
|
|
1156
|
+
tokens.push({ text: flags.slice(index), role: "literal" });
|
|
1157
|
+
break;
|
|
1158
|
+
}
|
|
1159
|
+
tokens.push({ text: flags.slice(index, close + 1), role: "argument" });
|
|
1160
|
+
index = close + 1;
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
if (flags.startsWith("--", index) || (flags[index] === "-" && flags[index + 1] !== undefined && flags[index + 1] !== "-")) {
|
|
1164
|
+
let end = index + 1;
|
|
1165
|
+
while (end < flags.length && flags[end] !== " " && flags[end] !== "[" && flags[end] !== "]" && flags[end] !== "<") {
|
|
1166
|
+
end += 1;
|
|
1167
|
+
}
|
|
1168
|
+
tokens.push({ text: flags.slice(index, end), role: "option" });
|
|
1169
|
+
index = end;
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
// Enum literals (a|b) or bare words after a flag.
|
|
1173
|
+
let end = index + 1;
|
|
1174
|
+
while (end < flags.length && flags[end] !== " " && flags[end] !== "[" && flags[end] !== "]" && flags[end] !== "<") {
|
|
1175
|
+
end += 1;
|
|
1176
|
+
}
|
|
1177
|
+
const piece = flags.slice(index, end);
|
|
1178
|
+
tokens.push({
|
|
1179
|
+
text: piece,
|
|
1180
|
+
role: piece.includes("|") ? "literal" : piece.startsWith("+") ? "dim" : "literal"
|
|
1181
|
+
});
|
|
1182
|
+
index = end;
|
|
1183
|
+
}
|
|
1184
|
+
return tokens;
|
|
1185
|
+
}
|
|
1186
|
+
function wrapOptionalParameterTokens(tokens, optional) {
|
|
1187
|
+
if (!optional) {
|
|
1188
|
+
return tokens;
|
|
1189
|
+
}
|
|
1190
|
+
return [{ text: "[", role: "dim" }, ...tokens, { text: "]", role: "dim" }];
|
|
1191
|
+
}
|
|
1192
|
+
function createCommandParameterToken(text, optional) {
|
|
1193
|
+
return {
|
|
1194
|
+
text: optional ? `[${text}]` : text,
|
|
1195
|
+
optional,
|
|
1196
|
+
tokens: wrapOptionalParameterTokens(tokenizeHelpFlags(text), optional)
|
|
1197
|
+
};
|
|
1114
1198
|
}
|
|
1115
1199
|
function formatCommandDynamicParameterTokens(field, casing) {
|
|
1116
1200
|
const optional = field.optional || field.hasDefault;
|
|
1117
|
-
return formatDynamicHelpFields(field, casing).map((row) =>
|
|
1201
|
+
return formatDynamicHelpFields(field, casing).map((row) => createCommandParameterToken(row.flags, optional));
|
|
1118
1202
|
}
|
|
1119
1203
|
function formatCommandParameterTokens(command, casing, globalLongOptionFlags) {
|
|
1120
1204
|
const collected = collectFields(command.params, casing, globalLongOptionFlags);
|
|
1121
1205
|
const fields = assignPositionals(collected.fields, command.positional);
|
|
1122
1206
|
return fields
|
|
1123
1207
|
.filter((field) => field.global !== true)
|
|
1124
|
-
.map((field) =>
|
|
1208
|
+
.map((field) => createCommandParameterToken(formatCommandParameterFieldFlags(field, globalLongOptionFlags), field.positionalIndex === undefined && (field.optional || field.hasDefault)))
|
|
1125
1209
|
.concat(collected.dynamicFields.flatMap((field) => formatCommandDynamicParameterTokens(field, casing)));
|
|
1126
1210
|
}
|
|
1127
|
-
function
|
|
1211
|
+
function collapseOptionalParameterTokens(parameterTokens) {
|
|
1212
|
+
const optionalCount = parameterTokens.filter((token) => token.optional).length;
|
|
1213
|
+
if (optionalCount <= MAX_INLINE_OPTIONAL_PARAMETER_TOKENS) {
|
|
1214
|
+
return parameterTokens;
|
|
1215
|
+
}
|
|
1216
|
+
const required = parameterTokens.filter((token) => !token.optional);
|
|
1217
|
+
const collapsedText = `+${optionalCount} options`;
|
|
1218
|
+
return [
|
|
1219
|
+
...required,
|
|
1220
|
+
{
|
|
1221
|
+
text: `[${collapsedText}]`,
|
|
1222
|
+
optional: true,
|
|
1223
|
+
tokens: [
|
|
1224
|
+
{ text: "[", role: "dim" },
|
|
1225
|
+
{ text: collapsedText, role: "dim" },
|
|
1226
|
+
{ text: "]", role: "dim" }
|
|
1227
|
+
]
|
|
1228
|
+
}
|
|
1229
|
+
];
|
|
1230
|
+
}
|
|
1231
|
+
function formatCommandRowNameTokens(node, casing, globalLongOptionFlags) {
|
|
1128
1232
|
const baseName = node.aliases.length === 0 ? node.name : `${node.name} (${node.aliases.join(", ")})`;
|
|
1233
|
+
const nameTokens = [{ text: baseName, role: "command" }];
|
|
1129
1234
|
const parameterTokens = node.kind === "command"
|
|
1130
|
-
? formatCommandParameterTokens(node, casing, globalLongOptionFlags)
|
|
1235
|
+
? collapseOptionalParameterTokens(formatCommandParameterTokens(node, casing, globalLongOptionFlags))
|
|
1131
1236
|
: [];
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1237
|
+
for (const token of parameterTokens) {
|
|
1238
|
+
nameTokens.push({ text: " ", role: "literal" }, ...token.tokens);
|
|
1239
|
+
}
|
|
1240
|
+
const name = parameterTokens.length === 0
|
|
1241
|
+
? baseName
|
|
1242
|
+
: `${baseName} ${parameterTokens.map((token) => token.text).join(" ")}`;
|
|
1243
|
+
return { name, nameTokens };
|
|
1134
1244
|
}
|
|
1135
|
-
function formatCommandRows(group, scope, casing, globalLongOptionFlags) {
|
|
1136
|
-
|
|
1137
|
-
name
|
|
1138
|
-
|
|
1139
|
-
|
|
1245
|
+
function formatCommandRows(group, scope, casing, globalLongOptionFlags, help) {
|
|
1246
|
+
const toRow = (child, depth) => {
|
|
1247
|
+
const { name, nameTokens } = formatCommandRowNameTokens(child, casing, globalLongOptionFlags);
|
|
1248
|
+
return {
|
|
1249
|
+
name,
|
|
1250
|
+
nameTokens,
|
|
1251
|
+
description: suppressEchoHelpDescription(child.description ?? "", child.name),
|
|
1252
|
+
kind: child.kind,
|
|
1253
|
+
depth
|
|
1254
|
+
};
|
|
1255
|
+
};
|
|
1256
|
+
if (help === "concise") {
|
|
1257
|
+
return getHelpChildren(group, scope).map((child) => toRow(child, 0));
|
|
1258
|
+
}
|
|
1259
|
+
const rows = [];
|
|
1260
|
+
const visit = (node, depth) => {
|
|
1261
|
+
for (const child of getHelpChildren(node, scope)) {
|
|
1262
|
+
rows.push(toRow(child, depth));
|
|
1263
|
+
if (child.kind === "group") {
|
|
1264
|
+
visit(child, depth + 1);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
visit(group, 0);
|
|
1269
|
+
return rows;
|
|
1140
1270
|
}
|
|
1141
1271
|
function formatGlobalOptionsLine(ctx) {
|
|
1142
1272
|
const flags = [];
|
|
@@ -1176,10 +1306,7 @@ function collectSchemaGlobalFieldRows(group, scope, casing, globalLongOptionFlag
|
|
|
1176
1306
|
if (seen.has(dedupeKey)) {
|
|
1177
1307
|
continue;
|
|
1178
1308
|
}
|
|
1179
|
-
seen.set(dedupeKey,
|
|
1180
|
-
flags: formatHelpFieldFlags(field, globalLongOptionFlags),
|
|
1181
|
-
description: formatHelpFieldDescription(field)
|
|
1182
|
-
});
|
|
1309
|
+
seen.set(dedupeKey, createHelpOptionRow(formatHelpFieldFlags(field, globalLongOptionFlags), formatHelpFieldDescription(field)));
|
|
1183
1310
|
}
|
|
1184
1311
|
return;
|
|
1185
1312
|
}
|
|
@@ -1203,6 +1330,23 @@ function formatHelpOptionList(rows) {
|
|
|
1203
1330
|
? helpFormatterPlain.formatOptionList(rows)
|
|
1204
1331
|
: formatOptionList(rows);
|
|
1205
1332
|
}
|
|
1333
|
+
function sortLeafHelpOptionFields(fields) {
|
|
1334
|
+
const positionals = [];
|
|
1335
|
+
const required = [];
|
|
1336
|
+
const optional = [];
|
|
1337
|
+
for (const field of fields) {
|
|
1338
|
+
if (field.positionalIndex !== undefined) {
|
|
1339
|
+
positionals.push(field);
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
if (!field.optional && !field.hasDefault) {
|
|
1343
|
+
required.push(field);
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
optional.push(field);
|
|
1347
|
+
}
|
|
1348
|
+
return [...positionals, ...required, ...optional];
|
|
1349
|
+
}
|
|
1206
1350
|
function buildUsageLine(breadcrumb, rootUsageName, suffix) {
|
|
1207
1351
|
const visibleBreadcrumb = breadcrumb.filter((segment) => segment.length > 0);
|
|
1208
1352
|
const usageBreadcrumb = breadcrumb[0] === "" ? [rootUsageName, ...visibleBreadcrumb] : visibleBreadcrumb;
|
|
@@ -1214,15 +1358,35 @@ function formatGroupUsageSuffix(group, scope, casing, globalLongOptionFlags) {
|
|
|
1214
1358
|
if (group.default !== undefined &&
|
|
1215
1359
|
group.default.hidden === true &&
|
|
1216
1360
|
group.default.scope.includes(scope)) {
|
|
1217
|
-
const parameterTokens = formatCommandParameterTokens(group.default, casing, globalLongOptionFlags);
|
|
1218
|
-
return ["[command]", "[OPTIONS]", ...parameterTokens].join(" ");
|
|
1361
|
+
const parameterTokens = collapseOptionalParameterTokens(formatCommandParameterTokens(group.default, casing, globalLongOptionFlags));
|
|
1362
|
+
return ["[command]", "[OPTIONS]", ...parameterTokens.map((token) => token.text)].join(" ");
|
|
1219
1363
|
}
|
|
1220
1364
|
return "[command] [OPTIONS]";
|
|
1221
1365
|
}
|
|
1366
|
+
function formatHelpDrillDownFooter(rootUsageName) {
|
|
1367
|
+
return text.muted(`Run ${rootUsageName} <command> --help for full options.`);
|
|
1368
|
+
}
|
|
1369
|
+
function formatStyledUsageLine(usageLine) {
|
|
1370
|
+
const parts = usageLine.split(" ").filter((part) => part.length > 0);
|
|
1371
|
+
if (parts.length === 0) {
|
|
1372
|
+
return text.usageCommand(usageLine);
|
|
1373
|
+
}
|
|
1374
|
+
const commandEnd = parts.findIndex((part) => part.startsWith("-") || part.startsWith("[") || part.startsWith("<"));
|
|
1375
|
+
const commandParts = commandEnd === -1 ? parts : parts.slice(0, commandEnd);
|
|
1376
|
+
const argParts = commandEnd === -1 ? [] : parts.slice(commandEnd);
|
|
1377
|
+
const command = commandParts.join(" ");
|
|
1378
|
+
if (argParts.length === 0) {
|
|
1379
|
+
return text.usageCommand(command);
|
|
1380
|
+
}
|
|
1381
|
+
return `${text.usageCommand(command)} ${renderHelpTokens(argParts.flatMap((part, index) => [
|
|
1382
|
+
...(index === 0 ? [] : [{ text: " ", role: "literal" }]),
|
|
1383
|
+
...tokenizeHelpFlags(part)
|
|
1384
|
+
]))}`;
|
|
1385
|
+
}
|
|
1222
1386
|
function renderGroupHelp(group, breadcrumb, scope, casing, globalOptions, rootUsageName, isRoot) {
|
|
1223
1387
|
const sections = [];
|
|
1224
1388
|
const globalLongOptionFlags = getGlobalLongOptionFlags(globalOptions.presetsEnabled, globalOptions.showVersion, globalOptions.controls);
|
|
1225
|
-
const commandRows = formatCommandRows(group, scope, casing, globalLongOptionFlags);
|
|
1389
|
+
const commandRows = formatCommandRows(group, scope, casing, globalLongOptionFlags, globalOptions.controls.help);
|
|
1226
1390
|
if (commandRows.length > 0) {
|
|
1227
1391
|
sections.push(`${text.sectionHeader("Commands")}\n${formatHelpCommandList(commandRows)}`);
|
|
1228
1392
|
}
|
|
@@ -1236,6 +1400,9 @@ function renderGroupHelp(group, breadcrumb, scope, casing, globalOptions, rootUs
|
|
|
1236
1400
|
sections.push(builtInLine);
|
|
1237
1401
|
}
|
|
1238
1402
|
}
|
|
1403
|
+
if (commandRows.length > 0) {
|
|
1404
|
+
sections.push(formatHelpDrillDownFooter(rootUsageName));
|
|
1405
|
+
}
|
|
1239
1406
|
return renderHelpDocument({
|
|
1240
1407
|
breadcrumb,
|
|
1241
1408
|
rootUsageName,
|
|
@@ -1250,12 +1417,8 @@ function renderLeafHelp(command, breadcrumb, casing, globalOptions, rootUsageNam
|
|
|
1250
1417
|
const globalLongOptionFlags = getGlobalLongOptionFlags(globalOptions.presetsEnabled, globalOptions.showVersion, globalOptions.controls);
|
|
1251
1418
|
const collected = collectFields(command.params, casing, globalLongOptionFlags);
|
|
1252
1419
|
const fields = assignPositionals(collected.fields, command.positional);
|
|
1253
|
-
const optionRows = fields
|
|
1254
|
-
.
|
|
1255
|
-
.map((field) => ({
|
|
1256
|
-
flags: formatHelpFieldFlags(field, globalLongOptionFlags),
|
|
1257
|
-
description: formatHelpFieldDescription(field)
|
|
1258
|
-
}))
|
|
1420
|
+
const optionRows = sortLeafHelpOptionFields(fields.filter((field) => field.global !== true))
|
|
1421
|
+
.map((field) => createHelpOptionRow(formatHelpFieldFlags(field, globalLongOptionFlags), formatHelpFieldDescription(field)))
|
|
1259
1422
|
.concat(collected.dynamicFields.flatMap((field) => formatDynamicHelpFields(field, casing)));
|
|
1260
1423
|
if (optionRows.length > 0) {
|
|
1261
1424
|
sections.push(`${text.sectionHeader("Options")}\n${formatHelpOptionList(optionRows)}`);
|
|
@@ -1288,7 +1451,7 @@ function renderJsonHelp(target, root, casing, globalOptions, rootUsageName) {
|
|
|
1288
1451
|
const globalLongOptionFlags = getGlobalLongOptionFlags(globalOptions.presetsEnabled, globalOptions.showVersion, globalOptions.controls);
|
|
1289
1452
|
const node = target.node;
|
|
1290
1453
|
if (node.kind === "group") {
|
|
1291
|
-
const commandRows = formatCommandRows(node, "cli", casing, globalLongOptionFlags);
|
|
1454
|
+
const commandRows = formatCommandRows(node, "cli", casing, globalLongOptionFlags, globalOptions.controls.help);
|
|
1292
1455
|
const isRoot = node === root;
|
|
1293
1456
|
return `${JSON.stringify({
|
|
1294
1457
|
schemaVersion: 1,
|
|
@@ -1297,7 +1460,12 @@ function renderJsonHelp(target, root, casing, globalOptions, rootUsageName) {
|
|
|
1297
1460
|
path: target.breadcrumb.filter((segment) => segment.length > 0),
|
|
1298
1461
|
usage: buildUsageLine(target.breadcrumb, rootUsageName, formatGroupUsageSuffix(node, "cli", casing, globalLongOptionFlags)),
|
|
1299
1462
|
...(node.description === undefined ? {} : { description: node.description }),
|
|
1300
|
-
commands: commandRows.map((row) => ({
|
|
1463
|
+
commands: commandRows.map((row) => ({
|
|
1464
|
+
name: row.name,
|
|
1465
|
+
description: row.description,
|
|
1466
|
+
kind: row.kind,
|
|
1467
|
+
depth: row.depth
|
|
1468
|
+
})),
|
|
1301
1469
|
options: isRoot
|
|
1302
1470
|
? collectSchemaGlobalFieldRows(node, "cli", casing, globalLongOptionFlags).map((row) => ({
|
|
1303
1471
|
name: row.flags.split(/[ ,]+/)[0]?.replace(/^--/, "") ?? row.flags,
|
|
@@ -1368,7 +1536,7 @@ function renderHelpDocument(input) {
|
|
|
1368
1536
|
if (remainingDescription.length > 0) {
|
|
1369
1537
|
lines.push(remainingDescription, "");
|
|
1370
1538
|
}
|
|
1371
|
-
lines.push(`Usage: ${
|
|
1539
|
+
lines.push(`Usage: ${formatStyledUsageLine(input.usageLine)}`, "");
|
|
1372
1540
|
if (input.requiresAuth) {
|
|
1373
1541
|
lines.push("Requires: authentication");
|
|
1374
1542
|
}
|
package/dist/composition.json
CHANGED
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
},
|
|
109
109
|
{
|
|
110
110
|
"name": "toolcraft",
|
|
111
|
-
"version": "0.0.
|
|
111
|
+
"version": "0.0.131",
|
|
112
112
|
"license": "MIT"
|
|
113
113
|
},
|
|
114
114
|
{
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
"name": "toolcraft-schema",
|
|
121
|
-
"version": "0.0.
|
|
121
|
+
"version": "0.0.131",
|
|
122
122
|
"license": "MIT"
|
|
123
123
|
},
|
|
124
124
|
{
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { helpFormatter } from "toolcraft-design/help-formatter";
|
|
2
|
-
export type { CommandInfo, FormatColumnsOptions, OptionInfo } from "toolcraft-design/help-formatter";
|
|
1
|
+
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "toolcraft-design/help-formatter";
|
|
2
|
+
export type { CommandInfo, FormatColumnsOptions, OptionInfo, HelpToken, HelpTokenRole } from "toolcraft-design/help-formatter";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { helpFormatter } from "toolcraft-design/help-formatter";
|
|
1
|
+
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "toolcraft-design/help-formatter";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { joinHelpTokens } from "./help-formatter.js";
|
|
1
2
|
export function stripAnsi(value) {
|
|
2
3
|
let output = "";
|
|
3
4
|
for (let index = 0; index < value.length; index += 1) {
|
|
@@ -54,26 +55,49 @@ function splitWords(value) {
|
|
|
54
55
|
}
|
|
55
56
|
return words;
|
|
56
57
|
}
|
|
57
|
-
function
|
|
58
|
-
|
|
58
|
+
function leadingWhitespace(value) {
|
|
59
|
+
let index = 0;
|
|
60
|
+
while (index < value.length && isWhitespace(value[index])) {
|
|
61
|
+
index += 1;
|
|
62
|
+
}
|
|
63
|
+
return { prefix: value.slice(0, index), rest: value.slice(index) };
|
|
64
|
+
}
|
|
65
|
+
function takePrefix(value, width) {
|
|
66
|
+
return { prefix: value.slice(0, width), rest: value.slice(width) };
|
|
67
|
+
}
|
|
68
|
+
function wrapWords(value, width, continuationWidth = width) {
|
|
69
|
+
// Preserve leading whitespace only on the first wrapped line so hang-indented
|
|
70
|
+
// left cells (command depth prefixes) do not re-indent every continuation.
|
|
71
|
+
const { prefix, rest } = leadingWhitespace(value);
|
|
72
|
+
const firstContentWidth = Math.max(1, width - prefix.length);
|
|
73
|
+
const words = splitWords(rest);
|
|
59
74
|
if (words.length === 0) {
|
|
60
|
-
return [
|
|
75
|
+
return [prefix];
|
|
61
76
|
}
|
|
62
77
|
const lines = [];
|
|
63
78
|
let line = "";
|
|
79
|
+
let isFirstLine = true;
|
|
64
80
|
for (const word of words) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
if (line.length + 1 + word.length <= width) {
|
|
81
|
+
const limit = isFirstLine ? firstContentWidth : continuationWidth;
|
|
82
|
+
if (line && line.length + 1 + word.length <= limit) {
|
|
70
83
|
line += ` ${word}`;
|
|
71
84
|
continue;
|
|
72
85
|
}
|
|
73
|
-
|
|
74
|
-
|
|
86
|
+
if (line) {
|
|
87
|
+
lines.push(isFirstLine ? `${prefix}${line}` : line);
|
|
88
|
+
isFirstLine = false;
|
|
89
|
+
line = "";
|
|
90
|
+
}
|
|
91
|
+
let remaining = word;
|
|
92
|
+
while (remaining.length > (isFirstLine ? firstContentWidth : continuationWidth)) {
|
|
93
|
+
const chunk = takePrefix(remaining, isFirstLine ? firstContentWidth : continuationWidth);
|
|
94
|
+
lines.push(isFirstLine ? `${prefix}${chunk.prefix}` : chunk.prefix);
|
|
95
|
+
isFirstLine = false;
|
|
96
|
+
remaining = chunk.rest;
|
|
97
|
+
}
|
|
98
|
+
line = remaining;
|
|
75
99
|
}
|
|
76
|
-
lines.push(line);
|
|
100
|
+
lines.push(isFirstLine ? `${prefix}${line}` : line);
|
|
77
101
|
return lines;
|
|
78
102
|
}
|
|
79
103
|
export function formatColumns(opts) {
|
|
@@ -91,33 +115,43 @@ export function formatColumns(opts) {
|
|
|
91
115
|
const indent = opts.indent ?? 2;
|
|
92
116
|
const maxLeftContentWidth = Math.max(...rows.map((row) => row.left.length));
|
|
93
117
|
const leftWidth = clamp(maxLeftContentWidth + gap, minLeftWidth, maxLeftWidth);
|
|
94
|
-
const rightWidth = Math.max(
|
|
118
|
+
const rightWidth = Math.max(1, totalWidth - leftWidth - indent);
|
|
119
|
+
const leftWrapWidth = Math.max(1, totalWidth - indent);
|
|
95
120
|
const firstIndent = " ".repeat(indent);
|
|
96
121
|
const continuationIndent = " ".repeat(indent + leftWidth);
|
|
97
122
|
return rows
|
|
98
123
|
.flatMap((row) => {
|
|
124
|
+
let leftLeadingWidth = 0;
|
|
125
|
+
while (leftLeadingWidth < row.left.length && isWhitespace(row.left[leftLeadingWidth])) {
|
|
126
|
+
leftLeadingWidth += 1;
|
|
127
|
+
}
|
|
128
|
+
// Continuations hang under the left cell start (including depth prefix) by +2.
|
|
129
|
+
const leftHangIndent = " ".repeat(indent + leftLeadingWidth + 2);
|
|
130
|
+
const leftLines = wrapWords(row.left, leftWrapWidth, Math.max(1, totalWidth - leftHangIndent.length));
|
|
99
131
|
if (row.right.length === 0) {
|
|
100
|
-
return
|
|
132
|
+
return leftLines.map((line, index) => index === 0 ? `${firstIndent}${line}` : `${leftHangIndent}${line}`);
|
|
101
133
|
}
|
|
102
134
|
const rightLines = wrapWords(row.right, rightWidth);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
135
|
+
const leftFitsInColumn = row.left.length < leftWidth;
|
|
136
|
+
if (leftFitsInColumn && leftLines.length === 1) {
|
|
137
|
+
const firstLine = `${firstIndent}${padEndVisible(leftLines[0] ?? "", leftWidth)}${rightLines[0]}`;
|
|
138
|
+
const continuationLines = rightLines
|
|
139
|
+
.slice(1)
|
|
140
|
+
.map((line) => `${continuationIndent}${line}`);
|
|
141
|
+
return [firstLine, ...continuationLines];
|
|
108
142
|
}
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
.map((line) => `${continuationIndent}${line}`);
|
|
113
|
-
return [firstLine, ...continuationLines];
|
|
143
|
+
const renderedLeft = leftLines.map((line, index) => index === 0 ? `${firstIndent}${line}` : `${leftHangIndent}${line}`);
|
|
144
|
+
const renderedRight = rightLines.map((line) => `${continuationIndent}${line}`);
|
|
145
|
+
return [...renderedLeft, ...renderedRight];
|
|
114
146
|
})
|
|
115
147
|
.join("\n");
|
|
116
148
|
}
|
|
117
149
|
export function formatCommandList(commands) {
|
|
118
150
|
return formatColumns({
|
|
119
151
|
rows: commands.map((cmd) => ({
|
|
120
|
-
left: cmd.
|
|
152
|
+
left: `${" ".repeat((cmd.depth ?? 0) * 2)}${cmd.nameTokens !== undefined && cmd.nameTokens.length > 0
|
|
153
|
+
? joinHelpTokens(cmd.nameTokens)
|
|
154
|
+
: cmd.name}`,
|
|
121
155
|
right: cmd.description
|
|
122
156
|
}))
|
|
123
157
|
});
|
|
@@ -125,7 +159,9 @@ export function formatCommandList(commands) {
|
|
|
125
159
|
export function formatOptionList(options) {
|
|
126
160
|
return formatColumns({
|
|
127
161
|
rows: options.map((opt) => ({
|
|
128
|
-
left: opt.
|
|
162
|
+
left: opt.flagTokens !== undefined && opt.flagTokens.length > 0
|
|
163
|
+
? joinHelpTokens(opt.flagTokens)
|
|
164
|
+
: opt.flags,
|
|
129
165
|
right: opt.description
|
|
130
166
|
}))
|
|
131
167
|
});
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
export type HelpTokenRole = "command" | "argument" | "option" | "literal" | "dim";
|
|
2
|
+
export interface HelpToken {
|
|
3
|
+
text: string;
|
|
4
|
+
role: HelpTokenRole;
|
|
5
|
+
}
|
|
1
6
|
export interface CommandInfo {
|
|
2
7
|
name: string;
|
|
8
|
+
/** Structured tokens for TTY/markdown styling. Plain `name` is used when absent. */
|
|
9
|
+
nameTokens?: HelpToken[];
|
|
3
10
|
description: string;
|
|
11
|
+
/** Nesting depth relative to the help target. Depth 0 is a direct child. */
|
|
12
|
+
depth?: number;
|
|
4
13
|
}
|
|
5
14
|
export interface OptionInfo {
|
|
6
15
|
flags: string;
|
|
16
|
+
/** Structured tokens for TTY/markdown styling. Plain `flags` is used when absent. */
|
|
17
|
+
flagTokens?: HelpToken[];
|
|
7
18
|
description: string;
|
|
8
19
|
}
|
|
9
20
|
export interface FormatColumnsOptions {
|
|
@@ -18,6 +29,9 @@ export interface FormatColumnsOptions {
|
|
|
18
29
|
indent?: number;
|
|
19
30
|
}
|
|
20
31
|
export declare function formatColumns(opts: FormatColumnsOptions): string;
|
|
32
|
+
export declare function styleHelpToken(token: HelpToken): string;
|
|
33
|
+
export declare function joinHelpTokens(tokens: HelpToken[]): string;
|
|
34
|
+
export declare function renderHelpTokens(tokens: HelpToken[]): string;
|
|
21
35
|
export declare function formatCommand(name: string, description: string): string;
|
|
22
36
|
export declare function formatUsage(command: string, args?: string): string;
|
|
23
37
|
export declare function formatOption(flags: string, description: string): string;
|
|
@@ -30,4 +44,7 @@ export declare const helpFormatter: {
|
|
|
30
44
|
readonly formatOption: typeof formatOption;
|
|
31
45
|
readonly formatCommandList: typeof formatCommandList;
|
|
32
46
|
readonly formatOptionList: typeof formatOptionList;
|
|
47
|
+
readonly styleHelpToken: typeof styleHelpToken;
|
|
48
|
+
readonly joinHelpTokens: typeof joinHelpTokens;
|
|
49
|
+
readonly renderHelpTokens: typeof renderHelpTokens;
|
|
33
50
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveOutputFormat } from "../internal/output-format.js";
|
|
2
|
+
import { typography } from "../tokens/typography.js";
|
|
1
3
|
import { text } from "./text.js";
|
|
2
4
|
const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
3
5
|
function normalizeInline(value) {
|
|
@@ -103,26 +105,67 @@ function splitWords(value) {
|
|
|
103
105
|
}
|
|
104
106
|
return words;
|
|
105
107
|
}
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
+
function leadingWhitespaceWidth(value) {
|
|
109
|
+
let index = 0;
|
|
110
|
+
while (index < value.length && isWhitespace(value[index])) {
|
|
111
|
+
index += 1;
|
|
112
|
+
}
|
|
113
|
+
return { prefix: value.slice(0, index), rest: value.slice(index) };
|
|
114
|
+
}
|
|
115
|
+
function takeVisiblePrefix(value, width) {
|
|
116
|
+
let visible = 0;
|
|
117
|
+
let index = 0;
|
|
118
|
+
while (index < value.length) {
|
|
119
|
+
const controlEnd = readControlSequence(value, index);
|
|
120
|
+
if (controlEnd !== undefined) {
|
|
121
|
+
index = controlEnd;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const segment = graphemeSegmenter.segment(value.slice(index))[Symbol.iterator]().next().value;
|
|
125
|
+
const cluster = segment?.segment ?? value[index] ?? "";
|
|
126
|
+
const nextWidth = clusterWidth(cluster);
|
|
127
|
+
if (visible > 0 && visible + nextWidth > width) {
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
visible += nextWidth;
|
|
131
|
+
index += cluster.length || 1;
|
|
132
|
+
}
|
|
133
|
+
return { prefix: value.slice(0, index), rest: value.slice(index) };
|
|
134
|
+
}
|
|
135
|
+
function wrapWords(value, width, continuationWidth = width) {
|
|
136
|
+
// Preserve leading whitespace only on the first wrapped line so hang-indented
|
|
137
|
+
// left cells (command depth prefixes) do not re-indent every continuation.
|
|
138
|
+
const { prefix, rest } = leadingWhitespaceWidth(value);
|
|
139
|
+
const prefixWidth = visibleWidth(prefix);
|
|
140
|
+
const firstContentWidth = Math.max(1, width - prefixWidth);
|
|
141
|
+
const words = splitWords(rest);
|
|
108
142
|
if (words.length === 0) {
|
|
109
|
-
return [
|
|
143
|
+
return [prefix];
|
|
110
144
|
}
|
|
111
145
|
const lines = [];
|
|
112
146
|
let line = "";
|
|
147
|
+
let isFirstLine = true;
|
|
113
148
|
for (const word of words) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (visibleWidth(line) + 1 + visibleWidth(word) <= width) {
|
|
149
|
+
const limit = isFirstLine ? firstContentWidth : continuationWidth;
|
|
150
|
+
if (line && visibleWidth(line) + 1 + visibleWidth(word) <= limit) {
|
|
119
151
|
line += ` ${word}`;
|
|
120
152
|
continue;
|
|
121
153
|
}
|
|
122
|
-
|
|
123
|
-
|
|
154
|
+
if (line) {
|
|
155
|
+
lines.push(isFirstLine ? `${prefix}${line}` : line);
|
|
156
|
+
isFirstLine = false;
|
|
157
|
+
line = "";
|
|
158
|
+
}
|
|
159
|
+
let remaining = word;
|
|
160
|
+
while (visibleWidth(remaining) > (isFirstLine ? firstContentWidth : continuationWidth)) {
|
|
161
|
+
const chunk = takeVisiblePrefix(remaining, isFirstLine ? firstContentWidth : continuationWidth);
|
|
162
|
+
lines.push(isFirstLine ? `${prefix}${chunk.prefix}` : chunk.prefix);
|
|
163
|
+
isFirstLine = false;
|
|
164
|
+
remaining = chunk.rest;
|
|
165
|
+
}
|
|
166
|
+
line = remaining;
|
|
124
167
|
}
|
|
125
|
-
lines.push(line);
|
|
168
|
+
lines.push(isFirstLine ? `${prefix}${line}` : line);
|
|
126
169
|
return lines;
|
|
127
170
|
}
|
|
128
171
|
function validateLayoutValue(value, name) {
|
|
@@ -150,29 +193,74 @@ export function formatColumns(opts) {
|
|
|
150
193
|
validateLayoutValue(indent, "indent");
|
|
151
194
|
const maxLeftContentWidth = Math.max(...rows.map((row) => visibleWidth(row.left)));
|
|
152
195
|
const leftWidth = clamp(maxLeftContentWidth + gap, minLeftWidth, maxLeftWidth);
|
|
153
|
-
const rightWidth = Math.max(
|
|
196
|
+
const rightWidth = Math.max(1, totalWidth - leftWidth - indent);
|
|
197
|
+
const leftWrapWidth = Math.max(1, totalWidth - indent);
|
|
154
198
|
const firstIndent = " ".repeat(indent);
|
|
155
199
|
const continuationIndent = " ".repeat(indent + leftWidth);
|
|
156
200
|
return rows
|
|
157
201
|
.flatMap((row) => {
|
|
202
|
+
const leftLeading = leadingWhitespaceWidth(row.left).prefix;
|
|
203
|
+
// Continuations hang under the left cell start (including depth prefix) by +2.
|
|
204
|
+
const leftHangIndent = " ".repeat(indent + visibleWidth(leftLeading) + 2);
|
|
205
|
+
const leftLines = wrapWords(row.left, leftWrapWidth, Math.max(1, totalWidth - visibleWidth(leftHangIndent)));
|
|
158
206
|
if (row.right.length === 0) {
|
|
159
|
-
return
|
|
207
|
+
return leftLines.map((line, index) => index === 0 ? `${firstIndent}${line}` : `${leftHangIndent}${line}`);
|
|
160
208
|
}
|
|
161
209
|
const rightLines = wrapWords(row.right, rightWidth);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
210
|
+
const leftFitsInColumn = visibleWidth(row.left) < leftWidth;
|
|
211
|
+
if (leftFitsInColumn && leftLines.length === 1) {
|
|
212
|
+
const firstLine = `${firstIndent}${padEndVisible(leftLines[0] ?? "", leftWidth)}${rightLines[0]}`;
|
|
213
|
+
const continuationLines = rightLines
|
|
214
|
+
.slice(1)
|
|
215
|
+
.map((line) => `${continuationIndent}${line}`);
|
|
216
|
+
return [firstLine, ...continuationLines];
|
|
167
217
|
}
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
.map((line) => `${continuationIndent}${line}`);
|
|
172
|
-
return [firstLine, ...continuationLines];
|
|
218
|
+
const renderedLeft = leftLines.map((line, index) => index === 0 ? `${firstIndent}${line}` : `${leftHangIndent}${line}`);
|
|
219
|
+
const renderedRight = rightLines.map((line) => `${continuationIndent}${line}`);
|
|
220
|
+
return [...renderedLeft, ...renderedRight];
|
|
173
221
|
})
|
|
174
222
|
.join("\n");
|
|
175
223
|
}
|
|
224
|
+
export function styleHelpToken(token) {
|
|
225
|
+
switch (token.role) {
|
|
226
|
+
case "command":
|
|
227
|
+
return text.command(token.text);
|
|
228
|
+
case "argument":
|
|
229
|
+
return styleArgumentToken(token.text);
|
|
230
|
+
case "option":
|
|
231
|
+
return text.option(token.text);
|
|
232
|
+
case "dim":
|
|
233
|
+
return styleDim(token.text);
|
|
234
|
+
case "literal":
|
|
235
|
+
return token.text;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function styleArgumentToken(content) {
|
|
239
|
+
// Token text already includes angle brackets. text.argument re-wraps in markdown,
|
|
240
|
+
// so strip first there; terminal/json keep the full `<value>` form.
|
|
241
|
+
const format = resolveOutputFormat();
|
|
242
|
+
if (format === "markdown" && content.startsWith("<") && content.endsWith(">")) {
|
|
243
|
+
return text.argument(content.slice(1, -1));
|
|
244
|
+
}
|
|
245
|
+
if (format === "json") {
|
|
246
|
+
return content;
|
|
247
|
+
}
|
|
248
|
+
return text.argument(content);
|
|
249
|
+
}
|
|
250
|
+
function styleDim(content) {
|
|
251
|
+
// Structural brackets stay unstyled in markdown/json; italicizing them as muted is wrong.
|
|
252
|
+
const format = resolveOutputFormat();
|
|
253
|
+
if (format === "json" || format === "markdown") {
|
|
254
|
+
return content;
|
|
255
|
+
}
|
|
256
|
+
return typography.dim(content);
|
|
257
|
+
}
|
|
258
|
+
export function joinHelpTokens(tokens) {
|
|
259
|
+
return tokens.map((token) => token.text).join("");
|
|
260
|
+
}
|
|
261
|
+
export function renderHelpTokens(tokens) {
|
|
262
|
+
return tokens.map((token) => styleHelpToken(token)).join("");
|
|
263
|
+
}
|
|
176
264
|
export function formatCommand(name, description) {
|
|
177
265
|
return formatColumns({
|
|
178
266
|
rows: [{ left: text.command(name), right: description }]
|
|
@@ -189,16 +277,24 @@ export function formatOption(flags, description) {
|
|
|
189
277
|
}
|
|
190
278
|
export function formatCommandList(commands) {
|
|
191
279
|
return formatColumns({
|
|
192
|
-
rows: commands.map((cmd) =>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
280
|
+
rows: commands.map((cmd) => {
|
|
281
|
+
const depthPrefix = " ".repeat((cmd.depth ?? 0) * 2);
|
|
282
|
+
const styledName = cmd.nameTokens !== undefined && cmd.nameTokens.length > 0
|
|
283
|
+
? renderHelpTokens(cmd.nameTokens)
|
|
284
|
+
: text.command(cmd.name);
|
|
285
|
+
return {
|
|
286
|
+
left: `${depthPrefix}${styledName}`,
|
|
287
|
+
right: cmd.description
|
|
288
|
+
};
|
|
289
|
+
})
|
|
196
290
|
});
|
|
197
291
|
}
|
|
198
292
|
export function formatOptionList(options) {
|
|
199
293
|
return formatColumns({
|
|
200
294
|
rows: options.map((opt) => ({
|
|
201
|
-
left:
|
|
295
|
+
left: opt.flagTokens !== undefined && opt.flagTokens.length > 0
|
|
296
|
+
? renderHelpTokens(opt.flagTokens)
|
|
297
|
+
: text.option(opt.flags),
|
|
202
298
|
right: opt.description
|
|
203
299
|
}))
|
|
204
300
|
});
|
|
@@ -209,5 +305,8 @@ export const helpFormatter = {
|
|
|
209
305
|
formatUsage,
|
|
210
306
|
formatOption,
|
|
211
307
|
formatCommandList,
|
|
212
|
-
formatOptionList
|
|
308
|
+
formatOptionList,
|
|
309
|
+
styleHelpToken,
|
|
310
|
+
joinHelpTokens,
|
|
311
|
+
renderHelpTokens
|
|
213
312
|
};
|
|
@@ -4,8 +4,8 @@ export type { Color } from "./color.js";
|
|
|
4
4
|
export { symbols } from "./symbols.js";
|
|
5
5
|
export { createLogger, logger } from "./logger.js";
|
|
6
6
|
export type { LoggerOutput } from "./logger.js";
|
|
7
|
-
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./help-formatter.js";
|
|
8
|
-
export type { CommandInfo, OptionInfo, FormatColumnsOptions } from "./help-formatter.js";
|
|
7
|
+
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "./help-formatter.js";
|
|
8
|
+
export type { CommandInfo, OptionInfo, FormatColumnsOptions, HelpToken, HelpTokenRole } from "./help-formatter.js";
|
|
9
9
|
export { formatCommandNotFound } from "./command-errors.js";
|
|
10
10
|
export { formatCommandNotFoundPanel } from "./command-errors.js";
|
|
11
11
|
export { renderTable } from "./table.js";
|
|
@@ -2,7 +2,7 @@ export { text } from "./text.js";
|
|
|
2
2
|
export { color } from "./color.js";
|
|
3
3
|
export { symbols } from "./symbols.js";
|
|
4
4
|
export { createLogger, logger } from "./logger.js";
|
|
5
|
-
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./help-formatter.js";
|
|
5
|
+
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "./help-formatter.js";
|
|
6
6
|
export { formatCommandNotFound } from "./command-errors.js";
|
|
7
7
|
export { formatCommandNotFoundPanel } from "./command-errors.js";
|
|
8
8
|
export { renderTable } from "./table.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { helpFormatter } from "./components/help-formatter.js";
|
|
2
|
-
export type { CommandInfo, FormatColumnsOptions, OptionInfo } from "./components/help-formatter.js";
|
|
1
|
+
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "./components/help-formatter.js";
|
|
2
|
+
export type { CommandInfo, FormatColumnsOptions, OptionInfo, HelpToken, HelpTokenRole } from "./components/help-formatter.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { helpFormatter } from "./components/help-formatter.js";
|
|
1
|
+
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "./components/help-formatter.js";
|
|
@@ -12,9 +12,9 @@ export type { Color } from "./components/color.js";
|
|
|
12
12
|
export { symbols } from "./components/symbols.js";
|
|
13
13
|
export { createLogger, logger } from "./components/logger.js";
|
|
14
14
|
export type { LoggerOutput } from "./components/logger.js";
|
|
15
|
-
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./components/help-formatter.js";
|
|
15
|
+
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "./components/help-formatter.js";
|
|
16
16
|
export * as helpFormatterPlain from "./components/help-formatter-plain.js";
|
|
17
|
-
export type { CommandInfo, OptionInfo, FormatColumnsOptions } from "./components/help-formatter.js";
|
|
17
|
+
export type { CommandInfo, OptionInfo, FormatColumnsOptions, HelpToken, HelpTokenRole } from "./components/help-formatter.js";
|
|
18
18
|
export { formatCommandNotFound } from "./components/command-errors.js";
|
|
19
19
|
export { formatCommandNotFoundPanel } from "./components/command-errors.js";
|
|
20
20
|
export { renderTable } from "./components/table.js";
|
|
@@ -10,7 +10,7 @@ export { text } from "./components/text.js";
|
|
|
10
10
|
export { color } from "./components/color.js";
|
|
11
11
|
export { symbols } from "./components/symbols.js";
|
|
12
12
|
export { createLogger, logger } from "./components/logger.js";
|
|
13
|
-
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./components/help-formatter.js";
|
|
13
|
+
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "./components/help-formatter.js";
|
|
14
14
|
export * as helpFormatterPlain from "./components/help-formatter-plain.js";
|
|
15
15
|
export { formatCommandNotFound } from "./components/command-errors.js";
|
|
16
16
|
export { formatCommandNotFoundPanel } from "./components/command-errors.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.131",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -153,7 +153,7 @@
|
|
|
153
153
|
"yaml"
|
|
154
154
|
],
|
|
155
155
|
"optionalDependencies": {
|
|
156
|
-
"toolcraft-schema": "0.0.
|
|
156
|
+
"toolcraft-schema": "0.0.131",
|
|
157
157
|
"toolcraft-design": "*",
|
|
158
158
|
"@poe-code/frontmatter": "*",
|
|
159
159
|
"@poe-code/agent-mcp-config": "*",
|