@canaryai/cli 0.1.9 → 0.1.11

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.
@@ -0,0 +1,91 @@
1
+ // src/auth.ts
2
+ import fs from "fs/promises";
3
+ import os from "os";
4
+ import path from "path";
5
+ var AUTH_DIR = path.join(os.homedir(), ".config", "canary-cli");
6
+ var AUTH_FILE = path.join(AUTH_DIR, "auth.json");
7
+ var ENV_URLS = {
8
+ prod: {
9
+ api: "https://api.trycanary.ai",
10
+ app: "https://app.trycanary.ai"
11
+ },
12
+ production: {
13
+ api: "https://api.trycanary.ai",
14
+ app: "https://app.trycanary.ai"
15
+ },
16
+ dev: {
17
+ api: "https://api.dev.trycanary.ai",
18
+ app: "https://app.dev.trycanary.ai"
19
+ },
20
+ local: {
21
+ api: "http://localhost:3000",
22
+ app: "http://localhost:5173"
23
+ }
24
+ };
25
+ function isMultiProfile(data) {
26
+ return typeof data === "object" && data !== null && "profiles" in data;
27
+ }
28
+ async function readAuthFile() {
29
+ try {
30
+ const content = await fs.readFile(AUTH_FILE, "utf8");
31
+ const data = JSON.parse(content);
32
+ if (isMultiProfile(data)) {
33
+ return data;
34
+ }
35
+ const legacy = data;
36
+ return {
37
+ profiles: { default: legacy },
38
+ default: "default"
39
+ };
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+ async function readStoredAuth(profile) {
45
+ const auth = await readAuthFile();
46
+ if (!auth) return null;
47
+ const key = profile ?? auth.default;
48
+ return auth.profiles[key] ?? null;
49
+ }
50
+ async function readStoredToken(profile) {
51
+ const auth = await readStoredAuth(profile);
52
+ return auth?.token ?? null;
53
+ }
54
+ async function readStoredApiUrl(profile) {
55
+ const auth = await readStoredAuth(profile);
56
+ return auth?.apiUrl ?? null;
57
+ }
58
+ async function saveAuth(auth, profile) {
59
+ await fs.mkdir(AUTH_DIR, { recursive: true, mode: 448 });
60
+ let existing;
61
+ try {
62
+ const content = await fs.readFile(AUTH_FILE, "utf8");
63
+ const data = JSON.parse(content);
64
+ if (isMultiProfile(data)) {
65
+ existing = data;
66
+ } else {
67
+ existing = {
68
+ profiles: { default: data },
69
+ default: "default"
70
+ };
71
+ }
72
+ } catch {
73
+ existing = { profiles: {}, default: "default" };
74
+ }
75
+ const key = profile ?? "default";
76
+ existing.profiles[key] = auth;
77
+ if (!existing.profiles[existing.default]) {
78
+ existing.default = key;
79
+ }
80
+ await fs.writeFile(AUTH_FILE, JSON.stringify(existing, null, 2), { encoding: "utf8", mode: 384 });
81
+ return AUTH_FILE;
82
+ }
83
+
84
+ export {
85
+ ENV_URLS,
86
+ readStoredAuth,
87
+ readStoredToken,
88
+ readStoredApiUrl,
89
+ saveAuth
90
+ };
91
+ //# sourceMappingURL=chunk-HJ2JWIJ7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/auth.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\ntype StoredAuth = {\n token?: string;\n apiUrl?: string;\n orgId?: string;\n orgName?: string;\n};\n\n/** Multi-profile auth file format */\ntype MultiProfileAuth = {\n profiles: Record<string, StoredAuth>;\n default: string;\n};\n\n/** Legacy flat format (single profile) */\ntype LegacyAuth = StoredAuth;\n\nconst AUTH_DIR = path.join(os.homedir(), \".config\", \"canary-cli\");\nconst AUTH_FILE = path.join(AUTH_DIR, \"auth.json\");\n\n/** Environment URL mappings shared across CLI commands */\nexport const ENV_URLS: Record<string, { api: string; app: string }> = {\n prod: {\n api: \"https://api.trycanary.ai\",\n app: \"https://app.trycanary.ai\",\n },\n production: {\n api: \"https://api.trycanary.ai\",\n app: \"https://app.trycanary.ai\",\n },\n dev: {\n api: \"https://api.dev.trycanary.ai\",\n app: \"https://app.dev.trycanary.ai\",\n },\n local: {\n api: \"http://localhost:3000\",\n app: \"http://localhost:5173\",\n },\n};\n\nfunction isMultiProfile(data: unknown): data is MultiProfileAuth {\n return typeof data === \"object\" && data !== null && \"profiles\" in data;\n}\n\n/** Read the raw auth file and normalize to multi-profile format */\nasync function readAuthFile(): Promise<MultiProfileAuth | null> {\n try {\n const content = await fs.readFile(AUTH_FILE, \"utf8\");\n const data = JSON.parse(content) as unknown;\n\n if (isMultiProfile(data)) {\n return data;\n }\n\n // Legacy flat format — treat as the \"default\" profile\n const legacy = data as LegacyAuth;\n return {\n profiles: { default: legacy },\n default: \"default\",\n };\n } catch {\n return null;\n }\n}\n\nexport async function readStoredAuth(profile?: string): Promise<StoredAuth | null> {\n const auth = await readAuthFile();\n if (!auth) return null;\n\n const key = profile ?? auth.default;\n return auth.profiles[key] ?? null;\n}\n\nexport async function readStoredToken(profile?: string): Promise<string | null> {\n const auth = await readStoredAuth(profile);\n return auth?.token ?? null;\n}\n\nexport async function readStoredApiUrl(profile?: string): Promise<string | null> {\n const auth = await readStoredAuth(profile);\n return auth?.apiUrl ?? null;\n}\n\nexport async function saveAuth(auth: StoredAuth, profile?: string): Promise<string> {\n await fs.mkdir(AUTH_DIR, { recursive: true, mode: 0o700 });\n\n // Read existing file (may be legacy or multi-profile)\n let existing: MultiProfileAuth;\n try {\n const content = await fs.readFile(AUTH_FILE, \"utf8\");\n const data = JSON.parse(content) as unknown;\n\n if (isMultiProfile(data)) {\n existing = data;\n } else {\n // Migrate legacy flat format into profiles.default\n existing = {\n profiles: { default: data as LegacyAuth },\n default: \"default\",\n };\n }\n } catch {\n existing = { profiles: {}, default: \"default\" };\n }\n\n const key = profile ?? \"default\";\n existing.profiles[key] = auth;\n\n // Set default to this profile if no default exists yet\n if (!existing.profiles[existing.default]) {\n existing.default = key;\n }\n\n await fs.writeFile(AUTH_FILE, JSON.stringify(existing, null, 2), { encoding: \"utf8\", mode: 0o600 });\n return AUTH_FILE;\n}\n"],"mappings":";AAAA,OAAO,QAAQ;AACf,OAAO,QAAQ;AACf,OAAO,UAAU;AAkBjB,IAAM,WAAW,KAAK,KAAK,GAAG,QAAQ,GAAG,WAAW,YAAY;AAChE,IAAM,YAAY,KAAK,KAAK,UAAU,WAAW;AAG1C,IAAM,WAAyD;AAAA,EACpE,MAAM;AAAA,IACJ,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAAA,EACA,YAAY;AAAA,IACV,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAAA,EACA,KAAK;AAAA,IACH,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAAA,EACA,OAAO;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACF;AAEA,SAAS,eAAe,MAAyC;AAC/D,SAAO,OAAO,SAAS,YAAY,SAAS,QAAQ,cAAc;AACpE;AAGA,eAAe,eAAiD;AAC9D,MAAI;AACF,UAAM,UAAU,MAAM,GAAG,SAAS,WAAW,MAAM;AACnD,UAAM,OAAO,KAAK,MAAM,OAAO;AAE/B,QAAI,eAAe,IAAI,GAAG;AACxB,aAAO;AAAA,IACT;AAGA,UAAM,SAAS;AACf,WAAO;AAAA,MACL,UAAU,EAAE,SAAS,OAAO;AAAA,MAC5B,SAAS;AAAA,IACX;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,eAAe,SAA8C;AACjF,QAAM,OAAO,MAAM,aAAa;AAChC,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,MAAM,WAAW,KAAK;AAC5B,SAAO,KAAK,SAAS,GAAG,KAAK;AAC/B;AAEA,eAAsB,gBAAgB,SAA0C;AAC9E,QAAM,OAAO,MAAM,eAAe,OAAO;AACzC,SAAO,MAAM,SAAS;AACxB;AAEA,eAAsB,iBAAiB,SAA0C;AAC/E,QAAM,OAAO,MAAM,eAAe,OAAO;AACzC,SAAO,MAAM,UAAU;AACzB;AAEA,eAAsB,SAAS,MAAkB,SAAmC;AAClF,QAAM,GAAG,MAAM,UAAU,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAGzD,MAAI;AACJ,MAAI;AACF,UAAM,UAAU,MAAM,GAAG,SAAS,WAAW,MAAM;AACnD,UAAM,OAAO,KAAK,MAAM,OAAO;AAE/B,QAAI,eAAe,IAAI,GAAG;AACxB,iBAAW;AAAA,IACb,OAAO;AAEL,iBAAW;AAAA,QACT,UAAU,EAAE,SAAS,KAAmB;AAAA,QACxC,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF,QAAQ;AACN,eAAW,EAAE,UAAU,CAAC,GAAG,SAAS,UAAU;AAAA,EAChD;AAEA,QAAM,MAAM,WAAW;AACvB,WAAS,SAAS,GAAG,IAAI;AAGzB,MAAI,CAAC,SAAS,SAAS,SAAS,OAAO,GAAG;AACxC,aAAS,UAAU;AAAA,EACrB;AAEA,QAAM,GAAG,UAAU,WAAW,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,EAAE,UAAU,QAAQ,MAAM,IAAM,CAAC;AAClG,SAAO;AACT;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readStoredToken
3
- } from "./chunk-SGNA6N2N.js";
3
+ } from "./chunk-HJ2JWIJ7.js";
4
4
 
