@griffin-app/griffin-cli 1.0.18 → 1.0.20

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.
Files changed (49) hide show
  1. package/dist/cli.js +183 -196
  2. package/dist/commands/env.d.ts +3 -3
  3. package/dist/commands/env.js +28 -45
  4. package/dist/commands/hub/apply.d.ts +1 -1
  5. package/dist/commands/hub/apply.js +54 -95
  6. package/dist/commands/hub/connect.d.ts +1 -1
  7. package/dist/commands/hub/connect.js +2 -6
  8. package/dist/commands/hub/destroy.d.ts +10 -0
  9. package/dist/commands/hub/destroy.js +141 -0
  10. package/dist/commands/hub/integrations.d.ts +9 -9
  11. package/dist/commands/hub/integrations.js +104 -115
  12. package/dist/commands/hub/login.js +3 -0
  13. package/dist/commands/hub/metrics.d.ts +2 -2
  14. package/dist/commands/hub/metrics.js +41 -69
  15. package/dist/commands/hub/monitor.d.ts +1 -1
  16. package/dist/commands/hub/monitor.js +30 -70
  17. package/dist/commands/hub/notifications.d.ts +3 -3
  18. package/dist/commands/hub/notifications.js +74 -105
  19. package/dist/commands/hub/run.d.ts +1 -1
  20. package/dist/commands/hub/run.js +118 -144
  21. package/dist/commands/hub/runs.d.ts +1 -1
  22. package/dist/commands/hub/runs.js +60 -81
  23. package/dist/commands/hub/secrets.d.ts +8 -8
  24. package/dist/commands/hub/secrets.js +88 -148
  25. package/dist/commands/hub/status.d.ts +1 -1
  26. package/dist/commands/hub/status.js +13 -26
  27. package/dist/commands/init.js +3 -3
  28. package/dist/commands/local/run.d.ts +2 -2
  29. package/dist/commands/local/run.js +37 -42
  30. package/dist/commands/validate.d.ts +1 -1
  31. package/dist/commands/validate.js +19 -39
  32. package/dist/commands/variables.d.ts +3 -3
  33. package/dist/commands/variables.js +51 -69
  34. package/dist/core/discovery.js +0 -2
  35. package/dist/core/monitor-helpers.d.ts +19 -0
  36. package/dist/core/monitor-helpers.js +48 -0
  37. package/dist/core/sdk.d.ts +5 -0
  38. package/dist/core/sdk.js +11 -0
  39. package/dist/core/state.d.ts +1 -1
  40. package/dist/core/state.js +0 -3
  41. package/dist/index.d.ts +1 -0
  42. package/dist/index.js +1 -0
  43. package/dist/schemas/state.js +1 -1
  44. package/dist/utils/command-wrapper.d.ts +10 -0
  45. package/dist/utils/command-wrapper.js +27 -0
  46. package/dist/utils/sdk-error.js +1 -1
  47. package/dist/utils/terminal.d.ts +4 -0
  48. package/dist/utils/terminal.js +17 -0
  49. package/package.json +4 -4
@@ -1,7 +1,7 @@
1
- import { loadState } from "../../core/state.js";
2
- import { createSdkWithCredentials } from "../../core/sdk.js";
1
+ import { createSdkAndState } from "../../core/sdk.js";
3
2
  import { terminal } from "../../utils/terminal.js";
4
3
  import { withSDKErrorHandling } from "../../utils/sdk-error.js";
