@griffin-app/griffin-cli 1.0.11 → 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,8 @@ 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 } from "./commands/env.js";
6
+ import { executeEnvList, executeEnvAdd, executeEnvRemove, } from "./commands/env.js";
7
+ import { executeVariablesList, executeVariablesAdd, executeVariablesRemove, } from "./commands/variables.js";
7
8
  // Local commands
8
9
  import { executeRunLocal } from "./commands/local/run.js";
9
10
  // Hub commands
@@ -16,9 +17,10 @@ import { executeApply } from "./commands/hub/apply.js";
16
17
  import { executeRun } from "./commands/hub/run.js";
17
18
  import { executeLogin } from "./commands/hub/login.js";
18
19
  import { executeLogout } from "./commands/hub/logout.js";
20
+ import { executeIntegrationsConnect } from "./commands/hub/integrations.js";
19
21
  import { executeNotificationsList, executeNotificationsTest, } from "./commands/hub/notifications.js";
20
22
  import { executeSecretsList, executeSecretsSet, executeSecretsGet, executeSecretsDelete, } from "./commands/hub/secrets.js";
21
- import { executeIntegrationsList, executeIntegrationsShow, executeIntegrationsAdd, executeIntegrationsUpdate, executeIntegrationsRemove, } from "./commands/hub/integrations.js";
23
+ import { executeIntegrationsList, executeIntegrationsShow, executeIntegrationsUpdate, executeIntegrationsRemove, } from "./commands/hub/integrations.js";
22
24
  const program = new Command();
23
25
  program
24
26
  .name("griffin")
@@ -52,6 +54,43 @@ env
52
54
  .action(async () => {
53
55
  await executeEnvList();
54
56
  });
57
+ env
58
+ .command("add <name>")
59
+ .description("Add a new environment")
60
+ .action(async (name) => {
61
+ await executeEnvAdd(name);
62
+ });
63
+ env
64
+ .command("remove <name>")
65
+ .description("Remove an environment")
66
+ .action(async (name) => {
67
+ await executeEnvRemove(name);
68
+ });
69
+ // Variables command group
70
+ const variables = program
71
+ .command("variables")
72
+ .description("Manage environment variables");
73
+ variables
74
+ .command("list")
75
+ .description("List all variables for an environment")
76
+ .requiredOption("--env <name>", "Environment name")
77
+ .action(async (options) => {
78
+ await executeVariablesList(options.env);
79
+ });
80
+ variables
81
+ .command("add <keyValue>")
82
+ .description("Add or update a variable (format: KEY=VALUE)")
83
+ .requiredOption("--env <name>", "Environment name")
84
+ .action(async (keyValue, options) => {
85
+ await executeVariablesAdd(keyValue, options.env);
86
+ });
87
+ variables
88
+ .command("remove <key>")
89
+ .description("Remove a variable")
90
+ .requiredOption("--env <name>", "Environment name")
91
+ .action(async (key, options) => {
92
+ await executeVariablesRemove(key, options.env);
93
+ });
55
94
  // Local command group
56
95
  const local = program.command("local").description("Local test execution");
57
96
  local
@@ -155,11 +194,15 @@ notifications
155
194
  });
156
195
  notifications
157
196
  .command("test")
158
- .description("Test a webhook configuration")
159
- .requiredOption("--webhook <url>", "Webhook URL to test")
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")
199
+ .option("--channel <channel>", "Slack channel (e.g. #alerts); use with Slack integration")
200
+ .option("--to-addresses <emails>", "Email recipients, comma-separated; use with email integration")
160
201
  .action(async (options) => {
161
202
  await executeNotificationsTest({
162
- webhook: options.webhook,
203
+ integration: options.integration,
204
+ channel: options.channel,
205
+ toAddresses: options.toAddresses,
163
206
  });
164
207
  });
165
208
  // Integrations command group
@@ -170,14 +213,14 @@ integrations
170
213
  .command("list")
171
214
  .description("List integrations")
172
215
  .option("--category <cat>", "Filter by category: secrets, notifications, metrics")
173
- .option("--provider-type <type>", "Filter by provider type")
216
+ .option("--provider <provider>", "Filter by provider")
174
217
  .option("--environment <env>", "Filter by environment")
175
218
  .option("--enabled", "Filter by enabled status")
176
219
  .option("--json", "Output as JSON")
