@griffin-app/griffin-cli 1.0.8 → 1.0.10

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
@@ -16,7 +16,9 @@ import { executeApply } from "./commands/hub/apply.js";
16
16
  import { executeRun } from "./commands/hub/run.js";
17
17
  import { executeLogin } from "./commands/hub/login.js";
18
18
  import { executeLogout } from "./commands/hub/logout.js";
19
- import { executeNotificationsList, executeNotificationsAdd, executeNotificationsDelete, executeNotificationsTest, } from "./commands/hub/notifications.js";
19
+ import { executeNotificationsList, executeNotificationsTest, } from "./commands/hub/notifications.js";
20
+ import { executeSecretsList, executeSecretsSet, executeSecretsGet, executeSecretsDelete, } from "./commands/hub/secrets.js";
21
+ import { executeIntegrationsList, executeIntegrationsShow, executeIntegrationsAdd, executeIntegrationsUpdate, executeIntegrationsRemove, } from "./commands/hub/integrations.js";
20
22
  const program = new Command();
21
23
  program
22
24
  .name("griffin")
@@ -53,7 +55,7 @@ env
53
55
  // Local command group
54
56
  const local = program.command("local").description("Local test execution");
55
57
  local
56
- .command("run <env>")
58
+ .command("run [env]")
57
59
  .description("Run tests locally against an environment")
58
60
  .action(async (env, options) => {
59
61
  await executeRunLocal({ env });
@@ -75,15 +77,14 @@ hub
75
77
  await executeStatus();
76
78
  });
77
79
  hub
78
- .command("metrics")
80
+ .command("metrics [env]")
79
81
  .description("Show metrics summary from the hub")
80
82
  .option("--period <period>", "Time window: 1h, 6h, 24h, 7d, 30d", "24h")
81
- .option("--environment <env>", "Filter by environment")
82
83
  .option("--json", "Output as JSON")
83
- .action(async (options) => {
84
+ .action(async (env, options) => {
84
85
  await executeMetrics({
85
86
  period: options.period,
86
- environment: options.environment,
87
+ environment: env,
87
88
  json: options.json,
88
89
  });
89
90
  });
@@ -99,14 +100,14 @@ hub
99
100
  });
100
101
  });
101
102
  hub
102
- .command("monitor <env>")
103
+ .command("plan [env]")
103
104
  .description("Show what changes would be applied")
104
105
  .option("--json", "Output in JSON format")
105
106
  .action(async (env, options) => {
106
107
  await executeMonitor({ ...options, env });
107
108
  });
108
109
  hub
109
- .command("apply <env>")
110
+ .command("apply [env]")
110
111
  .description("Apply changes to the hub")
111
112
  .option("--auto-approve", "Skip confirmation prompt")
112
113
  .option("--dry-run", "Show what would be done without making changes")
@@ -115,7 +116,7 @@ hub
115
116
  await executeApply({ ...options, env });
116
117
  });
117
118
  hub
118
- .command("run <env>")
119
+ .command("run [env]")
119
120
  .description("Trigger a monitor run on the hub")
120
121
  .requiredOption("--monitor <name>", "Monitor name to run")
121
122
  .option("--wait", "Wait for run to complete")
@@ -138,7 +139,7 @@ hub
138
139
  // Notifications command group
139
140
  const notifications = hub
140
141
  .command("notifications")
141
- .description("Manage notification rules");
142
+ .description("View notification rules (rules are defined in monitor DSL and synced via griffin hub apply)");
142
143
  notifications
143
144
  .command("list")
144
145
  .description("List notification rules")
@@ -153,39 +154,133 @@ notifications
153
154
  });
154
155
  });
155
156
  notifications
