@solidactions/cli 0.7.0 → 1.0.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/dist/index.js CHANGED
@@ -4,12 +4,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const commander_1 = require("commander");
5
5
  const deploy_1 = require("./commands/deploy");
6
6
  const init_1 = require("./commands/init");
7
- const logs_1 = require("./commands/logs");
8
- const logs_build_1 = require("./commands/logs-build");
9
- const steps_1 = require("./commands/steps");
10
- const run_1 = require("./commands/run");
11
- const runs_1 = require("./commands/runs");
12
7
  const pull_1 = require("./commands/pull");
8
+ const project_list_1 = require("./commands/project-list");
9
+ const project_logs_1 = require("./commands/project-logs");
10
+ const run_start_1 = require("./commands/run-start");
11
+ const run_list_1 = require("./commands/run-list");
12
+ const run_view_1 = require("./commands/run-view");
13
13
  const env_list_1 = require("./commands/env-list");
14
14
  const env_delete_1 = require("./commands/env-delete");
15
15
  const env_map_1 = require("./commands/env-map");
@@ -19,10 +19,11 @@ const env_set_1 = require("./commands/env-set");
19
19
  const schedule_set_1 = require("./commands/schedule-set");
20
20
  const schedule_list_1 = require("./commands/schedule-list");
21
21
  const schedule_delete_1 = require("./commands/schedule-delete");
22
- const webhooks_1 = require("./commands/webhooks");
22
+ const webhook_list_1 = require("./commands/webhook-list");
23
23
  const dev_1 = require("./commands/dev");
24
24
  const ai_init_1 = require("./commands/ai-init");
25
25
  const ai_examples_1 = require("./commands/ai-examples");
26
+ const workspaces_1 = require("./commands/workspaces");
26
27
  // eslint-disable-next-line @typescript-eslint/no-var-requires
27
28
  const pkg = require('../package.json');
28
29
  const program = new commander_1.Command();
@@ -31,7 +32,7 @@ program
31
32
  .description('SolidActions CLI - Deploy and manage workflow automation')
32
33
  .version(pkg.version);
33
34
  // =============================================================================
34
- // Authentication & Configuration
35
+ // Top-level commands
35
36
  // =============================================================================
36
37
  program
37
38
  .command('init')
@@ -39,6 +40,7 @@ program
39
40
  .argument('<api-key>', 'Your SolidActions API key')
40
41
  .option('--dev', 'Use local development server (http://localhost:8000)')
41
42
  .option('--host <url>', 'Custom API host URL')
43
+ .option('--workspace <name-or-id>', 'Set workspace by name, slug, or ID (skips interactive prompt)')
42
44
  .action((apiKey, options) => {
43
45
  (0, init_1.init)(apiKey, options);
44
46
  });
@@ -54,90 +56,97 @@ program
54
56
  .action(() => {
55
57
  (0, init_1.whoami)();
56
58
  });
59
+ program
60
+ .command('dev')
61
+ .description('Run a workflow locally using an in-memory mock server (no deploy needed)')
62
+ .argument('<file>', 'Workflow file to run (e.g., src/simple-steps.ts)')
63
+ .option('-i, --input <json>', 'JSON input for the workflow', '{}')
64
+ .action((file, options) => {
65
+ (0, dev_1.dev)(file, options);
66
+ });
57
67
  // =============================================================================
58
- // Project Management
68
+ // project <subcommand>
59
69
  // =============================================================================
60
- program
70
+ const project = program.command('project').description('Manage projects');
71
+ project
61
72
  .command('deploy')
62
73
  .description('Deploy a project to SolidActions')
63
74
  .argument('<project-name>', 'Project name (will be created if it doesn\'t exist)')
64
75
  .argument('[path]', 'Source directory to deploy (defaults to current directory)')
65
76
  .option('-e, --env <environment>', 'Target environment (production/staging/dev)', 'dev')
66
77
  .option('--create', 'Create environment project if it doesn\'t exist')
78
+ .option('--config-only', 'Sync YAML env declarations without building/deploying')
67
79
  .action((projectName, path, options) => {
68
80
  (0, deploy_1.deploy)(projectName, path, options);
69
81
  });
