@emeryld/manager 1.3.0 → 1.3.1

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
@@ -65,8 +65,73 @@ You can also decide where the report lands: the manager will ask whether to stre
65
65
 
66
66
  The `robot metadata` action now starts with the same interactive settings screen as the format checker: pick which kinds of symbols to include, decide whether to limit the scan to exported declarations, and adjust the column width. Use ↑/↓ to move between rows, type new values (comma-separated lists for the kinds), and press Enter once the validation message disappears.
67
67
 
68
+ The column width now controls how much context is emitted for each declaration. Symbols that start past the configured column keep their name/export status but drop heavyweight fields (IO signatures, docstrings, JSX locations, etc.), so you still see everything in the workspace but only the left-most definitions contribute rich metadata to your LLM prompt.
69
+
68
70
  Before the extraction runs you can also choose how to consume the results. The default `console` stream prints the JSON into your terminal, while `editor` writes the report to a temporary file (non-saved) and tries to open it in your editor via your configured `$EDITOR`, `code`, or the OS `open/xdg-open` helpers.
69
71
 
72
+ To keep the payload more token-friendly, the interactive settings screen now exposes `Condense output for compact JSON` and `Docstring length limit`. Enabling the condensed output replaces the verbose object format with a small `fields` + `rows` array layout, and the docstring limit trims comments to the first N characters (set the value to `0` for unlimited). Set your preferred defaults in `.vscode/settings.json` under `manager.robot.condenseOutput` and `manager.robot.maxDocStringLength`.
73
+
74
+ ### Running with arguments
75
+ Run `pnpm manager-cli`, pick the workspace (or “All packages”), then select the `robot metadata` action. The helper walks you through:
76
+
77
+ 1. Set the symbol kinds to capture (comma-separated list or `all`).
78
+ 2. Toggle `Only exported symbols`, adjust `Maximum columns`, `Condense output for compact JSON`, and `Docstring length limit`.
79
+ 3. Confirm the summary to trigger the run and choose whether to stream results to the console or editor.
80
+
81
+ The same knobs live in `.vscode/settings.json` under `manager.robot`, for example:
82
+
83
+ ```json
84
+ {
85
+ "manager.robot": {
86
+ "includeKinds": ["function", "type", "const"],
87
+ "exportedOnly": true,
88
+ "maxColumns": 120,
89
+ "condenseOutput": true,
90
+ "maxDocStringLength": 80
91
+ }
92
+ }
93
+ ```
94
+
95
+ ### Example output
96
+ `robot metadata` prints a summary followed by the parsed payload. With the default formatting you get readable JSON:
97
+
98
+ ```text
99
+ Robot extraction complete (functions=4 components=1 types=2 consts=0 classes=1)
100
+ Estimated tokens: 72
101
+ Detailed results:
102
+ {
103
+ "functions": [
104
+ {
105
+ "kind": "function",
106
+ "name": "buildManagerConfig",
107
+ "location": { "file": "src/menu.ts", "line": 45, "column": 3 },
108
+ "docString": "Builds the menu configuration for the helper CLI.",
109
+ "exported": true,
110
+ "inputs": ["packages: LoadedPackage[]"],
111
+ "output": "HelperScriptEntry[]"
112
+ }
113
+ ],
114
+ ...
115
+ }
116
+ ```
117
+
118
+ Enabling `Condense output for compact JSON` yields a compact payload that reduces tokens even when the detailed rows are long:
119
+
120
+ ```text
121
+ Robot extraction complete (functions=4 components=1 types=2 consts=0 classes=1)
122
+ Estimated tokens: 48
123
+ Detailed results (condensed):
124
+ {
125
+ "version": 1,
126
+ "summary": { "functions": 4, "components": 1, "types": 2, "consts": 0, "classes": 1 },
127
+ "fields": ["kind","file","line","column","name","signature","docString","exported"],
128
+ "rows": [
129
+ ["function","src/menu.ts",45,3,"buildManagerConfig","(packages: LoadedPackage[]) => HelperScriptEntry[]","Builds the menu configuration...",true],
130
+ ["component","src/ui/banner.ts",12,7,"Banner","(props: BannerProps) => JSX.Element","Lightweight banner used in the hero layout.",true]
131
+ ]
132
+ }
133
+ ```
134
+
70
135
  ## Non-interactive release (Codex/CI)
