@solidactions/cli 1.23.0 → 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.
- package/README.md +2 -0
- 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 +102 -23
- package/dist/commands/oauth-actions-list.js +1 -1
- package/dist/commands/oauth-actions-search.js +1 -1
- 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 +29 -16
- package/dist/utils/api.js +70 -1
- package/dist/utils/config-write-target.js +41 -2
- 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
|
@@ -7,15 +7,17 @@ exports.getConfig = getConfig;
|
|
|
7
7
|
exports.saveConfig = saveConfig;
|
|
8
8
|
exports.clearConfig = clearConfig;
|
|
9
9
|
exports.resolveLoginHost = resolveLoginHost;
|
|
10
|
+
exports.backupPathFor = backupPathFor;
|
|
10
11
|
exports.loginHostLines = loginHostLines;
|
|
11
12
|
exports.login = login;
|
|
12
13
|
exports.logout = logout;
|
|
13
14
|
exports.whoami = whoami;
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
const readline_1 = __importDefault(require("readline"));
|
|
14
17
|
const chalk_1 = __importDefault(require("chalk"));
|
|
15
|
-
const api_1 = require("../utils/api");
|
|
16
|
-
const workspaces_1 = require("./workspaces");
|
|
17
18
|
const config_1 = require("../utils/config");
|
|
18
19
|
const config_write_target_1 = require("../utils/config-write-target");
|
|
20
|
+
const workspace_lookup_1 = require("../utils/workspace-lookup");
|
|
19
21
|
function getConfig() {
|
|
20
22
|
const resolved = (0, config_1.resolveConfig)();
|
|
21
23
|
return resolved ? resolved.config : null;
|
|
@@ -37,6 +39,18 @@ function resolveLoginHost(options) {
|
|
|
37
39
|
}
|
|
38
40
|
return { host: 'https://app.solidactions.com', isDefault: true };
|
|
39
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Path for a timestamped backup of `targetPath`, e.g.
|
|
44
|
+
* `config.json.bak-2026-07-05T12-30-00Z`. Pure — takes `now` so tests are
|
|
45
|
+
* deterministic.
|
|
46
|
+
*/
|
|
47
|
+
function backupPathFor(targetPath, now = new Date()) {
|
|
48
|
+
const timestamp = now.toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/:/g, '-');
|
|
49
|
+
return `${targetPath}.bak-${timestamp}`;
|
|
50
|
+
}
|
|
51
|
+
const LOGIN_REFUSAL_MESSAGE = 'Refusing to write config non-interactively. Pass --global to update the machine-wide config ' +
|
|
52
|
+
'at ~/.solidactions/config.json (a backup is kept if one exists), or --local to write ' +
|
|
53
|
+
'./.solidactions/config.json for just this directory.';
|
|
40
54
|
function loginHostLines(resolved) {
|
|
41
55
|
if (resolved.isDefault) {
|
|
42
56
|
return [
|
|
@@ -46,6 +60,36 @@ function loginHostLines(resolved) {
|
|
|
46
60
|
}
|
|
47
61
|
return [`Host: ${resolved.host}`];
|
|
48
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
|
+
}
|
|
49
93
|
async function login(apiKey, options) {
|
|
50
94
|
const resolved = resolveLoginHost(options);
|
|
51
95
|
const host = resolved.host;
|
|
@@ -54,39 +98,71 @@ async function login(apiKey, options) {
|
|
|
54
98
|
console.log(chalk_1.default.gray('Generate an API key at: ') + chalk_1.default.blue(`${host}/settings/api-keys`));
|
|
55
99
|
process.exit(1);
|
|
56
100
|
}
|
|
57
|
-
// Determine target location.
|
|
58
|
-
const target = await (0, config_write_target_1.decideWriteTarget)({ local: options.local, global: options.global });
|
|
59
|
-
const targetPath = (0, config_write_target_1.pathForTarget)(target);
|
|
60
101
|
console.log(chalk_1.default.blue(`Initializing SolidActions CLI...`));
|
|
61
102
|
for (const line of loginHostLines(resolved)) {
|
|
62
103
|
console.log(resolved.isDefault ? chalk_1.default.yellow(line) : chalk_1.default.gray(line));
|
|
63
104
|
}
|
|
64
|
-
if ((0, config_1.readConfigFile)(targetPath)) {
|
|
65
|
-
console.log(chalk_1.default.yellow(`Existing config at ${targetPath} will be overwritten.`));
|
|
66
|
-
}
|
|
67
105
|
const config = {
|
|
68
106
|
host,
|
|
69
107
|
apiKey: apiKey.trim(),
|
|
70
108
|
};
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
await (0, config_write_target_1.ensureGitignoreCovers)(process.cwd(), !!options.gitignore);
|
|
74
|
-
}
|
|
75
|
-
console.log(chalk_1.default.green('Logged in successfully!'));
|
|
76
|
-
console.log(chalk_1.default.gray(`Configuration saved to ${targetPath}`));
|
|
77
|
-
console.log('');
|
|
78
|
-
// Workspace selection — ensureWorkspaceSelected writes to the right file via the resolver.
|
|
109
|
+
// 1. Validate the key BEFORE any disk write.
|
|
110
|
+
let workspaces;
|
|
79
111
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
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}.`));
|
|
82
117
|
}
|
|
83
118
|
else {
|
|
84
|
-
|
|
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;
|
|
85
140
|
}
|
|
86
141
|
}
|
|
87
|
-
|
|
88
|
-
console.log(chalk_1.default.yellow('
|
|
142
|
+
else {
|
|
143
|
+
console.log(chalk_1.default.yellow('No workspace set — run `solidactions workspace set <name>` later.'));
|
|
89
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);
|
|
148
|
+
const existingRaw = fs_1.default.existsSync(targetPath) ? fs_1.default.readFileSync(targetPath, 'utf-8') : null;
|
|
149
|
+
const wouldChange = existingRaw !== null && existingRaw.trim() !== JSON.stringify(config, null, 2).trim();
|
|
150
|
+
if (wouldChange) {
|
|
151
|
+
const backupPath = backupPathFor(targetPath);
|
|
152
|
+
const proceed = await (0, config_write_target_1.confirmOverwrite)(targetPath, backupPath);
|
|
153
|
+
if (!proceed) {
|
|
154
|
+
console.log(chalk_1.default.yellow('Aborted. No changes were made.'));
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
fs_1.default.copyFileSync(targetPath, backupPath);
|
|
158
|
+
console.log(chalk_1.default.gray(`Backup saved to ${backupPath}`));
|
|
159
|
+
}
|
|
160
|
+
(0, config_1.writeConfigFile)(targetPath, config);
|
|
161
|
+
if (target === 'local') {
|
|
162
|
+
await (0, config_write_target_1.ensureGitignoreCovers)(process.cwd(), !!options.gitignore);
|
|
163
|
+
}
|
|
164
|
+
console.log(chalk_1.default.green('Logged in successfully!'));
|
|
165
|
+
console.log(chalk_1.default.gray(`Configuration saved to ${targetPath}`));
|
|
90
166
|
console.log('');
|
|
91
167
|
console.log(chalk_1.default.blue('Next step — scaffold a new project (includes AI tooling):'));
|
|
92
168
|
console.log(chalk_1.default.gray(' solidactions init <project-name> Creates ./<project-name>/ with scaffold + AI skills'));
|
|
@@ -123,7 +199,8 @@ function logout(options = {}) {
|
|
|
123
199
|
console.log(chalk_1.default.green(`Logged out. Removed ${targetPath}`));
|
|
124
200
|
}
|
|
125
201
|
else {
|
|
126
|
-
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);
|
|
127
204
|
}
|
|
128
205
|
}
|
|
129
206
|
function whoami() {
|
|
@@ -151,8 +228,10 @@ function whoami() {
|
|
|
151
228
|
: config.workspaceId
|
|
152
229
|
? `${config.workspaceId} (slug unknown — run 'workspace set <slug>' to populate)`
|
|
153
230
|
: '';
|
|
231
|
+
const isFileSource = (src) => src !== null && src !== 'env' && src !== 'cli';
|
|
232
|
+
const workspaceInheritedFromOtherFile = isFileSource(sources.workspaceId) && sources.workspaceId !== sources.apiKey;
|
|
154
233
|
console.log(chalk_1.default.blue('Current configuration:'));
|
|
155
234
|
console.log(` Host: ${config.host.padEnd(50)} ${fmt(sources.host)}`);
|
|
156
235
|
console.log(` API Key: ${maskedKey.padEnd(50)} ${fmt(sources.apiKey)}`);
|
|
157
|
-
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)') : ''}`);
|
|
158
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}" — check the platform name and that a connection exists (\`solidactions env pull --update-oauth\` syncs connections).`);
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
for (const a of actions) {
|
|
@@ -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}" — check the platform name and that a connection exists (\`solidactions env pull --update-oauth\` syncs connections).`);
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
for (const a of actions) {
|
|
@@ -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
|
@@ -71,17 +71,23 @@ program
|
|
|
71
71
|
.name('solidactions')
|
|
72
72
|
.description('SolidActions CLI - Deploy and manage workflow automation')
|
|
73
73
|
.version(pkg.version);
|
|
74
|
-
|
|
74
|
+
// Long form is --workspace-override (NOT --workspace) — `login` already owns
|
|
75
|
+
// `--workspace` for its own purpose (set the workspace at login time), and
|
|
76
|
+
// commander silently drops a subcommand option whose long name collides with
|
|
77
|
+
// a parent option of the same name, so `login --workspace <x>` was being
|
|
78
|
+
// swallowed by this global flag instead of reaching login()'s own option.
|
|
79
|
+
// The short form `-w` is unaffected and remains the primary way to use this.
|
|
80
|
+
program.option('-w, --workspace-override <id-or-slug-or-name>', 'Override active workspace for this command (short form: -w)');
|
|
75
81
|
program.hook('preAction', (thisCommand, actionCommand) => {
|
|
76
82
|
const opts = thisCommand.opts();
|
|
77
|
-
const wsOverride = opts.
|
|
83
|
+
const wsOverride = opts.workspaceOverride;
|
|
78
84
|
if (wsOverride) {
|
|
79
85
|
// workspace set is a write — -w is for read-paths only.
|
|
80
86
|
const fullName = actionCommand.name();
|
|
81
87
|
const parentName = actionCommand.parent?.name?.();
|
|
82
88
|
const isWorkspaceSet = fullName === 'set' && parentName === 'workspace';
|
|
83
89
|
if (isWorkspaceSet) {
|
|
84
|
-
console.error(chalk_1.default.yellow('warn:') + ' -w/--workspace is ignored on `workspace set`; the positional argument is the new workspace.');
|
|
90
|
+
console.error(chalk_1.default.yellow('warn:') + ' -w/--workspace-override is ignored on `workspace set`; the positional argument is the new workspace.');
|
|
85
91
|
return;
|
|
86
92
|
}
|
|
87
93
|
(0, config_1.setCliWorkspaceOverride)(wsOverride);
|
|
@@ -96,7 +102,7 @@ program
|
|
|
96
102
|
.argument('<api-key>', 'Your SolidActions API key')
|
|
97
103
|
.option('--dev', 'Use local development server (http://localhost:8000)')
|
|
98
104
|
.option('--host <url>', 'Custom API host URL')
|
|
99
|
-
.option('--workspace <name-or-id>', 'Set workspace by name, slug, or ID (skips interactive prompt)')
|
|
105
|
+
.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
106
|
.option('--local', 'Save config to ./.solidactions/config.json in the current folder')
|
|
101
107
|
.option('--global', 'Save config to ~/.solidactions/config.json (default if prompted)')
|
|
102
108
|
.option('--gitignore', 'With --local, add .solidactions/ to .gitignore without prompting')
|
|
@@ -180,15 +186,18 @@ project
|
|
|
180
186
|
.command('logs')
|
|
181
187
|
.description('View build/deployment logs for a project')
|
|
182
188
|
.argument('<project>', 'Project name (or family name with -e)')
|
|
183
|
-
.option('-e, --
|
|
189
|
+
.option('-e, --env <environment>', 'Environment to resolve (production/staging/dev)')
|
|
190
|
+
.addOption(new commander_1.Option('--environment <environment>', 'Alias of --env').hideHelp())
|
|
184
191
|
.action((projectName, options) => {
|
|
185
|
-
|
|
192
|
+
const environment = options.env ?? options.environment;
|
|
193
|
+
(0, project_logs_1.logsBuild)(projectName, environment);
|
|
186
194
|
});
|
|
187
195
|
project
|
|
188
196
|
.command('list')
|
|
189
197
|
.description('List all projects')
|
|
190
|
-
.
|
|
191
|
-
|
|
198
|
+
.option('--json', 'Output as JSON')
|
|
199
|
+
.action((options) => {
|
|
200
|
+
(0, project_list_1.projectList)(options);
|
|
192
201
|
});
|
|
193
202
|
// =============================================================================
|
|
194
203
|
// run <subcommand>
|
|
@@ -217,8 +226,10 @@ runCmd
|
|
|
217
226
|
.option('--detailed', 'Include timeline, steps, and logs per run (default limit: 5)')
|
|
218
227
|
.option('--has-errors', 'Show only runs with errors (step errors, retries, or degraded results)')
|
|
219
228
|
.option('--json', 'Output as JSON')
|
|
220
|
-
.option('-e, --
|
|
229
|
+
.option('-e, --env <environment>', 'Environment to filter by (production/staging/dev)')
|
|
230
|
+
.addOption(new commander_1.Option('--environment <environment>', 'Alias of --env').hideHelp())
|
|
221
231
|
.action((projectName, options) => {
|
|
232
|
+
options.environment = options.env ?? options.environment;
|
|
222
233
|
(0, run_list_1.runs)(projectName, options);
|
|
223
234
|
});
|
|
224
235
|
runCmd
|
|
@@ -235,10 +246,10 @@ runCmd
|
|
|
235
246
|
// =============================================================================
|
|
236
247
|
// env <subcommand>
|
|
237
248
|
// =============================================================================
|
|
238
|
-
const env = program.command('env').description('Manage
|
|
249
|
+
const env = program.command('env').description('Manage variables');
|
|
239
250
|
env
|
|
240
251
|
.command('set')
|
|
241
|
-
.description('Set
|
|
252
|
+
.description('Set a variable (create or update, global or project)')
|
|
242
253
|
.argument('<key-or-project>', 'Variable key (global) or project name')
|
|
243
254
|
.argument('<value-or-key>', 'Variable value (global) or variable key (project)')
|
|
244
255
|
.argument('[value]', 'Variable value (when first arg is project)')
|
|
@@ -256,17 +267,19 @@ env
|
|
|
256
267
|
});
|
|
257
268
|
env
|
|
258
269
|
.command('list')
|
|
259
|
-
.description('List
|
|
270
|
+
.description('List variables')
|
|
260
271
|
.argument('[project]', 'Project name (omit for global variables)')
|
|
261
272
|
.option('-e, --env <environment>', 'Filter by environment (production/staging/dev)')
|
|
273
|
+
.option('--json', 'Output as JSON')
|
|
262
274
|
.action((projectName, options) => {
|
|
263
275
|
(0, env_list_1.envList)(projectName, options);
|
|
264
276
|
});
|
|
265
277
|
env
|
|
266
278
|
.command('delete')
|
|
267
|
-
.description('Delete
|
|
279
|
+
.description('Delete a variable')
|
|
268
280
|
.argument('<key-or-project>', 'Variable key (global) or project name')
|
|
269
281
|
.argument('[key]', 'Variable key (if first arg is project)')
|
|
282
|
+
.option('-e, --env <environment>', 'Environment to delete from', 'dev')
|
|
270
283
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
271
284
|
.action((keyOrProject, key, options) => {
|
|
272
285
|
(0, env_delete_1.envDelete)(keyOrProject, key, options);
|
|
@@ -283,7 +296,7 @@ env
|
|
|
283
296
|
});
|
|
284
297
|
env
|
|
285
298
|
.command('pull')
|
|
286
|
-
.description('Pull resolved
|
|
299
|
+
.description('Pull resolved variables to a local file')
|
|
287
300
|
.argument('<project>', 'Project name')
|
|
288
301
|
.option('-e, --env <environment>', 'Environment (production/staging/dev)', 'dev')
|
|
289
302
|
.option('-o, --output <file>', 'Output file path (defaults to .env or .env.{environment})')
|
|
@@ -294,7 +307,7 @@ env
|
|
|
294
307
|
});
|
|
295
308
|
env
|
|
296
309
|
.command('push')
|
|
297
|
-
.description('Push
|
|
310
|
+
.description('Push variables from .env file to a project')
|
|
298
311
|
.argument('<project>', 'Project name')
|
|
299
312
|
.argument('[path]', 'Source directory with solidactions.yaml and .env file', '.')
|
|
300
313
|
.option('-e, --env <environment>', 'Target environment (production/staging/dev)', 'dev')
|
|
@@ -313,7 +326,7 @@ schedule
|
|
|
313
326
|
.description('Set a cron schedule for a workflow')
|
|
314
327
|
.argument('<project>', 'Project name')
|
|
315
328
|
.argument('<cron>', 'Cron expression (e.g., "0 9 * * *" for daily at 9am)')
|
|
316
|
-
.option('
|
|
329
|
+
.option('--workflow <name>', 'Workflow name (if project has multiple)')
|
|
317
330
|
.option('-i, --input <json>', 'JSON input to pass to scheduled runs')
|
|
318
331
|
.option('-z, --timezone <iana>', 'IANA timezone the cron is evaluated in (e.g. America/Chicago); defaults to UTC')
|
|
319
332
|
.option('-y, --yes', 'Skip confirmation if schedule already exists')
|