4
+ import { withCommandErrorHandler, outputJsonOrContinue, } from "../../utils/command-wrapper.js";
5
5
  function orExit(value, errorMessage) {
6
6
  if (value === undefined) {
7
7
  terminal.error(errorMessage);
@@ -12,81 +12,61 @@ function orExit(value, errorMessage) {
12
12
  }
13
13
  /**
14
14
  * List notification rules (read-only).
15
- * Rules are defined in monitor DSL and synced via `griffin hub apply`.
15
+ * Rules are defined in monitor DSL and synced via `griffin apply`.
16
16
  */
17
- export async function executeNotificationsList(options) {
18
- try {
19
- const state = await loadState();
20
- if (!state.hub?.baseUrl) {
21
- terminal.error("Hub connection not configured.");
22
- terminal.dim("Connect with:");
23
- terminal.dim(" griffin hub connect --url <url> --token <token>");
24
- terminal.exit(1);
25
- }
26
- const sdk = await createSdkWithCredentials(state.hub.baseUrl);
27
- const response = await withSDKErrorHandling(() => sdk.getNotificationsRules({
28
- query: {
29
- monitorId: options.monitor,
30
- enabled: options.enabled,
31
- },
32
- }), "Failed to fetch notification rules");
33
- const rules = response?.data?.data || [];
34
- if (options.json) {
35
- terminal.log(JSON.stringify(rules, null, 2));
36
- return;
37
- }
38
- if (rules.length === 0) {
39
- terminal.info("No notification rules found.");
40
- terminal.dim("Define notifications in your monitor DSL and run griffin hub apply.");
41
- return;
42
- }
43
- terminal.info("Notification Rules (synced from monitor DSL)");
44
- terminal.blank();
45
- const table = terminal.table({
46
- head: ["ID", "Monitor", "Integration", "Trigger", "Enabled"],
47
- });
48
- for (const rule of rules) {
49
- const triggerDesc = formatTrigger(rule.trigger);
50
- const enabled = rule.enabled ? "✓" : "✗";
51
- table.push([
52
- rule.id.substring(0, 8) + "...",
53
- rule.monitorId ?? "-",
54
- rule.integrationName ?? "-",
55
- triggerDesc,
56
- enabled,
57
- ]);
58
- }
59
- terminal.log(table.toString());
17
+ export const executeNotificationsList = withCommandErrorHandler(async (options) => {
18
+ const { sdk } = await createSdkAndState();
19
+ const response = await withSDKErrorHandling(() => sdk.getNotificationsRules({
20
+ query: {
21
+ monitorId: options.monitor,
22
+ enabled: options.enabled,
23
+ },
24
+ }), "Failed to fetch notification rules");
25
+ const rules = response?.data?.data || [];
26
+ if (outputJsonOrContinue(rules, options.json))
27
+ return;
28
+ if (rules.length === 0) {
29
+ terminal.info("No notification rules found.");
30
+ terminal.dim("Define notifications in your monitor DSL and run griffin apply.");
31
+ return;
60
32
  }
61
- catch (error) {
62
- terminal.error(error.message);
63
- terminal.exit(1);
33
+ terminal.info("Notification Rules (synced from monitor DSL)");
34
+ terminal.blank();
35
+ const table = terminal.table({
36
+ head: ["ID", "Monitor", "Integration", "Trigger", "Enabled"],
37
+ });
38
+ for (const rule of rules) {
39
+ const triggerDesc = formatTrigger(rule.trigger);
40
+ const enabled = rule.enabled ? "✓" : "✗";
41
+ table.push([
42
+ rule.id.substring(0, 8) + "...",
43
+ rule.monitorId ?? "-",
44
+ rule.integrationName ?? "-",
45
+ triggerDesc,
46
+ enabled,
47
+ ]);
64
48
  }
65
- }
49
+ terminal.log(table.toString());
50
+ });
66
51
  async function fetchNotificationIntegrations(sdk) {
67
52
  const result = await withSDKErrorHandling(() => sdk.getIntegrations({
68
53
  query: { category: "notifications", enabled: true },
69
54
  }), "Failed to fetch integrations");
70
55
  const list = result?.data?.data ?? [];
71
- return list
72
- .filter((i) => i.category === "notifications")
73
- .map((i) => ({
74
- id: i.id,
75
- name: i.name,
76
- provider: i.provider,
77
- }));
56
+ return list.filter((i) => i.category === "notifications");
78
57
  }
79
- function promptRoutingForProvider(provider) {
58
+ async function promptRoutingForProvider(provider) {
80
59
  switch (provider) {
81
60
  case "slack": {
82
61
  return terminal
83
62
  .input("Slack channel (e.g. #alerts)", "#alerts")
84
63
  .then((value) => ({
85
- provider: "slack",
64
+ channelType: "slack",
86
65
  channel: (value ?? "").trim() || "#alerts",
87
66
  }));
88
67
  }
89
- case "email": {
68
+ case "email":
69
+ case "resend":
90
70
  return terminal.input("To addresses (comma-separated)").then((value) => {
91
71
  const toAddresses = (value ?? "")
92
72
  .split(",")
@@ -96,11 +76,13 @@ function promptRoutingForProvider(provider) {
96
76
  terminal.error("At least one email address is required.");
97
77
  terminal.exit(1);
98
78
  }
99
- return { provider: "email", toAddresses };
79
+ return { channelType: "email", toAddresses };
100
80
  });
101
- }
81
+ case "webhook":
82
+ return { channelType: "webhook" };
102
83
  default:
103
- return Promise.resolve({ provider: "webhook" });
84
+ terminal.error(`Unsupported provider: ${provider}`);
85
+ throw new Error(`Unsupported provider: ${provider}`);
104
86
  }
105
87
  }
106
88
  /**
@@ -108,7 +90,7 @@ function promptRoutingForProvider(provider) {
108
90
  */
109
91
  function buildRoutingFromOptions(options) {
110
92
  if (options.channel !== undefined && options.channel !== "") {
111
- return { provider: "slack", channel: options.channel };
93
+ return { channelType: "slack", channel: options.channel };
112
94
  }
113
95
  if (options.toAddresses !== undefined && options.toAddresses !== "") {
114
96
  const toAddresses = options.toAddresses
@@ -116,10 +98,10 @@ function buildRoutingFromOptions(options) {
116
98
  .map((s) => s.trim())
117
99
  .filter(Boolean);
118
100
  if (toAddresses.length > 0) {
119
- return { provider: "email", toAddresses };
101
+ return { channelType: "email", toAddresses };
120
102
  }
121
103
  }
122
- return { provider: "webhook" };
104
+ return { channelType: "webhook" };
123
105
  }
124
106
  /**
125
107
  * Run interactive wizard: select integration, then prompt for provider-specific options (channel, to-addresses, or none).
@@ -128,7 +110,7 @@ async function executeNotificationsTestWizard(sdk, options) {
128
110
  const integrations = await fetchNotificationIntegrations(sdk);
129
111
  if (integrations.length === 0) {
130
112
  terminal.error("No notification integrations found.");
131
- terminal.dim("Connect one with: griffin hub integrations connect notifications <provider>");
113
+ terminal.dim("Connect one with: griffin integrations connect notifications <provider>");
132
114
  terminal.exit(1);
133
115
  }
134
116
  if (options.integration) {
@@ -160,47 +142,34 @@ function canBuildRoutingFromOptions(options) {
160
142
  * Test a notification integration. Without options, runs a wizard (select integration, then provider-specific prompts).
161
143
  * With --integration and routing flags, runs non-interactively.
162
144
  */
163
- export async function executeNotificationsTest(options) {
164
- try {
165
- const state = await loadState();
166
- if (!state.hub?.baseUrl) {
167
- terminal.error("Hub connection not configured.");
168
- terminal.dim("Connect with:");
169
- terminal.dim(" griffin hub connect --url <url> --token <token>");
170
- terminal.exit(1);
171
- }
172
- const sdk = await createSdkWithCredentials(state.hub.baseUrl);
173
- const useWizard = !options.integration || !canBuildRoutingFromOptions(options);
174
- let integration;
175
- let routing;
176
- if (useWizard) {
177
- const wizardResult = await executeNotificationsTestWizard(sdk, options);
178
- integration = wizardResult.integration;
179
- routing = wizardResult.routing;
180
- }
181
- else {
182
- integration = options.integration;
183
- routing = buildRoutingFromOptions(options);
184
- }
185
- const spinner = terminal.spinner("Sending test notification...").start();
186
- const response = await withSDKErrorHandling(() => sdk.postNotificationsTest({
187
- body: { integration, routing },
188
- }), "Failed to send test notification");
189
- spinner.stop();
190
- const result = response?.data?.data;
191
- if (result?.success) {
192
- terminal.success(result.message || "Test notification delivered successfully");
193
- }
194
- else {
195
- terminal.error("Test notification failed");
196
- terminal.exit(1);
197
- }
145
+ export const executeNotificationsTest = withCommandErrorHandler(async (options) => {
146
+ const { sdk } = await createSdkAndState();
147
+ const useWizard = !options.integration || !canBuildRoutingFromOptions(options);
148
+ let integration;
149
+ let routing;
150
+ if (useWizard) {
151
+ const wizardResult = await executeNotificationsTestWizard(sdk, options);
152
+ integration = wizardResult.integration;
153
+ routing = wizardResult.routing;
198
154
  }
199
- catch (error) {
200
- terminal.error(error.message);
155
+ else {
156
+ integration = options.integration;
157
+ routing = buildRoutingFromOptions(options);
158
+ }
159
+ const spinner = terminal.spinner("Sending test notification...").start();
160
+ const response = await withSDKErrorHandling(() => sdk.postNotificationsTest({
161
+ body: { integration, routing },
162
+ }), "Failed to send test notification");
163
+ spinner.stop();
164
+ const result = response?.data?.data;
165
+ if (result?.success) {
166
+ terminal.success(result.message || "Test notification delivered successfully");
167
+ }
168
+ else {
169
+ terminal.error("Test notification failed");
201
170
  terminal.exit(1);
202
171
  }
203
- }
172
+ });
204
173
  /**
205
174
  * Format trigger for display
206
175
  */
@@ -7,4 +7,4 @@ export interface RunOptions {
7
7
  /**
8
8
  * Trigger a monitor run on the hub
9
9
  */
10
- export declare function executeRun(options: RunOptions): Promise<void>;
10
+ export declare const executeRun: (options: RunOptions) => Promise<void>;
@@ -1,162 +1,136 @@
1
- import { loadState, resolveEnvironment } from "../../core/state.js";
2
- import { createSdkWithCredentials } from "../../core/sdk.js";
3
- import { discoverMonitors, formatDiscoveryErrors, } from "../../core/discovery.js";
1
+ import { resolveEnvironment } from "../../core/state.js";
2
+ import { createSdkAndState } from "../../core/sdk.js";
4
3
  import { computeDiff } from "../../core/diff.js";
5
4
  import { terminal } from "../../utils/terminal.js";
6
5
  import { withSDKErrorHandling } from "../../utils/sdk-error.js";
6
+ import { withCommandErrorHandler } from "../../utils/command-wrapper.js";
7
+ import { discoverLocalMonitors, } from "../../core/monitor-helpers.js";
7
8
  import { loadVariables } from "../../core/variables.js";
8
9
  import { resolveMonitor } from "../../resolve.js";
9
10
  /**
10
11
  * Trigger a monitor run on the hub
11
12
  */
12
- export async function executeRun(options) {
13
- try {
14
- // Load state
15
- const state = await loadState();
16
- // Resolve environment
17
- const envName = await resolveEnvironment(options.env);
18
- if (!state.hub?.baseUrl) {
19
- terminal.error("Hub connection not configured.");
20
- terminal.dim("Connect with:");
21
- terminal.dim(" griffin hub connect --url <url> --token <token>");
22
- terminal.exit(1);
23
- }
24
- // Create SDK clients with credentials
25
- const sdk = await createSdkWithCredentials(state.hub.baseUrl);
26
- // Discover local monitors
27
- const discoveryPattern = state.discovery?.pattern || "**/__griffin__/*.{ts,js}";
28
- const discoveryIgnore = state.discovery?.ignore || [
29
- "node_modules/**",
30
- "dist/**",
31
- ];
32
- const spinner = terminal.spinner("Discovering local monitors...").start();
33
- const { monitors: discoveredMonitors, errors } = await discoverMonitors(discoveryPattern, discoveryIgnore);
34
- if (errors.length > 0) {
35
- spinner.fail("Discovery failed");
36
- terminal.error(formatDiscoveryErrors(errors));
37
- terminal.exit(1);
38
- }
39
- // Find local monitor by name
40
- const localMonitor = discoveredMonitors.find((p) => p.monitor.name === options.monitor);
41
- if (!localMonitor) {
42
- spinner.fail(`Monitor "${options.monitor}" not found locally`);
43
- terminal.blank();
44
- terminal.info("Available monitors:");
45
- for (const p of discoveredMonitors) {
46
- terminal.dim(` - ${p.monitor.name}`);
47
- }
48
- terminal.exit(1);
49
- }
50
- spinner.succeed(`Found local monitor: ${terminal.colors.cyan(options.monitor)}`);
51
- // Fetch remote monitors for this project + environment
52
- const fetchSpinner = terminal.spinner("Checking hub...").start();
53
- const response = await withSDKErrorHandling(() => sdk.getMonitor({
54
- query: {
55
- projectId: state.projectId,
56
- environment: envName,
57
- },
58
- }), "Failed to fetch monitors from hub");
59
- const remoteMonitors = response?.data?.data;
60
- // Find remote monitor by name
61
- const remoteMonitor = remoteMonitors.find((p) => p.name === options.monitor);
62
- if (!remoteMonitor) {
63
- fetchSpinner.fail(`Monitor "${options.monitor}" not found on hub`);
64
- terminal.dim("Run 'griffin hub apply' to sync your monitors first");
65
- terminal.exit(1);
66
- }
67
- fetchSpinner.succeed("Monitor found on hub");
68
- // Load variables and resolve local monitor before computing diff
69
- const variables = await loadVariables(envName);
70
- const resolvedLocalMonitor = resolveMonitor(localMonitor.monitor, state.projectId, envName, variables);
71
- // Compute diff to check if local monitor differs from remote
72
- const diff = computeDiff([resolvedLocalMonitor], [remoteMonitor], {
73
- includeDeletions: false,
74
- });
75
- const hasDiff = diff.actions.length > 0 &&
76
- diff.actions.some((a) => a.type === "update" || a.type === "create");
77
- if (hasDiff && !options.force) {
78
- terminal.error(`Local monitor "${options.monitor}" differs from hub`);
79
- terminal.blank();
80
- terminal.warn("The monitor on the hub is different from your local version.");
81
- terminal.dim("Run 'griffin hub apply' to sync, or use --force to run anyway.");
82
- terminal.exit(1);
83
- }
84
- // Trigger the run
13
+ export const executeRun = withCommandErrorHandler(async (options) => {
14
+ const { sdk, state } = await createSdkAndState();
15
+ // Resolve environment
16
+ const envName = await resolveEnvironment(options.env);
17
+ // Discover local monitors
18
+ const { monitors: discoveredMonitors } = await discoverLocalMonitors(state);
19
+ // Find local monitor by name
20
+ const localMonitor = discoveredMonitors.find((p) => p.monitor.name === options.monitor);
21
+ if (!localMonitor) {
22
+ terminal.error(`Monitor "${options.monitor}" not found locally`);
85
23
  terminal.blank();
86
- terminal.info(`Triggering run for monitor: ${terminal.colors.cyan(options.monitor)}`);
87
- terminal.log(`Target environment: ${terminal.colors.cyan(envName)}`);
88
- if (hasDiff && options.force) {
89
- terminal.warn("Running with --force (local changes not applied)");
24
+ terminal.info("Available monitors:");
25
+ for (const p of discoveredMonitors) {
26
+ terminal.dim(` - ${p.monitor.name}`);
90
27
  }
91
- const triggerSpinner = terminal.spinner("Triggering run...").start();
92
- const runResponse = await withSDKErrorHandling(() => sdk.postRunsTriggerByMonitorId({
93
- path: {
94
- monitorId: remoteMonitor.id,
95
- },
96
- body: {
97
- environment: envName,
98
- },
99
- }), "Failed to trigger run");
100
- const run = runResponse?.data?.data;
101
- triggerSpinner.succeed("Run triggered");
28
+ terminal.exit(1);
29
+ }
30
+ // Fetch remote monitors for this project + environment
31
+ const fetchSpinner = terminal.spinner("Checking hub...").start();
32
+ const response = await withSDKErrorHandling(() => sdk.getMonitor({
33
+ query: {
34
+ projectId: state.projectId,
35
+ environment: envName,
36
+ },
37
+ }), "Failed to fetch monitors from hub");
38
+ const remoteMonitors = response?.data?.data;
39
+ // Find remote monitor by name
40
+ const remoteMonitor = remoteMonitors.find((p) => p.name === options.monitor);
41
+ if (!remoteMonitor) {
42
+ fetchSpinner.fail(`Monitor "${options.monitor}" not found on hub`);
43
+ terminal.dim("Run 'griffin apply' to sync your monitors first");
44
+ terminal.exit(1);
45
+ }
46
+ fetchSpinner.succeed("Monitor found on hub");
47
+ // Load variables and resolve local monitor before computing diff
48
+ const variables = await loadVariables(envName);
49
+ const resolvedLocalMonitor = resolveMonitor(localMonitor.monitor, state.projectId, envName, variables);
50
+ // Compute diff to check if local monitor differs from remote
51
+ const diff = computeDiff([resolvedLocalMonitor], [remoteMonitor], {
52
+ includeDeletions: false,
53
+ });
54
+ const hasDiff = diff.actions.length > 0 &&
55
+ diff.actions.some((a) => a.type === "update" || a.type === "create");
56
+ if (hasDiff && !options.force) {
57
+ terminal.error(`Local monitor "${options.monitor}" differs from hub`);
102
58
  terminal.blank();
103
- terminal.log(`Run ID: ${terminal.colors.dim(run.id)}`);
104
- terminal.log(`Status: ${terminal.colors.cyan(run.status)}`);
105
- terminal.log(`Started: ${terminal.colors.dim(new Date(run.startedAt).toLocaleString())}`);
106
- // Wait for completion if requested
107
- if (options.wait) {
108
- terminal.blank();
109
- const waitSpinner = terminal
110
- .spinner("Waiting for run to complete...")
111
- .start();
112
- const runId = run.id;
113
- let completed = false;
114
- while (!completed) {
115
- await new Promise((resolve) => setTimeout(resolve, 2000)); // Poll every 2 seconds
116
- const runStatusResponse = await withSDKErrorHandling(() => sdk.getRunsById({
117
- path: {
118
- id: runId,
119
- },
120
- }), "Failed to fetch run status");
121
- const run = runStatusResponse?.data?.data;
122
- if (run.status === "completed" || run.status === "failed") {
123
- completed = true;
124
- if (run.success) {
125
- waitSpinner.succeed(`Run ${run.status}`);
126
- }
127
- else {
128
- waitSpinner.fail(`Run ${run.status}`);
129
- }
59
+ terminal.warn("The monitor on the hub is different from your local version.");
60
+ terminal.dim("Run 'griffin apply' to sync, or use --force to run anyway.");
61
+ terminal.exit(1);
62
+ }
63
+ // Trigger the run
64
+ terminal.blank();
65
+ terminal.info(`Triggering run for monitor: ${terminal.colors.cyan(options.monitor)}`);
66
+ terminal.log(`Target environment: ${terminal.colors.cyan(envName)}`);
67
+ if (hasDiff && options.force) {
68
+ terminal.warn("Running with --force (local changes not applied)");
69
+ }
70
+ const triggerSpinner = terminal.spinner("Triggering run...").start();
71
+ const runResponse = await withSDKErrorHandling(() => sdk.postRunsTriggerByMonitorId({
72
+ path: {
73
+ monitorId: remoteMonitor.id,
74
+ },
75
+ body: {
76
+ environment: envName,
77
+ },
78
+ }), "Failed to trigger run");
79
+ const run = runResponse?.data?.data;
80
+ triggerSpinner.succeed("Run triggered");
81
+ terminal.blank();
82
+ terminal.log(`Run ID: ${terminal.colors.dim(run.id)}`);
83
+ terminal.log(`Status: ${terminal.colors.cyan(run.status)}`);
84
+ terminal.log(`Started: ${terminal.colors.dim(new Date(run.startedAt).toLocaleString())}`);
85
+ // Wait for completion if requested
86
+ if (options.wait) {
87
+ terminal.blank();
88
+ const waitSpinner = terminal
89
+ .spinner("Waiting for run to complete...")
90
+ .start();
91
+ const runId = run.id;
92
+ let completed = false;
93
+ while (!completed) {
94
+ await new Promise((resolve) => setTimeout(resolve, 2000)); // Poll every 2 seconds
95
+ const runStatusResponse = await withSDKErrorHandling(() => sdk.getRunsById({
96
+ path: {
97
+ id: runId,
98
+ },
99
+ }), "Failed to fetch run status");
100
+ const run = runStatusResponse?.data?.data;
101
+ if (run.status === "completed" || run.status === "failed") {
102
+ completed = true;
103
+ if (run.success) {
104
+ waitSpinner.succeed(`Run ${run.status}`);
105
+ }
106
+ else {
107
+ waitSpinner.fail(`Run ${run.status}`);
108
+ }
109
+ terminal.blank();
110
+ if (run.duration_ms) {
111
+ terminal.log(`Duration: ${terminal.colors.dim((run.duration_ms / 1000).toFixed(2) + "s")}`);
112
+ }
113
+ if (run.success !== undefined) {
114
+ const successText = run.success
115
+ ? terminal.colors.green("Yes")
116
+ : terminal.colors.red("No");
117
+ terminal.log(`Success: ${successText}`);
118
+ }
119
+ if (run.errors && run.errors.length > 0) {
130
120
  terminal.blank();
131
- if (run.duration_ms) {
132
- terminal.log(`Duration: ${terminal.colors.dim((run.duration_ms / 1000).toFixed(2) + "s")}`);
133
- }
134
- if (run.success !== undefined) {
135
- const successText = run.success
136
- ? terminal.colors.green("Yes")
137
- : terminal.colors.red("No");
138
- terminal.log(`Success: ${successText}`);
139
- }
140
- if (run.errors && run.errors.length > 0) {
141
- terminal.blank();
142
- terminal.error("Errors:");
143
- for (const error of run.errors) {
144
- terminal.dim(` - ${error}`);
145
- }
146
- }
147
- if (!run.success) {
148
- terminal.exit(1);
121
+ terminal.error("Errors:");
122
+ for (const error of run.errors) {
123
+ terminal.dim(` - ${error}`);
149
124
  }
150
125
  }
126
+ if (!run.success) {
127
+ terminal.exit(1);
128
+ }
151
129
  }
152
130
  }
153
- else {
154
- terminal.blank();
155
- terminal.dim("Run started. Use 'griffin hub runs' to check progress.");
156
- }
157
131
  }
158
- catch (error) {
159
- terminal.error(error.message);
160
- terminal.exit(1);
132
+ else {
133
+ terminal.blank();
134
+ terminal.dim("Run started. Use 'griffin runs' to check progress.");
161
135
  }
162
- }
136
+ });
@@ -5,4 +5,4 @@ export interface RunsOptions {
5
5
  /**
6
6
  * Show recent runs from the hub
7
7
  */
8
- export declare function executeRuns(options: RunsOptions): Promise<void>;
8
+ export declare const executeRuns: (options: RunsOptions) => Promise<void>;