157
+ .command("test")
158
+ .description("Test a webhook configuration")
159
+ .requiredOption("--webhook <url>", "Webhook URL to test")
160
+ .action(async (options) => {
161
+ await executeNotificationsTest({
162
+ webhook: options.webhook,
163
+ });
164
+ });
165
+ // Integrations command group
166
+ const integrations = hub
167
+ .command("integrations")
168
+ .description("Manage integrations (notifications, secrets, metrics)");
169
+ integrations
170
+ .command("list")
171
+ .description("List integrations")
172
+ .option("--category <cat>", "Filter by category: secrets, notifications, metrics")
173
+ .option("--provider-type <type>", "Filter by provider type")
174
+ .option("--environment <env>", "Filter by environment")
175
+ .option("--enabled", "Filter by enabled status")
176
+ .option("--json", "Output as JSON")
177
+ .action(async (options) => {
178
+ await executeIntegrationsList({
179
+ category: options.category,
180
+ providerType: options.providerType,
181
+ environment: options.environment,
182
+ enabled: options.enabled ? true : undefined,
183
+ json: options.json,
184
+ });
185
+ });
186
+ integrations
187
+ .command("show <id>")
188
+ .description("Show integration details")
189
+ .option("--json", "Output as JSON")
190
+ .action(async (id, options) => {
191
+ await executeIntegrationsShow({ id, json: options.json });
192
+ });
193
+ integrations
156
194
  .command("add")