5
5
  // src/local-run.ts
6
6
  import process from "process";
@@ -331,4 +331,4 @@ export {
331
331
  createTunnel,
332
332
  connectTunnel
333
333
  };
334
- //# sourceMappingURL=chunk-NRMZHITS.js.map
334
+ //# sourceMappingURL=chunk-VYBCH4ZP.js.map
@@ -1,11 +1,17 @@
1
1
  import {
2
2
  readStoredApiUrl,
3
3
  readStoredToken
4
- } from "./chunk-SGNA6N2N.js";
4
+ } from "./chunk-HJ2JWIJ7.js";
5
5
  import "./chunk-DGUM43GV.js";
6
6
 
7
7
  // src/feature-flag.ts
8
8
  import process from "process";
9
+ var ENV_URLS = {
10
+ prod: "https://api.trycanary.ai",
11
+ production: "https://api.trycanary.ai",
12
+ dev: "https://api.dev.trycanary.ai",
13
+ local: "http://localhost:3000"
14
+ };
9
15
  function getArgValue(argv, key) {
10
16
  const index = argv.indexOf(key);
11
17
  if (index === -1 || index >= argv.length - 1) return void 0;
@@ -16,7 +22,13 @@ function hasFlag(argv, ...flags) {
16
22
  }
17
23
  async function resolveConfig(argv) {
18
24
  const storedApiUrl = await readStoredApiUrl();
19
- const apiUrl = getArgValue(argv, "--api-url") ?? process.env.CANARY_API_URL ?? storedApiUrl ?? "https://api.trycanary.ai";
25
+ const env = getArgValue(argv, "--env");
26
+ if (env && !ENV_URLS[env]) {
27
+ console.error(`Unknown environment: ${env}`);
28
+ console.error("Valid environments: prod, dev, local");
29
+ process.exit(1);
30
+ }
31
+ const apiUrl = getArgValue(argv, "--api-url") ?? (env ? ENV_URLS[env] : void 0) ?? process.env.CANARY_API_URL ?? storedApiUrl ?? "https://api.trycanary.ai";
20
32
  const token = getArgValue(argv, "--token") ?? process.env.CANARY_API_TOKEN ?? await readStoredToken();
21
33
  if (!token) {
22
34
  console.error("Error: No API token found.");
@@ -172,8 +184,9 @@ function printFeatureFlagHelp() {
172
184
  " disable <name> --org <orgId> Disable a flag for an organization",
173
185
  "",
174
186
  "Options:",
187
+ " --env <env> Target environment (prod, dev, local)",
175
188
  " --json Output as JSON (list only)",
176
- " --api-url <url> API URL override",
189
+ " --api-url <url> API URL override (takes precedence over --env)",
177
190
  " --token <key> API token override"
178
191
  ].join("\n")
179
192
  );
@@ -210,4 +223,4 @@ async function runFeatureFlag(argv) {
210
223
  export {
211
224
  runFeatureFlag
212
225
  };
213
- //# sourceMappingURL=feature-flag-43WAHIUZ.js.map
226
+ //# sourceMappingURL=feature-flag-PN5IFFQR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/feature-flag.ts"],"sourcesContent":["/**\n * CLI Feature Flag Management\n *\n * Allows superadmins to manage feature flags via the CLI.\n */\n\nimport process from \"node:process\";\nimport { readStoredToken, readStoredApiUrl } from \"./auth.js\";\n\nconst ENV_URLS: Record<string, string> = {\n prod: \"https://api.trycanary.ai\",\n production: \"https://api.trycanary.ai\",\n dev: \"https://api.dev.trycanary.ai\",\n local: \"http://localhost:3000\",\n};\n\ntype FeatureFlag = {\n id: string;\n name: string;\n description: string | null;\n createdAt: string;\n};\n\ntype FeatureFlagListResponse = {\n ok: boolean;\n flags?: Array<FeatureFlag & { gates?: Array<{ gateType: string; gateValue: string }> }>;\n error?: string;\n};\n\ntype ApiResponse = {\n ok: boolean;\n error?: string;\n flag?: FeatureFlag;\n};\n\nfunction getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1 || index >= argv.length - 1) return undefined;\n return argv[index + 1];\n}\n\nfunction hasFlag(argv: string[], ...flags: string[]): boolean {\n return flags.some((flag) => argv.includes(flag));\n}\n\nasync function resolveConfig(argv: string[]) {\n const storedApiUrl = await readStoredApiUrl();\n const env = getArgValue(argv, \"--env\");\n\n if (env && !ENV_URLS[env]) {\n console.error(`Unknown environment: ${env}`);\n console.error(\"Valid environments: prod, dev, local\");\n process.exit(1);\n }\n\n const apiUrl =\n getArgValue(argv, \"--api-url\") ??\n (env ? ENV_URLS[env] : undefined) ??\n process.env.CANARY_API_URL ??\n storedApiUrl ??\n \"https://api.trycanary.ai\";\n\n const token =\n getArgValue(argv, \"--token\") ?? process.env.CANARY_API_TOKEN ?? (await readStoredToken());\n\n if (!token) {\n console.error(\"Error: No API token found.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n return { apiUrl, token };\n}\n\nasync function apiRequest(\n apiUrl: string,\n token: string,\n method: string,\n path: string,\n body?: Record<string, unknown>\n): Promise<ApiResponse> {\n const res = await fetch(`${apiUrl}${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n ...(body ? { body: JSON.stringify(body) } : {}),\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n const json = (await res.json()) as ApiResponse;\n return json;\n}\n\nasync function handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const jsonOutput = hasFlag(argv, \"--json\");\n const res = await fetch(`${apiUrl}/superadmin/feature-flags`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n const json = (await res.json()) as FeatureFlagListResponse;\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n const flags = json.flags ?? [];\n\n if (jsonOutput) {\n console.log(JSON.stringify(flags, null, 2));\n return;\n }\n\n if (flags.length === 0) {\n console.log(\"No feature flags found.\");\n return;\n }\n\n for (const flag of flags) {\n const gates = flag.gates ?? [];\n const gateStr =\n gates.length > 0\n ? gates.map((g) => `${g.gateType}:${g.gateValue}`).join(\", \")\n : \"(no gates)\";\n console.log(` ${flag.name} ${flag.description ?? \"\"} [${gateStr}]`);\n }\n}\n\nasync function handleCreate(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag create <name> [--description <text>]\");\n process.exit(1);\n }\n\n const description = getArgValue(argv, \"--description\") ?? null;\n const result = await apiRequest(apiUrl, token, \"POST\", \"/superadmin/feature-flags\", {\n name,\n description,\n });\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Created feature flag: ${name}`);\n}\n\nasync function handleDelete(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag delete <name>\");\n process.exit(1);\n }\n\n const result = await apiRequest(\n apiUrl,\n token,\n \"DELETE\",\n `/superadmin/feature-flags/${encodeURIComponent(name)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Deleted feature flag: ${name}`);\n}\n\nasync function handleEnable(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n const orgId = getArgValue(argv, \"--org\");\n\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag enable <name> --org <orgId>\");\n process.exit(1);\n }\n\n if (!orgId) {\n console.error(\"Error: Missing --org <orgId>.\");\n console.error(\"Usage: canary feature-flag enable <name> --org <orgId>\");\n process.exit(1);\n }\n\n const result = await apiRequest(\n apiUrl,\n token,\n \"POST\",\n `/superadmin/feature-flags/${encodeURIComponent(name)}/organizations/${encodeURIComponent(orgId)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Enabled ${name} for org ${orgId}`);\n}\n\nasync function handleDisable(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n const orgId = getArgValue(argv, \"--org\");\n\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag disable <name> --org <orgId>\");\n process.exit(1);\n }\n\n if (!orgId) {\n console.error(\"Error: Missing --org <orgId>.\");\n console.error(\"Usage: canary feature-flag disable <name> --org <orgId>\");\n process.exit(1);\n }\n\n const result = await apiRequest(\n apiUrl,\n token,\n \"DELETE\",\n `/superadmin/feature-flags/${encodeURIComponent(name)}/organizations/${encodeURIComponent(orgId)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Disabled ${name} for org ${orgId}`);\n}\n\nfunction printFeatureFlagHelp(): void {\n console.log(\n [\n \"Usage: canary feature-flag <sub-command> [options]\",\n \"\",\n \"Sub-commands:\",\n \" list List all feature flags\",\n \" create <name> [--description <text>] Create a new flag\",\n \" delete <name> Delete a flag and all its gates\",\n \" enable <name> --org <orgId> Enable a flag for an organization\",\n \" disable <name> --org <orgId> Disable a flag for an organization\",\n \"\",\n \"Options:\",\n \" --env <env> Target environment (prod, dev, local)\",\n \" --json Output as JSON (list only)\",\n \" --api-url <url> API URL override (takes precedence over --env)\",\n \" --token <key> API token override\",\n ].join(\"\\n\")\n );\n}\n\nexport async function runFeatureFlag(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === \"help\" || hasFlag(argv, \"--help\", \"-h\")) {\n printFeatureFlagHelp();\n return;\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n switch (subCommand) {\n case \"list\":\n await handleList(rest, apiUrl, token);\n break;\n case \"create\":\n await handleCreate(rest, apiUrl, token);\n break;\n case \"delete\":\n await handleDelete(rest, apiUrl, token);\n break;\n case \"enable\":\n await handleEnable(rest, apiUrl, token);\n break;\n case \"disable\":\n await handleDisable(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printFeatureFlagHelp();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -4,13 +4,14 @@ import {
4
4
  createTunnel,
5
5
  runLocalTest,
6
6
  runTunnel
7
- } from "./chunk-NRMZHITS.js";
7
+ } from "./chunk-VYBCH4ZP.js";
8
8
  import {
9
+ ENV_URLS,
9
10
  readStoredApiUrl,
10
11
  readStoredAuth,
11
12
  readStoredToken,
12
13
  saveAuth
13
- } from "./chunk-SGNA6N2N.js";
14
+ } from "./chunk-HJ2JWIJ7.js";
14
15
  import {
15
16
  __require
16
17
  } from "./chunk-DGUM43GV.js";
