@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.
- package/README.md +2 -2
- package/dist/commands/ai-init.js +44 -29
- package/dist/commands/deploy.js +1 -1
- package/dist/commands/dev.js +25 -2
- package/dist/commands/docs-push.js +1 -1
- package/dist/commands/env-delete.js +18 -10
- package/dist/commands/env-list.js +40 -29
- package/dist/commands/env-pull.js +6 -4
- package/dist/commands/env-push.js +4 -4
- package/dist/commands/env-set.js +12 -3
- package/dist/commands/init.js +18 -1
- package/dist/commands/login.js +76 -20
- package/dist/commands/oauth-actions-list.js +5 -2
- package/dist/commands/oauth-actions-platforms.js +41 -0
- package/dist/commands/oauth-actions-search.js +5 -2
- package/dist/commands/project-list.js +19 -13
- package/dist/commands/pull.js +16 -1
- package/dist/commands/role-push.js +3 -3
- package/dist/commands/run-list.js +24 -12
- package/dist/commands/run-start.js +7 -1
- package/dist/commands/run-view.js +5 -0
- package/dist/commands/skill-pull.js +1 -1
- package/dist/commands/skill-push.js +3 -3
- package/dist/index.js +38 -17
- package/dist/utils/api.js +70 -1
- package/dist/utils/config-write-target.js +16 -0
- package/dist/utils/config.js +17 -2
- package/dist/utils/env.js +15 -3
- package/dist/utils/mcp.js +15 -5
- package/dist/utils/table.js +62 -0
- package/package.json +1 -1
package/dist/commands/login.js
CHANGED
|
@@ -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(
|
|
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 ===
|
|
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(
|
|
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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)`));
|
package/dist/commands/pull.js
CHANGED
|
@@ -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)({
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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, --
|
|
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
|
-
|
|
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
|
-
.
|
|
191
|
-
|
|
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('
|
|
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, --
|
|
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
|
|
250
|
+
const env = program.command('env').description('Manage variables');
|
|
239
251
|
env
|
|
240
252
|
.command('set')
|
|
241
|
-
.description('Set
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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('
|
|
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
|
// =============================================================================
|