@griffin-app/griffin-cli 1.0.12 → 1.0.13

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/cli.js CHANGED
@@ -3,7 +3,7 @@ import { Command } from "commander";
3
3
  import { executeInit } from "./commands/init.js";
4
4
  import { executeValidate } from "./commands/validate.js";
5
5
  import { executeGenerateKey } from "./commands/generate-key.js";
6
- import { executeEnvList, executeEnvAdd, executeEnvRemove } from "./commands/env.js";
6
+ import { executeEnvList, executeEnvAdd, executeEnvRemove, } from "./commands/env.js";
7
7
  import { executeVariablesList, executeVariablesAdd, executeVariablesRemove, } from "./commands/variables.js";
8
8
  // Local commands
9
9
  import { executeRunLocal } from "./commands/local/run.js";
@@ -67,7 +67,9 @@ env
67
67
  await executeEnvRemove(name);
68
68
  });
69
69
  // Variables command group
70
- const variables = program.command("variables").description("Manage environment variables");
70
+ const variables = program
71
+ .command("variables")
72
+ .description("Manage environment variables");
71
73
  variables
72
74
  .command("list")
73
75
  .description("List all variables for an environment")
@@ -192,8 +194,8 @@ notifications
192
194
  });
193
195
  notifications
194
196
  .command("test")
195
- .description("Send a test notification via a configured integration")
196
- .requiredOption("--integration <id|name>", "Integration id or name (e.g. Slack); use 'griffin hub integrations list --category notifications' to list")
197
+ .description("Send a test notification (interactive wizard, or use flags for scripts)")
198
+ .option("--integration <id|name>", "Integration id or name; omit to choose from a list")
197
199
  .option("--channel <channel>", "Slack channel (e.g. #alerts); use with Slack integration")
198
200
  .option("--to-addresses <emails>", "Email recipients, comma-separated; use with email integration")
