@griffin-app/griffin-cli 1.0.8 → 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 CHANGED
@@ -16,7 +16,8 @@ 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";
20
21
  const program = new Command();
21
22
  program
22
23
  .name("griffin")
@@ -53,7 +54,7 @@ env
53
54
  // Local command group
54
55
  const local = program.command("local").description("Local test execution");
55
56
  local
56
- .command("run <env>")
57
+ .command("run [env]")
57
58
  .description("Run tests locally against an environment")
58
59
  .action(async (env, options) => {
59
60
  await executeRunLocal({ env });
@@ -75,15 +76,14 @@ hub
75
76
  await executeStatus();
76
77
  });
77
78
  hub
78
- .command("metrics")
79
+ .command("metrics [env]")
79
80
  .description("Show metrics summary from the hub")
80
81
  .option("--period <period>", "Time window: 1h, 6h, 24h, 7d, 30d", "24h")
81
- .option("--environment <env>", "Filter by environment")
82
82
  .option("--json", "Output as JSON")
83
- .action(async (options) => {
83
+ .action(async (env, options) => {
84
84
  await executeMetrics({
85
85
  period: options.period,
86
- environment: options.environment,
86
+ environment: env,
87
87
  json: options.json,
88
88
  });
89
89
  });
@@ -99,14 +99,14 @@ hub
99
99
  });
100
100
  });
101
101
  hub
102
- .command("monitor <env>")
102
+ .command("plan [env]")
103
103
  .description("Show what changes would be applied")
104
104
  .option("--json", "Output in JSON format")
105
105
  .action(async (env, options) => {
106
106
  await executeMonitor({ ...options, env });
107
107
  });
108
108
  hub
109
- .command("apply <env>")
109
+ .command("apply [env]")
110
110
  .description("Apply changes to the hub")
111
111
  .option("--auto-approve", "Skip confirmation prompt")
112
112
  .option("--dry-run", "Show what would be done without making changes")
@@ -115,7 +115,7 @@ hub
115
115
  await executeApply({ ...options, env });
116
116
  });
117
117
  hub
118
- .command("run <env>")
118
+ .command("run [env]")
119
119
  .description("Trigger a monitor run on the hub")
120
120
  .requiredOption("--monitor <name>", "Monitor name to run")
121
121
  .option("--wait", "Wait for run to complete")
@@ -138,7 +138,7 @@ hub
138
138
  // Notifications command group
139
139
  const notifications = hub
140
140
  .command("notifications")
141
- .description("Manage notification rules");
141
+ .description("View notification rules (rules are defined in monitor DSL and synced via griffin hub apply)");
142
142
  notifications
143
143
  .command("list")
144
144
  .description("List notification rules")
@@ -152,33 +152,6 @@ notifications
152
152
  json: options.json,
153
153
  });
154
154
  });
155
- notifications
156
- .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")
165
- .action(async (options) => {
166
- await executeNotificationsAdd({
167
- name: options.name,
168
- monitor: options.monitor,
169
- environment: options.environment,
170
- location: options.location,
171
- trigger: options.trigger,
172
- webhook: options.webhook,
173
- cooldown: options.cooldown ? parseInt(options.cooldown, 10) : undefined,
174
- });
175
- });
176
- notifications
177
- .command("delete <id>")
178
- .description("Delete a notification rule")
179
- .action(async (id) => {
180
- await executeNotificationsDelete({ id });
181
- });
182
155
  notifications
183
156
  .command("test")
184
157
  .description("Test a webhook configuration")
@@ -188,5 +161,56 @@ notifications
188
161
  webhook: options.webhook,
189
162
  });
190
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
+ });
191
215
  // Parse arguments
192
216
  program.parse();
@@ -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,
@@ -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
  */
@@ -3,7 +3,8 @@ import { createSdkWithCredentials } from "../../core/sdk.js";
3
3
  import { terminal } from "../../utils/terminal.js";
4
4
  import { withSDKErrorHandling } from "../../utils/sdk-error.js";