177
220
  .action(async (options) => {
178
221
  await executeIntegrationsList({
179
222
  category: options.category,
180
- providerType: options.providerType,
223
+ provider: options.provider,
181
224
  environment: options.environment,
182
225
  enabled: options.enabled ? true : undefined,
183
226
  json: options.json,
@@ -191,24 +234,47 @@ integrations
191
234
  await executeIntegrationsShow({ id, json: options.json });
192
235
  });
193
236
  integrations
194
- .command("add")
195
- .description("Add an integration (no credentials in Phase 1; use API for credentials)")
196
- .requiredOption("--category <cat>", "Category: secrets, notifications, metrics")
197
- .requiredOption("--provider-type <type>", "Provider type (e.g. slack_webhook, datadog)")
198
- .requiredOption("--name <name>", "Display name")
199
- .option("--environment <env>", "Environment scope (omit for org-wide)")
200
- .option("--enabled", "Enabled")
237
+ .command("connect [type] [provider]")
238
+ .description("Connect an integration.")
239
+ .option("--name <name>", "Display name for the integration")
240
+ .option("--environment <env>", "Environment scope")
201
241
  .option("--json", "Output as JSON")
202
- .action(async (options) => {
203
- await executeIntegrationsAdd({
204
- category: options.category,
205
- providerType: options.providerType,
242
+ .action(async (category, provider, options) => {
243
+ await executeIntegrationsConnect({
244
+ category: category ?? "",
245
+ provider: provider ?? "",
206
246
  name: options.name,
207
247
  environment: options.environment,
208
- enabled: options.enabled,
209
248
  json: options.json,
210
249
  });
211
250
  });
251
+ //integrations
252
+ // .command("add")
253
+ // .description(
254
+ // "Add an integration (no credentials in Phase 1; use API for credentials)",
255
+ // )
256
+ // .requiredOption(
257
+ // "--category <cat>",
258
+ // "Category: secrets, notifications, metrics",
259
+ // )
260
+ // .requiredOption(
261
+ // "--provider-type <type>",
262
+ // "Provider type (e.g. slack_webhook, datadog)",
263
+ // )
264
+ // .requiredOption("--name <name>", "Display name")
265
+ // .option("--environment <env>", "Environment scope (omit for org-wide)")
266
+ // .option("--enabled", "Enabled")
267
+ // .option("--json", "Output as JSON")
268
+ // .action(async (options) => {
269
+ // await executeIntegrationsAdd({
270
+ // category: options.category,
271
+ // providerType: options.providerType,
272
+ // name: options.name,
273
+ // environment: options.environment,
274
+ // enabled: options.enabled,
275
+ // json: options.json,
276
+ // });
277
+ // });
212
278
  integrations
213
279
  .command("update <id>")
214
280
  .description("Update an integration")
@@ -232,23 +298,6 @@ integrations
232
298
  .action(async (id) => {
233
299
  await executeIntegrationsRemove({ id });
234
300
  });
235
- integrations
236
- .command("connect <provider>")
237
- .description("Connect an OAuth integration (opens browser)")
238
- .option("--name <name>", "Display name for the integration")
239
- .option("--environment <env>", "Environment scope")
240
- .option("--category <cat>", "Category (default: notifications)")
241
- .option("--json", "Output integration ID as JSON")
242
- .action(async (provider, options) => {
243
- const { executeIntegrationsConnect } = await import("./commands/hub/integrations.js");
244
- await executeIntegrationsConnect({
245
- providerType: provider,
246
- name: options.name,
247
- environment: options.environment,
248
- category: options.category,
249
- json: options.json,
250
- });
251
- });
252
301
  // Secrets command group
253
302
  const secrets = hub
254
303
  .command("secrets")
@@ -2,3 +2,11 @@
2
2
  * List all available environments
3
3
  */
4
4
  export declare function executeEnvList(): Promise<void>;
5
+ /**
6
+ * Add a new environment
7
+ */
8
+ export declare function executeEnvAdd(name: string): Promise<void>;
9
+ /**
10
+ * Remove an environment
11
+ */
12
+ export declare function executeEnvRemove(name: string): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { loadState } from "../core/state.js";
1
+ import { loadState, addEnvironment, removeEnvironment } from "../core/state.js";
2
2
  import { terminal } from "../utils/terminal.js";
3
3
  /**
4
4
  * List all available environments
@@ -27,3 +27,33 @@ export async function executeEnvList() {
27
27
  terminal.exit(1);
28
28
  }
29
29
  }
30
+ /**
31
+ * Add a new environment
32
+ */
33
+ export async function executeEnvAdd(name) {
34
+ try {
35
+ await addEnvironment(name, {});
36
+ terminal.success(`Environment '${name}' added successfully.`);
37
+ terminal.blank();
38
+ terminal.dim(`Add variables with: griffin variables add KEY=VALUE --env ${name}`);
39
+ terminal.blank();
40
+ }
41
+ catch (error) {
42
+ terminal.error(error.message);
43
+ terminal.exit(1);
44
+ }
45
+ }
46
+ /**
47
+ * Remove an environment
48
+ */
49
+ export async function executeEnvRemove(name) {
50
+ try {
51
+ await removeEnvironment(name);
52
+ terminal.success(`Environment '${name}' removed successfully.`);
53
+ terminal.blank();
54
+ }
55
+ catch (error) {
56
+ terminal.error(error.message);
57
+ terminal.exit(1);
58
+ }
59
+ }
@@ -1,6 +1,7 @@
1
+ import type { IntegrationCategory, Provider } from "@griffin-app/griffin-ts/types";
1
2
  export interface IntegrationsListOptions {
2
- category?: string;
3
- providerType?: string;
3
+ category?: IntegrationCategory;
4
+ provider?: Provider;
4
5
  environment?: string;
5
6
  enabled?: boolean;
6
7
  json?: boolean;
@@ -12,8 +13,8 @@ export interface IntegrationsShowOptions {
12
13
  }
13
14
  export declare function executeIntegrationsShow(options: IntegrationsShowOptions): Promise<void>;
14
15
  export interface IntegrationsAddOptions {
15
- category: string;
16
- providerType: string;
16
+ category: IntegrationCategory;
17
+ provider: Provider;
17
18
  name: string;
18
19
  config?: Record<string, string>;
19
20
  environment?: string;
@@ -36,10 +37,11 @@ export interface IntegrationsRemoveOptions {
36
37
  }
37
38
  export declare function executeIntegrationsRemove(options: IntegrationsRemoveOptions): Promise<void>;
38
39
  export interface IntegrationsConnectOptions {
39
- providerType: string;
40
+ category: IntegrationCategory;
41
+ provider: Provider;
40
42
  name?: string;
41
43
  environment?: string;
42
- category?: string;
43
44
  json?: boolean;
44
45
  }
46
+ export declare function printConnectHelp(): void;
45
47
  export declare function executeIntegrationsConnect(options: IntegrationsConnectOptions): Promise<void>;
@@ -1,59 +1,21 @@
1
1
  import { exec } from "node:child_process";
2
2
  import { platform } from "node:os";
3
3
  import { loadState } from "../../core/state.js";
4
- import { getHubCredentials } from "../../core/credentials.js";
5
4
  import { terminal } from "../../utils/terminal.js";
6
- async function hubFetch(path, options = {}) {
5
+ import { getProvider, getProviders, getCategories, getAllProvidersByCategory, } from "../../providers/registry.js";
6
+ import { createSdkWithCredentials } from "../../core/sdk.js";
7
+ export async function executeIntegrationsList(options) {
7
8
  const state = await loadState();
8
- if (!state.hub?.baseUrl) {
9
- terminal.error("Hub connection not configured.");
10
- terminal.dim("Connect with: griffin hub connect --url <url> --token <token>");
11
- process.exit(1);
12
- }
13
- const credentials = await getHubCredentials();
14
- const url = `${state.hub.baseUrl.replace(/\/$/, "")}${path}`;
15
- const headers = {
16
- "Content-Type": "application/json",
17
- };
18
- if (credentials?.token) {
19
- headers["Authorization"] = `Bearer ${credentials.token}`;
20
- }
21
- const res = await fetch(url, {
22
- method: options.method ?? "GET",
23
- headers,
24
- body: options.body ? JSON.stringify(options.body) : undefined,
9
+ const sdk = await createSdkWithCredentials(state.hub.baseUrl);
10
+ const result = await sdk.getIntegrations({
11
+ query: {
12
+ category: options.category,
13
+ provider: options.provider,
14
+ environment: options.environment,
15
+ enabled: options.enabled,
16
+ },
25
17
  });
26
- const text = await res.text();
27
- let json;
28
- try {
29
- json = text ? JSON.parse(text) : {};
30
- }
31
- catch {
32
- return { error: res.ok ? "Invalid response" : text || res.statusText };
33
- }
34
- if (!res.ok) {
35
- return { error: json.error ?? res.statusText };
36
- }
37
- return json;
38
- }
39
- export async function executeIntegrationsList(options) {
40
- const params = new URLSearchParams();
41
- if (options.category)
42
- params.set("category", options.category);
43
- if (options.providerType)
44
- params.set("providerType", options.providerType);
45
- if (options.environment != null)
46
- params.set("environment", options.environment);
47
- if (options.enabled != null)
48
- params.set("enabled", String(options.enabled));
49
- const qs = params.toString();
50
- const path = `/integrations${qs ? `?${qs}` : ""}`;
51
- const result = await hubFetch(path);
52
- if (result.error) {
53
- terminal.error(result.error);
54
- process.exit(1);
55
- }
56
- const list = result.data ?? [];
18
+ const list = result.data?.data ?? [];
57
19
  if (options.json) {
58
20
  terminal.log(JSON.stringify(list, null, 2));
59
21
  return;
@@ -69,9 +31,9 @@ export async function executeIntegrationsList(options) {
69
31
  });
70
32
  for (const i of list) {
71
33
  table.push([
72
- i.id.substring(0, 12) + (i.id.length > 12 ? "…" : ""),
34
+ i.id,
73
35
  i.category,
74
- i.providerType,
36
+ i.provider,
75
37
  i.name,
76
38
  i.environment ?? "(all)",
77
39
  i.enabled ? "✓" : "✗",
@@ -80,12 +42,12 @@ export async function executeIntegrationsList(options) {
80
42
  terminal.log(table.toString());
81
43
  }
82
44
  export async function executeIntegrationsShow(options) {
83
- const result = await hubFetch(`/integrations/${encodeURIComponent(options.id)}`);
84
- if (result.error) {
85
- terminal.error(result.error);
86
- process.exit(1);
87
- }
88
- const integration = result.data;
45
+ const state = await loadState();
46
+ const sdk = await createSdkWithCredentials(state.hub.baseUrl);
47
+ const result = await sdk.getIntegrationsById({
48
+ path: { id: options.id },
49
+ });
50
+ const integration = result.data?.data;
89
51
  if (!integration) {
90
52
  terminal.error("Integration not found.");
91
53
  process.exit(1);
@@ -97,7 +59,7 @@ export async function executeIntegrationsShow(options) {
97
59
  terminal.info(integration.name);
98
60
  terminal.dim(`ID: ${integration.id}`);
99
61
  terminal.log(`Category: ${integration.category}`);
100
- terminal.log(`Provider: ${integration.providerType}`);
62
+ terminal.log(`Provider: ${integration.provider}`);
101
63
  terminal.log(`Environment: ${integration.environment ?? "(all)"}`);
102
64
  terminal.log(`Enabled: ${integration.enabled ? "Yes" : "No"}`);
103
65
  terminal.log(`Has credentials: ${integration.hasCredentials ? "Yes" : "No"}`);
@@ -108,23 +70,20 @@ export async function executeIntegrationsShow(options) {
108
70
  export async function executeIntegrationsAdd(options) {
109
71
  const body = {
110
72
  category: options.category,
111
- providerType: options.providerType,
73
+ provider: options.provider,
112
74
  name: options.name,
113
75
  config: options.config ?? {},
114
76
  environment: options.environment ?? null,
115
77
  enabled: options.enabled ?? true,
116
78
  };
117
- const result = await hubFetch("/integrations", {
118
- method: "POST",
79
+ const state = await loadState();
80
+ const sdk = await createSdkWithCredentials(state.hub.baseUrl);
81
+ const result = await sdk.postIntegrations({
119
82
  body,
120
83
  });
121
- if (result.error) {
122
- terminal.error(result.error);
123
- process.exit(1);
124
- }
125
- const integration = result.data;
84
+ const integration = result.data?.data;
126
85
  if (!integration) {
127
- terminal.error("Create failed.");
86
+ terminal.error("Integration not found.");
128
87
  process.exit(1);
129
88
  }
130
89
  if (options.json) {
@@ -135,37 +94,44 @@ export async function executeIntegrationsAdd(options) {
135
94
  }
136
95
  export async function executeIntegrationsUpdate(options) {
137
96
  const body = {};
138
- if (options.name !== undefined)
139
- body.name = options.name;
140
- if (options.config !== undefined)
141
- body.config = options.config;
142
- if (options.environment !== undefined)
143
- body.environment = options.environment;
144
- if (options.enabled !== undefined)
145
- body.enabled = options.enabled;
146
- const result = await hubFetch(`/integrations/${encodeURIComponent(options.id)}`, { method: "PATCH", body });
147
- if (result.error) {
148
- terminal.error(result.error);
149
- process.exit(1);
150
- }
151
- const integration = result.data;
97
+ //if (options.name !== undefined) body.name = options.name;
98
+ //if (options.config !== undefined) body.config = options.config;
99
+ //if (options.environment !== undefined) body.environment = options.environment;
100
+ //if (options.enabled !== undefined) body.enabled = options.enabled;
101
+ const state = await loadState();
102
+ const sdk = await createSdkWithCredentials(state.hub.baseUrl);
103
+ const result = await sdk.patchIntegrationsById({
104
+ path: { id: options.id },
105
+ body: {
106
+ name: options.name,
107
+ config: options.config,
108
+ environment: options.environment,
109
+ enabled: options.enabled,
110
+ },
111
+ });
112
+ const integration = result.data?.data;
152
113
  if (!integration) {
153
- terminal.error("Update failed.");
114
+ terminal.error("Integration not found.");
154
115
  process.exit(1);
155
116
  }
156
117
  if (options.json) {
157
118
  terminal.log(JSON.stringify(integration, null, 2));
158
119
  return;
159
120
  }
160
- terminal.info(`Updated integration ${integration.name}`);
121
+ terminal.success(`Updated integration ${integration.name}`);
161
122
  }
162
123
  export async function executeIntegrationsRemove(options) {
163
- const result = await hubFetch(`/integrations/${encodeURIComponent(options.id)}`, { method: "DELETE" });
164
- if (result.error) {
165
- terminal.error(result.error);
124
+ const state = await loadState();
125
+ const sdk = await createSdkWithCredentials(state.hub.baseUrl);
126
+ const result = await sdk.deleteIntegrationsById({
127
+ path: { id: options.id },
128
+ });
129
+ const integration = result.data?.data;
130
+ if (!integration) {
131
+ terminal.error("Integration not found.");
166
132
  process.exit(1);
167
133
  }
168
- terminal.info("Integration removed.");
134
+ terminal.success(`Integration removed.`);
169
135
  }
170
136
  function openBrowser(url) {
171
137
  const cmd = platform() === "darwin"
@@ -182,19 +148,32 @@ function openBrowser(url) {
182
148
  const POLL_INITIAL_MS = 2000;
183
149
  const POLL_MAX_MS = 10000;
184
150
  const POLL_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes
185
- export async function executeIntegrationsConnect(options) {
186
- const category = options.category ?? "notifications";
151
+ export function printConnectHelp() {
152
+ terminal.log("Usage: griffin hub integrations connect <category> <provider> [options]");
153
+ terminal.blank();
154
+ terminal.log("Categories and Providers:");
155
+ const byCategory = getAllProvidersByCategory();
156
+ for (const category of getCategories()) {
157
+ const providers = byCategory.get(category) ?? [];
158
+ terminal.log(` ${category}`);
159
+ for (const p of providers) {
160
+ terminal.log(` ${p.provider.padEnd(14)} ${p.displayName}`);
161
+ }
162
+ terminal.blank();
163
+ }
164
+ }
165
+ async function connectOAuth(options, _provider) {
187
166
  const body = {
188
- category,
189
- providerType: options.providerType,
190
- name: options.name ?? undefined,
167
+ category: options.category,
168
+ provider: options.provider,
169
+ name: options.name ?? options.provider,
191
170
  environment: options.environment ?? undefined,
192
171
  };
193
- const result = await hubFetch("/integrations/oauth/initiate", { method: "POST", body });
194
- if (result.error) {
195
- terminal.error(result.error);
196
- process.exit(1);
197
- }
172
+ const state = await loadState();
173
+ const sdk = await createSdkWithCredentials(state.hub.baseUrl);
174
+ const result = await sdk.postIntegrationsOauthInitiate({
175
+ body,
176
+ });
198
177
  const data = result.data;
199
178
  if (!data) {
200
179
  terminal.error("Initiate failed.");
@@ -208,10 +187,9 @@ export async function executeIntegrationsConnect(options) {
208
187
  const start = Date.now();
209
188
  let interval = POLL_INITIAL_MS;
210
189
  const poll = async () => {
211
- const statusResult = await hubFetch(`/integrations/oauth/status/${encodeURIComponent(data.transactionId)}`);
212
- if (statusResult.error) {
213
- throw new Error(statusResult.error);
214
- }
190
+ const statusResult = await sdk.getIntegrationsOauthStatusByTransactionId({
191
+ path: { transactionId: data.transactionId },
192
+ });
215
193
  const status = statusResult.data;
216
194
  if (!status) {
217
195
  throw new Error("Invalid status response");
@@ -253,3 +231,78 @@ export async function executeIntegrationsConnect(options) {
253
231
  process.exit(1);
254
232
  }
255
233
  }
234
+ async function connectCredentials(options, provider) {
235
+ const config = {};
236
+ for (const field of provider.configFields ?? []) {
237
+ const value = await terminal.input(`${field.label}${field.required ? " (required)" : ""}`, field.default);
238
+ if (field.required && !value?.trim()) {
239
+ terminal.error(`${field.label} is required`);
240
+ process.exit(1);
241
+ }
242
+ if (value?.trim())
243
+ config[field.key] = value.trim();
244
+ }
245
+ const credentials = {};
246
+ for (const field of provider.credentialFields ?? []) {
247
+ const value = field.secret
248
+ ? await terminal.password(`${field.label}${field.required ? " (required)" : ""}`)
249
+ : await terminal.input(`${field.label}${field.required ? " (required)" : ""}`, field.default);
250
+ if (field.required && !value?.trim()) {
251
+ terminal.error(`${field.label} is required`);
252
+ process.exit(1);
253
+ }
254
+ if (value?.trim())
255
+ credentials[field.key] = value.trim();
256
+ }
257
+ const body = {
258
+ category: options.category,
259
+ provider: options.provider,
260
+ name: options.name ?? provider.displayName,
261
+ config,
262
+ credentials: Object.keys(credentials).length > 0 ? credentials : undefined,
263
+ environment: options.environment ?? null,
264
+ enabled: true,
265
+ };
266
+ const state = await loadState();
267
+ const sdk = await createSdkWithCredentials(state.hub.baseUrl);
268
+ const result = await sdk.postIntegrations({
269
+ body,
270
+ });
271
+ const integration = result.data?.data;
272
+ if (!integration) {
273
+ terminal.error("Integration not found.");
274
+ process.exit(1);
275
+ }
276
+ if (options.json) {
277
+ terminal.log(JSON.stringify(integration, null, 2));
278
+ return;
279
+ }
280
+ terminal.success(`Integration connected: ${integration.name} (${integration.id})`);
281
+ }
282
+ export async function executeIntegrationsConnect(options) {
283
+ if (!options.category || !options.provider) {
284
+ printConnectHelp();
285
+ process.exit(0);
286
+ }
287
+ const providerDef = getProvider(options.category, options.provider);
288
+ if (!providerDef) {
289
+ terminal.error(`Unknown provider: ${options.category}/${options.provider}`);
290
+ const providersForCategory = getProviders(options.category);
291
+ if (providersForCategory.length > 0) {
292
+ terminal.info(`Available providers for ${options.category}:`);
293
+ for (const p of providersForCategory) {
294
+ terminal.log(` - ${p.provider} (${p.displayName})`);
295
+ }
296
+ }
297
+ else {
298
+ terminal.info("Available categories: " + getCategories().join(", "));
299
+ }
300
+ process.exit(1);
301
+ }
302
+ if (providerDef.authMethod === "oauth") {
303
+ await connectOAuth(options, providerDef);
304
+ }
305
+ else {
306
+ await connectCredentials(options, providerDef);
307
+ }
308
+ }
@@ -4,7 +4,12 @@ export interface NotificationsListOptions {
4
4
  json?: boolean;
5
5
  }
6
6
  export interface NotificationsTestOptions {
7
- webhook: string;
7
+ /** Integration id or name. If omitted, wizard runs (select integration + provider options). */
8
+ integration?: string;
9
+ /** Slack channel (e.g. #alerts). Use with Slack integration. */
10
+ channel?: string;
11
+ /** Email recipients, comma-separated. Use with email integration. */
12
+ toAddresses?: string;
8
13
  }
9
14
  /**
10
15
  * List notification rules (read-only).
@@ -12,6 +17,7 @@ export interface NotificationsTestOptions {
12
17
  */
13
18
  export declare function executeNotificationsList(options: NotificationsListOptions): Promise<void>;
14
19
  /**
15
- * Test a 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.
16
22
  */
17
23
  export declare function executeNotificationsTest(options: NotificationsTestOptions): Promise<void>;