199
201
  .action(async (options) => {
@@ -39,7 +39,7 @@ export declare function executeIntegrationsRemove(options: IntegrationsRemoveOpt
39
39
  export interface IntegrationsConnectOptions {
40
40
  category: IntegrationCategory;
41
41
  provider: Provider;
42
- name: string;
42
+ name?: string;
43
43
  environment?: string;
44
44
  json?: boolean;
45
45
  }
@@ -166,7 +166,7 @@ async function connectOAuth(options, _provider) {
166
166
  const body = {
167
167
  category: options.category,
168
168
  provider: options.provider,
169
- name: options.name,
169
+ name: options.name ?? options.provider,
170
170
  environment: options.environment ?? undefined,
171
171
  };
172
172
  const state = await loadState();
@@ -4,7 +4,8 @@ export interface NotificationsListOptions {
4
4
  json?: boolean;
5
5
  }
6
6
  export interface NotificationsTestOptions {
7
- integration: string;
7
+ /** Integration id or name. If omitted, wizard runs (select integration + provider options). */
8
+ integration?: string;
8
9
  /** Slack channel (e.g. #alerts). Use with Slack integration. */
9
10
  channel?: string;
10
11
  /** Email recipients, comma-separated. Use with email integration. */
@@ -16,6 +17,7 @@ export interface NotificationsTestOptions {
16
17
  */
17
18
  export declare function executeNotificationsList(options: NotificationsListOptions): Promise<void>;
18
19
  /**
19
- * Test a notification integration (same resolution path as production). Requires routing (--channel for Slack, --to-addresses for email, or omit both for webhook).
20
+ * Test a notification integration. Without options, runs a wizard (select integration, then provider-specific prompts).
21
+ * With --integration and routing flags, runs non-interactively.
20
22
  */
21
23
  export declare function executeNotificationsTest(options: NotificationsTestOptions): Promise<void>;
@@ -2,6 +2,14 @@ import { loadState } from "../../core/state.js";
2
2
  import { createSdkWithCredentials } from "../../core/sdk.js";
3
3
  import { terminal } from "../../utils/terminal.js";
4
4
  import { withSDKErrorHandling } from "../../utils/sdk-error.js";
5
+ function orExit(value, errorMessage) {
6
+ if (value === undefined) {
7
+ terminal.error(errorMessage);
8
+ terminal.exit(1);
9
+ throw new Error(errorMessage);
10
+ }
11
+ return value;
12
+ }
5
13
  /**
6
14
  * List notification rules (read-only).
7
15
  * Rules are defined in monitor DSL and synced via `griffin hub apply`.
@@ -55,6 +63,46 @@ export async function executeNotificationsList(options) {
55
63
  terminal.exit(1);
56
64
  }
57
65
  }
66
+ async function fetchNotificationIntegrations(sdk) {
67
+ const result = await withSDKErrorHandling(() => sdk.getIntegrations({
68
+ query: { category: "notifications", enabled: true },
69
+ }), "Failed to fetch integrations");
70
+ 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
+ }));
78
+ }
79
+ function promptRoutingForProvider(provider) {
80
+ switch (provider) {
81
+ case "slack": {
82
+ return terminal
83
+ .input("Slack channel (e.g. #alerts)", "#alerts")
84
+ .then((value) => ({
85
+ provider: "slack",
86
+ channel: (value ?? "").trim() || "#alerts",
87
+ }));
88
+ }
89
+ case "email": {
90
+ return terminal.input("To addresses (comma-separated)").then((value) => {
91
+ const toAddresses = (value ?? "")
92
+ .split(",")
93
+ .map((s) => s.trim())
94
+ .filter(Boolean);
95
+ if (toAddresses.length === 0) {
96
+ terminal.error("At least one email address is required.");
97
+ terminal.exit(1);
98
+ }
99
+ return { provider: "email", toAddresses };
100
+ });
101
+ }
102
+ default:
103
+ return Promise.resolve({ provider: "webhook" });
104
+ }
105
+ }
58
106
  /**
59
107
  * Build routing object from CLI options. Exactly one of channel, toAddresses, or webhook (none) must be used.
60
108
  */
@@ -74,7 +122,43 @@ function buildRoutingFromOptions(options) {
74
122
  return { provider: "webhook" };
75
123
  }
76
124
  /**
77
- * Test a notification integration (same resolution path as production). Requires routing (--channel for Slack, --to-addresses for email, or omit both for webhook).
125
+ * Run interactive wizard: select integration, then prompt for provider-specific options (channel, to-addresses, or none).
126
+ */
127
+ async function executeNotificationsTestWizard(sdk, options) {
128
+ const integrations = await fetchNotificationIntegrations(sdk);
129
+ if (integrations.length === 0) {
130
+ terminal.error("No notification integrations found.");
131
+ terminal.dim("Connect one with: griffin hub integrations connect notifications <provider>");
132
+ terminal.exit(1);
133
+ }
134
+ if (options.integration) {
135
+ const match = orExit(integrations.find((i) => i.id === options.integration ||
136
+ i.name.toLowerCase() === (options.integration ?? "").toLowerCase()), `Integration not found: ${options.integration}`);
137
+ const routing = await promptRoutingForProvider(match.provider);
138
+ return { integration: match.id, routing };
139
+ }
140
+ const choiceLabels = integrations.map((i) => `${i.name} (${i.provider})`);
141
+ const choice = await terminal.select("Select integration to test", choiceLabels);
142
+ const found = orExit(integrations.find((i) => `${i.name} (${i.provider})` === choice), "No integration selected.");
143
+ const routing = await promptRoutingForProvider(found.provider);
144
+ return { integration: found.id, routing };
145
+ }
146
+ function canBuildRoutingFromOptions(options) {
147
+ if (options.channel !== undefined && options.channel !== "")
148
+ return true;
149
+ if (options.toAddresses !== undefined && options.toAddresses !== "") {
150
+ const toAddresses = options.toAddresses
151
+ .split(",")
152
+ .map((s) => s.trim())
153
+ .filter(Boolean);
154
+ if (toAddresses.length > 0)
155
+ return true;
156
+ }
157
+ return false;
158
+ }
159
+ /**
160
+ * Test a notification integration. Without options, runs a wizard (select integration, then provider-specific prompts).
161
+ * With --integration and routing flags, runs non-interactively.
78
162
  */
79
163
  export async function executeNotificationsTest(options) {
80
164
  try {
@@ -85,11 +169,22 @@ export async function executeNotificationsTest(options) {
85
169
  terminal.dim(" griffin hub connect --url <url> --token <token>");
86
170
  terminal.exit(1);
87
171
  }
88
- const routing = buildRoutingFromOptions(options);
89
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
+ }
90
185
  const spinner = terminal.spinner("Sending test notification...").start();
91
186
  const response = await withSDKErrorHandling(() => sdk.postNotificationsTest({
92
- body: { integration: options.integration, routing },
187
+ body: { integration, routing },
93
188
  }), "Failed to send test notification");
94
189
  spinner.stop();
95
190
  const result = response?.data?.data;
@@ -1,4 +1,4 @@
1
- import { initState, stateExists, getStateFilePath, } from "../core/state.js";
1
+ import { initState, stateExists, getStateFilePath } from "../core/state.js";
2
2
  import { detectProjectId } from "../core/project.js";
3
3
  import { terminal } from "../utils/terminal.js";
4
4
  /**
@@ -9,6 +9,7 @@ function createMonitor(name) {
9
9
  environment: "test",
10
10
  version: "1.0",
11
11
  frequency: { every: 5, unit: "MINUTE" },
12
+ notifications: [],
12
13
  nodes: [],
13
14
  edges: [],
14
15
  };
@@ -11,6 +11,7 @@ function createMonitor(name, overrides) {
11
11
  frequency: { every: 5, unit: "MINUTE" },
12
12
  nodes: [],
13
13
  edges: [],
14
+ notifications: [],
14
15
  ...overrides,
15
16
  };
16
17
  }
@@ -24,6 +25,7 @@ function createResolvedMonitor(name, overrides) {
24
25
  frequency: { every: 5, unit: "MINUTE" },
25
26
  nodes: [],
26
27
  edges: [],
28
+ notifications: [],
27
29
  ...overrides,
28
30
  };
29
31
  }
@@ -247,6 +247,21 @@ function compareTopLevel(local, remote) {
247
247
  newValue: localLocations,
248
248
  });
249
249
  }
250
+ // Compare notifications (normalize empty array to undefined)
251
+ const localNotifications = local.notifications && local.notifications.length > 0
252
+ ? local.notifications
253
+ : undefined;
254
+ const remoteNotifications = remote.notifications && remote.notifications.length > 0
255
+ ? remote.notifications
256
+ : undefined;
257
+ console.log("REMOTE NOTIFICATIONS", remoteNotifications);
258
+ if (!deepEqual(localNotifications, remoteNotifications)) {
259
+ changes.push({
260
+ field: "notifications",
261
+ oldValue: remoteNotifications,
262
+ newValue: localNotifications,
263
+ });
264
+ }
250
265
  return changes;
251
266
  }
252
267
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@griffin-app/griffin-cli",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "CLI tool for running and managing griffin API tests",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,9 +24,9 @@
24
24
  "author": "",
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
- "@griffin-app/griffin-hub-sdk": "1.0.15",
27
+ "@griffin-app/griffin-hub-sdk": "1.0.16",
28
28
  "@griffin-app/griffin-plan-executor": "0.1.19",
29
- "@griffin-app/griffin-ts": "0.1.17",
29
+ "@griffin-app/griffin-ts": "0.1.18",
30
30
  "better-auth": "^1.4.17",
31
31
  "cli-table3": "^0.6.5",
32
32
  "commander": "^12.1.0",