@griffin-app/griffin-cli 1.0.7 → 1.0.9
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 +96 -4
- package/dist/commands/env.js +2 -7
- package/dist/commands/hub/apply.js +4 -2
- package/dist/commands/hub/login.js +12 -25
- package/dist/commands/hub/metrics.d.ts +12 -0
- package/dist/commands/hub/metrics.js +78 -0
- package/dist/commands/hub/monitor.js +4 -2
- package/dist/commands/hub/notifications.d.ts +17 -0
- package/dist/commands/hub/notifications.js +113 -0
- package/dist/commands/hub/run.js +1 -1
- package/dist/commands/hub/secrets.d.ts +35 -0
- package/dist/commands/hub/secrets.js +185 -0
- package/dist/commands/init.js +1 -6
- package/dist/commands/local/run.d.ts +1 -1
- package/dist/core/apply.test.js +3 -1
- package/dist/core/state.d.ts +0 -4
- package/dist/core/state.js +8 -26
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/output/context.d.ts +18 -0
- package/dist/output/context.js +22 -0
- package/dist/output/index.d.ts +3 -0
- package/dist/output/index.js +2 -0
- package/dist/output/renderer.d.ts +6 -0
- package/dist/output/renderer.js +348 -0
- package/dist/output/types.d.ts +153 -0
- package/dist/output/types.js +1 -0
- package/dist/schemas/state.d.ts +16 -17
- package/dist/schemas/state.js +22 -16
- package/dist/test-runner.js +1 -1
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -9,12 +9,15 @@ import { executeRunLocal } from "./commands/local/run.js";
|
|
|
9
9
|
// Hub commands
|
|
10
10
|
import { executeConnect } from "./commands/hub/connect.js";
|
|
11
11
|
import { executeStatus } from "./commands/hub/status.js";
|
|
12
|
+
import { executeMetrics } from "./commands/hub/metrics.js";
|
|
12
13
|
import { executeRuns } from "./commands/hub/runs.js";
|
|
13
14
|
import { executeMonitor } from "./commands/hub/monitor.js";
|
|
14
15
|
import { executeApply } from "./commands/hub/apply.js";
|
|
15
16
|
import { executeRun } from "./commands/hub/run.js";
|
|
16
17
|
import { executeLogin } from "./commands/hub/login.js";
|
|
17
18
|
import { executeLogout } from "./commands/hub/logout.js";
|
|
19
|
+
import { executeNotificationsList, executeNotificationsTest, } from "./commands/hub/notifications.js";
|
|
20
|
+
import { executeSecretsList, executeSecretsSet, executeSecretsGet, executeSecretsDelete, } from "./commands/hub/secrets.js";
|
|
18
21
|
const program = new Command();
|
|
19
22
|
program
|
|
20
23
|
.name("griffin")
|
|
@@ -51,7 +54,7 @@ env
|
|
|
51
54
|
// Local command group
|
|
52
55
|
const local = program.command("local").description("Local test execution");
|
|
53
56
|
local
|
|
54
|
-
.command("run
|
|
57
|
+
.command("run [env]")
|
|
55
58
|
.description("Run tests locally against an environment")
|
|
56
59
|
.action(async (env, options) => {
|
|
57
60
|
await executeRunLocal({ env });
|
|
@@ -72,6 +75,18 @@ hub
|
|
|
72
75
|
.action(async () => {
|
|
73
76
|
await executeStatus();
|
|
74
77
|
});
|
|
78
|
+
hub
|
|
79
|
+
.command("metrics [env]")
|
|
80
|
+
.description("Show metrics summary from the hub")
|
|
81
|
+
.option("--period <period>", "Time window: 1h, 6h, 24h, 7d, 30d", "24h")
|
|
82
|
+
.option("--json", "Output as JSON")
|
|
83
|
+
.action(async (env, options) => {
|
|
84
|
+
await executeMetrics({
|
|
85
|
+
period: options.period,
|
|
86
|
+
environment: env,
|
|
87
|
+
json: options.json,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
75
90
|
hub
|
|
76
91
|
.command("runs")
|
|
77
92
|
.description("Show recent runs from the hub")
|
|
@@ -84,14 +99,14 @@ hub
|
|
|
84
99
|
});
|
|
85
100
|
});
|
|
86
101
|
hub
|
|
87
|
-
.command("
|
|
102
|
+
.command("plan [env]")
|
|
88
103
|
.description("Show what changes would be applied")
|
|
89
104
|
.option("--json", "Output in JSON format")
|
|
90
105
|
.action(async (env, options) => {
|
|
91
106
|
await executeMonitor({ ...options, env });
|
|
92
107
|
});
|
|
93
108
|
hub
|
|
94
|
-
.command("apply
|
|
109
|
+
.command("apply [env]")
|
|
95
110
|
.description("Apply changes to the hub")
|
|
96
111
|
.option("--auto-approve", "Skip confirmation prompt")
|
|
97
112
|
.option("--dry-run", "Show what would be done without making changes")
|
|
@@ -100,7 +115,7 @@ hub
|
|
|
100
115
|
await executeApply({ ...options, env });
|
|
101
116
|
});
|
|
102
117
|
hub
|
|
103
|
-
.command("run
|
|
118
|
+
.command("run [env]")
|
|
104
119
|
.description("Trigger a monitor run on the hub")
|
|
105
120
|
.requiredOption("--monitor <name>", "Monitor name to run")
|
|
106
121
|
.option("--wait", "Wait for run to complete")
|
|
@@ -120,5 +135,82 @@ hub
|
|
|
120
135
|
.action(async (options) => {
|
|
121
136
|
await executeLogout(options);
|
|
122
137
|
});
|
|
138
|
+
// Notifications command group
|
|
139
|
+
const notifications = hub
|
|
140
|
+
.command("notifications")
|
|
141
|
+
.description("View notification rules (rules are defined in monitor DSL and synced via griffin hub apply)");
|
|
142
|
+
notifications
|
|
143
|
+
.command("list")
|
|
144
|
+
.description("List notification rules")
|
|
145
|
+
.option("--monitor <id>", "Filter by monitor ID")
|
|
146
|
+
.option("--enabled", "Filter by enabled status")
|
|
147
|
+
.option("--json", "Output as JSON")
|
|
148
|
+
.action(async (options) => {
|
|
149
|
+
await executeNotificationsList({
|
|
150
|
+
monitor: options.monitor,
|
|
151
|
+
enabled: options.enabled ? true : undefined,
|
|
152
|
+
json: options.json,
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
notifications
|
|
156
|
+
.command("test")
|
|
157
|
+
.description("Test a webhook configuration")
|
|
158
|
+
.requiredOption("--webhook <url>", "Webhook URL to test")
|
|
159
|
+
.action(async (options) => {
|
|
160
|
+
await executeNotificationsTest({
|
|
161
|
+
webhook: options.webhook,
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
// Secrets command group
|
|
165
|
+
const secrets = hub
|
|
166
|
+
.command("secrets")
|
|
167
|
+
.description("Manage secrets (platform storage)");
|
|
168
|
+
secrets
|
|
169
|
+
.command("list")
|
|
170
|
+
.description("List secrets for an environment")
|
|
171
|
+
.option("--environment <env>", "Environment name", "default")
|
|
172
|
+
.option("--json", "Output as JSON")
|
|
173
|
+
.action(async (options) => {
|
|
174
|
+
await executeSecretsList({
|
|
175
|
+
environment: options.environment,
|
|
176
|
+
json: options.json,
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
secrets
|
|
180
|
+
.command("set <name>")
|
|
181
|
+
.description("Create or update a secret (prompts for value)")
|
|
182
|
+
.option("--environment <env>", "Environment name", "default")
|
|
183
|
+
.option("--value <value>", "Secret value (avoid for sensitive data)")
|
|
184
|
+
.action(async (name, options) => {
|
|
185
|
+
await executeSecretsSet({
|
|
186
|
+
name,
|
|
187
|
+
environment: options.environment,
|
|
188
|
+
value: options.value,
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
secrets
|
|
192
|
+
.command("get <name>")
|
|
193
|
+
.description("Show secret metadata (not the value)")
|
|
194
|
+
.option("--environment <env>", "Environment name", "default")
|
|
195
|
+
.option("--json", "Output as JSON")
|
|
196
|
+
.action(async (name, options) => {
|
|
197
|
+
await executeSecretsGet({
|
|
198
|
+
name,
|
|
199
|
+
environment: options.environment,
|
|
200
|
+
json: options.json,
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
secrets
|
|
204
|
+
.command("delete <name>")
|
|
205
|
+
.description("Delete a secret")
|
|
206
|
+
.option("--environment <env>", "Environment name", "default")
|
|
207
|
+
.option("--force", "Skip confirmation prompt")
|
|
208
|
+
.action(async (name, options) => {
|
|
209
|
+
await executeSecretsDelete({
|
|
210
|
+
name,
|
|
211
|
+
environment: options.environment,
|
|
212
|
+
force: options.force,
|
|
213
|
+
});
|
|
214
|
+
});
|
|
123
215
|
// Parse arguments
|
|
124
216
|
program.parse();
|
package/dist/commands/env.js
CHANGED
|
@@ -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
|
|
20
|
-
const
|
|
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
|
|
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,
|
|
@@ -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 {
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare const METRICS_PERIODS: readonly ["1h", "6h", "24h", "7d", "30d"];
|
|
2
|
+
type MetricsPeriod = (typeof METRICS_PERIODS)[number];
|
|
3
|
+
export interface MetricsOptions {
|
|
4
|
+
period?: MetricsPeriod;
|
|
5
|
+
environment?: string;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Show metrics summary from the hub
|
|
10
|
+
*/
|
|
11
|
+
export declare function executeMetrics(options: MetricsOptions): Promise<void>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { loadState } from "../../core/state.js";
|
|
2
|
+
import { getHubCredentials } from "../../core/credentials.js";
|
|
3
|
+
import { terminal } from "../../utils/terminal.js";
|
|
4
|
+
const METRICS_PERIODS = ["1h", "6h", "24h", "7d", "30d"];
|
|
5
|
+
/**
|
|
6
|
+
* Show metrics summary from the hub
|
|
7
|
+
*/
|
|
8
|
+
export async function executeMetrics(options) {
|
|
9
|
+
try {
|
|
10
|
+
const state = await loadState();
|
|
11
|
+
if (!state.hub?.baseUrl) {
|
|
12
|
+
terminal.error("No hub connection configured.");
|
|
13
|
+
terminal.dim("Connect with:");
|
|
14
|
+
terminal.dim(" griffin hub connect --url <url> --token <token>");
|
|
15
|
+
terminal.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const credentials = await getHubCredentials();
|
|
18
|
+
const token = credentials?.token;
|
|
19
|
+
if (!token) {
|
|
20
|
+
terminal.error("No API token. Run 'griffin hub login' or connect with --token.");
|
|
21
|
+
terminal.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const baseUrl = state.hub.baseUrl;
|
|
24
|
+
const period = options.period ?? "24h";
|
|
25
|
+
const url = new URL(baseUrl);
|
|
26
|
+
url.pathname = "/metrics/summary";
|
|
27
|
+
url.searchParams.set("period", period);
|
|
28
|
+
if (options.environment) {
|
|
29
|
+
url.searchParams.set("environment", options.environment);
|
|
30
|
+
}
|
|
31
|
+
const spinner = terminal.spinner("Fetching metrics...").start();
|
|
32
|
+
const response = await fetch(url.toString(), {
|
|
33
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
34
|
+
});
|
|
35
|
+
spinner.stop();
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const text = await response.text();
|
|
38
|
+
terminal.error(`Failed to fetch metrics: ${response.status} ${response.statusText}`);
|
|
39
|
+
if (text)
|
|
40
|
+
terminal.dim(text);
|
|
41
|
+
terminal.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const body = (await response.json());
|
|
44
|
+
const data = body.data;
|
|
45
|
+
if (options.json) {
|
|
46
|
+
terminal.log(JSON.stringify(data, null, 2));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
terminal.info(`Metrics (${period})`);
|
|
50
|
+
terminal.dim(`${new Date(data.periodStart).toLocaleString()} – ${new Date(data.periodEnd).toLocaleString()}`);
|
|
51
|
+
terminal.blank();
|
|
52
|
+
terminal.log("Monitors:");
|
|
53
|
+
terminal.log(` Total: ${data.monitors.total} Passing: ${terminal.colors.green(String(data.monitors.passing))} Failing: ${data.monitors.failing > 0 ? terminal.colors.red(String(data.monitors.failing)) : "0"} No recent runs: ${data.monitors.noRecentRuns}`);
|
|
54
|
+
terminal.blank();
|
|
55
|
+
terminal.log("Runs:");
|
|
56
|
+
terminal.log(` Total: ${data.runs.total} Success rate: ${data.runs.successRate.toFixed(1)}%`);
|
|
57
|
+
terminal.blank();
|
|
58
|
+
if (data.latency.p50DurationMs != null ||
|
|
59
|
+
data.latency.p95DurationMs != null ||
|
|
60
|
+
data.latency.p99DurationMs != null) {
|
|
61
|
+
terminal.log("Latency (completed runs):");
|
|
62
|
+
terminal.log(` p50: ${data.latency.p50DurationMs ?? "-"} ms p95: ${data.latency.p95DurationMs ?? "-"} ms p99: ${data.latency.p99DurationMs ?? "-"} ms`);
|
|
63
|
+
terminal.blank();
|
|
64
|
+
}
|
|
65
|
+
terminal.log(`Uptime: ${data.uptimePercent.toFixed(1)}%`);
|
|
66
|
+
terminal.blank();
|
|
67
|
+
if (data.failingMonitors.length > 0) {
|
|
68
|
+
terminal.warn("Failing monitors:");
|
|
69
|
+
for (const m of data.failingMonitors) {
|
|
70
|
+
terminal.log(` ${terminal.colors.red("✗")} ${m.monitorName} (${m.monitorId}) – ${m.consecutiveFailures} consecutive failure(s), last at ${new Date(m.lastFailureAt).toLocaleString()}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
terminal.error(error.message);
|
|
76
|
+
terminal.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -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
|
|
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,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface NotificationsListOptions {
|
|
2
|
+
monitor?: string;
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
json?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface NotificationsTestOptions {
|
|
7
|
+
webhook: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* List notification rules (read-only).
|
|
11
|
+
* Rules are defined in monitor DSL and synced via `griffin hub apply`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function executeNotificationsList(options: NotificationsListOptions): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Test a webhook
|
|
16
|
+
*/
|
|
17
|
+
export declare function executeNotificationsTest(options: NotificationsTestOptions): Promise<void>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { loadState } from "../../core/state.js";
|
|
2
|
+
import { createSdkWithCredentials } from "../../core/sdk.js";
|
|
3
|
+
import { terminal } from "../../utils/terminal.js";
|
|
4
|
+
import { withSDKErrorHandling } from "../../utils/sdk-error.js";
|
|
5
|
+
/**
|
|
6
|
+
* List notification rules (read-only).
|
|
7
|
+
* Rules are defined in monitor DSL and synced via `griffin hub apply`.
|
|
8
|
+
*/
|
|
9
|
+
export async function executeNotificationsList(options) {
|
|
10
|
+
try {
|
|
11
|
+
const state = await loadState();
|
|
12
|
+
if (!state.hub?.baseUrl) {
|
|
13
|
+
terminal.error("Hub connection not configured.");
|
|
14
|
+
terminal.dim("Connect with:");
|
|
15
|
+
terminal.dim(" griffin hub connect --url <url> --token <token>");
|
|
16
|
+
terminal.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const sdk = await createSdkWithCredentials(state.hub.baseUrl);
|
|
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 (options.json) {
|
|
27
|
+
terminal.log(JSON.stringify(rules, null, 2));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (rules.length === 0) {
|
|
31
|
+
terminal.info("No notification rules found.");
|
|
32
|
+
terminal.dim("Define notifications in your monitor DSL and run griffin hub apply.");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
terminal.info("Notification Rules (synced from monitor DSL)");
|
|
36
|
+
terminal.blank();
|
|
37
|
+
const table = terminal.table({
|
|
38
|
+
head: ["ID", "Monitor", "Integration", "Trigger", "Enabled"],
|
|
39
|
+
});
|
|
40
|
+
for (const rule of rules) {
|
|
41
|
+
const triggerDesc = formatTrigger(rule.trigger);
|
|
42
|
+
const enabled = rule.enabled ? "✓" : "✗";
|
|
43
|
+
table.push([
|
|
44
|
+
rule.id.substring(0, 8) + "...",
|
|
45
|
+
rule.monitorId ?? "-",
|
|
46
|
+
rule.integrationName ?? "-",
|
|
47
|
+
triggerDesc,
|
|
48
|
+
enabled,
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
terminal.log(table.toString());
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
terminal.error(error.message);
|
|
55
|
+
terminal.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Test a webhook
|
|
60
|
+
*/
|
|
61
|
+
export async function executeNotificationsTest(options) {
|
|
62
|
+
try {
|
|
63
|
+
const state = await loadState();
|
|
64
|
+
if (!state.hub?.baseUrl) {
|
|
65
|
+
terminal.error("Hub connection not configured.");
|
|
66
|
+
terminal.dim("Connect with:");
|
|
67
|
+
terminal.dim(" griffin hub connect --url <url> --token <token>");
|
|
68
|
+
terminal.exit(1);
|
|
69
|
+
}
|
|
70
|
+
const sdk = await createSdkWithCredentials(state.hub.baseUrl);
|
|
71
|
+
const spinner = terminal.spinner("Sending test notification...").start();
|
|
72
|
+
const response = await withSDKErrorHandling(() => sdk.postNotificationsTest({
|
|
73
|
+
body: {
|
|
74
|
+
channel: {
|
|
75
|
+
type: "webhook",
|
|
76
|
+
url: options.webhook,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
}), "Failed to send test notification");
|
|
80
|
+
spinner.stop();
|
|
81
|
+
const result = response?.data?.data;
|
|
82
|
+
if (result?.success) {
|
|
83
|
+
terminal.success(result.message || "Test notification delivered successfully");
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
terminal.error("Test notification failed");
|
|
87
|
+
terminal.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
terminal.error(error.message);
|
|
92
|
+
terminal.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Format trigger for display
|
|
97
|
+
*/
|
|
98
|
+
function formatTrigger(trigger) {
|
|
99
|
+
switch (trigger.type) {
|
|
100
|
+
case "run_failed":
|
|
101
|
+
return "Run failed";
|
|
102
|
+
case "run_recovered":
|
|
103
|
+
return "Run recovered";
|
|
104
|
+
case "consecutive_failures":
|
|
105
|
+
return `${trigger.threshold} consecutive failures`;
|
|
106
|
+
case "success_rate_below":
|
|
107
|
+
return `Success rate < ${trigger.threshold}% (${trigger.window_minutes}m)`;
|
|
108
|
+
case "latency_above":
|
|
109
|
+
return `${trigger.percentile} latency > ${trigger.threshold_ms}ms (${trigger.window_minutes}m)`;
|
|
110
|
+
default:
|
|
111
|
+
return String(trigger.type);
|
|
112
|
+
}
|
|
113
|
+
}
|
package/dist/commands/hub/run.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadState, resolveEnvironment } from "../../core/state.js";
|
|
2
2
|
import { createSdkWithCredentials } from "../../core/sdk.js";
|
|
3
|
-
import { discoverMonitors, formatDiscoveryErrors } from "../../core/discovery.js";
|
|
3
|
+
import { discoverMonitors, formatDiscoveryErrors, } from "../../core/discovery.js";
|
|
4
4
|
import { computeDiff } from "../../core/diff.js";
|
|
5
5
|
import { terminal } from "../../utils/terminal.js";
|
|
6
6
|
import { withSDKErrorHandling } from "../../utils/sdk-error.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface SecretsListOptions {
|
|
2
|
+
environment?: string;
|
|
3
|
+
json?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface SecretsSetOptions {
|
|
6
|
+
name: string;
|
|
7
|
+
environment?: string;
|
|
8
|
+
value?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SecretsGetOptions {
|
|
11
|
+
name: string;
|
|
12
|
+
environment?: string;
|
|
13
|
+
json?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface SecretsDeleteOptions {
|
|
16
|
+
name: string;
|
|
17
|
+
environment?: string;
|
|
18
|
+
force?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* List secrets (metadata only)
|
|
22
|
+
*/
|
|
23
|
+
export declare function executeSecretsList(options: SecretsListOptions): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Set a secret (prompts for value if not provided)
|
|
26
|
+
*/
|
|
27
|
+
export declare function executeSecretsSet(options: SecretsSetOptions): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Get secret metadata only
|
|
30
|
+
*/
|
|
31
|
+
export declare function executeSecretsGet(options: SecretsGetOptions): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Delete a secret (with confirmation unless --force)
|
|
34
|
+
*/
|
|
35
|
+
export declare function executeSecretsDelete(options: SecretsDeleteOptions): Promise<void>;
|