@@ -331,24 +332,12 @@ function countHealed(eventLogPath) {
331
332
  import process2 from "process";
332
333
  import readline from "readline";
333
334
  import { spawn as spawn2 } from "child_process";
334
- var ENV_URLS = {
335
- prod: {
336
- api: "https://api.trycanary.ai",
337
- app: "https://app.trycanary.ai"
338
- },
339
- production: {
340
- api: "https://api.trycanary.ai",
341
- app: "https://app.trycanary.ai"
342
- },
343
- dev: {
344
- api: "https://api.dev.trycanary.ai",
345
- app: "https://app.dev.trycanary.ai"
346
- },
347
- local: {
348
- api: "http://localhost:3000",
349
- app: "http://localhost:5173"
350
- }
351
- };
335
+ function envToProfile(env) {
336
+ if (env === "prod" || env === "production") return "production";
337
+ if (env === "dev") return "dev";
338
+ if (env === "local") return "local";
339
+ return env;
340
+ }
352
341
  function getArgValue(argv, key) {
353
342
  const index = argv.indexOf(key);
354
343
  if (index === -1) return void 0;
@@ -522,9 +511,11 @@ Choice [1-${orgs.length}]: `);
522
511
  finalOrgName = selectedOrg.name;
523
512
  }
524
513
  }
525
- const filePath = await saveAuth({ token: finalToken, apiUrl, orgId: finalOrgId, orgName: finalOrgName });
514
+ const profileName = env ? envToProfile(env) : void 0;
515
+ const filePath = await saveAuth({ token: finalToken, apiUrl, orgId: finalOrgId, orgName: finalOrgName }, profileName);
526
516
  const displayName = finalOrgName ? ` to ${finalOrgName}` : "";
527
- console.log(`Login successful${displayName}. Token saved to ${filePath}`);
517
+ const profileLabel = profileName ? ` (profile: ${profileName})` : "";
518
+ console.log(`Login successful${displayName}${profileLabel}. Token saved to ${filePath}`);
528
519
  console.log("Set CANARY_API_TOKEN to use the CLI without re-login.");
529
520
  }
530
521
 
@@ -965,8 +956,8 @@ function isSuperadminToken(token) {
965
956
  // src/index.ts
966
957
  var require2 = createRequire2(import.meta.url);
967
958
  var pkg = require2("../package.json");
968
- var loadMcp = () => import("./mcp-5N5Z343W.js").then((m) => m.runMcp);
969
- var loadLocalBrowser = () => import("./local-browser-REU2RIYX.js").then((m) => m.runLocalBrowser);
959
+ var loadMcp = () => import("./mcp-I6FCGDDR.js").then((m) => m.runMcp);
960
+ var loadLocalBrowser = () => import("./local-browser-VOBIUIGT.js").then((m) => m.runLocalBrowser);
970
961
  var canary = { run };
971
962
  var baseDir = typeof __dirname !== "undefined" ? __dirname : path4.dirname(fileURLToPath2(import.meta.url));
972
963
  var preloadPath = path4.join(baseDir, "runner", "preload.js");
@@ -1015,7 +1006,8 @@ function printHelp({ isSuperadmin }) {
1015
1006
  " canary debug-session [--env dev|local] [--json] Create browser debug session",
1016
1007
  " canary psql <query> [--json] Execute read-only SQL",
1017
1008
  " canary redis <command> [--json] Execute read-only Redis commands",
1018
- " canary feature-flag <sub-command> Manage feature flags"
1009
+ " canary feature-flag <sub-command> Manage feature flags",
1010
+ " canary knobs <sub-command> Manage knobs (global config)"
1019
1011
  );
1020
1012
  }
1021
1013
  lines.push(
@@ -1062,7 +1054,14 @@ function printHelp({ isSuperadmin }) {
1062
1054
  " create <name> [--description <text>] Create a flag",
1063
1055
  " delete <name> Delete a flag and its gates",
1064
1056
  " enable <name> --org <orgId> Enable for an org",
1065
- " disable <name> --org <orgId> Disable for an org"
1057
+ " disable <name> --org <orgId> Disable for an org",
1058
+ "",
1059
+ "Knobs sub-commands:",
1060
+ " list List all knobs",
1061
+ " get <key> Get a knob value",
1062
+ " set <key> <value> --type <type> Set a knob value",
1063
+ " delete <key> Delete a knob",
1064
+ " toggle <key> Toggle a boolean knob"
1066
1065
  );
1067
1066
  }
1068
1067
  lines.push(
@@ -1180,20 +1179,25 @@ async function main(argv) {
1180
1179
  return;
1181
1180
  }
1182
1181
  if (command === "psql") {
1183
- const { runPsql } = await import("./psql-7AEFGJWI.js");
1182
+ const { runPsql } = await import("./psql-A3BADRQN.js");
1184
1183
  await runPsql(rest);
1185
1184
  return;
1186
1185
  }
1187
1186
  if (command === "redis") {
1188
- const { runRedis } = await import("./redis-BXYEPX4T.js");
1187
+ const { runRedis } = await import("./redis-N2DSDDQU.js");
1189
1188
  await runRedis(rest);
1190
1189
  return;
1191
1190
  }
1192
1191
  if (command === "feature-flag") {
1193
- const { runFeatureFlag } = await import("./feature-flag-43WAHIUZ.js");
1192
+ const { runFeatureFlag } = await import("./feature-flag-PN5IFFQR.js");
1194
1193
  await runFeatureFlag(rest);
1195
1194
  return;
1196
1195
  }
1196
+ if (command === "knobs") {
1197
+ const { runKnobs } = await import("./knobs-DAG7HD2F.js");
1198
+ await runKnobs(rest);
1199
+ return;
1200
+ }
1197
1201
  console.log(`Unknown command "${command}".`);
1198
1202
  const token = await resolveToken();
1199
1203
  printHelp({ isSuperadmin: isSuperadminToken(token) });