71
136
  - **Syntax**: `pnpm manager-cli <pkg|all> --non-interactive [publish flags]`
72
137
  - **Requirements**: provide the selection (`<pkg>` or `all`) and one of `--bump <type>`, `--sync <version>`, or `--noop` (skip the version change but still tag/publish). Use `--non-interactive`, `--ci`, `--yes`, or `-y` interchangeably to answer every prompt in the affirmative.
@@ -35,6 +35,8 @@ async function promptRobotSettingsSequential(defaults) {
35
35
  includeKinds: await promptKinds(defaults.includeKinds),
36
36
  exportedOnly: await promptBoolean('Only consider exported symbols', defaults.exportedOnly),
37
37
  maxColumns: await promptNumber('Maximum columns', defaults.maxColumns),
38
+ condenseOutput: await promptBoolean('Condense output for compact JSON', defaults.condenseOutput),
39
+ maxDocStringLength: await promptNumber('Docstring length limit (0 = unlimited)', defaults.maxDocStringLength, { allowZero: true }),
38
40
  };
39
41
  }
40
42
  async function promptKinds(fallback) {
@@ -68,17 +70,21 @@ async function promptBoolean(label, fallback) {
68
70
  console.log(colors.yellow('Please answer "yes" or "no", or leave blank to keep the default.'));
69
71
  }
70
72
  }