157
- .description("Create a notification rule")
158
- .requiredOption("--name <name>", "Rule name")
159
- .option("--monitor <id>", "Monitor ID (optional, applies to all if not specified)")
160
- .option("--environment <env>", "Environment filter")
161
- .option("--location <loc>", "Location filter")
162
- .requiredOption("--trigger <trigger>", "Trigger: run_failed, run_recovered, consecutive_failures:N, success_rate_below:threshold:window_min, latency_above:threshold_ms:percentile:window_min")
163
- .requiredOption("--webhook <url>", "Webhook URL")
164
- .option("--cooldown <minutes>", "Cooldown period in minutes", "15")
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/--no-enabled", "Enabled", true)
201
+ .option("--json", "Output as JSON")
165
202
  .action(async (options) => {
166
- await executeNotificationsAdd({
203
+ await executeIntegrationsAdd({
204
+ category: options.category,
205
+ providerType: options.providerType,
167
206
  name: options.name,
168
- monitor: options.monitor,
169
207
  environment: options.environment,
170
- location: options.location,
171
- trigger: options.trigger,
172
- webhook: options.webhook,
173
- cooldown: options.cooldown ? parseInt(options.cooldown, 10) : undefined,
208
+ enabled: options.enabled,
209
+ json: options.json,
174
210
  });
175
211
  });
176
- notifications
177
- .command("delete <id>")
178
- .description("Delete a notification rule")
212
+ integrations
213
+ .command("update <id>")
214
+ .description("Update an integration")
215
+ .option("--name <name>", "Display name")
216
+ .option("--environment <env>", "Environment scope")
217
+ .option("--enabled/--no-enabled", "Enabled")
218
+ .option("--json", "Output as JSON")
219
+ .action(async (id, options) => {
220
+ await executeIntegrationsUpdate({
221
+ id,
222
+ name: options.name,
223
+ environment: options.environment,
224
+ enabled: options.enabled,
225
+ json: options.json,
226
+ });
227
+ });
228
+ integrations
229
+ .command("remove <id>")
230
+ .description("Remove an integration")
231
+ .option("--force", "Skip confirmation")
179
232
  .action(async (id) => {
180
- await executeNotificationsDelete({ id });
233
+ await executeIntegrationsRemove({ id });
181
234
  });
182
- notifications
183
- .command("test")
184
- .description("Test a webhook configuration")
185
- .requiredOption("--webhook <url>", "Webhook URL to test")
235
+ // Secrets command group
236
+ const secrets = hub
237
+ .command("secrets")
238
+ .description("Manage secrets (platform storage)");
239
+ secrets
240
+ .command("list")
241
+ .description("List secrets for an environment")
242
+ .option("--environment <env>", "Environment name", "default")
243
+ .option("--json", "Output as JSON")
186
244
  .action(async (options) => {
187
- await executeNotificationsTest({
188
- webhook: options.webhook,
245
+ await executeSecretsList({
246
+ environment: options.environment,
247
+ json: options.json,
248
+ });
249
+ });
250
+ secrets
251
+ .command("set <name>")
252
+ .description("Create or update a secret (prompts for value)")
253
+ .option("--environment <env>", "Environment name", "default")
254
+ .option("--value <value>", "Secret value (avoid for sensitive data)")
255
+ .action(async (name, options) => {
256
+ await executeSecretsSet({
257
+ name,
258
+ environment: options.environment,
259
+ value: options.value,
260
+ });
261
+ });
262
+ secrets
263
+ .command("get <name>")
264
+ .description("Show secret metadata (not the value)")
265
+ .option("--environment <env>", "Environment name", "default")
266
+ .option("--json", "Output as JSON")
267
+ .action(async (name, options) => {
268
+ await executeSecretsGet({
269
+ name,
270
+ environment: options.environment,
271
+ json: options.json,
272
+ });
273
+ });
274
+ secrets
275
+ .command("delete <name>")
276
+ .description("Delete a secret")
277
+ .option("--environment <env>", "Environment name", "default")
278
+ .option("--force", "Skip confirmation prompt")
279
+ .action(async (name, options) => {
280
+ await executeSecretsDelete({
281
+ name,
282
+ environment: options.environment,
283
+ force: options.force,
189
284
  });
190
285
  });
191
286
  // Parse arguments
@@ -16,13 +16,8 @@ export async function executeEnvList() {
16
16
  terminal.info("Available environments:");
17
17
  terminal.blank();
18
18
  for (const envName of environments) {
19
- const isDefault = state.defaultEnvironment === envName;
20
- const marker = isDefault
21
- ? terminal.colors.green("●")
22
- : terminal.colors.dim("○");
23
- const envDisplay = isDefault
24
- ? terminal.colors.cyan(envName) + terminal.colors.dim(" (default)")
25
- : envName;
19
+ const marker = terminal.colors.green("●");
20
+ const envDisplay = terminal.colors.cyan(envName);
26
21
  terminal.log(` ${marker} ${envDisplay}`);
27
22
  }
28
23
  terminal.blank();
@@ -1,5 +1,5 @@
1
1
  import { loadState, resolveEnvironment } from "../../core/state.js";
2
- import { discoverMonitors, formatDiscoveryErrors } from "../../core/discovery.js";
2
+ import { discoverMonitors, formatDiscoveryErrors, } from "../../core/discovery.js";
3
3
  import { computeDiff, formatDiff } from "../../core/diff.js";
4
4
  import { applyDiff, formatApplyResult } from "../../core/apply.js";
5
5
  import { createSdkWithCredentials } from "../../core/sdk.js";
@@ -42,7 +42,9 @@ export async function executeApply(options) {
42
42
  }
43
43
  spinner.succeed(`Found ${monitors.length} local monitor(s)`);
44
44
  // Fetch remote monitors for this project + environment
45
- const fetchSpinner = terminal.spinner("Fetching remote monitors...").start();
45
+ const fetchSpinner = terminal
46
+ .spinner("Fetching remote monitors...")
47
+ .start();
46
48
  const response = await withSDKErrorHandling(() => sdk.getMonitor({
47
49
  query: {
48
50
  projectId: state.projectId,
@@ -0,0 +1,37 @@
1
+ export interface IntegrationsListOptions {
2
+ category?: string;
3
+ providerType?: string;
4
+ environment?: string;
5
+ enabled?: boolean;
6
+ json?: boolean;
7
+ }
8
+ export declare function executeIntegrationsList(options: IntegrationsListOptions): Promise<void>;
9
+ export interface IntegrationsShowOptions {
10
+ id: string;
11
+ json?: boolean;
12
+ }
13
+ export declare function executeIntegrationsShow(options: IntegrationsShowOptions): Promise<void>;
14
+ export interface IntegrationsAddOptions {
15
+ category: string;
16
+ providerType: string;
17
+ name: string;
18
+ config?: Record<string, string>;
19
+ environment?: string;
20
+ enabled?: boolean;
21
+ json?: boolean;
22
+ }
23
+ export declare function executeIntegrationsAdd(options: IntegrationsAddOptions): Promise<void>;
24
+ export interface IntegrationsUpdateOptions {
25
+ id: string;
26
+ name?: string;
27
+ config?: Record<string, string>;
28
+ environment?: string;
29
+ enabled?: boolean;
30
+ json?: boolean;
31
+ }
32
+ export declare function executeIntegrationsUpdate(options: IntegrationsUpdateOptions): Promise<void>;
33
+ export interface IntegrationsRemoveOptions {
34
+ id: string;
35
+ force?: boolean;
36
+ }
37
+ export declare function executeIntegrationsRemove(options: IntegrationsRemoveOptions): Promise<void>;
@@ -0,0 +1,167 @@
1
+ import { loadState } from "../../core/state.js";
2
+ import { getHubCredentials } from "../../core/credentials.js";
3
+ import { terminal } from "../../utils/terminal.js";
4
+ async function hubFetch(path, options = {}) {
5
+ const state = await loadState();
6
+ if (!state.hub?.baseUrl) {
7
+ terminal.error("Hub connection not configured.");
8
+ terminal.dim("Connect with: griffin hub connect --url <url> --token <token>");
9
+ process.exit(1);
10
+ }
11
+ const credentials = await getHubCredentials();
12
+ const url = `${state.hub.baseUrl.replace(/\/$/, "")}${path}`;
13
+ const headers = {
14
+ "Content-Type": "application/json",
15
+ };
16
+ if (credentials?.token) {
17
+ headers["Authorization"] = `Bearer ${credentials.token}`;
18
+ }
19
+ const res = await fetch(url, {
20
+ method: options.method ?? "GET",
21
+ headers,
22
+ body: options.body ? JSON.stringify(options.body) : undefined,
23
+ });
24
+ const text = await res.text();
25
+ let json;
26
+ try {
27
+ json = text ? JSON.parse(text) : {};
28
+ }
29
+ catch {
30
+ return { error: res.ok ? "Invalid response" : text || res.statusText };
31
+ }
32
+ if (!res.ok) {
33
+ return { error: json.error ?? res.statusText };
34
+ }
35
+ return json;
36
+ }
37
+ export async function executeIntegrationsList(options) {
38
+ const params = new URLSearchParams();
39
+ if (options.category)
40
+ params.set("category", options.category);
41
+ if (options.providerType)
42
+ params.set("providerType", options.providerType);
43
+ if (options.environment != null)
44
+ params.set("environment", options.environment);
45
+ if (options.enabled != null)
46
+ params.set("enabled", String(options.enabled));
47
+ const qs = params.toString();
48
+ const path = `/integrations${qs ? `?${qs}` : ""}`;
49
+ const result = await hubFetch(path);
50
+ if (result.error) {
51
+ terminal.error(result.error);
52
+ process.exit(1);
53
+ }
54
+ const list = result.data ?? [];
55
+ if (options.json) {
56
+ terminal.log(JSON.stringify(list, null, 2));
57
+ return;
58
+ }
59
+ if (list.length === 0) {
60
+ terminal.info("No integrations found.");
61
+ return;
62
+ }
63
+ terminal.info("Integrations");
64
+ terminal.blank();
65
+ const table = terminal.table({
66
+ head: ["ID", "Category", "Provider", "Name", "Environment", "Enabled"],
67
+ });
68
+ for (const i of list) {
69
+ table.push([
70
+ i.id.substring(0, 12) + (i.id.length > 12 ? "…" : ""),
71
+ i.category,
72
+ i.providerType,
73
+ i.name,
74
+ i.environment ?? "(all)",
75
+ i.enabled ? "✓" : "✗",
76
+ ]);
77
+ }
78
+ terminal.log(table.toString());
79
+ }
80
+ export async function executeIntegrationsShow(options) {
81
+ const result = await hubFetch(`/integrations/${encodeURIComponent(options.id)}`);
82
+ if (result.error) {
83
+ terminal.error(result.error);
84
+ process.exit(1);
85
+ }
86
+ const integration = result.data;
87
+ if (!integration) {
88
+ terminal.error("Integration not found.");
89
+ process.exit(1);
90
+ }
91
+ if (options.json) {
92
+ terminal.log(JSON.stringify(integration, null, 2));
93
+ return;
94
+ }
95
+ terminal.info(integration.name);
96
+ terminal.dim(`ID: ${integration.id}`);
97
+ terminal.log(`Category: ${integration.category}`);
98
+ terminal.log(`Provider: ${integration.providerType}`);
99
+ terminal.log(`Environment: ${integration.environment ?? "(all)"}`);
100
+ terminal.log(`Enabled: ${integration.enabled ? "Yes" : "No"}`);
101
+ terminal.log(`Has credentials: ${integration.hasCredentials ? "Yes" : "No"}`);
102
+ if (Object.keys(integration.config).length > 0) {
103
+ terminal.log("Config: " + JSON.stringify(integration.config));
104
+ }
105
+ }
106
+ export async function executeIntegrationsAdd(options) {
107
+ const body = {
108
+ category: options.category,
109
+ providerType: options.providerType,
110
+ name: options.name,
111
+ config: options.config ?? {},
112
+ environment: options.environment ?? null,
113
+ enabled: options.enabled ?? true,
114
+ };
115
+ const result = await hubFetch("/integrations", {
116
+ method: "POST",
117
+ body,
118
+ });
119
+ if (result.error) {
120
+ terminal.error(result.error);
121
+ process.exit(1);
122
+ }
123
+ const integration = result.data;
124
+ if (!integration) {
125
+ terminal.error("Create failed.");
126
+ process.exit(1);
127
+ }
128
+ if (options.json) {
129
+ terminal.log(JSON.stringify(integration, null, 2));
130
+ return;
131
+ }
132
+ terminal.info(`Created integration ${integration.name} (${integration.id})`);
133
+ }
134
+ export async function executeIntegrationsUpdate(options) {
135
+ const body = {};
136
+ if (options.name !== undefined)
137
+ body.name = options.name;
138
+ if (options.config !== undefined)
139
+ body.config = options.config;
140
+ if (options.environment !== undefined)
141
+ body.environment = options.environment;
142
+ if (options.enabled !== undefined)
143
+ body.enabled = options.enabled;
144
+ const result = await hubFetch(`/integrations/${encodeURIComponent(options.id)}`, { method: "PATCH", body });
145
+ if (result.error) {
146
+ terminal.error(result.error);
147
+ process.exit(1);
148
+ }
149
+ const integration = result.data;
150
+ if (!integration) {
151
+ terminal.error("Update failed.");
152
+ process.exit(1);
153
+ }
154
+ if (options.json) {
155
+ terminal.log(JSON.stringify(integration, null, 2));
156
+ return;
157
+ }
158
+ terminal.info(`Updated integration ${integration.name}`);
159
+ }
160
+ export async function executeIntegrationsRemove(options) {
161
+ const result = await hubFetch(`/integrations/${encodeURIComponent(options.id)}`, { method: "DELETE" });
162
+ if (result.error) {
163
+ terminal.error(result.error);
164
+ process.exit(1);
165
+ }
166
+ terminal.info("Integration removed.");
167
+ }
@@ -1,20 +1,20 @@
1
1
  // CLI implementation
2
2
  import { createAuthClient } from "better-auth/client";
3
3
  import { deviceAuthorizationClient, jwtClient, } from "better-auth/client/plugins";
4
- import { getProjectId, loadState, saveState } from "../../core/state.js";
4
+ import { loadState, saveState } from "../../core/state.js";
5
5
  import { saveHubCredentials } from "../../core/credentials.js";
6
6
  import { terminal } from "../../utils/terminal.js";
7
7
  import { randomBytes } from "crypto";
8
- import { createEmptyState } from "../../schemas/state.js";
9
- const baseURL = "http://localhost:4000/api/auth";
10
- const hubBaseUrl = "http://localhost:3000";
11
- //const baseURL = "https://cloud.griffin.app"
12
8
  const oauthGrant = "urn:ietf:params:oauth:grant-type:device_code";
13
- const authClient = createAuthClient({
14
- baseURL: baseURL,
15
- plugins: [deviceAuthorizationClient(), jwtClient()],
16
- });
9
+ function createAuthClientFromState(state) {
10
+ return createAuthClient({
11
+ baseURL: state.cloud.authUrl,
12
+ plugins: [deviceAuthorizationClient(), jwtClient()],
13
+ });
14
+ }
17
15
  async function pollForToken(clientId, deviceCode, interval) {
16
+ const state = await loadState();
17
+ const authClient = createAuthClientFromState(state);
18
18
  const { data, error } = await authClient.device.token({
19
19
  grant_type: oauthGrant,
20
20
  device_code: deviceCode,
@@ -39,16 +39,9 @@ async function pollForToken(clientId, deviceCode, interval) {
39
39
  }
40
40
  }
41
41
  export async function executeLogin() {
42
- let state;
43
- let clientId;
44
- try {
45
- state = await loadState();
46
- clientId = state.hub?.clientId;
47
- }
48
- catch (error) { }
49
- if (!clientId) {
50
- clientId = randomBytes(16).toString("hex");
51
- }
42
+ const state = await loadState();
43
+ const clientId = state.hub?.clientId ?? randomBytes(16).toString("hex");
44
+ const authClient = createAuthClientFromState(state);
52
45
  const { data } = await authClient.device.code({
53
46
  client_id: clientId,
54
47
  });
@@ -69,17 +62,11 @@ export async function executeLogin() {
69
62
  terminal.success("Login successful");
70
63
  terminal.log(` Token saved to user credentials`);
71
64
  }
72
- if (!state) {
73
- const projectId = await getProjectId();
74
- state = createEmptyState(projectId);
75
- }
76
- // Save hub config to project state (without token)
77
65
  await saveState({
78
66
  ...state,
79
67
  hub: {
80
68
  ...state.hub,
81
69
  clientId: clientId,
82
- baseUrl: hubBaseUrl,
83
70
  },
84
71
  });
85
72
  }
@@ -1,5 +1,5 @@
1
1
  import { loadState, resolveEnvironment } from "../../core/state.js";
2
- import { discoverMonitors, formatDiscoveryErrors } from "../../core/discovery.js";
2
+ import { discoverMonitors, formatDiscoveryErrors, } from "../../core/discovery.js";
3
3
  import { createSdkWithCredentials } from "../../core/sdk.js";
4
4
  import { computeDiff, formatDiff, formatDiffJson } from "../../core/diff.js";
5
5
  import { terminal } from "../../utils/terminal.js";
@@ -38,7 +38,9 @@ export async function executeMonitor(options) {
38
38
  // Create SDK clients with credentials
39
39
  const sdk = await createSdkWithCredentials(state.hub.baseUrl);
40
40
  // Fetch remote monitors for this project + environment
41
- const fetchSpinner = terminal.spinner("Fetching remote monitors...").start();
41
+ const fetchSpinner = terminal
42
+ .spinner("Fetching remote monitors...")
43
+ .start();
42
44
  const response = await withSDKErrorHandling(() => sdk.getMonitor({
43
45
  query: {
44
46
  projectId: state.projectId,
@@ -3,33 +3,14 @@ export interface NotificationsListOptions {
3
3
  enabled?: boolean;
4
4
  json?: boolean;
5
5
  }
6
- export interface NotificationsAddOptions {
7
- name: string;
8
- monitor?: string;
9
- environment?: string;
10
- location?: string;
11
- trigger: string;
12
- webhook: string;
13
- cooldown?: number;
14
- }
15
- export interface NotificationsDeleteOptions {
16
- id: string;
17
- }
18
6
  export interface NotificationsTestOptions {
19
7
  webhook: string;
20
8
  }
21
9
  /**
22
- * List notification rules
10
+ * List notification rules (read-only).
11
+ * Rules are defined in monitor DSL and synced via `griffin hub apply`.
23
12
  */
24
13
  export declare function executeNotificationsList(options: NotificationsListOptions): Promise<void>;
25
- /**
26
- * Add a notification rule
27
- */
28
- export declare function executeNotificationsAdd(options: NotificationsAddOptions): Promise<void>;
29
- /**
30
- * Delete a notification rule
31
- */
32
- export declare function executeNotificationsDelete(options: NotificationsDeleteOptions): Promise<void>;
33
14
  /**
34
15
  * Test a webhook
35
16
  */