5
5
  /**
6
- * List notification rules
6
+ * List notification rules (read-only).
7
+ * Rules are defined in monitor DSL and synced via `griffin hub apply`.
7
8
  */
8
9
  export async function executeNotificationsList(options) {
9
10
  try {
@@ -28,26 +29,22 @@ export async function executeNotificationsList(options) {
28
29
  }
29
30
  if (rules.length === 0) {
30
31
  terminal.info("No notification rules found.");
32
+ terminal.dim("Define notifications in your monitor DSL and run griffin hub apply.");
31
33
  return;
32
34
  }
33
- terminal.info("Notification Rules");
35
+ terminal.info("Notification Rules (synced from monitor DSL)");
34
36
  terminal.blank();
35
37
  const table = terminal.table({
36
- head: ["ID", "Name", "Monitor", "Trigger", "Channels", "Enabled"],
38
+ head: ["ID", "Monitor", "Integration", "Trigger", "Enabled"],
37
39
  });
38
40
  for (const rule of rules) {
39
41
  const triggerDesc = formatTrigger(rule.trigger);
40
- const channels = rule.channels
41
- .map((ch) => (ch.type === "webhook" ? "webhook" : ch.type))
42
- .join(", ");
43
- const monitorName = rule.monitorId || "all monitors";
44
42
  const enabled = rule.enabled ? "✓" : "✗";
45
43
  table.push([
46
44
  rule.id.substring(0, 8) + "...",
47
- rule.name,
48
- monitorName,
45
+ rule.monitorId ?? "-",
46
+ rule.integrationName ?? "-",
49
47
  triggerDesc,
50
- channels,
51
48
  enabled,
52
49
  ]);
53
50
  }
@@ -58,76 +55,6 @@ export async function executeNotificationsList(options) {
58
55
  terminal.exit(1);
59
56
  }
60
57
  }
61
- /**
62
- * Add a notification rule
63
- */
64
- export async function executeNotificationsAdd(options) {
65
- try {
66
- const state = await loadState();
67
- if (!state.hub?.baseUrl) {
68
- terminal.error("Hub connection not configured.");
69
- terminal.dim("Connect with:");
70
- terminal.dim(" griffin hub connect --url <url> --token <token>");
71
- terminal.exit(1);
72
- }
73
- // Parse trigger
74
- const trigger = parseTrigger(options.trigger);
75
- if (!trigger) {
76
- terminal.error(`Invalid trigger: ${options.trigger}. Expected format: type[:value]`);
77
- terminal.dim("Examples:");
78
- terminal.dim(" run_failed");
79
- terminal.dim(" consecutive_failures:3");
80
- terminal.dim(" success_rate_below:80:60");
81
- terminal.exit(1);
82
- }
83
- const sdk = await createSdkWithCredentials(state.hub.baseUrl);
84
- const response = await withSDKErrorHandling(() => sdk.postNotificationsRules({
85
- body: {
86
- name: options.name,
87
- monitorId: options.monitor,
88
- environment: options.environment,
89
- location: options.location,
90
- trigger,
91
- channels: [
92
- {
93
- type: "webhook",
94
- url: options.webhook,
95
- },
96
- ],
97
- cooldownMinutes: options.cooldown,
98
- },
99
- }), "Failed to create notification rule");
100
- const rule = response?.data?.data;
101
- if (rule) {
102
- terminal.success(`Created notification rule: ${rule.id}`);
103
- }
104
- }
105
- catch (error) {
106
- terminal.error(error.message);
107
- terminal.exit(1);
108
- }
109
- }
110
- /**
111
- * Delete a notification rule
112
- */
113
- export async function executeNotificationsDelete(options) {
114
- try {
115
- const state = await loadState();
116
- if (!state.hub?.baseUrl) {
117
- terminal.error("Hub connection not configured.");
118
- terminal.dim("Connect with:");
119
- terminal.dim(" griffin hub connect --url <url> --token <token>");
120
- terminal.exit(1);
121
- }
122
- const sdk = await createSdkWithCredentials(state.hub.baseUrl);
123
- await withSDKErrorHandling(() => sdk.deleteNotificationsRulesById({ path: { id: options.id } }), "Failed to delete notification rule");
124
- terminal.success(`Deleted notification rule: ${options.id}`);
125
- }
126
- catch (error) {
127
- terminal.error(error.message);
128
- terminal.exit(1);
129
- }
130
- }
131
58
  /**
132
59
  * Test a webhook
133
60
  */
@@ -165,45 +92,6 @@ export async function executeNotificationsTest(options) {
165
92
  terminal.exit(1);
166
93
  }
167
94
  }
