tty-table 6.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,6 +33,61 @@ $ node examples/data/fake-stream.js | tty-table --format json --header examples/
33
33
  $ tty-table -h
34
34
  ```
35
35
 
36
+ ### MCP server
37
+
38
+ Expose tty-table to MCP clients (Claude, IDE agents, Cursor, etc.) over stdio.
39
+
40
+ #### Client configuration
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "tty-table": {
46
+ "command": "npx",
47
+ "args": ["-y", "--package=tty-table", "tty-table-mcp"]
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ #### Tools
54
+
55
+ | Tool | Description |
56
+ |------|-------------|
57
+ | `render_table` | Render data as an ASCII/Unicode terminal table. Accepts `header`, `rows`, and any tty-table `options`; returns the rendered table as text. |
58
+
59
+ **Arguments for `render_table`:**
60
+
61
+ | Name | Type | Required | Description |
62
+ |------|------|----------|-------------|
63
+ | `header` | `(string \| {value, align?, width?})[]` | no | Column definitions |
64
+ | `rows` | `(unknown[] \| Record<string, unknown>)[]` | yes | Row data (arrays of cells, or objects keyed by column name) |
65
+ | `options` | `object` | no | Any tty-table option (e.g. `width`, `borderStyle`, `align`, `compact`) |
66
+
67
+ Example tool call:
68
+
69
+ ```json
70
+ {
71
+ "header": [{ "value": "name" }, { "value": "score", "align": "right" }],
72
+ "rows": [["Ada", 100], ["Grace", 98]],
73
+ "options": { "width": 40 }
74
+ }
75
+ ```
76
+
77
+ Rendered result:
78
+
79
+ ```text
80
+ ┌───────┬───────┐
81
+ │ name │ score │
82
+ ├───────┼───────┤
83
+ │ Ada │ 100 │
84
+ ├───────┼───────┤
85
+ │ Grace │ 98 │
86
+ └───────┴───────┘
87
+ ```
88
+
89
+ > Additional tools may be added in future releases under the same MCP server.
90
+
36
91
  ### Browser & Browser Console
37
92
 
38
93
  - View in Chrome or Chromium at [http://localhost:8070/examples/browser-example.html](http://localhost:8070/examples/browser-example.html) using a dockerized apache instance:
@@ -3902,28 +3902,28 @@ var TtyTable = (() => {
3902
3902
  var addPadding = (config, width) => {
3903
3903
  return width + config.paddingLeft + config.paddingRight;
3904
3904
  };
3905
+ var getDisplayWidth = (value) => {
3906
+ const lines = (0, import_strip_ansi2.default)(value.toString()).split(/[\n\r]/);
3907
+ let widest = 0;
3908
+ for (const line of lines) {
3909
+ const width = (0, import_wcwidth.default)(line);
3910
+ if (width > widest) widest = width;
3911
+ }
3912
+ return widest;
3913
+ };
3905
3914
  var getMaxLength = (columnOptions, rows, columnIndex) => {
3906
- let iterable;
3915
+ let widest = 0;
3907
3916
  if (columnOptions && (columnOptions.value || columnOptions.alias)) {
3908
- let val = columnOptions.alias || columnOptions.value;
3909
- val = val.toString();
3910
- const headerRow = Array(rows[0].length);
3911
- headerRow[columnIndex] = val;
3912
- iterable = rows.slice();
3913
- iterable.push(headerRow);
3914
- } else {
3915
- iterable = rows;
3917
+ const value = columnOptions.alias || columnOptions.value;
3918
+ widest = getDisplayWidth(value);
3916
3919
  }
3917
- const widest = iterable.reduce((prev, row) => {
3920
+ for (const row of rows) {
3918
3921
  if (row[columnIndex]) {
3919
3922
  const value = row[columnIndex].value ? row[columnIndex].value : row[columnIndex];
3920
- const width = Math.max(
3921
- ...(0, import_strip_ansi2.default)(value.toString()).split(/[\n\r]/).map((s) => (0, import_wcwidth.default)(s))
3922
- );
3923
- return width > prev ? width : prev;
3923
+ const width = getDisplayWidth(value);
3924
+ if (width > widest) widest = width;
3924
3925
  }
3925
- return prev;
3926
- }, 0);
3926
+ }
3927
3927
  return widest;
3928
3928
  };
3929
3929
  var getAvailableWidth = (config) => {
@@ -3988,13 +3988,13 @@ var TtyTable = (() => {
3988
3988
  emptySpace--;
3989
3989
  const padBoth = Math.floor(emptySpace / 2);
3990
3990
  const padRemainder = emptySpace % 2;
3991
- line = Array(padBoth + 1).join(" ") + line + Array(padBoth + 1 + padRemainder).join(" ");
3991
+ line = " ".repeat(padBoth) + line + " ".repeat(padBoth + padRemainder);
3992
3992
  break;
3993
3993
  case cellOptions[alignTgt] === "right":
3994
- line = Array(emptySpace - cellOptions.paddingRight).join(" ") + line + Array(cellOptions.paddingRight + 1).join(" ");
3994
+ line = " ".repeat(emptySpace - cellOptions.paddingRight - 1) + line + " ".repeat(cellOptions.paddingRight);
3995
3995
  break;
3996
3996
  default:
3997
- line = Array(cellOptions.paddingLeft + 1).join(" ") + line + Array(emptySpace - cellOptions.paddingLeft).join(" ");
3997
+ line = " ".repeat(cellOptions.paddingLeft) + line + " ".repeat(emptySpace - cellOptions.paddingLeft - 1);
3998
3998
  }
3999
3999
  }
4000
4000
  return startMatches[0] + line + endMatches[0];
@@ -4065,26 +4065,18 @@ var TtyTable = (() => {
4065
4065
  // src/render.ts
4066
4066
  var import_strip_ansi3 = __toESM(require_strip_ansi());
4067
4067
  var stringifyData = (config, inputData) => {
4068
- const sections = {
4069
- header: [],
4070
- body: [],
4071
- footer: []
4072
- };
4073
- const marginLeft = Array(config.marginLeft + 1).join(" ");
4068
+ const sections = { header: [], body: [], footer: [] };
4069
+ const marginLeft = " ".repeat(config.marginLeft);
4074
4070
  const borderStyle = config.borderCharacters[config.borderStyle];
4075
4071
  const borders = [];
4076
4072
  const constructorType = getConstructorGeometry(inputData[0] || [], config);
4077
4073
  const rows = coerceConstructorGeometry(config, inputData, constructorType);
4078
- if (!globalThis.columnWidths) {
4079
- globalThis.columnWidths = {};
4080
- }
4074
+ if (!globalThis.columnWidths) globalThis.columnWidths = {};
4081
4075
  if (globalThis.columnWidths[config.tableId]) {
4082
4076
  config.table.columnWidths = globalThis.columnWidths[config.tableId];
4083
4077
  } else {
4084
4078
  const formattedRows = rows.map((row, rowIndex) => {
4085
- return row.map((cell, cellIndex) => {
4086
- return buildCell(config, cell, cellIndex, "body", rowIndex, rows, inputData, true);
4087
- });
4079
+ return row.map((cell, cellIndex) => buildCell(config, cell, cellIndex, "body", rowIndex, rows, inputData, true));
4088
4080
  });
4089
4081
  globalThis.columnWidths[config.tableId] = config.table.columnWidths = getColumnWidths(config, formattedRows);
4090
4082
  }
@@ -4093,60 +4085,49 @@ var TtyTable = (() => {
4093
4085
  sections.header = [];
4094
4086
  break;
4095
4087
  case config.showHeader === true:
4096
- // explicitly true, show
4097
4088
  case !!config.table.header[0].find((obj) => obj.value || obj.alias):
4098
- sections.header = config.table.header.map((row) => {
4099
- return buildRow(config, row, "header", null, rows, inputData);
4100
- });
4089
+ sections.header = config.table.header.map((row) => buildRow(config, row, "header", null, rows, inputData));
4101
4090
  break;
4102
4091
  default:
4103
4092
  sections.header = [];
4104
4093
  }
4105
- sections.body = rows.map((row, rowIndex) => {
4106
- return buildRow(config, row, "body", rowIndex, rows, inputData);
4107
- });
4094
+ sections.body = rows.map((row, rowIndex) => buildRow(config, row, "body", rowIndex, rows, inputData));
4108
4095
  sections.footer = config.table.footer instanceof Array && config.table.footer.length > 0 ? [config.table.footer] : [];
4109
- sections.footer = sections.footer.map((row) => {
4110
- return buildRow(config, row, "footer", null, rows, inputData);
4111
- });
4096
+ sections.footer = sections.footer.map((row) => buildRow(config, row, "footer", null, rows, inputData));
4112
4097
  for (let a = 0; a < 3; a++) {
4113
4098
  borders[a] = borderStyle[a].l;
4114
4099
  config.table.columnWidths.forEach((columnWidth, index, arr) => {
4115
- borders[a] += Array(Math.max(columnWidth, 2)).join(borderStyle[a].h);
4100
+ borders[a] += borderStyle[a].h.repeat(Math.max(columnWidth, 2) - 1);
4116
4101
  borders[a] += index + 1 < arr.length ? borderStyle[a].j : "";
4117
4102
  });
4118
4103
  borders[a] += borderStyle[a].r;
4119
4104
  borders[a] = a < 2 ? `${marginLeft + borders[a]}