71
- async function promptNumber(label, fallback) {
73
+ async function promptNumber(label, fallback, options) {
72
74
  const question = colors.cyan(`${label} [default ${fallback}]: `);
75
+ const min = options?.allowZero ? 0 : 1;
76
+ const description = options?.allowZero ? 'non-negative integer' : 'positive integer';
73
77
  while (true) {
74
78
  const answer = await askLine(question);
75
79
  if (!answer)
76
80
  return fallback;
77
81
  const parsed = Number(answer);
78
- if (!Number.isNaN(parsed) && parsed > 0) {
79
- return Math.floor(parsed);
82
+ if (!Number.isNaN(parsed)) {
83
+ const floored = Math.floor(parsed);
84
+ if (floored >= min)
85
+ return floored;
80
86
  }
81
- console.log(colors.yellow('Provide a positive integer or leave blank to keep the default.'));
87
+ console.log(colors.yellow(`Provide a ${description} or leave blank to keep the default.`));
82
88
  }
83
89
  }
84
90
  async function promptRobotSettingsInteractive(defaults) {
@@ -18,6 +18,18 @@ export const SETTING_DESCRIPTORS = [
18
18
  unit: 'columns',
19
19
  type: 'number',
20
20
  },
21
+ {
22
+ key: 'condenseOutput',
23
+ label: 'Condense output for compact JSON',
24
+ type: 'boolean',
25
+ },
26
+ {
27
+ key: 'maxDocStringLength',
28
+ label: 'Docstring length limit (0=unlimited)',
29
+ unit: 'characters',
30
+ type: 'number',
31
+ allowZero: true,
32
+ },
21
33
  ];
22
34
  export function formatValue(value, descriptor) {
23
35
  if (descriptor.type === 'boolean') {
@@ -63,6 +75,10 @@ export function validateRobotSettings(settings) {
63
75
  if (!Number.isFinite(settings.maxColumns) || settings.maxColumns <= 0) {
64
76
  return 'Maximum columns must be a positive number.';
65
77
  }
78
+ if (!Number.isFinite(settings.maxDocStringLength) ||
79
+ settings.maxDocStringLength < 0) {
80
+ return 'Docstring length limit must be zero or greater.';
81
+ }
66
82
  return undefined;
67
83
  }
68
84
  function parsePositiveInteger(inputValue, options) {
@@ -9,6 +9,8 @@ export const DEFAULT_ROBOT_SETTINGS = {
9
9
  includeKinds: [...ROBOT_KINDS],
10
10
  exportedOnly: true,
11
11
  maxColumns: 160,
12
+ condenseOutput: true,
13
+ maxDocStringLength: 0,
12
14
  };
13
15
  function coerceNumber(value, fallback) {
14
16
  const num = Number(value);
@@ -28,6 +30,13 @@ function coerceBoolean(value, fallback) {
28
30
  }
29
31
  return fallback;
30
32
  }
33
+ function coerceNonNegativeNumber(value, fallback) {
34
+ const num = Number(value);
35
+ if (Number.isNaN(num) || num < 0) {
36
+ return fallback;
37
+ }
38
+ return Math.floor(num);
39
+ }
31
40
  function coerceKinds(value) {
32
41
  const knownKinds = new Set(ROBOT_KINDS);
33
42
  if (typeof value === 'string') {
@@ -75,6 +84,8 @@ export async function loadRobotSettings() {
75
84
  includeKinds: coerceKinds(record.includeKinds ?? record.kinds),
76
85
  exportedOnly: coerceBoolean(record.exportedOnly ?? DEFAULT_ROBOT_SETTINGS.exportedOnly, DEFAULT_ROBOT_SETTINGS.exportedOnly),
77
86
  maxColumns: coerceNumber(record.maxColumns ?? DEFAULT_ROBOT_SETTINGS.maxColumns, DEFAULT_ROBOT_SETTINGS.maxColumns),
87
+ condenseOutput: coerceBoolean(record.condenseOutput ?? DEFAULT_ROBOT_SETTINGS.condenseOutput, DEFAULT_ROBOT_SETTINGS.condenseOutput),
88
+ maxDocStringLength: coerceNonNegativeNumber(record.maxDocStringLength ?? DEFAULT_ROBOT_SETTINGS.maxDocStringLength, DEFAULT_ROBOT_SETTINGS.maxDocStringLength),
78
89
  };
79
90
  }
80
91
  catch {
@@ -13,6 +13,7 @@ import { collectComponents } from './extractors/components.js';
13
13
  import { collectConstants } from './extractors/constants.js';
14
14
  import { collectTypes } from './extractors/types.js';
15
15
  import { collectClasses } from './extractors/classes.js';
16
+ import { serializeRobotResult } from './serializer.js';
16
17
  export async function runRobot() {
17
18
  const defaults = await loadRobotSettings();
18
19
  let settings = defaults;
@@ -111,23 +112,26 @@ async function executeRobotExtraction(settings, exportMode, scanRoot, scanLabel)
111
112
  consts: result.consts.length,
112
113
  classes: result.classes.length,
113
114
  };
114
- const snapshot = JSON.stringify(result);
115
- const tokenEstimate = estimateTokenCount(snapshot);
115
+ const serializedResult = serializeRobotResult(result, settings);
116
+ const tokenEstimate = estimateTokenCount(serializedResult);
116
117
  result.tokenEstimate = tokenEstimate;
118
+ const detailLabel = settings.condenseOutput
119
+ ? 'Detailed results (condensed)'
120
+ : 'Detailed results';
117
121
  const summaryText = `Robot extraction complete (functions=${summary.functions} components=${summary.components} types=${summary.types} consts=${summary.consts} classes=${summary.classes})`;
118
122
  console.log(colors.green(summaryText));
119
123
  console.log(colors.dim(`Estimated tokens: ${tokenEstimate}`));
120
124
  if (exportMode === 'console') {
121
- console.log(colors.dim('Detailed results:'));
122
- console.log(JSON.stringify(result, null, 2));
125
+ console.log(colors.dim(detailLabel));
126
+ console.log(serializedResult);
123
127
  }
124
128
  else {
125
129
  await exportReportLines('robot-metadata', 'json', [
126
130
  summaryText,
127
131
  `Token estimate: ${tokenEstimate}`,
128
132
  '',
129
- 'Detailed results:',
130
- JSON.stringify(result, null, 2),
133
+ `${detailLabel}:`,
134
+ serializedResult,
131
135
  ]);
132
136
  }
133
137
  return result;
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { getDocString, getLocation, isNodeExported, withinColumnLimit, } from './shared.js';
2
+ import { EMPTY_LOCATION, getDocString, getLocation, isNodeExported, shouldIncludeContext, } from './shared.js';
3
3
  export function collectClasses(options) {
4
4
  const { sourceFile } = options.context;
5
5
  const records = [];
@@ -10,26 +10,27 @@ export function collectClasses(options) {
10
10
  }
11
11
  const name = node.name?.text;
12
12
  const location = getLocation(node, sourceFile);
13
- if (!withinColumnLimit(location, options.settings.maxColumns)) {
14
- ts.forEachChild(node, visit);
15
- return;
16
- }
13
+ const includeContext = shouldIncludeContext(location, options.settings.maxColumns);
17
14
  const exported = isNodeExported(node);
18
15
  if (options.settings.exportedOnly && !exported) {
19
16
  ts.forEachChild(node, visit);
20
17
  return;
21
18
  }
22
- const extendsClause = node.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword);
23
- const implementsClause = node.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword);
24
- const extendsText = extendsClause?.types
25
- .map((expr) => expr.getText(sourceFile))
26
- .join(', ');
27
- const implementsList = implementsClause?.types.map((expr) => expr.getText(sourceFile));
19
+ let extendsText;
20
+ let implementsList;
21
+ if (includeContext) {
22
+ const extendsClause = node.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword);
23
+ const implementsClause = node.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword);
24
+ extendsText = extendsClause?.types
25
+ .map((expr) => expr.getText(sourceFile))
26
+ .join(', ');
27
+ implementsList = implementsClause?.types.map((expr) => expr.getText(sourceFile));
28
+ }
28
29
  records.push({
29
30
  kind: 'class',
30
31
  name,
31
- location,
32
- docString: getDocString(node),
32
+ location: includeContext ? location : EMPTY_LOCATION,
33
+ docString: includeContext ? getDocString(node) : undefined,
33
34
  exported,
34
35
  extends: extendsText,
35
36
  implements: implementsList,
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { describeParameter, getDocString, getFunctionName, getLocation, isComponentFunction, isNodeExported, withinColumnLimit, } from './shared.js';
2
+ import { describeParameter, EMPTY_LOCATION, getDocString, getFunctionName, getLocation, isComponentFunction, isNodeExported, shouldIncludeContext, } from './shared.js';
3
3
  export function collectComponents(options) {
4
4
  const { sourceFile } = options.context;
5
5
  const records = [];
@@ -14,24 +14,31 @@ export function collectComponents(options) {
14
14
  return;
15
15
  }
16
16
  const location = getLocation(node, sourceFile);
17
- if (!withinColumnLimit(location, options.settings.maxColumns)) {
18
- return;
19
- }
17
+ const includeContext = shouldIncludeContext(location, options.settings.maxColumns);
20
18
  const exported = isNodeExported(node);
21
19
  if (options.settings.exportedOnly && !exported)
22
20
  return;
23
- const inputs = node.parameters.map((param) => describeParameter(param, sourceFile));
24
- const output = node.type?.getText(sourceFile);
25
- const jsxNode = findFirstJsx(node);
21
+ const inputs = includeContext
22
+ ? node.parameters.map((param) => describeParameter(param, sourceFile))
23
+ : [];
24
+ const output = includeContext
25
+ ? node.type?.getText(sourceFile)
26
+ : undefined;
27
+ const docString = includeContext ? getDocString(node) : undefined;
28
+ let jsxLocation;
29
+ if (includeContext) {
30
+ const jsxNode = findFirstJsx(node);
31
+ jsxLocation = jsxNode ? getLocation(jsxNode, sourceFile) : location;
32
+ }
26
33
  records.push({
27
34
  kind: 'component',
28
35
  name,
29
- location,
30
- docString: getDocString(node),
36
+ location: includeContext ? location : EMPTY_LOCATION,
37
+ docString,
31
38
  exported,
32
39
  inputs,
33
40
  output,
34
- jsxLocation: jsxNode ? getLocation(jsxNode, sourceFile) : location,
41
+ jsxLocation,
35
42
  });
36
43
  }
37
44
  ts.forEachChild(node, visit);
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { getDocString, getLocation, isNodeExported, withinColumnLimit, } from './shared.js';
2
+ import { EMPTY_LOCATION, getDocString, getLocation, isNodeExported, shouldIncludeContext, } from './shared.js';
3
3
  export function collectConstants(options) {
4
4
  const { sourceFile } = options.context;
5
5
  const records = [];
@@ -23,15 +23,18 @@ export function collectConstants(options) {
23
23
  if (!ts.isIdentifier(declaration.name))
24
24
  return;
25
25
  const location = getLocation(declaration.name, sourceFile);
26
- if (!withinColumnLimit(location, options.settings.maxColumns))
27
- return;
26
+ const includeContext = shouldIncludeContext(location, options.settings.maxColumns);
27
+ const docString = includeContext ? getDocString(declaration) : undefined;
28
+ const value = includeContext
29
+ ? declaration.initializer?.getText(sourceFile) ?? 'undefined'
30
+ : '';
28
31
  records.push({
29
32
  kind: 'const',
30
33
  name: declaration.name.text,
31
- location,
32
- docString: getDocString(declaration),
34
+ location: includeContext ? location : EMPTY_LOCATION,
35
+ docString,
33
36
  exported,
34
- value: declaration.initializer?.getText(sourceFile) ?? 'undefined',
37
+ value,
35
38
  });
36
39
  });
37
40
  }
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { describeParameter, getDocString, getFunctionName, getLocation, isComponentFunction, isNodeExported, withinColumnLimit, } from './shared.js';
2
+ import { describeParameter, EMPTY_LOCATION, getDocString, getFunctionName, getLocation, isComponentFunction, isNodeExported, shouldIncludeContext, } from './shared.js';
3
3
  export function collectFunctions(options) {
4
4
  const { sourceFile } = options.context;
5
5
  const records = [];
@@ -15,19 +15,22 @@ export function collectFunctions(options) {
15
15
  return;
16
16
  }
17
17
  const location = getLocation(node, sourceFile);
18
- if (!withinColumnLimit(location, options.settings.maxColumns)) {
19
- return;
20
- }
18
+ const includeContext = shouldIncludeContext(location, options.settings.maxColumns);
21
19
  const exported = isNodeExported(node);
22
20
  if (options.settings.exportedOnly && !exported)
23
21
  return;
24
- const inputs = node.parameters.map((param) => describeParameter(param, sourceFile));
25
- const output = node.type?.getText(sourceFile);
22
+ const inputs = includeContext
23
+ ? node.parameters.map((param) => describeParameter(param, sourceFile))
24
+ : [];
25
+ const output = includeContext
26
+ ? node.type?.getText(sourceFile)
27
+ : undefined;
28
+ const docString = includeContext ? getDocString(node) : undefined;
26
29
  records.push({
27
30
  kind: 'function',
28
31
  name,
29
- location,
30
- docString: getDocString(node),
32
+ location: includeContext ? location : EMPTY_LOCATION,
33
+ docString,
31
34
  exported,
32
35
  inputs,
33
36
  output,
@@ -44,11 +44,16 @@ export function isNodeExported(node) {
44
44
  return true;
45
45
  return false;
46
46
  }
47
- export function withinColumnLimit(location, maxColumns) {
47
+ export function shouldIncludeContext(location, maxColumns) {
48
48
  if (maxColumns <= 0)
49
49
  return true;
50
50
  return location.column <= maxColumns;
51
51
  }
52
+ export const EMPTY_LOCATION = {
53
+ file: '',
54
+ line: 0,
55
+ column: 0,
56
+ };
52
57
  export function getFunctionName(node) {
53
58
  if ('name' in node && node.name && ts.isIdentifier(node.name)) {
54
59
  return node.name.text;
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { getDocString, getLocation, isNodeExported, withinColumnLimit, } from './shared.js';
2
+ import { EMPTY_LOCATION, getDocString, getLocation, isNodeExported, shouldIncludeContext, } from './shared.js';
3
3
  export function collectTypes(options) {
4
4
  const { sourceFile } = options.context;
5
5
  const records = [];
@@ -16,10 +16,7 @@ export function collectTypes(options) {
16
16
  }
17
17
  if (typeKind) {
18
18
  const location = getLocation(node, sourceFile);
19
- if (!withinColumnLimit(location, options.settings.maxColumns)) {
20
- ts.forEachChild(node, visit);
21
- return;
22
- }
19
+ const includeContext = shouldIncludeContext(location, options.settings.maxColumns);
23
20
  const exported = isNodeExported(node);
24
21
  if (options.settings.exportedOnly && !exported) {
25
22
  ts.forEachChild(node, visit);
@@ -29,11 +26,11 @@ export function collectTypes(options) {
29
26
  records.push({
30
27
  kind: 'type',
31
28
  name,
32
- location,
33
- docString: getDocString(node),
29
+ location: includeContext ? location : EMPTY_LOCATION,
30
+ docString: includeContext ? getDocString(node) : undefined,
34
31
  exported,
35
32
  typeKind,
36
- definition: node.getText(sourceFile),
33
+ definition: includeContext ? node.getText(sourceFile) : '',
37
34
  });
38
35
  }
39
36
  ts.forEachChild(node, visit);
@@ -0,0 +1,97 @@
1
+ const CONDENSED_FIELDS = [
2
+ 'kind',
3
+ 'file',
4
+ 'line',
5
+ 'column',
6
+ 'name',
7
+ 'signature',
8
+ 'docString',
9
+ 'exported',
10
+ ];
11
+ export function serializeRobotResult(result, settings) {
12
+ if (!settings.condenseOutput) {
13
+ return JSON.stringify(result, null, 2);
14
+ }
15
+ return JSON.stringify(buildCondensedPayload(result, settings));
16
+ }
17
+ function buildCondensedPayload(result, settings) {
18
+ const rows = [];
19
+ const limit = settings.maxDocStringLength;
20
+ for (const record of flattenRecords(result)) {
21
+ rows.push([
22
+ record.kind,
23
+ record.location.file,
24
+ record.location.line,
25
+ record.location.column,
26
+ record.name ?? '',
27
+ buildSignature(record),
28
+ trimDocString(record.docString, limit),
29
+ record.exported,
30
+ ]);
31
+ }
32
+ return {
33
+ version: 1,
34
+ summary: {
35
+ functions: result.functions.length,
36
+ components: result.components.length,
37
+ types: result.types.length,
38
+ consts: result.consts.length,
39
+ classes: result.classes.length,
40
+ },
41
+ fields: [...CONDENSED_FIELDS],
42
+ rows,
43
+ };
44
+ }
45
+ function flattenRecords(result) {
46
+ return [
47
+ ...result.functions,
48
+ ...result.components,
49
+ ...result.types,
50
+ ...result.consts,
51
+ ...result.classes,
52
+ ];
53
+ }
54
+ function buildSignature(record) {
55
+ switch (record.kind) {
56
+ case 'function':
57
+ case 'component': {
58
+ const params = record.inputs.join(', ');
59
+ const output = record.output ? ` => ${normalize(record.output)}` : '';
60
+ return `(${params})${output}`;
61
+ }
62
+ case 'type': {
63
+ const displayKind = record.typeKind === 'type-alias'
64
+ ? 'type alias'
65
+ : record.typeKind;
66
+ return normalize(`${displayKind} ${record.definition}`);
67
+ }
68
+ case 'const':
69
+ return normalize(record.value);
70
+ case 'class': {
71
+ const parts = [];
72
+ if (record.extends)
73
+ parts.push(`extends ${record.extends}`);
74
+ if (record.implements?.length) {
75
+ parts.push(`implements ${record.implements.join(', ')}`);
76
+ }
77
+ return normalize(parts.join(' '));
78
+ }
79
+ default:
80
+ return '';
81
+ }
82
+ }
83
+ function trimDocString(value, limit) {
84
+ if (!value) {
85
+ return '';
86
+ }
87
+ const normalized = normalize(value);
88
+ if (limit <= 0 || normalized.length <= limit) {
89
+ return normalized;
90
+ }
91
+ return `${normalized.slice(0, limit)}...`;
92
+ }
93
+ function normalize(value) {
94
+ return (value ?? '')
95
+ .replace(/\s+/g, ' ')
96
+ .trim();
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emeryld/manager",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Interactive manager for pnpm monorepos (update/test/build/publish).",
5
5
  "license": "MIT",
6
6
  "type": "module",