168
- /**
169
- * Parse trigger string into trigger object
170
- */
171
- function parseTrigger(triggerStr) {
172
- const parts = triggerStr.split(":");
173
- const type = parts[0];
174
- switch (type) {
175
- case "run_failed":
176
- return { type: "run_failed" };
177
- case "run_recovered":
178
- return { type: "run_recovered" };
179
- case "consecutive_failures":
180
- if (parts.length < 2)
181
- return null;
182
- return {
183
- type: "consecutive_failures",
184
- threshold: parseInt(parts[1], 10),
185
- };
186
- case "success_rate_below":
187
- if (parts.length < 3)
188
- return null;
189
- return {
190
- type: "success_rate_below",
191
- threshold: parseFloat(parts[1]),
192
- window_minutes: parseInt(parts[2], 10),
193
- };
194
- case "latency_above":
195
- if (parts.length < 4)
196
- return null;
197
- return {
198
- type: "latency_above",
199
- threshold_ms: parseInt(parts[1], 10),
200
- percentile: parts[2],
201
- window_minutes: parseInt(parts[3], 10),
202
- };
203
- default:
204
- return null;
205
- }
206
- }
207
95
  /**
208
96
  * Format trigger for display
209
97
  */
@@ -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>;
@@ -0,0 +1,185 @@
1
+ import { createInterface } from "node:readline";
2
+ import { loadState } from "../../core/state.js";
3
+ import { terminal } from "../../utils/terminal.js";
4
+ import { withSDKErrorHandling } from "../../utils/sdk-error.js";
5
+ import { createSdkWithCredentials } from "../../core/sdk.js";
6
+ const SECRET_NAME_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;
7
+ /**
8
+ * List secrets (metadata only)
9
+ */
10
+ export async function executeSecretsList(options) {
11
+ try {
12
+ const state = await loadState();
13
+ if (!state.hub?.baseUrl) {
14
+ terminal.error("Hub connection not configured.");
15
+ terminal.dim("Connect with:");
16
+ terminal.dim(" griffin hub connect --url <url> --token <token>");
17
+ terminal.exit(1);
18
+ }
19
+ const baseUrl = state.hub.baseUrl;
20
+ const sdk = await createSdkWithCredentials(baseUrl);
21
+ const env = options.environment ?? "default";
22
+ const response = await withSDKErrorHandling(async () => {
23
+ return sdk.getSecrets({
24
+ query: {
25
+ environment: env,
26
+ },
27
+ });
28
+ }, "Failed to list secrets");
29
+ const secrets = response?.data?.data ?? [];
30
+ if (options.json) {
31
+ terminal.log(JSON.stringify(secrets, null, 2));
32
+ return;
33
+ }
34
+ if (secrets.length === 0) {
35
+ terminal.info(`No secrets found for environment "${env}".`);
36
+ return;
37
+ }
38
+ terminal.info(`Secrets (environment: ${env})`);
39
+ terminal.blank();
40
+ const table = terminal.table({
41
+ head: ["Name", "Created", "Updated"],
42
+ });
43
+ for (const s of secrets) {
44
+ table.push([s.name, s.createdAt ?? "-", s.updatedAt ?? "-"]);
45
+ }
46
+ terminal.log(table.toString());
47
+ }
48
+ catch (error) {
49
+ terminal.error(error.message);
50
+ terminal.exit(1);
51
+ }
52
+ }
53
+ /**
54
+ * Set a secret (prompts for value if not provided)
55
+ */
56
+ export async function executeSecretsSet(options) {
57
+ try {
58
+ if (!SECRET_NAME_REGEX.test(options.name)) {
59
+ terminal.error("Secret name must start with a letter or underscore and contain only letters, numbers, and underscores.");
60
+ terminal.exit(1);
61
+ }
62
+ const state = await loadState();
63
+ if (!state.hub?.baseUrl) {
64
+ terminal.error("Hub connection not configured.");
65
+ terminal.dim("Connect with:");
66
+ terminal.dim(" griffin hub connect --url <url> --token <token>");
67
+ terminal.exit(1);
68
+ }
69
+ let value = options.value;
70
+ if (value === undefined) {
71
+ value = await promptSecret("Enter secret value:");
72
+ if (!value) {
73
+ terminal.error("Secret value cannot be empty.");
74
+ terminal.exit(1);
75
+ }
76
+ }
77
+ const baseUrl = state.hub.baseUrl;
78
+ const env = options.environment ?? "default";
79
+ const sdk = await createSdkWithCredentials(baseUrl);
80
+ const response = await withSDKErrorHandling(async () => {
81
+ return sdk.putSecretsByName({
82
+ path: { name: options.name },
83
+ body: { value, environment: env },
84
+ });
85
+ }, "Failed to set secret");
86
+ const result = response?.data;
87
+ terminal.success(`Secret "${result.data.name}" saved.`);
88
+ }
89
+ catch (error) {
90
+ terminal.error(error.message);
91
+ terminal.exit(1);
92
+ }
93
+ }
94
+ function promptSecret(promptText) {
95
+ return new Promise((resolve) => {
96
+ const rl = createInterface({
97
+ input: process.stdin,
98
+ output: process.stdout,
99
+ });
100
+ rl.question(promptText, (answer) => {
101
+ rl.close();
102
+ resolve(answer.trim());
103
+ });
104
+ });
105
+ }
106
+ /**
107
+ * Get secret metadata only
108
+ */
109
+ export async function executeSecretsGet(options) {
110
+ try {
111
+ const state = await loadState();
112
+ if (!state.hub?.baseUrl) {
113
+ terminal.error("Hub connection not configured.");
114
+ terminal.dim("Connect with:");
115
+ terminal.dim(" griffin hub connect --url <url> --token <token>");
116
+ terminal.exit(1);
117
+ }
118
+ const baseUrl = state.hub.baseUrl;
119
+ const env = options.environment ?? "default";
120
+ const sdk = await createSdkWithCredentials(baseUrl);
121
+ const response = await withSDKErrorHandling(async () => {
122
+ return sdk.getSecretsByName({
123
+ path: { name: options.name },
124
+ query: { environment: env },
125
+ });
126
+ }, "Failed to get secret");
127
+ const secret = response?.data?.data;
128
+ if (options.json) {
129
+ terminal.log(JSON.stringify(secret, null, 2));
130
+ return;
131
+ }
132
+ terminal.info(`Secret: ${secret.name}`);
133
+ terminal.dim(`Created: ${secret.createdAt ?? "-"}`);
134
+ terminal.dim(`Updated: ${secret.updatedAt ?? "-"}`);
135
+ }
136
+ catch (error) {
137
+ terminal.error(error.message);
138
+ terminal.exit(1);
139
+ }
140
+ }
141
+ /**
142
+ * Delete a secret (with confirmation unless --force)
143
+ */
144
+ export async function executeSecretsDelete(options) {
145
+ try {
146
+ const state = await loadState();
147
+ if (!state.hub?.baseUrl) {
148
+ terminal.error("Hub connection not configured.");
149
+ terminal.dim("Connect with:");
150
+ terminal.dim(" griffin hub connect --url <url> --token <token>");
151
+ terminal.exit(1);
152
+ }
153
+ const baseUrl = state.hub.baseUrl;
154
+ const env = options.environment ?? "default";
155
+ const sdk = await createSdkWithCredentials(baseUrl);
156
+ if (!options.force) {
157
+ const rl = createInterface({
158
+ input: process.stdin,
159
+ output: process.stdout,
160
+ });
161
+ const answer = await new Promise((resolve) => {
162
+ rl.question(`Delete secret "${options.name}" (environment: ${env})? [y/N] `, (a) => {
163
+ rl.close();
164
+ resolve(a.trim().toLowerCase());
165
+ });
166
+ });
167
+ if (answer !== "y" && answer !== "yes") {
168
+ terminal.info("Aborted.");
169
+ return;
170
+ }
171
+ }
172
+ const response = await withSDKErrorHandling(async () => {
173
+ return sdk.deleteSecretsByName({
174
+ path: { name: options.name },
175
+ query: { environment: env },
176
+ });
177
+ }, "Failed to delete secret");
178
+ const result = response?.data;
179
+ terminal.success(`Secret deleted.`);
180
+ }
181
+ catch (error) {
182
+ terminal.error(error.message);
183
+ terminal.exit(1);
184
+ }
185
+ }
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { initState, stateExists, getStateFilePath, addEnvironment, } from "../core/state.js";
3
+ import { initState, stateExists, getStateFilePath, } from "../core/state.js";
4
4
  import { detectProjectId } from "../core/project.js";