4120
4105
  ` : marginLeft + borders[a];
4121
4106
  }
4122
- let output = borders[0];
4123
- Object.keys(sections).forEach((p, i) => {
4124
- while (sections[p].length) {
4125
- const row = sections[p].shift();
4107
+ const output = [borders[0]];
4108
+ for (const [sectionIndex, sectionName] of Object.keys(sections).entries()) {
4109
+ const section = sections[sectionName];
4110
+ for (let rowIndex = 0; rowIndex < section.length; rowIndex++) {
4111
+ const row = section[rowIndex];
4126
4112
  row.forEach((line) => {
4127
- output = `${output + marginLeft + borderStyle[1].v + line.join(borderStyle[1].v) + borderStyle[1].v}
4128
- `;
4113
+ output.push(marginLeft + borderStyle[1].v + line.join(borderStyle[1].v) + borderStyle[1].v + "\n");
4129
4114
  });
4130
4115
  switch (true) {
4131
- // skip if end of body and no footer
4132
- case (sections[p].length === 0 && i === 1 && sections.footer.length === 0):
4116
+ case (rowIndex === section.length - 1 && sectionIndex === 1 && sections.footer.length === 0):
4133
4117
  break;
4134
- // skip if end of footer
4135
- case (sections[p].length === 0 && i === 2):
4118
+ case (rowIndex === section.length - 1 && sectionIndex === 2):
4136
4119
  break;
4137
- // skip if compact
4138
- case (config.compact && p === "body" && !row.empty):
4120
+ case (config.compact && sectionName === "body" && !row.empty):
4139
4121
  break;
4140
- // skip if border style is "none"
4141
4122
  case (config.borderStyle === "none" && config.compact):
4142
4123
  break;
4143
4124
  default:
4144
- output += borders[1];
4125
+ output.push(borders[1]);
4145
4126
  }
4146
4127
  }
4147
- });
4148
- output += borders[2];
4149
- const finalOutput = Array(config.marginTop + 1).join("\n") + output;
4128
+ }
4129
+ output.push(borders[2]);
4130
+ const finalOutput = "\n".repeat(config.marginTop) + output.join("");
4150
4131
  config.height = finalOutput.split(/\r\n|\r|\n/).length;
4151
4132
  return finalOutput;
4152
4133
  };
@@ -4157,11 +4138,8 @@ var TtyTable = (() => {
4157
4138
  return row;
4158
4139
  }
4159
4140
  const lengthDifference = config.table.columnWidths.length - row.length;
4160
- if (lengthDifference > 0) {
4161
- row = row.concat(Array.apply(null, new Array(lengthDifference)).map(() => null));
4162
- } else if (lengthDifference < 0) {
4163
- row.length = config.table.columnWidths.length;
4164
- }
4141
+ if (lengthDifference > 0) row = row.concat(Array.apply(null, new Array(lengthDifference)).map(() => null));
4142
+ else if (lengthDifference < 0) row.length = config.table.columnWidths.length;
4165
4143
  row = row.map((elem, elemIndex) => {
4166
4144
  const cell = buildCell(config, elem, elemIndex, rowType, rowIndex, rowData, inputData);
4167
4145
  minRowHeight = minRowHeight < cell.length ? cell.length : minRowHeight;
@@ -4170,18 +4148,12 @@ var TtyTable = (() => {
4170
4148
  minRowHeight = rowType === "header" ? minRowHeight : minRowHeight + (config.paddingBottom + config.paddingTop);
4171
4149
  const linedRow = Array.apply(null, { length: minRowHeight }).map(Function.call, () => []);
4172
4150
  row.forEach(function(cell, a) {
4173
- const whitespace = Array(config.table.columnWidths[a]).join(" ");
4151
+ const whitespace = " ".repeat(Math.max(config.table.columnWidths[a] - 1, 0));
4174
4152
  if (rowType === "body") {
4175
- for (let i = 0; i < config.paddingTop; i++) {
4176
- cell.unshift(whitespace);
4177
- }
4178
- for (let i = 0; i < config.paddingBottom; i++) {
4179
- cell.push(whitespace);
4180
- }
4181
- }
4182
- for (let i = 0; i < minRowHeight; i++) {
4183
- linedRow[i].push(typeof cell[i] !== "undefined" ? cell[i] : whitespace);
4153
+ for (let i = 0; i < config.paddingTop; i++) cell.unshift(whitespace);
4154
+ for (let i = 0; i < config.paddingBottom; i++) cell.push(whitespace);
4184
4155
  }
4156
+ for (let i = 0; i < minRowHeight; i++) linedRow[i].push(typeof cell[i] !== "undefined" ? cell[i] : whitespace);
4185
4157
  });
4186
4158
  return linedRow;
4187
4159
  };
@@ -4200,9 +4172,7 @@ var TtyTable = (() => {
4200
4172
  switch (true) {
4201
4173
  case (typeof elem === "undefined" || elem === null):
4202
4174
  cellValue = config.errorOnNull ? config.defaultErrorValue : config.defaultValue;
4203
- if (!isColorEnabled()) {
4204
- cellValue = (0, import_strip_ansi3.default)(cellValue);
4205
- }
4175
+ if (!isColorEnabled()) cellValue = (0, import_strip_ansi3.default)(cellValue);
4206
4176
  cellOptions.isNull = true;
4207
4177
  break;
4208
4178
  case (typeof elem === "object" && elem !== null && typeof elem.value !== "undefined"):
@@ -4215,13 +4185,7 @@ var TtyTable = (() => {
4215
4185
  },
4216
4186
  style,
4217
4187
  resetStyle
4218
- })(
4219
- cellValue,
4220
- columnIndex,
4221
- rowIndex,
4222
- rowData,
4223
- inputData
4224
- );
4188
+ })(cellValue, columnIndex, rowIndex, rowData, inputData);
4225
4189
  break;
4226
4190
  default:
4227
4191
  cellValue = elem;
@@ -4233,25 +4197,13 @@ var TtyTable = (() => {
4233
4197
  },
4234
4198
  style,
4235
4199
  resetStyle
4236
- })(
4237
- cellValue,
4238
- columnIndex,
4239
- rowIndex,
4240
- rowData,
4241
- inputData
4242
- );
4243
- }
4244
- if (dryRun) {
4245
- return cellValue;
4200
+ })(cellValue, columnIndex, rowIndex, rowData, inputData);
4246
4201
  }
4202
+ if (dryRun) return cellValue;
4247
4203
  }
4248
- if (!cellOptions.reset) {
4249
- cellValue = colorizeCell(cellValue, cellOptions, rowType);
4250
- }
4204
+ if (!cellOptions.reset) cellValue = colorizeCell(cellValue, cellOptions, rowType);
4251
4205
  const { cell, innerWidth } = wrapCellText(cellOptions, cellValue, columnIndex, cellOptions, rowType);
4252
- if (rowType === "header") {
4253
- config.table.columnInnerWidths.push(innerWidth);
4254
- }
4206
+ if (rowType === "header") config.table.columnInnerWidths.push(innerWidth);
4255
4207
  return cell;
4256
4208
  };
4257
4209
  var getConstructorGeometry = (row, config) => {
@@ -4260,17 +4212,9 @@ var TtyTable = (() => {
4260
4212
  const keys = Object.keys(row);
4261
4213
  if (config.adapter === "automattic") {
4262
4214
  const key = keys[0];
4263
- if (row[key] instanceof Array) {
4264
- type = "automattic-cross";
4265
- } else {
4266
- type = "automattic-vertical";
4267
- }
4268
- } else {
4269
- type = "o-horizontal";
4270
- }
4271
- } else {
4272
- type = "a-horizontal";
4273
- }
4215
+ type = row[key] instanceof Array ? "automattic-cross" : "automattic-vertical";
4216
+ } else type = "o-horizontal";
4217
+ } else type = "a-horizontal";
4274
4218
  return type;
4275
4219
  };
4276
4220
  var coerceConstructorGeometry = (config, rows, constructorType) => {
@@ -4280,16 +4224,14 @@ var TtyTable = (() => {
4280
4224
  config.columnSettings[0] = config.columnSettings[0] || {};
4281
4225
  config.columnSettings[0].color = config.headerColor;
4282
4226
  output = rows.map((obj) => {
4283
- const arr = [];
4284
4227
  const key = Object.keys(obj)[0];
4285
- arr.push(key);
4286
- return arr.concat(obj[key]);
4228
+ return [key].concat(obj[key]);
4287
4229
  });
4288
4230
  break;
4289
4231
  case "automattic-vertical":
4290
4232
  config.columnSettings[0] = config.columnSettings[0] || {};
4291
4233
  config.columnSettings[0].color = config.headerColor;
4292
- output = rows.map(function(value) {
4234
+ output = rows.map((value) => {
4293
4235
  const key = Object.keys(value)[0];
4294
4236
  return [key, value[key]];
4295
4237
  });
@@ -4297,14 +4239,11 @@ var TtyTable = (() => {
4297
4239
  case "o-horizontal":
4298
4240
  if (config.table.header[0].length && config.table.header[0].every((obj) => obj.value)) {
4299
4241
  output = rows.map((row) => config.table.header[0].map((obj) => row[obj.value]));
4300
- } else {
4301
- output = rows.map((obj) => Object.values(obj));
4302
- }
4242
+ } else output = rows.map((obj) => Object.values(obj));
4303
4243
  break;
4304
4244
  case "a-horizontal":
4305
4245
  output = rows;
4306
4246
  break;
4307
- default:
4308
4247
  }
4309
4248
  return output;
4310
4249
  };