70
- program
82
+ project
71
83
  .command('pull')
72
84
  .description('Pull project source from SolidActions')
73
85
  .argument('<project-name>', 'Project name')
74
86
  .argument('[path]', 'Destination directory (defaults to current directory)')
75
- .action((projectName, path) => {
76
- (0, pull_1.pull)(projectName, path);
87
+ .option('-y, --yes', 'Skip overwrite confirmation')
88
+ .action((projectName, path, options) => {
89
+ (0, pull_1.pull)(projectName, path, options);
90
+ });
91
+ project
92
+ .command('logs')
93
+ .description('View build/deployment logs for a project')
94
+ .argument('<project>', 'Project name')
95
+ .action((projectName) => {
96
+ (0, project_logs_1.logsBuild)(projectName);
97
+ });
98
+ project
99
+ .command('list')
100
+ .description('List all projects')
101
+ .action(() => {
102
+ (0, project_list_1.projectList)();
77
103
  });
78
104
  // =============================================================================
79
- // Workflow Execution
105
+ // run <subcommand>
80
106
  // =============================================================================
81
- program
82
- .command('run')
107
+ const runCmd = program.command('run').description('Manage workflow runs');
108
+ runCmd
109
+ .command('start')
83
110
  .description('Trigger a workflow run')
84
111
  .argument('<project>', 'Project name')
85
112
  .argument('<workflow>', 'Workflow name')
86
113
  .option('-e, --env <environment>', 'Environment (production/staging/dev)', 'dev')
87
114
  .option('-i, --input <json>', 'JSON input for the workflow')
88
115
  .option('-w, --wait', 'Wait for the workflow to complete')
89
- .action((project, workflow, options) => {
90
- (0, run_1.run)(project, workflow, options);
116
+ .action((projectName, workflow, options) => {
117
+ (0, run_start_1.run)(projectName, workflow, options);
91
118
  });
92
- program
93
- .command('runs')
119
+ runCmd
120
+ .command('list')
94
121
  .description('List recent workflow runs')
95
122
  .argument('[project]', 'Filter by project name')
96
123
  .option('-l, --limit <number>', 'Number of runs to show', parseInt)
97
- .action((project, options) => {
98
- (0, runs_1.runs)(project, options);
124
+ .option('--offset <number>', 'Skip first N runs (pagination)', parseInt)
125
+ .option('--status <status>', 'Filter by status (completed, failed, queued, running)')
126
+ .option('--since <duration>', 'Filter to runs since (e.g., 1h, 30m, 2d, 1w)')
127
+ .option('--workflow <name>', 'Filter by workflow name')
128
+ .option('--detailed', 'Include timeline, steps, and logs per run (default limit: 5)')
129
+ .option('--json', 'Output as JSON')
130
+ .action((projectName, options) => {
131
+ (0, run_list_1.runs)(projectName, options);
99
132
  });
100
- program
101
- .command('dev')
102
- .description('Run a workflow locally using an in-memory mock server (no deploy needed)')
103
- .argument('<file>', 'Workflow file to run (e.g., src/simple-steps.ts)')
104
- .option('-i, --input <json>', 'JSON input for the workflow', '{}')
105
- .action((file, options) => {
106
- (0, dev_1.dev)(file, options);
107
- });
108
- // =============================================================================
109
- // Logs
110
- // =============================================================================
111
- program
112
- .command('logs')
113
- .description('View logs for a workflow run')
133
+ runCmd
134
+ .command('view')
135
+ .description('View details, timeline, steps, or logs for a workflow run')
114
136
  .argument('<run-id>', 'Run ID')
115
- .option('-f, --follow', 'Follow log output (tail -f style)')
137
+ .option('--timeline', 'Show only timeline data')
138
+ .option('--steps', 'Show only step data')
139
+ .option('--logs', 'Show raw logs')
140
+ .option('--json', 'Output as JSON')
116
141
  .action((runId, options) => {
117
- (0, logs_1.logs)(runId, options);
118
- });
119
- program
120
- .command('logs:build')
121
- .description('View build/deployment logs for a project')
122
- .argument('<project>', 'Project name')
123
- .action((project) => {
124
- (0, logs_build_1.logsBuild)(project);
125
- });
126
- program
127
- .command('steps')
128
- .description('View step outputs and timing for a workflow run')
129
- .argument('<run-id>', 'Run ID')
130
- .option('-s, --step <name>', 'Show output for a specific step')
131
- .option('--json', 'Output as JSON (for piping/redirection)')
132
- .option('-f, --follow', 'Follow step progress for in-progress runs')
133
- .action((runId, options) => {
134
- (0, steps_1.steps)(runId, options);
142
+ (0, run_view_1.runView)(runId, options);
135
143
  });
136
144
  // =============================================================================
137
- // Environment Variables
145
+ // env <subcommand>
138
146
  // =============================================================================
139
- program
140
- .command('env:set')
147
+ const env = program.command('env').description('Manage environment variables');
148
+ env
149
+ .command('set')
141
150
  .description('Set an environment variable (create or update, global or project)')
142
151
  .argument('<key-or-project>', 'Variable key (global) or project name')
143
152
  .argument('<value-or-key>', 'Variable value (global) or variable key (project)')
@@ -149,19 +158,20 @@ program
149
158
  .option('--staging-inherit', 'Staging inherits from production (global only)')
150
159
  .option('--dev-inherit', 'Dev inherits from production (global only)')
151
160
  .option('--dev-inherit-staging', 'Dev inherits from staging (global only)')
161
+ .option('-y, --yes', 'Skip overwrite confirmation')
152
162
  .action((keyOrProject, valueOrKey, value, options) => {
153
163
  (0, env_set_1.envSet)(keyOrProject, valueOrKey, value, options);
154
164
  });
155
- program
156
- .command('env:list')
165
+ env
166
+ .command('list')
157
167
  .description('List environment variables')
158
168
  .argument('[project]', 'Project name (omit for global variables)')
159
169
  .option('-e, --env <environment>', 'Filter by environment (production/staging/dev)')
160
- .action((project, options) => {
161
- (0, env_list_1.envList)(project, options);
170
+ .action((projectName, options) => {
171
+ (0, env_list_1.envList)(projectName, options);
162
172
  });
163
- program
164
- .command('env:delete')
173
+ env
174
+ .command('delete')
165
175
  .description('Delete an environment variable')
166
176
  .argument('<key-or-project>', 'Variable key (global) or project name')
167
177
  .argument('[key]', 'Variable key (if first arg is project)')
@@ -169,28 +179,29 @@ program
169
179
  .action((keyOrProject, key, options) => {
170
180
  (0, env_delete_1.envDelete)(keyOrProject, key, options);
171
181
  });
172
- program
173
- .command('env:map')
182
+ env
183
+ .command('map')
174
184
  .description('Map a global variable to a project-specific key')
175
185
  .argument('<project>', 'Project name')
176
186
  .argument('<key>', 'Project-specific variable name')
177
187
  .argument('<global-key>', 'Global variable name to map from')
178
- .action((project, key, globalKey) => {
179
- (0, env_map_1.envMap)(project, key, globalKey);
188
+ .option('-y, --yes', 'Skip overwrite confirmation')
189
+ .action((projectName, key, globalKey, options) => {
190
+ (0, env_map_1.envMap)(projectName, key, globalKey, options);
180
191
  });
181
- program
182
- .command('env:pull')
192
+ env
193
+ .command('pull')
183
194
  .description('Pull resolved environment variables to a local file')
184
195
  .argument('<project>', 'Project name')
185
196
  .option('-e, --env <environment>', 'Environment (production/staging/dev)', 'dev')
186
197
  .option('-o, --output <file>', 'Output file path (defaults to .env or .env.{environment})')
187
198
  .option('-y, --yes', 'Skip confirmation for secrets')
188
199
  .option('--update-oauth', 'Only pull OAuth tokens and merge into existing .env file')
189
- .action((project, options) => {
190
- (0, env_pull_1.envPull)(project, options);
200
+ .action((projectName, options) => {
201
+ (0, env_pull_1.envPull)(projectName, options);
191
202
  });
192
- program
193
- .command('env:push')
203
+ env
204
+ .command('push')
194
205
  .description('Push environment variables from .env file to a project')
195
206
  .argument('<project>', 'Project name')
196
207
  .argument('[path]', 'Source directory with solidactions.yaml and .env file', '.')
@@ -198,61 +209,82 @@ program
198
209
  .option('--new-only', 'Only push new or empty variables (skip existing)')
199
210
  .option('--include-undeclared', 'Also push vars not declared in solidactions.yaml')
200
211
  .option('-y, --yes', 'Skip confirmation prompt')
201
- .action((project, path, options) => {
202
- (0, env_push_1.envPush)(project, path, options);
212
+ .action((projectName, path, options) => {
213
+ (0, env_push_1.envPush)(projectName, path, options);
203
214
  });
204
215
  // =============================================================================
205
- // Scheduling
216
+ // schedule <subcommand>
206
217
  // =============================================================================
207
- program
208
- .command('schedule:set')
218
+ const schedule = program.command('schedule').description('Manage cron schedules');
219
+ schedule
220
+ .command('set')
209
221
  .description('Set a cron schedule for a workflow')
210
222
  .argument('<project>', 'Project name')
211
223
  .argument('<cron>', 'Cron expression (e.g., "0 9 * * *" for daily at 9am)')
212
224
  .option('-w, --workflow <name>', 'Workflow name (if project has multiple)')
213
225
  .option('-i, --input <json>', 'JSON input to pass to scheduled runs')
214
- .action((project, cron, options) => {
215
- (0, schedule_set_1.scheduleSet)(project, cron, options);
226
+ .option('-y, --yes', 'Skip confirmation if schedule already exists')
227
+ .action((projectName, cron, options) => {
228
+ (0, schedule_set_1.scheduleSet)(projectName, cron, options);
216
229
  });
217
- program
218
- .command('schedule:list')
230
+ schedule
231
+ .command('list')
219
232
  .description('List schedules for a project')
220
233
  .argument('<project>', 'Project name')
221
- .action((project) => {
222
- (0, schedule_list_1.scheduleList)(project);
234
+ .action((projectName) => {
235
+ (0, schedule_list_1.scheduleList)(projectName);
223
236
  });
224
- program
225
- .command('schedule:delete')
237
+ schedule
238
+ .command('delete')
226
239
  .description('Delete a schedule')
227
240
  .argument('<project>', 'Project name')
228
241
  .argument('<schedule-id>', 'Schedule ID')
229
242
  .option('-y, --yes', 'Skip confirmation prompt')
230
- .action((project, scheduleId, options) => {
231
- (0, schedule_delete_1.scheduleDelete)(project, scheduleId, options);
243
+ .action((projectName, scheduleId, options) => {
244
+ (0, schedule_delete_1.scheduleDelete)(projectName, scheduleId, options);
232
245
  });
233
246
  // =============================================================================
234
- // Webhooks
247
+ // webhook <subcommand>
235
248
  // =============================================================================
236
- program
237
- .command('webhooks')
249
+ const webhook = program.command('webhook').description('Manage webhooks');
250
+ webhook
251
+ .command('list')
238
252
  .description('List webhook URLs for a project')
239
253
  .argument('<project>', 'Project name')
240
254
  .option('-e, --env <environment>', 'Environment (production/staging/dev)')
241
255
  .option('--show-secrets', 'Show webhook secrets')
242
- .action((project, options) => {
243
- (0, webhooks_1.webhookList)(project, options);
256
+ .action((projectName, options) => {
257
+ (0, webhook_list_1.webhookList)(projectName, options);
244
258
  });
245
259
  // =============================================================================
246
- // AI Helper
260
+ // workspace <subcommand>
247
261
  // =============================================================================
248
- program
249
- .command('ai:init')
262
+ const workspace = program.command('workspace').description('Manage workspaces');
263
+ workspace
264
+ .command('list')
265
+ .description('List your workspaces across all organizations')
266
+ .action(() => {
267
+ (0, workspaces_1.workspacesList)();
268
+ });
269
+ workspace
270
+ .command('set')
271
+ .description('Set the active workspace for CLI operations')
272
+ .argument('<workspace-id>', 'Workspace ID, slug, or name')
273
+ .action((workspaceId) => {
274
+ (0, workspaces_1.workspaceSet)(workspaceId);
275
+ });
276
+ // =============================================================================
277
+ // ai <subcommand>
278
+ // =============================================================================
279
+ const ai = program.command('ai').description('AI helper tools');
280
+ ai
281
+ .command('init')
250
282
  .description('Install AI helper documentation (CLAUDE.md or AGENTS.md) for AI-assisted workflow development')
251
283
  .option('--claude', 'Use CLAUDE.md (for Claude Code)')
252
284
  .option('--agents', 'Use AGENTS.md (for Cursor, Windsurf, etc.)')
253
285
  .action((options) => { (0, ai_init_1.aiInit)(options); });
254
- program
255
- .command('ai:examples')
286
+ ai
287
+ .command('examples')
256
288
  .description('Install example workflows for AI reference')
257
289
  .argument('[names...]', 'Example names to install (omit for interactive selector)')
258
290
  .option('--all', 'Install all available examples')
@@ -0,0 +1,129 @@
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.getApiHeaders = getApiHeaders;
7
+ exports.ensureWorkspaceSelected = ensureWorkspaceSelected;
8
+ exports.requireConfig = requireConfig;
9
+ exports.requireConfigWithWorkspace = requireConfigWithWorkspace;
10
+ const axios_1 = __importDefault(require("axios"));
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const readline_1 = __importDefault(require("readline"));
13
+ const init_1 = require("../commands/init");
14
+ /**
15
+ * Get standard API headers including X-Workspace-Id if configured.
16
+ */
17
+ function getApiHeaders(config, contentType) {
18
+ const headers = {
19
+ 'Authorization': `Bearer ${config.apiKey}`,
20
+ 'Accept': 'application/json',
21
+ };
22
+ if (contentType) {
23
+ headers['Content-Type'] = contentType;
24
+ }
25
+ if (config.workspaceId) {
26
+ headers['X-Workspace-Id'] = config.workspaceId;
27
+ }
28
+ return headers;
29
+ }
30
+ /**
31
+ * Ensure a workspace is selected. If not configured, fetches workspaces and auto-selects
32
+ * (if only one) or prompts the user to choose.
33
+ *
34
+ * Returns the config with workspaceId set, or exits if no workspaces available.
35
+ */
36
+ async function ensureWorkspaceSelected(config) {
37
+ if (config.workspaceId) {
38
+ return config;
39
+ }
40
+ // Fetch workspaces from API (this endpoint doesn't require X-Workspace-Id)
41
+ let workspaces;
42
+ try {
43
+ const response = await axios_1.default.get(`${config.host}/api/v1/workspaces`, {
44
+ headers: {
45
+ 'Authorization': `Bearer ${config.apiKey}`,
46
+ 'Accept': 'application/json',
47
+ },
48
+ });
49
+ // API returns { workspaces: { "Org Name": [{ id, name, role, tenant_name, ... }] } }
50
+ // Flatten the grouped structure into a flat array
51
+ const grouped = response.data.workspaces || response.data.teams || response.data.data || response.data;
52
+ if (typeof grouped === 'object' && !Array.isArray(grouped)) {
53
+ workspaces = [];
54
+ for (const orgName of Object.keys(grouped)) {
55
+ for (const ws of grouped[orgName]) {
56
+ workspaces.push({
57
+ id: ws.id,
58
+ name: ws.name,
59
+ org_name: ws.tenant_name || orgName,
60
+ role: ws.role,
61
+ });
62
+ }
63
+ }
64
+ }
65
+ else {
66
+ workspaces = Array.isArray(grouped) ? grouped : [];
67
+ }
68
+ }
69
+ catch (error) {
70
+ if (error.response?.status === 401) {
71
+ console.error(chalk_1.default.red('Authentication failed. Run `solidactions init <api-key>` to reconfigure.'));
72
+ }
73
+ else {
74
+ console.error(chalk_1.default.red('Failed to fetch workspaces:'), error.response?.data?.message || error.message);
75
+ }
76
+ process.exit(1);
77
+ }
78
+ if (workspaces.length === 0) {
79
+ console.error(chalk_1.default.red('No workspaces found. Create a workspace at your SolidActions dashboard first.'));
80
+ process.exit(1);
81
+ }
82
+ let selected;
83
+ if (workspaces.length === 1) {
84
+ selected = workspaces[0];
85
+ console.log(chalk_1.default.gray(`Auto-selected workspace: ${selected.name}`));
86
+ }
87
+ else {
88
+ // Prompt user to select
89
+ console.log(chalk_1.default.blue('\nSelect a workspace:\n'));
90
+ workspaces.forEach((ws, i) => {
91
+ console.log(` ${chalk_1.default.white(`${i + 1}.`)} ${ws.name} ${chalk_1.default.gray(`(${ws.org_name}, ${ws.role})`)}`);
92
+ });
93
+ console.log('');
94
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
95
+ const answer = await new Promise((resolve) => {
96
+ rl.question(chalk_1.default.blue('Enter number: '), resolve);
97
+ });
98
+ rl.close();
99
+ const index = parseInt(answer, 10) - 1;
100
+ if (isNaN(index) || index < 0 || index >= workspaces.length) {
101
+ console.error(chalk_1.default.red('Invalid selection.'));
102
+ process.exit(1);
103
+ }
104
+ selected = workspaces[index];
105
+ }
106
+ // Save workspace selection to config
107
+ config.workspaceId = selected.id;
108
+ (0, init_1.saveConfig)(config);
109
+ console.log(chalk_1.default.green(`Workspace set: ${selected.name}`));
110
+ return config;
111
+ }
112
+ /**
113
+ * Get config and ensure it's initialized. Exits if not.
114
+ */
115
+ function requireConfig() {
116
+ const config = (0, init_1.getConfig)();
117
+ if (!config?.apiKey) {
118
+ console.error(chalk_1.default.red('Not initialized. Run `solidactions init <api-key>` first.'));
119
+ process.exit(1);
120
+ }
121
+ return config;
122
+ }
123
+ /**
124
+ * Get config with workspace selected. Use this for any command that needs workspace context.
125
+ */
126
+ async function requireConfigWithWorkspace() {
127
+ const config = requireConfig();
128
+ return ensureWorkspaceSelected(config);
129
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidactions/cli",
3
- "version": "0.7.0",
3
+ "version": "1.0.0",
4
4
  "description": "SolidActions CLI - Deploy and manage workflow automation",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -36,7 +36,6 @@
36
36
  "prepublishOnly": "npm run build"
37
37
  },
38
38
  "dependencies": {
39
- "adm-zip": "^0.5.16",
40
39
  "archiver": "^7.0.0",
41
40
  "axios": "^1.6.0",
42
41
  "chalk": "^4.1.2",
@@ -45,15 +44,16 @@
45
44
  "form-data": "^4.0.0",
46
45
  "fs-extra": "^11.0.0",
47
46
  "js-yaml": "^4.1.0",
48
- "prompts": "^2.4.2"
47
+ "prompts": "^2.4.2",
48
+ "tar": "^7.5.12"
49
49
  },
50
50
  "devDependencies": {
51
- "@types/adm-zip": "^0.5.7",
52
51
  "@types/archiver": "^6.0.0",
53
52
  "@types/fs-extra": "^11.0.0",
54
53
  "@types/js-yaml": "^4.0.9",
55
54
  "@types/node": "^20.0.0",
56
55
  "@types/prompts": "^2.4.9",
56
+ "@types/tar": "^6.1.13",
57
57
  "typescript": "^5.0.0"
58
58
  }
59
59
  }
@@ -1,119 +0,0 @@
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.logs = logs;
7
- const axios_1 = __importDefault(require("axios"));
8
- const chalk_1 = __importDefault(require("chalk"));
9
- const init_1 = require("./init");
10
- async function logs(runId, options) {
11
- const config = (0, init_1.getConfig)();
12
- if (!config?.apiKey) {
13
- console.error(chalk_1.default.red('Not initialized. Run "solidactions init <api-key>" first.'));
14
- process.exit(1);
15
- }
16
- console.log(chalk_1.default.blue(`Fetching logs for run: ${runId}...`));
17
- try {
18
- // Get the run status first
19
- const runResponse = await axios_1.default.get(`${config.host}/api/v1/runs/${runId}`, {
20
- headers: {
21
- 'Authorization': `Bearer ${config.apiKey}`,
22
- 'Accept': 'application/json',
23
- },
24
- });
25
- const runData = runResponse.data;
26
- console.log(chalk_1.default.gray(`Status: ${runData.status}`));
27
- console.log(chalk_1.default.gray('---'));
28
- // Get the logs for this run
29
- const logsResponse = await axios_1.default.get(`${config.host}/api/v1/runs/${runId}/logs`, {
30
- headers: {
31
- 'Authorization': `Bearer ${config.apiKey}`,
32
- 'Accept': 'application/json',
33
- },
34
- });
35
- const errors = logsResponse.data.errors || [];
36
- if (errors.length > 0) {
37
- for (const err of errors) {
38
- console.log(chalk_1.default.red(`Worker ${err.worker}: ${err.error}`));
39
- }
40
- console.log(chalk_1.default.gray('---'));
41
- }
42
- const logData = logsResponse.data.logs || '';
43
- let printed = displayLogs(logData);
44
- if (options.follow && runData.status === 'running') {
45
- console.log(chalk_1.default.gray('\n--- Following logs (Ctrl+C to stop) ---\n'));
46
- const pollInterval = setInterval(async () => {
47
- try {
48
- const refreshResponse = await axios_1.default.get(`${config.host}/api/v1/runs/${runId}/logs`, {
49
- headers: {
50
- 'Authorization': `Bearer ${config.apiKey}`,
51
- 'Accept': 'application/json',
52
- },
53
- });
54
- const newLogData = refreshResponse.data.logs || '';
55
- if (newLogData.length > printed) {
56
- const newContent = newLogData.substring(printed);
57
- printed += displayLogs(newContent);
58
- }
59
- // Check if run is complete
60
- const runStatus = await axios_1.default.get(`${config.host}/api/v1/runs/${runId}`, {
61
- headers: {
62
- 'Authorization': `Bearer ${config.apiKey}`,
63
- 'Accept': 'application/json',
64
- },
65
- });
66
- if (['completed', 'failed', 'acknowledged'].includes(runStatus.data.status)) {
67
- clearInterval(pollInterval);
68
- console.log(chalk_1.default.gray(`\n--- Run ${runStatus.data.status} ---`));
69
- process.exit(runStatus.data.status === 'completed' ? 0 : 1);
70
- }
71
- }
72
- catch {
73
- // Ignore transient errors during follow
74
- }
75
- }, 2000);
76
- }
77
- }
78
- catch (error) {
79
- if (error.response) {
80
- if (error.response.status === 401) {
81
- console.error(chalk_1.default.red('Authentication failed. Run "solidactions init <api-key>" to re-configure.'));
82
- }
83
- else if (error.response.status === 404) {
84
- console.error(chalk_1.default.red('Run not found.'));
85
- }
86
- else {
87
- console.error(chalk_1.default.red(`Failed: ${error.response.status}`), error.response.data);
88
- }
89
- }
90
- else {
91
- console.error(chalk_1.default.red('Connection failed:'), error.message);
92
- }
93
- process.exit(1);
94
- }
95
- }
96
- /**
97
- * Display log content. Handles both string logs and array-of-entry logs.
98
- * Returns the number of characters printed (for follow-mode diffing).
99
- */
100
- function displayLogs(logData) {
101
- if (typeof logData === 'string') {
102
- if (logData.trim()) {
103
- console.log(logData);
104
- }
105
- return logData.length;
106
- }
107
- // Handle array format in case the API changes
108
- for (const entry of logData) {
109
- const message = entry.message || entry.content || '';
110
- if (!message.trim())
111
- continue;
112
- const timestamp = entry.timestamp ? new Date(entry.timestamp).toLocaleTimeString() : '??:??:??';
113
- const stream = entry.stream || 'stdout';
114
- const streamIndicator = stream === 'stderr' ? chalk_1.default.red('[err]') : chalk_1.default.gray('[out]');
115
- const coloredMessage = stream === 'stderr' ? chalk_1.default.red(message) : chalk_1.default.white(message);
116
- console.log(`${chalk_1.default.gray(`[${timestamp}]`)} ${streamIndicator} ${coloredMessage}`);
117
- }
118
- return logData.length;
119
- }