@solidactions/cli 1.23.1 → 1.24.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.
@@ -151,8 +151,23 @@ function mergeConfigs(env, local, localPath, global, globalPath) {
151
151
  };
152
152
  const host = pick('host');
153
153
  const apiKey = pick('apiKey');
154
- const workspace = pick('workspace');
155
- const workspaceId = pick('workspaceId');
154
+ // A local config that defines its own credentials (host or apiKey) is
155
+ // anchored to a different account/tenant than the global config — its
156
+ // workspace/workspaceId must never fall back to global (F-C3).
157
+ // Pure workspace-pin local files (no host/apiKey of their own) keep
158
+ // inheriting global credentials and so may still inherit the workspace.
159
+ const localDefinesCreds = !!(local && (local.host !== undefined || local.apiKey !== undefined));
160
+ const pickWorkspaceField = (key) => {
161
+ if (env[key] !== undefined)
162
+ return { value: env[key], source: 'env' };
163
+ if (local && local[key] !== undefined)
164
+ return { value: local[key], source: localPath };
165
+ if (!localDefinesCreds && global && global[key] !== undefined)
166
+ return { value: global[key], source: globalPath };
167
+ return { value: undefined, source: null };
168
+ };
169
+ const workspace = pickWorkspaceField('workspace');
170
+ const workspaceId = pickWorkspaceField('workspaceId');
156
171
  if (!host.value && !apiKey.value) {
157
172
  return null;
158
173
  }
package/dist/utils/env.js CHANGED
@@ -10,6 +10,8 @@ exports.getYamlDeclaredVars = getYamlDeclaredVars;
10
10
  exports.loadSolidActionsConfig = loadSolidActionsConfig;
11
11
  exports.isReservedEnvName = isReservedEnvName;
12
12
  exports.reservedEnvNameError = reservedEnvNameError;
13
+ exports.isValidEnvName = isValidEnvName;
14
+ exports.envNameError = envNameError;
13
15
  const fs_1 = __importDefault(require("fs"));
14
16
  const js_yaml_1 = __importDefault(require("js-yaml"));
15
17
  /**
@@ -81,7 +83,7 @@ function parseYamlEnvVars(config) {
81
83
  }
82
84
  /**
83
85
  * Extract declared variable keys from solidactions.yaml env config.
84
- * Returns a set of env var keys that are declared in YAML.
86
+ * Returns a set of variable keys that are declared in YAML.
85
87
  */