5
5
  import { terminal } from "../utils/terminal.js";
6
6
  const VARIABLES_FILE = "variables.yaml";
@@ -34,11 +34,6 @@ export async function executeInit(options) {
34
34
  // Initialize state file
35
35
  await initState(projectId);
36
36
  terminal.success(`Created state file: ${terminal.colors.dim(getStateFilePath())}`);
37
- // Create default environments
38
- await addEnvironment("dev", {});
39
- await addEnvironment("staging", {});
40
- await addEnvironment("production", {});
41
- terminal.success("Created default environments (dev, staging, production)");
42
37
  // Create variables.yaml if it doesn't exist
43
38
  const variablesPath = path.join(process.cwd(), VARIABLES_FILE);
44
39
  try {
@@ -1,4 +1,4 @@
1
1
  export interface RunLocalOptions {
2
- env: string;
2
+ env?: string;
3
3
  }
4
4
  export declare function executeRunLocal(options: RunLocalOptions): Promise<void>;
@@ -116,7 +116,9 @@ describe("applyDiff", () => {
116
116
  it("should apply delete action", async () => {
117
117
  const remoteMonitor = createMonitor("old-monitor");
118
118
  const diff = {
119
- actions: [{ type: "delete", monitor: null, remoteMonitor, reason: "removed" }],
119
+ actions: [
120
+ { type: "delete", monitor: null, remoteMonitor, reason: "removed" },
121
+ ],
120
122
  summary: { creates: 0, updates: 0, deletes: 1, noops: 0 },
121
123
  };
122
124
  const mockMonitorApi = {
@@ -34,10 +34,6 @@ export declare function addEnvironment(name: string, config: EnvironmentConfig):
34
34
  * Remove an environment
35
35
  */
36
36
  export declare function removeEnvironment(name: string): Promise<void>;
37
- /**
38
- * Set the default environment
39
- */
40
- export declare function setDefaultEnvironment(name: string): Promise<void>;
41
37
  /**
42
38
  * Get the current environment name (from flag, env var, or default)
43
39
  */
@@ -84,10 +84,6 @@ export async function initState(projectId) {
84
84
  export async function addEnvironment(name, config) {
85
85
  const state = await loadState();
86
86
  state.environments[name] = config;
87
- // Set as default if it's the first environment
88
- if (Object.keys(state.environments).length === 1) {
89
- state.defaultEnvironment = name;
90
- }
91
87
  await saveState(state);
92
88
  }
93
89
  /**
@@ -95,26 +91,13 @@ export async function addEnvironment(name, config) {
95
91
  */
96
92
  export async function removeEnvironment(name) {
97
93
  const state = await loadState();
98
- if (!(name in state.environments)) {
99
- throw new Error(`Environment '${name}' does not exist`);
94
+ if (name === "default") {
95
+ throw new Error("Cannot remove default environment");
100
96
  }
101
- delete state.environments[name];
102
- // Update default if we removed it
103
- if (state.defaultEnvironment === name) {
104
- const remaining = Object.keys(state.environments);
105
- state.defaultEnvironment = remaining.length > 0 ? remaining[0] : undefined;
106
- }
107
- await saveState(state);
108
- }
109
- /**
110
- * Set the default environment
111
- */
112
- export async function setDefaultEnvironment(name) {
113
- const state = await loadState();
114
97
  if (!(name in state.environments)) {
115
98
  throw new Error(`Environment '${name}' does not exist`);
116
99
  }
117
- state.defaultEnvironment = name;
100
+ delete state.environments[name];
118
101
  await saveState(state);
119
102
  }
120
103
  /**
@@ -123,14 +106,13 @@ export async function setDefaultEnvironment(name) {
123
106
  export async function resolveEnvironment(envFlag) {
124
107
  const state = await loadState();
125
108
  // Priority: CLI flag > ENV var > default > error
126
- const envName = envFlag || process.env.GRIFFIN_ENV || state.defaultEnvironment;
127
- if (!envName) {
128
- throw new Error("No environment specified. Pass the environment name as the first argument (e.g. griffin hub apply production). You can also set GRIFFIN_ENV or a default with 'griffin env default <name>'.");
109
+ if (!envFlag) {
110
+ return "default";
129
111
  }
130
- if (!(envName in state.environments)) {
131
- throw new Error(`Environment '${envName}' not found. Available: ${Object.keys(state.environments).join(", ")}`);
112
+ if (!(envFlag in state.environments)) {
113
+ throw new Error(`Environment '${envFlag}' not found. Available: ${Object.keys(state.environments).join(", ")}`);
132
114
  }
133
- return envName;
115
+ return envFlag;
134
116
  }
135
117
  /**
136
118
  * Get environment configuration
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export type { DiffAction, DiffResult } from "./core/diff.js";
5
5
  export type { ApplyResult, ApplyAction, ApplyError } from "./core/apply.js";
6
6
  export { createEmptyState, StateFileSchema, HubConfigSchema, } from "./schemas/state.js";
7
7
  export { createEmptyCredentials, CredentialsFileSchema, HubCredentialsSchema, } from "./schemas/credentials.js";
8
- export { getStateDirPath, getStateFilePath, stateExists, loadState, saveState, initState, addEnvironment, removeEnvironment, setDefaultEnvironment, resolveEnvironment, getEnvironment, } from "./core/state.js";
8
+ export { getStateDirPath, getStateFilePath, stateExists, loadState, saveState, initState, addEnvironment, removeEnvironment, resolveEnvironment, getEnvironment, } from "./core/state.js";
9
9
  export { getCredentialsDirPath, getCredentialsFilePath, credentialsExist, loadCredentials, saveCredentials, saveHubCredentials, getHubCredentials, removeHubCredentials, } from "./core/credentials.js";
10
10
  export { discoverMonitors, formatDiscoveryErrors } from "./core/discovery.js";
11
11
  export { computeDiff, formatDiff, formatDiffJson } from "./core/diff.js";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Export core functions
2
2
  export { createEmptyState, StateFileSchema, HubConfigSchema, } from "./schemas/state.js";
3
3
  export { createEmptyCredentials, CredentialsFileSchema, HubCredentialsSchema, } from "./schemas/credentials.js";
4
- export { getStateDirPath, getStateFilePath, stateExists, loadState, saveState, initState, addEnvironment, removeEnvironment, setDefaultEnvironment, resolveEnvironment, getEnvironment, } from "./core/state.js";
4
+ export { getStateDirPath, getStateFilePath, stateExists, loadState, saveState, initState, addEnvironment, removeEnvironment, resolveEnvironment, getEnvironment, } from "./core/state.js";
5
5
  export { getCredentialsDirPath, getCredentialsFilePath, credentialsExist, loadCredentials, saveCredentials, saveHubCredentials, getHubCredentials, removeHubCredentials, } from "./core/credentials.js";
6
6
  export { discoverMonitors, formatDiscoveryErrors } from "./core/discovery.js";
7
7
  export { computeDiff, formatDiff, formatDiffJson } from "./core/diff.js";
@@ -1,16 +1,12 @@
1
1
  import { Type, type Static } from "typebox";
2
- /**
3
- * State file schema for tracking project configuration
4
- * Stored in .griffin/state.json
5
- *
6
- * Note: The hub is now the source of truth for monitors.
7
- * This file only stores configuration (project, environments, runner connection).
8
- */
9
2
  export declare const EnvironmentConfigSchema: Type.TObject<{}>;
10
3
  export type EnvironmentConfig = Static<typeof EnvironmentConfigSchema>;
4
+ export declare const CloudConfigSchema: Type.TObject<{
5
+ authUrl: Type.TString;
6
+ }>;
11
7
  export declare const HubConfigSchema: Type.TObject<{
12
8
  baseUrl: Type.TString;
13
- clientId: Type.TString;
9
+ clientId: Type.TOptional<Type.TString>;
14
10
  }>;
15
11
  export type HubConfig = Static<typeof HubConfigSchema>;
16
12
  export declare const DiscoveryConfigSchema: Type.TObject<{
@@ -18,15 +14,22 @@ export declare const DiscoveryConfigSchema: Type.TObject<{
18
14
  ignore: Type.TOptional<Type.TArray<Type.TString>>;
19
15
  }>;
20
16
  export type DiscoveryConfig = Static<typeof DiscoveryConfigSchema>;
17
+ export declare const EnvironmentsConfigSchema: Type.TIntersect<[Type.TObject<{
18
+ default: Type.TObject<{}>;
19
+ }>, Type.TRecord<"^.*$", Type.TObject<{}>>]>;
21
20
  export declare const StateFileSchema: Type.TObject<{
22
21
  stateVersion: Type.TLiteral<1>;
23
22
  projectId: Type.TString;
24
- environments: Type.TRecord<"^.*$", Type.TObject<{}>>;
25
- defaultEnvironment: Type.TOptional<Type.TString>;
26
- hub: Type.TOptional<Type.TObject<{
23
+ environments: Type.TIntersect<[Type.TObject<{
24
+ default: Type.TObject<{}>;
25
+ }>, Type.TRecord<"^.*$", Type.TObject<{}>>]>;
26
+ hub: Type.TObject<{
27
27
  baseUrl: Type.TString;
28
- clientId: Type.TString;
29
- }>>;
28
+ clientId: Type.TOptional<Type.TString>;
29
+ }>;
30
+ cloud: Type.TObject<{
31
+ authUrl: Type.TString;
32
+ }>;
30
33
  discovery: Type.TOptional<Type.TObject<{
31
34
  pattern: Type.TOptional<Type.TString>;
32
35
  ignore: Type.TOptional<Type.TArray<Type.TString>>;
@@ -37,7 +40,3 @@ export type StateFile = Static<typeof StateFileSchema>;
37
40
  * Create an empty state file
38
41
  */
39
42
  export declare function createEmptyState(projectId: string): StateFile;
40
- /**
41
- * Create state file with a default local environment
42
- */
43
- export declare function createStateWithDefaultEnv(projectId: string): StateFile;
@@ -6,24 +6,35 @@ import { Type } from "typebox";
6
6
  * Note: The hub is now the source of truth for monitors.
7
7
  * This file only stores configuration (project, environments, runner connection).
8
8
  */
9
+ const authUrl = "https://www.getgriffinapp.com/api/auth";
10
+ const hubBaseUrl = "https://griffin-hub.fly.dev";
9
11
  export const EnvironmentConfigSchema = Type.Object({});
12
+ export const CloudConfigSchema = Type.Object({
13
+ authUrl: Type.String(),
14
+ });
10
15
  export const HubConfigSchema = Type.Object({
11
16
  baseUrl: Type.String(),
12
- clientId: Type.String(),
17
+ clientId: Type.Optional(Type.String()),
13
18
  });
14
19
  export const DiscoveryConfigSchema = Type.Object({
15
20
  pattern: Type.Optional(Type.String()),
16
21
  ignore: Type.Optional(Type.Array(Type.String())),
17
22
  });
23
+ export const EnvironmentsConfigSchema = Type.Intersect([
24
+ Type.Object({
25
+ default: EnvironmentConfigSchema,
26
+ }),
27
+ Type.Record(Type.String(), EnvironmentConfigSchema),
28
+ ]);
18
29
  // State schema (hub is source of truth for monitors)
19
30
  export const StateFileSchema = Type.Object({
20
31
  stateVersion: Type.Literal(1),
21
32
  projectId: Type.String(),
22
33
  // Environment configuration
23
- environments: Type.Record(Type.String(), EnvironmentConfigSchema),
24
- defaultEnvironment: Type.Optional(Type.String()),
34
+ environments: EnvironmentsConfigSchema,
25
35
  // Hub connection (for remote execution)
26
- hub: Type.Optional(HubConfigSchema),
36
+ hub: HubConfigSchema,
37
+ cloud: CloudConfigSchema,
27
38
  // Discovery settings
28
39
  discovery: Type.Optional(DiscoveryConfigSchema),
29
40
  });
@@ -31,22 +42,17 @@ export const StateFileSchema = Type.Object({
31
42
  * Create an empty state file
32
43
  */
33
44
  export function createEmptyState(projectId) {
34
- return {
35
- stateVersion: 1,
36
- projectId,
37
- environments: {},
38
- };
39
- }
40
- /**
41
- * Create state file with a default local environment
42
- */
43
- export function createStateWithDefaultEnv(projectId) {
44
45
  return {
45
46
  stateVersion: 1,
46
47
  projectId,
47
48
  environments: {
48
- local: {},
49
+ default: {},
50
+ },
51
+ hub: {
52
+ baseUrl: hubBaseUrl,
53
+ },
54
+ cloud: {
55
+ authUrl: authUrl,
49
56
  },
50
- defaultEnvironment: "local",
51
57
  };
52
58
  }
@@ -25,7 +25,7 @@ export async function runTestFile(filePath, envName) {
25
25
  terminal.dim(`Project ID: ${projectId}`);
26
26
  const resolvedMonitor = resolveMonitor(rawMonitor, projectId, envName, variables);
27
27
  const secretRegistry = new SecretProviderRegistry();
28
- secretRegistry.register(new EnvSecretProvider());
28
+ secretRegistry.setProvider(new EnvSecretProvider());
29
29
  try {
30
30
  const result = await executeMonitorV1({
31
31
  ...resolvedMonitor,
@@ -109,8 +109,6 @@ export async function withSDKErrorHandling(fn, context) {
109
109
  return await fn();
110
110
  }
111
111
  catch (error) {
112
- console.error("error");
113
- console.error(error);
114
112
  handleSDKError(error, context);
115
113
  }
116
114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@griffin-app/griffin-cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "CLI tool for running and managing griffin API tests",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,9 +23,9 @@
23
23
  "author": "",
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
- "@griffin-app/griffin-hub-sdk": "1.0.8",
27
- "@griffin-app/griffin-plan-executor": "0.1.14",
28
- "@griffin-app/griffin-ts": "0.1.14",
26
+ "@griffin-app/griffin-hub-sdk": "1.0.10",
27
+ "@griffin-app/griffin-plan-executor": "0.1.16",
28
+ "@griffin-app/griffin-ts": "0.1.15",
29
29
  "better-auth": "^1.4.17",
30
30
  "cli-table3": "^0.6.5",
31
31
  "commander": "^12.1.0",