@solidactions/cli 1.23.1 → 1.25.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.
@@ -13,11 +13,11 @@ exports.login = login;
13
13
  exports.logout = logout;
14
14
  exports.whoami = whoami;
15
15
  const fs_1 = __importDefault(require("fs"));
16
+ const readline_1 = __importDefault(require("readline"));
16
17
  const chalk_1 = __importDefault(require("chalk"));
17
- const api_1 = require("../utils/api");
18
- const workspaces_1 = require("./workspaces");
19
18
  const config_1 = require("../utils/config");
20
19
  const config_write_target_1 = require("../utils/config-write-target");
20
+ const workspace_lookup_1 = require("../utils/workspace-lookup");
21
21
  function getConfig() {
22
22
  const resolved = (0, config_1.resolveConfig)();
23
23
  return resolved ? resolved.config : null;
@@ -60,6 +60,36 @@ function loginHostLines(resolved) {
60
60
  }
61
61
  return [`Host: ${resolved.host}`];
62
62
  }
63
+ /**
64
+ * Prompt the user to pick a workspace from an already-fetched list. Auto-
65
+ * selects when there's exactly one. Returns undefined if none exist.
66
+ */
67
+ async function selectWorkspaceInteractively(workspaces) {
68
+ if (workspaces.length === 0) {
69
+ console.log(chalk_1.default.yellow('No workspaces found. Create one at your SolidActions dashboard, then run `solidactions workspace set <name>`.'));
70
+ return undefined;
71
+ }
72
+ if (workspaces.length === 1) {
73
+ console.log(chalk_1.default.gray(`Auto-selected workspace: ${workspaces[0].name}`));
74
+ return workspaces[0];
75
+ }
76
+ console.log(chalk_1.default.blue('\nSelect a workspace:\n'));
77
+ workspaces.forEach((ws, i) => {
78
+ console.log(` ${chalk_1.default.white(`${i + 1}.`)} ${ws.name}`);
79
+ });
80
+ console.log('');
81
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
82
+ const answer = await new Promise((resolve) => {
83
+ rl.question(chalk_1.default.blue('Enter number: '), resolve);
84
+ });
85
+ rl.close();
86
+ const index = parseInt(answer, 10) - 1;
87
+ if (isNaN(index) || index < 0 || index >= workspaces.length) {
88
+ console.error(chalk_1.default.red('Invalid selection.'));
89
+ process.exit(1);
90
+ }
91
+ return workspaces[index];
92
+ }
63
93
  async function login(apiKey, options) {
64
94
  const resolved = resolveLoginHost(options);
65
95
  const host = resolved.host;
@@ -68,9 +98,6 @@ async function login(apiKey, options) {
68
98
  console.log(chalk_1.default.gray('Generate an API key at: ') + chalk_1.default.blue(`${host}/settings/api-keys`));
69
99
  process.exit(1);
70
100
  }
71
- // Determine target location.
72
- const target = await (0, config_write_target_1.decideWriteTarget)({ local: options.local, global: options.global }, undefined, LOGIN_REFUSAL_MESSAGE);
73
- const targetPath = (0, config_write_target_1.pathForTarget)(target);
74
101
  console.log(chalk_1.default.blue(`Initializing SolidActions CLI...`));
75
102
  for (const line of loginHostLines(resolved)) {
76
103
  console.log(resolved.isDefault ? chalk_1.default.yellow(line) : chalk_1.default.gray(line));
@@ -79,6 +106,45 @@ async function login(apiKey, options) {
79
106
  host,
80
107
  apiKey: apiKey.trim(),
81
108
  };
109
+ // 1. Validate the key BEFORE any disk write.
110
+ let workspaces;
111
+ try {
112
+ workspaces = await (0, workspace_lookup_1.fetchWorkspaces)(config);
113
+ }
114
+ catch (e) {
115
+ if (e.response?.status === 401) {
116
+ console.error(chalk_1.default.red(`Invalid API key for ${host}.`));
117
+ }
118
+ else {
119
+ console.error(chalk_1.default.red(`Could not reach ${host}: ${e.message}`));
120
+ }
121
+ process.exit(1);
122
+ return;
123
+ }
124
+ // 2. Resolve the workspace (still before writing).
125
+ if (options.workspace) {
126
+ const match = (0, workspace_lookup_1.matchWorkspace)(options.workspace, workspaces);
127
+ if (!match) {
128
+ console.error(chalk_1.default.red(`Workspace "${options.workspace}" not found. Run \`solidactions workspace list\` to list available workspaces.`));
129
+ process.exit(1);
130
+ return;
131
+ }
132
+ config.workspace = match.slug ?? match.name;
133
+ config.workspaceId = match.id;
134
+ }
135
+ else if (process.stdin.isTTY) {
136
+ const selected = await selectWorkspaceInteractively(workspaces);
137
+ if (selected) {
138
+ config.workspace = selected.slug ?? selected.name;
139
+ config.workspaceId = selected.id;
140
+ }
141
+ }
142
+ else {
143
+ console.log(chalk_1.default.yellow('No workspace set — run `solidactions workspace set <name>` later.'));
144
+ }
145
+ // 3. Determine target location, back up if needed, and write ONCE.
146
+ const target = await (0, config_write_target_1.decideWriteTarget)({ local: options.local, global: options.global }, undefined, LOGIN_REFUSAL_MESSAGE);
147
+ const targetPath = (0, config_write_target_1.pathForTarget)(target);
82
148
  const existingRaw = fs_1.default.existsSync(targetPath) ? fs_1.default.readFileSync(targetPath, 'utf-8') : null;
83
149
  const wouldChange = existingRaw !== null && existingRaw.trim() !== JSON.stringify(config, null, 2).trim();
84
150
  if (wouldChange) {
@@ -98,19 +164,6 @@ async function login(apiKey, options) {
98
164
  console.log(chalk_1.default.green('Logged in successfully!'));
99
165
  console.log(chalk_1.default.gray(`Configuration saved to ${targetPath}`));
100
166
  console.log('');
101
- // Workspace selection — ensureWorkspaceSelected writes to the right file via the resolver.
102
- try {
103
- if (options.workspace) {
104
- await (0, workspaces_1.workspaceSet)(options.workspace);
105
- }
106
- else {
107
- await (0, api_1.ensureWorkspaceSelected)(config);
108
- }
109
- }
110
- catch {
111
- console.log(chalk_1.default.yellow('Could not set workspace. Run `solidactions workspace set` later.'));
112
- }
113
- console.log('');
114
167
  console.log(chalk_1.default.blue('Next step — scaffold a new project (includes AI tooling):'));
115
168
  console.log(chalk_1.default.gray(' solidactions init <project-name> Creates ./<project-name>/ with scaffold + AI skills'));
116
169
  console.log(chalk_1.default.gray(' solidactions init Scaffolds in the current (empty) directory'));
@@ -146,7 +199,8 @@ function logout(options = {}) {
146
199
  console.log(chalk_1.default.green(`Logged out. Removed ${targetPath}`));
147
200
  }
148
201
  else {
149
- console.log(chalk_1.default.gray(`Not logged in (no config at ${targetPath}).`));
202
+ console.log(chalk_1.default.gray(`Not logged in (no config at ${targetPath}) — nothing to remove.`));
203
+ process.exit(0);
150
204
  }
151
205
  }
152
206
  function whoami() {
@@ -174,8 +228,10 @@ function whoami() {
174
228
  : config.workspaceId
175
229
  ? `${config.workspaceId} (slug unknown — run 'workspace set <slug>' to populate)`
176
230
  : '';
231
+ const isFileSource = (src) => src !== null && src !== 'env' && src !== 'cli';
232
+ const workspaceInheritedFromOtherFile = isFileSource(sources.workspaceId) && sources.workspaceId !== sources.apiKey;
177
233
  console.log(chalk_1.default.blue('Current configuration:'));
178
234
  console.log(` Host: ${config.host.padEnd(50)} ${fmt(sources.host)}`);
179
235
  console.log(` API Key: ${maskedKey.padEnd(50)} ${fmt(sources.apiKey)}`);
180
- console.log(` Workspace: ${workspaceLabel.padEnd(50)} ${fmt(sources.workspaceId)}`);
236
+ console.log(` Workspace: ${workspaceLabel.padEnd(50)} ${fmt(sources.workspaceId)}${workspaceInheritedFromOtherFile ? chalk_1.default.yellow(' (inherited from a different config file)') : ''}`);
181
237
  }
@@ -23,7 +23,7 @@ async function oauthActionsList(platform, options) {
23
23
  return;
24
24
  }
25
25
  if (actions.length === 0) {
26
- console.log('No actions found.');
26
+ console.log(`No actions found for platform "${platform}".`);
27
27
  return;
28
28
  }
29
29
  for (const a of actions) {
@@ -34,7 +34,10 @@ async function oauthActionsList(platform, options) {
34
34
  console.log(chalk_1.default.gray(`${actions.length} action(s) — use "solidactions oauth-actions search ${platform} <query>" to narrow, or "oauth-actions show ${platform} <action_id>" for detail.`));
35
35
  }
36
36
  catch (error) {
37
- if (error.response?.status === 401) {
37
+ if (error.response?.status === 404 && error.response.data?.code === 'platform_unknown') {
38
+ console.error(chalk_1.default.red(`Unknown platform "${platform}". Run \`solidactions oauth-actions platforms\` to see available platforms.`));
39
+ }
40
+ else if (error.response?.status === 401) {
38
41
  console.error(chalk_1.default.red('Authentication failed. Run "solidactions login <api-key>".'));
39
42
  }
40
43
  else if (error.response) {
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.oauthActionsPlatforms = oauthActionsPlatforms;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const api_1 = require("../utils/api");
10
+ async function oauthActionsPlatforms(options) {
11
+ const config = await (0, api_1.requireConfigWithWorkspace)();
12
+ try {
13
+ const response = await axios_1.default.get(`${config.host}/api/v1/oauth-actions/platforms`, {
14
+ headers: (0, api_1.getApiHeaders)(config),
15
+ });
16
+ const platforms = response.data.platforms || [];
17
+ if (options.json) {
18
+ process.stdout.write(JSON.stringify(platforms, null, 2) + '\n');
19
+ return;
20
+ }
21
+ if (platforms.length === 0) {
22
+ console.log('No platforms available.');
23
+ return;
24
+ }
25
+ for (const platform of platforms) {
26
+ console.log(platform);
27
+ }
28
+ }
29
+ catch (error) {
30
+ if (error.response?.status === 401) {
31
+ console.error(chalk_1.default.red('Authentication failed. Run "solidactions login <api-key>".'));
32
+ }
33
+ else if (error.response) {
34
+ console.error(chalk_1.default.red(`Failed: ${error.response.status}`), error.response.data);
35
+ }
36
+ else {
37
+ console.error(chalk_1.default.red('Connection failed:'), error.message);
38
+ }
39
+ process.exit(1);
40
+ }
41
+ }
@@ -27,7 +27,7 @@ async function oauthActionsSearch(platform, query, options) {
27
27
  return;
28
28
  }
29
29
  if (actions.length === 0) {
30
- console.log('No actions found.');
30
+ console.log(`No actions found for platform "${platform}".`);
31
31
  return;
32
32
  }
33
33
  for (const a of actions) {
@@ -45,7 +45,10 @@ async function oauthActionsSearch(platform, query, options) {
45
45
  console.log(chalk_1.default.gray(`Run 'solidactions oauth-actions show ${platform} <action_id>' for full schema + paste-ready snippet.`));
46
46
  }
47
47
  catch (error) {
48
- if (error.response?.status === 401) {
48
+ if (error.response?.status === 404 && error.response.data?.code === 'platform_unknown') {
49
+ console.error(chalk_1.default.red(`Unknown platform "${platform}". Run \`solidactions oauth-actions platforms\` to see available platforms.`));
50
+ }
51
+ else if (error.response?.status === 401) {
49
52
  console.error(chalk_1.default.red('Authentication failed. Run "solidactions login <api-key>" to re-configure.'));
50
53
  }
51
54
  else if (error.response) {
@@ -7,30 +7,36 @@ exports.projectList = projectList;
7
7
  const axios_1 = __importDefault(require("axios"));
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
9
  const api_1 = require("../utils/api");
10
- async function projectList() {
10
+ const table_1 = require("../utils/table");
11
+ async function projectList(options = {}) {
11
12
  const config = await (0, api_1.requireConfigWithWorkspace)();
12
- console.log(chalk_1.default.blue('Projects:\n'));
13
13
  try {
14
14
  const response = await axios_1.default.get(`${config.host}/api/v1/projects`, {
15
15
  headers: (0, api_1.getApiHeaders)(config),
16
16
  });
17
17
  const projects = response.data.data || [];
18
+ if (options.json) {
19
+ console.log(JSON.stringify(projects, null, 2));
20
+ return;
21
+ }
22
+ // Header is only printed after the fetch succeeds — an auth/network
23
+ // failure used to print "Projects:" followed immediately by the error.
24
+ console.log(chalk_1.default.blue('Projects:\n'));
18
25
  if (projects.length === 0) {
19
26
  console.log(chalk_1.default.gray('No projects found.'));
20
27
  return;
21
28
  }
22
- console.log(chalk_1.default.gray('NAME'.padEnd(25) + 'STATUS'.padEnd(15) + 'SNAPSHOT'.padEnd(30) + 'ENVIRONMENTS'));
23
- console.log(chalk_1.default.gray('-'.repeat(100)));
24
- for (const project of projects) {
25
- const name = (project.name || '?').padEnd(25);
26
- const status = project.status || '?';
27
- const statusColor = status === 'ready' ? chalk_1.default.green : status === 'building' ? chalk_1.default.yellow : chalk_1.default.gray;
28
- const snapshot = (project.snapshot_name || '-').padEnd(30);
29
+ const rows = projects.map((project) => {
29
30
  const envs = (project.environments || [project.environment || 'production']).join(', ');
30
- console.log(name +
31
- statusColor(status.padEnd(15)) +
32
- chalk_1.default.gray(snapshot) +
33
- chalk_1.default.gray(envs));
31
+ return [project.name || '?', project.status || '?', project.snapshot_name || '-', envs];
32
+ });
33
+ const lines = (0, table_1.renderTable)(['NAME', 'STATUS', 'SNAPSHOT', 'ENVIRONMENTS'], rows, {
34
+ minWidths: [25, 15, 30],
35
+ });
36
+ console.log(chalk_1.default.gray(lines[0]));
37
+ console.log(chalk_1.default.gray(lines[1]));
38
+ for (const line of lines.slice(2)) {
39
+ console.log(line);
34
40
  }
35
41
  console.log('');
36
42
  console.log(chalk_1.default.gray(`${projects.length} project(s)`));
@@ -45,8 +45,23 @@ async function pull(projectName, destPath, options = {}) {
45
45
  console.log(chalk_1.default.gray(`Downloaded ${buffer.length} bytes`));
46
46
  console.log(chalk_1.default.yellow(`Extracting to ${destination}...`));
47
47
  fs_1.default.mkdirSync(destination, { recursive: true });
48
+ // The server streams back the exact archive `deploy` uploaded: a
49
+ // root-level Dockerfile plus every project file nested under
50
+ // tenantcode/ (see deploy.ts:510-527). Unwrap it here so the pulled
51
+ // project's files land at dest root, matching what the user actually
52
+ // wrote — dropping the generated Dockerfile and any noCache
53
+ // cache-buster entries (deploy.ts's cacheBusterEntry).
48
54
  const readable = stream_1.Readable.from(buffer);
49
- await (0, promises_1.pipeline)(readable, (0, zlib_1.createGunzip)(), (0, tar_1.extract)({ cwd: destination, strip: 0 }));
55
+ await (0, promises_1.pipeline)(readable, (0, zlib_1.createGunzip)(), (0, tar_1.extract)({
56
+ cwd: destination,
57
+ strip: 1,
58
+ // `filter` receives the pre-strip path (verified against this
59
+ // version of the `tar` package).
60
+ filter: (entryPath) => {
61
+ const top = entryPath.split('/')[0];
62
+ return top === 'tenantcode' && !/\/sa-nocache-[0-9a-f-]+$/.test(entryPath);
63
+ },
64
+ }));
50
65
  console.log(chalk_1.default.green(`Project "${projectName}" pulled successfully!`));
51
66
  }
52
67
  catch (error) {
@@ -69,7 +69,7 @@ async function rolePushWithConfig(dir, options, config) {
69
69
  readResult = await (0, mcp_1.callCrewsTool)(config, 'roles', { action: 'read', name });
70
70
  }
71
71
  catch (e) {
72
- process.stderr.write(chalk_1.default.red(`error: MCP request failed — ${e.message}\n`));
72
+ process.stderr.write(chalk_1.default.red(`error: ${e.message}\n`));
73
73
  process.exit(1);
74
74
  }
75
75
  if (!readResult.ok) {
@@ -110,7 +110,7 @@ async function rolePushWithConfig(dir, options, config) {
110
110
  });
111
111
  }
112
112
  catch (e) {
113
- process.stderr.write(chalk_1.default.red(`error: MCP request failed — ${e.message}\n`));
113
+ process.stderr.write(chalk_1.default.red(`error: ${e.message}\n`));
114
114
  process.exit(1);
115
115
  }
116
116
  // On name collision, switch to the edit path.
@@ -126,7 +126,7 @@ async function rolePushWithConfig(dir, options, config) {
126
126
  });
127
127
  }
128
128
  catch (e) {
129
- process.stderr.write(chalk_1.default.red(`error: MCP request failed — ${e.message}\n`));
129
+ process.stderr.write(chalk_1.default.red(`error: ${e.message}\n`));
130
130
  process.exit(1);
131
131
  }
132
132
  if (!result.ok) {
@@ -10,6 +10,7 @@ exports.detailedOutcomeTag = detailedOutcomeTag;
10
10
  const axios_1 = __importDefault(require("axios"));
11
11
  const chalk_1 = __importDefault(require("chalk"));
12
12
  const api_1 = require("../utils/api");
13
+ const table_1 = require("../utils/table");
13
14
  /**
14
15
  * Build the params object for GET /api/v1/runs.
15
16
  * Extracted as a pure function so it can be unit-tested without mocking axios.
@@ -124,19 +125,30 @@ function displaySummaryTable(runsList, projectName) {
124
125
  const header = projectName ? `Recent runs for "${projectName}":` : 'Recent runs:';
125
126
  console.log(chalk_1.default.blue(header));
126
127
  console.log('');
127
- console.log(chalk_1.default.gray('ID'.padEnd(8) + 'WORKFLOW'.padEnd(25) + 'STATUS'.padEnd(12) + 'TRIGGERED'.padEnd(22) + 'TRIGGERED BY'));
128
- console.log(chalk_1.default.gray('-'.repeat(90)));
129
- for (const run of runsList) {
130
- const id = String(run.id || '?').padEnd(8);
131
- const workflow = truncate(run.workflow_name || '?', 24).padEnd(25);
132
- const { label: status, attention } = summaryStatusLabel(run);
133
- const statusColor = attention ? chalk_1.default.yellow : getStatusColor(status);
128
+ const headers = ['ID', 'WORKFLOW', 'STATUS', 'TRIGGERED', 'TRIGGERED BY'];
129
+ const rows = runsList.map((run) => {
130
+ const { label: status } = summaryStatusLabel(run);
134
131
  const triggeredAt = run.triggered_at ? new Date(run.triggered_at).toLocaleString() : '-';
135
- const triggeredBy = run.triggered_by || '-';
136
- console.log(chalk_1.default.gray(id) +
137
- workflow +
138
- statusColor(status.padEnd(12)) +
139
- chalk_1.default.gray(triggeredAt.padEnd(22)) +
132
+ return [
133
+ String(run.id || '?'),
134
+ (0, table_1.sanitizeCell)((0, table_1.truncateCell)(run.workflow_name || '?', 24)),
135
+ status,
136
+ triggeredAt,
137
+ (0, table_1.sanitizeCell)(run.triggered_by || '-'),
138
+ ];
139
+ });
140
+ const widths = (0, table_1.computeColumnWidths)(headers, rows, { minWidths: [8, 25, 12, 22, 0] });
141
+ console.log(chalk_1.default.gray(headers.map((h, i) => h.padEnd(widths[i])).join('')));
142
+ console.log(chalk_1.default.gray('-'.repeat(widths.reduce((a, b) => a + b, 0))));
143
+ for (let i = 0; i < runsList.length; i++) {
144
+ const run = runsList[i];
145
+ const [id, workflow, status, triggeredAt, triggeredBy] = rows[i];
146
+ const { attention } = summaryStatusLabel(run);
147
+ const statusColor = attention ? chalk_1.default.yellow : getStatusColor(status);
148
+ console.log(chalk_1.default.gray(id.padEnd(widths[0])) +
149
+ workflow.padEnd(widths[1]) +
150
+ statusColor(status.padEnd(widths[2])) +
151
+ chalk_1.default.gray(triggeredAt.padEnd(widths[3])) +
140
152
  chalk_1.default.gray(triggeredBy));
141
153
  }
142
154
  console.log('');
@@ -72,7 +72,13 @@ async function run(projectName, workflowName, options) {
72
72
  console.error(chalk_1.default.red('Authentication failed. Run "solidactions login <api-key>" to re-configure.'));
73
73
  }
74
74
  else if (error.response.status === 404) {
75
- console.error(chalk_1.default.red('Project or workflow not found.'));
75
+ const envsList = await (0, api_1.describeProjectEnvironments)(config, projectName);
76
+ if (envsList) {
77
+ console.error(chalk_1.default.red(`Project "${projectName}" has no ${environment} environment (exists in: ${envsList}). Pass -e <env> to target a different environment.`));
78
+ }
79
+ else {
80
+ console.error(chalk_1.default.red('Project or workflow not found.'));
81
+ }
76
82
  }
77
83
  else if (error.response.status === 422) {
78
84
  console.error(chalk_1.default.red('Validation error:'), error.response.data.message);
@@ -12,6 +12,11 @@ const axios_1 = __importDefault(require("axios"));
12
12
  const chalk_1 = __importDefault(require("chalk"));
13
13
  const api_1 = require("../utils/api");
14
14
  async function runView(runId, options) {
15
+ if (!/^\d+$/.test(runId)) {
16
+ console.error(chalk_1.default.red(`Invalid run id: "${runId}" (run ids are numeric — see \`solidactions run list\`).`));
17
+ process.exit(1);
18
+ return;
19
+ }
15
20
  const config = await (0, api_1.requireConfigWithWorkspace)();
16
21
  try {
17
22
  // Fetch run data (includes session timing)
@@ -42,7 +42,7 @@ async function skillPullWithConfig(name, dest, options, config) {
42
42
  result = await (0, mcp_1.callCrewsTool)(config, 'skills', { action: 'read', identifier: name });
43
43
  }
44
44
  catch (e) {
45
- process.stderr.write(chalk_1.default.red(`error: MCP request failed — ${e.message}\n`));
45
+ process.stderr.write(chalk_1.default.red(`error: ${e.message}\n`));
46
46
  process.exit(1);
47
47
  }
48
48
  if (!result.ok) {
@@ -193,7 +193,7 @@ async function pushParsedSkill(payload, options, config) {
193
193
  readResult = await (0, mcp_1.callCrewsTool)(config, tool, readArgs);
194
194
  }
195
195
  catch (e) {
196
- throw new Error(`MCP request failed — ${e.message}`);
196
+ throw new Error(`${e.message}`);
197
197
  }
198
198
  if (!readResult.ok) {
199
199
  const code = readResult.data?.code;
@@ -215,7 +215,7 @@ async function pushParsedSkill(payload, options, config) {
215
215
  result = await (0, mcp_1.callCrewsTool)(config, tool, createArgs);
216
216
  }
217
217
  catch (e) {
218
- throw new Error(`MCP request failed — ${e.message}`);
218
+ throw new Error(`${e.message}`);
219
219
  }
220
220
  // On name collision, switch to the edit path. properties → properties_patch.
221
221
  if (!result.ok && result.data?.code === 'name_collision') {
@@ -226,7 +226,7 @@ async function pushParsedSkill(payload, options, config) {
226
226
  result = await (0, mcp_1.callCrewsTool)(config, tool, editArgs);
227
227
  }
228
228
  catch (e) {
229
- throw new Error(`MCP request failed — ${e.message}`);
229
+ throw new Error(`${e.message}`);
230
230
  }
231
231
  if (!result.ok) {
232
232
  const errData = result.data;
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ const ai_examples_1 = require("./commands/ai-examples");
33
33
  const oauth_actions_search_1 = require("./commands/oauth-actions-search");
34
34
  const oauth_actions_list_1 = require("./commands/oauth-actions-list");
35
35
  const oauth_actions_show_1 = require("./commands/oauth-actions-show");
36
+ const oauth_actions_platforms_1 = require("./commands/oauth-actions-platforms");
36
37
  const workspaces_1 = require("./commands/workspaces");
37
38
  const skill_push_1 = require("./commands/skill-push");
38
39
  const skill_pull_1 = require("./commands/skill-pull");
@@ -71,17 +72,23 @@ program
71
72
  .name('solidactions')
72
73
  .description('SolidActions CLI - Deploy and manage workflow automation')
73
74
  .version(pkg.version);
74
- program.option('-w, --workspace <id-or-slug-or-name>', 'override active workspace for this command');
75
+ // Long form is --workspace-override (NOT --workspace) `login` already owns
76
+ // `--workspace` for its own purpose (set the workspace at login time), and
77
+ // commander silently drops a subcommand option whose long name collides with
78
+ // a parent option of the same name, so `login --workspace <x>` was being
79
+ // swallowed by this global flag instead of reaching login()'s own option.
80
+ // The short form `-w` is unaffected and remains the primary way to use this.
81
+ program.option('-w, --workspace-override <id-or-slug-or-name>', 'Override active workspace for this command (short form: -w)');
75
82
  program.hook('preAction', (thisCommand, actionCommand) => {
76
83
  const opts = thisCommand.opts();
77
- const wsOverride = opts.workspace;
84
+ const wsOverride = opts.workspaceOverride;
78
85
  if (wsOverride) {
79
86
  // workspace set is a write — -w is for read-paths only.
80
87
  const fullName = actionCommand.name();
81
88
  const parentName = actionCommand.parent?.name?.();
82
89
  const isWorkspaceSet = fullName === 'set' && parentName === 'workspace';
83
90
  if (isWorkspaceSet) {
84
- console.error(chalk_1.default.yellow('warn:') + ' -w/--workspace is ignored on `workspace set`; the positional argument is the new workspace.');
91
+ console.error(chalk_1.default.yellow('warn:') + ' -w/--workspace-override is ignored on `workspace set`; the positional argument is the new workspace.');
85
92
  return;
86
93
  }
87
94
  (0, config_1.setCliWorkspaceOverride)(wsOverride);
@@ -96,7 +103,7 @@ program
96
103
  .argument('<api-key>', 'Your SolidActions API key')
97
104
  .option('--dev', 'Use local development server (http://localhost:8000)')
98
105
  .option('--host <url>', 'Custom API host URL')
99
- .option('--workspace <name-or-id>', 'Set workspace by name, slug, or ID (skips interactive prompt)')
106
+ .option('--workspace <name-or-id>', 'Set workspace by name, slug, or ID (skips interactive prompt). Non-interactive logins without this flag leave the workspace unset.')
100
107
  .option('--local', 'Save config to ./.solidactions/config.json in the current folder')
101
108
  .option('--global', 'Save config to ~/.solidactions/config.json (default if prompted)')
102
109
  .option('--gitignore', 'With --local, add .solidactions/ to .gitignore without prompting')
@@ -180,15 +187,18 @@ project
180
187
  .command('logs')
181
188
  .description('View build/deployment logs for a project')
182
189
  .argument('<project>', 'Project name (or family name with -e)')
183
- .option('-e, --environment <environment>', 'Environment to resolve (production/staging/dev)')
190
+ .option('-e, --env <environment>', 'Environment to resolve (production/staging/dev)')
191
+ .addOption(new commander_1.Option('--environment <environment>', 'Alias of --env').hideHelp())
184
192
  .action((projectName, options) => {
185
- (0, project_logs_1.logsBuild)(projectName, options.environment);
193
+ const environment = options.env ?? options.environment;
194
+ (0, project_logs_1.logsBuild)(projectName, environment);
186
195
  });
187
196
  project
188
197
  .command('list')
189
198
  .description('List all projects')
190
- .action(() => {
191
- (0, project_list_1.projectList)();
199
+ .option('--json', 'Output as JSON')
200
+ .action((options) => {
201
+ (0, project_list_1.projectList)(options);
192
202
  });
193
203
  // =============================================================================
194
204
  // run <subcommand>
@@ -201,7 +211,7 @@ runCmd
201
211
  .argument('<workflow>', 'Workflow name')
202
212
  .option('-e, --env <environment>', 'Environment (production/staging/dev)', 'dev')
203
213
  .option('-i, --input <json>', 'JSON input for the workflow')
204
- .option('-w, --wait', 'Wait for the workflow to complete')
214
+ .option('--wait', 'Wait for the workflow to complete')
205
215
  .action((projectName, workflow, options) => {
206
216
  (0, run_start_1.run)(projectName, workflow, options);
207
217
  });
@@ -217,8 +227,10 @@ runCmd
217
227
  .option('--detailed', 'Include timeline, steps, and logs per run (default limit: 5)')
218
228
  .option('--has-errors', 'Show only runs with errors (step errors, retries, or degraded results)')
219
229
  .option('--json', 'Output as JSON')
220
- .option('-e, --environment <environment>', 'Environment to filter by (production/staging/dev)')
230
+ .option('-e, --env <environment>', 'Environment to filter by (production/staging/dev)')
231
+ .addOption(new commander_1.Option('--environment <environment>', 'Alias of --env').hideHelp())
221
232
  .action((projectName, options) => {
233
+ options.environment = options.env ?? options.environment;
222
234
  (0, run_list_1.runs)(projectName, options);
223
235
  });
224
236
  runCmd
@@ -235,10 +247,10 @@ runCmd
235
247
  // =============================================================================
236
248
  // env <subcommand>
237
249
  // =============================================================================
238
- const env = program.command('env').description('Manage environment variables');
250
+ const env = program.command('env').description('Manage variables');
239
251
  env
240
252
  .command('set')
241
- .description('Set an environment variable (create or update, global or project)')
253
+ .description('Set a variable (create or update, global or project)')
242
254
  .argument('<key-or-project>', 'Variable key (global) or project name')
243
255
  .argument('<value-or-key>', 'Variable value (global) or variable key (project)')
244
256
  .argument('[value]', 'Variable value (when first arg is project)')
@@ -256,17 +268,19 @@ env
256
268
  });
257
269
  env
258
270
  .command('list')
259
- .description('List environment variables')
271
+ .description('List variables')
260
272
  .argument('[project]', 'Project name (omit for global variables)')
261
273
  .option('-e, --env <environment>', 'Filter by environment (production/staging/dev)')
274
+ .option('--json', 'Output as JSON')
262
275
  .action((projectName, options) => {
263
276
  (0, env_list_1.envList)(projectName, options);
264
277
  });
265
278
  env
266
279
  .command('delete')
267
- .description('Delete an environment variable')
280
+ .description('Delete a variable')
268
281
  .argument('<key-or-project>', 'Variable key (global) or project name')
269
282
  .argument('[key]', 'Variable key (if first arg is project)')
283
+ .option('-e, --env <environment>', 'Environment to delete from', 'dev')
270
284
  .option('-y, --yes', 'Skip confirmation prompt')
271
285
  .action((keyOrProject, key, options) => {
272
286
  (0, env_delete_1.envDelete)(keyOrProject, key, options);
@@ -283,7 +297,7 @@ env
283
297
  });
284
298
  env
285
299
  .command('pull')
286
- .description('Pull resolved environment variables to a local file')
300
+ .description('Pull resolved variables to a local file')
287
301
  .argument('<project>', 'Project name')
288
302
  .option('-e, --env <environment>', 'Environment (production/staging/dev)', 'dev')
289
303
  .option('-o, --output <file>', 'Output file path (defaults to .env or .env.{environment})')
@@ -294,7 +308,7 @@ env
294
308
  });
295
309
  env
296
310
  .command('push')
297
- .description('Push environment variables from .env file to a project')
311
+ .description('Push variables from .env file to a project')
298
312
  .argument('<project>', 'Project name')
299
313
  .argument('[path]', 'Source directory with solidactions.yaml and .env file', '.')
300
314
  .option('-e, --env <environment>', 'Target environment (production/staging/dev)', 'dev')
@@ -313,7 +327,7 @@ schedule
313
327
  .description('Set a cron schedule for a workflow')
314
328
  .argument('<project>', 'Project name')
315
329
  .argument('<cron>', 'Cron expression (e.g., "0 9 * * *" for daily at 9am)')
316
- .option('-w, --workflow <name>', 'Workflow name (if project has multiple)')
330
+ .option('--workflow <name>', 'Workflow name (if project has multiple)')
317
331
  .option('-i, --input <json>', 'JSON input to pass to scheduled runs')
318
332
  .option('-z, --timezone <iana>', 'IANA timezone the cron is evaluated in (e.g. America/Chicago); defaults to UTC')
319
333
  .option('-y, --yes', 'Skip confirmation if schedule already exists')
@@ -410,6 +424,13 @@ oauthActionsCmd
410
424
  .action((platform, actionId, options) => {
411
425
  (0, oauth_actions_show_1.oauthActionsShow)(platform, actionId, options);
412
426
  });
427
+ oauthActionsCmd
428
+ .command('platforms')
429
+ .description('List available OAuth-backed platforms')
430
+ .option('--json', 'Emit raw JSON for AI/script consumption')
431
+ .action((options) => {
432
+ (0, oauth_actions_platforms_1.oauthActionsPlatforms)(options);
433
+ });
413
434
  // =============================================================================
414
435
  // ai <subcommand>
415
436
  // =============================================================================