86
88
  function getYamlDeclaredVars(config) {
87
89
  const parsedVars = parseYamlEnvVars(config);
@@ -103,10 +105,20 @@ function loadSolidActionsConfig(yamlPath) {
103
105
  * matching the server-side rule (App\Support\ReservedEnvNames).
104
106
  */
105
107
  exports.RESERVED_ENV_PREFIX = 'SOLIDACTIONS_';
108
+ /** Case-insensitive: the server rejects any casing of the prefix, not just uppercase. */
106
109
  function isReservedEnvName(key) {
107
- return key.startsWith(exports.RESERVED_ENV_PREFIX);
110
+ return key.toUpperCase().startsWith(exports.RESERVED_ENV_PREFIX);
108
111
  }
109
112
  function reservedEnvNameError(key) {
110
- const suggestion = key.replace(/^SOLIDACTIONS_/, 'MY_');
113
+ const suggestion = key.replace(/^SOLIDACTIONS_/i, 'MY_');
111
114
  return `"${key}" uses the reserved ${exports.RESERVED_ENV_PREFIX} prefix. These names are set by the platform at runtime (API credentials, run context) and a custom variable would clobber them, causing authentication failures. Choose a different name (e.g. "${suggestion}").`;
112
115
  }
116
+ /** Env/variable key naming rule: letters, digits, underscore; no leading digit. */
117
+ function isValidEnvName(key) {
118
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
119
+ }
120
+ function envNameError(key) {
121
+ if (key.trim() === '')
122
+ return 'Variable key is required.';
123
+ return `Invalid variable name "${key}" — names must match [A-Za-z_][A-Za-z0-9_]* (letters, digits, underscore; no leading digit).`;
124
+ }
package/dist/utils/mcp.js CHANGED
@@ -46,6 +46,9 @@ const http = __importStar(require("http"));
46
46
  const https = __importStar(require("https"));
47
47
  const url_1 = require("url");
48
48
  const api_1 = require("./api");
49
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
50
+ const pkg = require('../../package.json');
51
+ const CLI_VERSION = pkg.version;
49
52
  /**
50
53
  * Internal: call a single MCP tool on the given endpoint path.
51
54
  *
@@ -85,7 +88,10 @@ async function callMcpTool(config, endpointPath, toolName, args) {
85
88
  let raw = '';
86
89
  res.on('data', (chunk) => { raw += chunk; });
87
90
  res.on('end', () => {
88
- if (res.statusCode && res.statusCode >= 400) {
91
+ if (res.statusCode === 404) {
92
+ reject(new Error(`MCP request failed: ${parsed.host} has no ${endpointPath} endpoint — the server may be older or newer than this CLI (${CLI_VERSION}). Raw: HTTP 404 ${raw}`));
93
+ }
94
+ else if (res.statusCode && res.statusCode >= 400) {
89
95
  reject(new Error(`MCP request failed with HTTP ${res.statusCode}: ${raw}`));
90
96
  }
91
97
  else {
@@ -116,15 +122,19 @@ async function callMcpTool(config, endpointPath, toolName, args) {
116
122
  }
117
123
  return { ok: !isError, data: toolData };
118
124
  }
125
+ const UNIFIED_MCP_PATH = '/mcp';
126
+ // Server consolidated per-domain MCP servers into one endpoint with namespaced tools.
127
+ const CREWS_TOOL_NAMES = { skills: 'crews_skills', roles: 'crews_roles' };
119
128
  /**
120
- * Call a single MCP tool on /mcp/crews.
129
+ * Call a single MCP tool on the unified /mcp endpoint, mapping legacy crews
130
+ * tool names ('skills'/'roles') to their namespaced equivalents.
121
131
  */
122
132
  async function callCrewsTool(config, toolName, args) {
123
- return callMcpTool(config, '/mcp/crews', toolName, args);
133
+ return callMcpTool(config, UNIFIED_MCP_PATH, CREWS_TOOL_NAMES[toolName] ?? toolName, args);
124
134
  }
125
135
  /**
126
- * Call a single MCP tool on /mcp/docs.
136
+ * Call a single MCP tool on the unified /mcp endpoint.
127
137
  */
128
138
  async function callDocsTool(config, toolName, args) {
129
- return callMcpTool(config, '/mcp/docs', toolName, args);
139
+ return callMcpTool(config, UNIFIED_MCP_PATH, toolName, args);
130
140
  }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ // src/utils/table.ts
3
+ //
4
+ // Shared plain-text table renderer (extracted from webhook-formatters.ts's
5
+ // columnWidth helper) used by project-list, env-list, and run-list.
6
+ //
7
+ // Operates on PLAIN (uncolored) cell values only — chalk ANSI escape codes
8
+ // count toward a JS string's .length, so colorizing a cell before padding
9
+ // would corrupt the alignment math. Callers that want color should apply it
10
+ // to the whole rendered line (e.g. chalk.gray(lines[i])), not to individual
11
+ // cell values passed in here.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.sanitizeCell = sanitizeCell;
14
+ exports.truncateCell = truncateCell;
15
+ exports.computeColumnWidths = computeColumnWidths;
16
+ exports.renderTable = renderTable;
17
+ const DEFAULT_MAX_WIDTH = 60;
18
+ /** Escape embedded newlines so a row can never span multiple terminal lines. */
19
+ function sanitizeCell(value) {
20
+ return value.replace(/\r?\n/g, '\\n');
21
+ }
22
+ function truncateCell(value, maxWidth = DEFAULT_MAX_WIDTH) {
23
+ if (value.length <= maxWidth)
24
+ return value;
25
+ return value.slice(0, Math.max(0, maxWidth - 1)) + '…';
26
+ }
27
+ /**
28
+ * Compute per-column widths (`max(minWidth, longest sanitized+truncated cell) + 2`)
29
+ * without rendering — for callers (e.g. run-list) that need to colorize a
30
+ * cell's text with chalk AFTER padding (coloring first would corrupt the
31
+ * alignment math, since ANSI escape codes count toward a JS string's .length).
32
+ */
33
+ function computeColumnWidths(headers, rows, opts = {}) {
34
+ const maxWidth = opts.maxWidth ?? DEFAULT_MAX_WIDTH;
35
+ const minWidths = opts.minWidths ?? [];
36
+ const clean = (v) => truncateCell(sanitizeCell(v), maxWidth);
37
+ return headers.map((h, i) => {
38
+ const longest = Math.max(clean(h).length, ...rows.map((r) => clean(r[i] ?? '').length));
39
+ return Math.max(minWidths[i] ?? 0, longest) + 2;
40
+ });
41
+ }
42
+ /**
43
+ * Render a simple aligned table as an array of lines: `[header, divider, ...rows]`.
44
+ * Column widths are dynamic — `max(minWidth, longest cell in that column) + 2`.
45
+ */
46
+ function renderTable(headers, rows, opts = {}) {
47
+ const maxWidth = opts.maxWidth ?? DEFAULT_MAX_WIDTH;
48
+ const minWidths = opts.minWidths ?? [];
49
+ const cleanHeaders = headers.map((h) => truncateCell(sanitizeCell(h), maxWidth));
50
+ const cleanRows = rows.map((row) => row.map((cell) => truncateCell(sanitizeCell(cell), maxWidth)));
51
+ const widths = cleanHeaders.map((h, i) => {
52
+ const longest = Math.max(h.length, ...cleanRows.map((r) => r[i]?.length ?? 0));
53
+ return Math.max(minWidths[i] ?? 0, longest) + 2;
54
+ });
55
+ const renderRow = (cells) => cells.map((cell, i) => (i === cells.length - 1 ? cell : (cell ?? '').padEnd(widths[i]))).join('');
56
+ const totalWidth = widths.reduce((a, b) => a + b, 0);
57
+ return [
58
+ renderRow(cleanHeaders),
59
+ '-'.repeat(totalWidth),
60
+ ...cleanRows.map(renderRow),
61
+ ];
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidactions/cli",
3
- "version": "1.23.1",
3
+ "version": "1.24.0",
4
4
  "description": "SolidActions CLI - Deploy and manage workflow automation",
5
5
  "main": "dist/index.js",
6
6
  "bin": {