@canaryai/cli 0.1.13 → 0.2.0

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.
@@ -20,6 +20,63 @@ function getArgValue(argv, key) {
20
20
  function hasFlag(argv, ...flags) {
21
21
  return flags.some((flag) => argv.includes(flag));
22
22
  }
23
+ function toLifecycleLabel(stage) {
24
+ switch (stage) {
25
+ case "deprecated":
26
+ return "deprecated";
27
+ case "ready_for_cleanup":
28
+ return "ready_for_cleanup";
29
+ default:
30
+ return "active";
31
+ }
32
+ }
33
+ function parseLifecycleStage(argv) {
34
+ const stage = getArgValue(argv, "--stage");
35
+ if (!stage || !["active", "deprecated", "ready_for_cleanup"].includes(stage)) {
36
+ console.error("Error: --stage is required and must be one of: active, deprecated, ready_for_cleanup");
37
+ process.exit(1);
38
+ }
39
+ return stage;
40
+ }
41
+ function formatValue(valueType, value) {
42
+ if (valueType === "json") {
43
+ return JSON.stringify(value);
44
+ }
45
+ return String(value);
46
+ }
47
+ function formatFinalValue(knob) {
48
+ if (knob.lifecycleStage === "active") {
49
+ return "(none)";
50
+ }
51
+ return formatValue(knob.valueType, knob.finalValue);
52
+ }
53
+ function parseTypedValue(raw, valueType) {
54
+ switch (valueType) {
55
+ case "boolean":
56
+ if (raw !== "true" && raw !== "false") {
57
+ console.error('Error: Boolean value must be "true" or "false".');
58
+ process.exit(1);
59
+ }
60
+ return raw === "true";
61
+ case "string":
62
+ return raw;
63
+ case "number": {
64
+ const num = parseFloat(raw);
65
+ if (Number.isNaN(num)) {
66
+ console.error(`Error: Invalid number: ${raw}`);
67
+ process.exit(1);
68
+ }
69
+ return num;
70
+ }
71
+ case "json":
72
+ try {
73
+ return JSON.parse(raw);
74
+ } catch {
75
+ console.error(`Error: Invalid JSON: ${raw}`);
76
+ process.exit(1);
77
+ }
78
+ }
79
+ }
23
80
  async function resolveConfig(argv) {
24
81
  const storedApiUrl = await readStoredApiUrl();
25
82
  const env = getArgValue(argv, "--env");
@@ -54,14 +111,7 @@ async function apiRequest(apiUrl, token, method, path, body) {
54
111
  const json = await res.json();
55
112
  return json;
56
113
  }
57
- function formatValue(knob) {
58
- if (knob.valueType === "json") {
59
- return JSON.stringify(knob.value);
60
- }
61
- return String(knob.value);
62
- }
63
- async function handleList(argv, apiUrl, token) {
64
- const jsonOutput = hasFlag(argv, "--json");
114
+ async function fetchKnobs(apiUrl, token) {
65
115
  const res = await fetch(`${apiUrl}/superadmin/knobs`, {
66
116
  headers: { Authorization: `Bearer ${token}` }
67
117
  });
@@ -75,7 +125,11 @@ async function handleList(argv, apiUrl, token) {
75
125
  console.error(`Error: ${json.error}`);
76
126
  process.exit(1);
77
127
  }
78
- const knobs = json.knobs ?? [];
128
+ return json.knobs ?? [];
129
+ }
130
+ async function handleList(argv, apiUrl, token) {
131
+ const jsonOutput = hasFlag(argv, "--json");
132
+ const knobs = await fetchKnobs(apiUrl, token);
79
133
  if (jsonOutput) {
80
134
  console.log(JSON.stringify(knobs, null, 2));
81
135
  return;
@@ -86,7 +140,11 @@ async function handleList(argv, apiUrl, token) {
86
140
  }
87
141
  for (const knob of knobs) {
88
142
  const desc = knob.description ? ` ${knob.description}` : "";
89
- console.log(` ${knob.key} [${knob.valueType}] = ${formatValue(knob)}${desc}`);
143
+ const lifecycle = `lifecycle=${toLifecycleLabel(knob.lifecycleStage)}`;
144
+ const finalValue = knob.lifecycleStage === "active" ? "" : ` final=${formatFinalValue(knob)}`;
145
+ console.log(
146
+ ` ${knob.key} [${knob.valueType}] = ${formatValue(knob.valueType, knob.value)} (${lifecycle}${finalValue})${desc}`
147
+ );
90
148
  }
91
149
  }
92
150
  async function handleGet(argv, apiUrl, token) {
@@ -97,20 +155,8 @@ async function handleGet(argv, apiUrl, token) {
97
155
  process.exit(1);
98
156
  }
99
157
  const jsonOutput = hasFlag(argv, "--json");
100
- const res = await fetch(`${apiUrl}/superadmin/knobs`, {
101
- headers: { Authorization: `Bearer ${token}` }
102
- });
103
- if (res.status === 401) {
104
- console.error("Error: Unauthorized. Your session may have expired.");
105
- console.error("Run: canary login");
106
- process.exit(1);
107
- }
108
- const json = await res.json();
109
- if (!json.ok) {
110
- console.error(`Error: ${json.error}`);
111
- process.exit(1);
112
- }
113
- const knob = (json.knobs ?? []).find((k) => k.key === key);
158
+ const knobs = await fetchKnobs(apiUrl, token);
159
+ const knob = knobs.find((k) => k.key === key);
114
160
  if (!knob) {
115
161
  console.error(`Knob not found: ${key}`);
116
162
  process.exit(1);
@@ -121,7 +167,9 @@ async function handleGet(argv, apiUrl, token) {
121
167
  }
122
168
  console.log(` Key: ${knob.key}`);
123
169
  console.log(` Type: ${knob.valueType}`);
124
- console.log(` Value: ${formatValue(knob)}`);
170
+ console.log(` Value: ${formatValue(knob.valueType, knob.value)}`);
171
+ console.log(` Lifecycle: ${toLifecycleLabel(knob.lifecycleStage)}`);
172
+ console.log(` Final value: ${formatFinalValue(knob)}`);
125
173
  console.log(` Description: ${knob.description ?? "(none)"}`);
126
174
  console.log(` Updated: ${knob.updatedAt ?? "(never)"}`);
127
175
  }
@@ -147,36 +195,7 @@ async function handleSet(argv, apiUrl, token) {
147
195
  console.error("Error: --type is required and must be one of: boolean, string, number, json");
148
196
  process.exit(1);
149
197
  }
150
- let value;
151
- switch (valueType) {
152
- case "boolean":
153
- if (rawValue !== "true" && rawValue !== "false") {
154
- console.error('Error: Boolean value must be "true" or "false".');
155
- process.exit(1);
156
- }
157
- value = rawValue === "true";
158
- break;
159
- case "string":
160
- value = rawValue;
161
- break;
162
- case "number": {
163
- const num = parseFloat(rawValue);
164
- if (Number.isNaN(num)) {
165
- console.error(`Error: Invalid number: ${rawValue}`);
166
- process.exit(1);
167
- }
168
- value = num;
169
- break;
170
- }
171
- case "json":
172
- try {
173
- value = JSON.parse(rawValue);
174
- } catch {
175
- console.error(`Error: Invalid JSON: ${rawValue}`);
176
- process.exit(1);
177
- }
178
- break;
179
- }
198
+ const value = parseTypedValue(rawValue, valueType);
180
199
  const description = getArgValue(argv, "--description") ?? void 0;
181
200
  const result = await apiRequest(apiUrl, token, "POST", "/superadmin/knobs", {
182
201
  key,
@@ -188,7 +207,7 @@ async function handleSet(argv, apiUrl, token) {
188
207
  console.error(`Error: ${result.error}`);
189
208
  process.exit(1);
190
209
  }
191
- console.log(`Set knob: ${key} = ${formatValue(result.knob)}`);
210
+ console.log(`Set knob: ${key} = ${formatValue(valueType, result.knob?.value)}`);
192
211
  }
193
212
  async function handleDelete(argv, apiUrl, token) {
194
213
  const key = argv[0];
@@ -226,7 +245,66 @@ async function handleToggle(argv, apiUrl, token) {
226
245
  console.error(`Error: ${result.error}`);
227
246
  process.exit(1);
228
247
  }
229
- console.log(`Toggled knob: ${key} \u2192 ${result.knob?.value}`);
248
+ const knob = result.knob;
249
+ console.log(`Toggled knob: ${key} -> ${formatValue(knob?.valueType ?? "boolean", knob?.value)}`);
250
+ }
251
+ async function handleLifecycle(argv, apiUrl, token) {
252
+ const key = argv[0];
253
+ if (!key || key.startsWith("--")) {
254
+ console.error("Error: Missing knob key.");
255
+ console.error(
256
+ "Usage: canary knobs lifecycle <key> --stage <active|deprecated|ready_for_cleanup> [--final-value <value>]"
257
+ );
258
+ process.exit(1);
259
+ }
260
+ const stage = parseLifecycleStage(argv);
261
+ const rawFinalValue = getArgValue(argv, "--final-value");
262
+ const clearFinalValue = hasFlag(argv, "--clear-final-value");
263
+ if (rawFinalValue !== void 0 && clearFinalValue) {
264
+ console.error("Error: use either --final-value or --clear-final-value, not both.");
265
+ process.exit(1);
266
+ }
267
+ const knobs = await fetchKnobs(apiUrl, token);
268
+ const knob = knobs.find((k) => k.key === key);
269
+ if (!knob) {
270
+ console.error(`Knob not found: ${key}`);
271
+ process.exit(1);
272
+ }
273
+ let finalValue = void 0;
274
+ if (stage === "active") {
275
+ if (rawFinalValue !== void 0) {
276
+ console.error("Error: active stage does not accept --final-value. Use --stage deprecated|ready_for_cleanup.");
277
+ process.exit(1);
278
+ }
279
+ finalValue = clearFinalValue ? void 0 : void 0;
280
+ } else {
281
+ if (clearFinalValue) {
282
+ console.error("Error: --clear-final-value can only be used with --stage active.");
283
+ process.exit(1);
284
+ }
285
+ if (rawFinalValue === void 0) {
286
+ console.error("Error: --final-value is required when stage is deprecated or ready_for_cleanup.");
287
+ process.exit(1);
288
+ }
289
+ finalValue = parseTypedValue(rawFinalValue, knob.valueType);
290
+ }
291
+ const result = await apiRequest(
292
+ apiUrl,
293
+ token,
294
+ "POST",
295
+ `/superadmin/knobs/${encodeURIComponent(key)}/lifecycle`,
296
+ {
297
+ stage,
298
+ finalValue
299
+ }
300
+ );
301
+ if (!result.ok || !result.knob) {
302
+ console.error(`Error: ${result.error ?? "Failed to update lifecycle"}`);
303
+ process.exit(1);
304
+ }
305
+ console.log(
306
+ `Updated lifecycle for ${key}: stage=${toLifecycleLabel(result.knob.lifecycleStage)}, final=${formatFinalValue(result.knob)}`
307
+ );
230
308
  }
231
309
  function printKnobsHelp() {
232
310
  console.log(
@@ -240,14 +318,19 @@ function printKnobsHelp() {
240
318
  " Set a knob value",
241
319
  " delete <key> Delete a knob",
242
320
  " toggle <key> Toggle a boolean knob",
321
+ " lifecycle <key> --stage <stage> [--final-value <value>]",
322
+ " Mark knob lifecycle + final value",
243
323
  "",
244
324
  "Types: boolean, string, number, json",
325
+ "Stages: active, deprecated, ready_for_cleanup",
245
326
  "",
246
327
  "Options:",
247
- " --env <env> Target environment (prod, dev, local)",
248
- " --json Output as JSON (list/get only)",
249
- " --api-url <url> API URL override (takes precedence over --env)",
250
- " --token <key> API token override"
328
+ " --final-value <value> Final value for deprecated/ready_for_cleanup",
329
+ " --clear-final-value Clear final value (only valid with --stage active)",
330
+ " --env <env> Target environment (prod, dev, local)",
331
+ " --json Output as JSON (list/get only)",
332
+ " --api-url <url> API URL override (takes precedence over --env)",
333
+ " --token <key> API token override"
251
334
  ].join("\n")
252
335
  );
253
336
  }
@@ -274,6 +357,9 @@ async function runKnobs(argv) {
274
357
  case "toggle":
275
358
  await handleToggle(rest, apiUrl, token);
276
359
  break;
360
+ case "lifecycle":
361
+ await handleLifecycle(rest, apiUrl, token);
362
+ break;
277
363
  default:
278
364
  console.error(`Unknown sub-command: ${subCommand}`);
279
365
  printKnobsHelp();
@@ -283,4 +369,4 @@ async function runKnobs(argv) {
283
369
  export {
284
370
  runKnobs
285
371
  };
286
- //# sourceMappingURL=knobs-T3O4Z3ZB.js.map
372
+ //# sourceMappingURL=knobs-HKONHY55.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/knobs.ts"],"sourcesContent":["/**\n * CLI Knobs Management\n *\n * Allows superadmins to manage knobs (global config) 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 LifecycleStage = \"active\" | \"deprecated\" | \"ready_for_cleanup\";\n\ntype SerializedKnob = {\n key: string;\n description: string | null;\n valueType: \"boolean\" | \"string\" | \"number\" | \"json\";\n value: unknown;\n lifecycleStage: LifecycleStage;\n finalValue: unknown;\n updatedAt: string | null;\n updatedBy: string | null;\n createdAt: string | null;\n};\n\ntype KnobListResponse = {\n ok: boolean;\n knobs?: SerializedKnob[];\n error?: string;\n};\n\ntype ApiResponse = {\n ok: boolean;\n error?: string;\n knob?: SerializedKnob;\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\nfunction toLifecycleLabel(stage: LifecycleStage): string {\n switch (stage) {\n case \"deprecated\":\n return \"deprecated\";\n case \"ready_for_cleanup\":\n return \"ready_for_cleanup\";\n default:\n return \"active\";\n }\n}\n\nfunction parseLifecycleStage(argv: string[]): LifecycleStage {\n const stage = getArgValue(argv, \"--stage\");\n if (!stage || ![\"active\", \"deprecated\", \"ready_for_cleanup\"].includes(stage)) {\n console.error(\"Error: --stage is required and must be one of: active, deprecated, ready_for_cleanup\");\n process.exit(1);\n }\n return stage as LifecycleStage;\n}\n\nfunction formatValue(valueType: SerializedKnob[\"valueType\"], value: unknown): string {\n if (valueType === \"json\") {\n return JSON.stringify(value);\n }\n return String(value);\n}\n\nfunction formatFinalValue(knob: SerializedKnob): string {\n if (knob.lifecycleStage === \"active\") {\n return \"(none)\";\n }\n return formatValue(knob.valueType, knob.finalValue);\n}\n\nfunction parseTypedValue(raw: string, valueType: SerializedKnob[\"valueType\"]): unknown {\n switch (valueType) {\n case \"boolean\":\n if (raw !== \"true\" && raw !== \"false\") {\n console.error('Error: Boolean value must be \"true\" or \"false\".');\n process.exit(1);\n }\n return raw === \"true\";\n case \"string\":\n return raw;\n case \"number\": {\n const num = parseFloat(raw);\n if (Number.isNaN(num)) {\n console.error(`Error: Invalid number: ${raw}`);\n process.exit(1);\n }\n return num;\n }\n case \"json\":\n try {\n return JSON.parse(raw);\n } catch {\n console.error(`Error: Invalid JSON: ${raw}`);\n process.exit(1);\n }\n }\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 fetchKnobs(apiUrl: string, token: string): Promise<SerializedKnob[]> {\n const res = await fetch(`${apiUrl}/superadmin/knobs`, {\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 KnobListResponse;\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n return json.knobs ?? [];\n}\n\nasync function handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const jsonOutput = hasFlag(argv, \"--json\");\n const knobs = await fetchKnobs(apiUrl, token);\n\n if (jsonOutput) {\n console.log(JSON.stringify(knobs, null, 2));\n return;\n }\n\n if (knobs.length === 0) {\n console.log(\"No knobs found.\");\n return;\n }\n\n for (const knob of knobs) {\n const desc = knob.description ? ` ${knob.description}` : \"\";\n const lifecycle = `lifecycle=${toLifecycleLabel(knob.lifecycleStage)}`;\n const finalValue =\n knob.lifecycleStage === \"active\" ? \"\" : ` final=${formatFinalValue(knob)}`;\n console.log(\n ` ${knob.key} [${knob.valueType}] = ${formatValue(knob.valueType, knob.value)} (${lifecycle}${finalValue})${desc}`\n );\n }\n}\n\nasync function handleGet(argv: string[], apiUrl: string, token: string): Promise<void> {\n const key = argv[0];\n if (!key || key.startsWith(\"--\")) {\n console.error(\"Error: Missing knob key.\");\n console.error(\"Usage: canary knobs get <key>\");\n process.exit(1);\n }\n\n const jsonOutput = hasFlag(argv, \"--json\");\n const knobs = await fetchKnobs(apiUrl, token);\n\n const knob = knobs.find((k) => k.key === key);\n\n if (!knob) {\n console.error(`Knob not found: ${key}`);\n process.exit(1);\n }\n\n if (jsonOutput) {\n console.log(JSON.stringify(knob, null, 2));\n return;\n }\n\n console.log(` Key: ${knob.key}`);\n console.log(` Type: ${knob.valueType}`);\n console.log(` Value: ${formatValue(knob.valueType, knob.value)}`);\n console.log(` Lifecycle: ${toLifecycleLabel(knob.lifecycleStage)}`);\n console.log(` Final value: ${formatFinalValue(knob)}`);\n console.log(` Description: ${knob.description ?? \"(none)\"}`);\n console.log(` Updated: ${knob.updatedAt ?? \"(never)\"}`);\n}\n\nasync function handleSet(argv: string[], apiUrl: string, token: string): Promise<void> {\n const key = argv[0];\n if (!key || key.startsWith(\"--\")) {\n console.error(\"Error: Missing knob key.\");\n console.error(\n \"Usage: canary knobs set <key> <value> --type <boolean|string|number|json> [--description <text>]\"\n );\n process.exit(1);\n }\n\n const rawValue = argv[1];\n if (rawValue === undefined || rawValue.startsWith(\"--\")) {\n console.error(\"Error: Missing knob value.\");\n console.error(\n \"Usage: canary knobs set <key> <value> --type <boolean|string|number|json> [--description <text>]\"\n );\n process.exit(1);\n }\n\n const valueType = getArgValue(argv, \"--type\") as SerializedKnob[\"valueType\"] | undefined;\n if (!valueType || ![\"boolean\", \"string\", \"number\", \"json\"].includes(valueType)) {\n console.error(\"Error: --type is required and must be one of: boolean, string, number, json\");\n process.exit(1);\n }\n\n const value = parseTypedValue(rawValue, valueType);\n const description = getArgValue(argv, \"--description\") ?? undefined;\n\n const result = await apiRequest(apiUrl, token, \"POST\", \"/superadmin/knobs\", {\n key,\n valueType,\n value,\n description,\n });\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Set knob: ${key} = ${formatValue(valueType, result.knob?.value)}`);\n}\n\nasync function handleDelete(argv: string[], apiUrl: string, token: string): Promise<void> {\n const key = argv[0];\n if (!key || key.startsWith(\"--\")) {\n console.error(\"Error: Missing knob key.\");\n console.error(\"Usage: canary knobs delete <key>\");\n process.exit(1);\n }\n\n const result = await apiRequest(\n apiUrl,\n token,\n \"DELETE\",\n `/superadmin/knobs/${encodeURIComponent(key)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Deleted knob: ${key}`);\n}\n\nasync function handleToggle(argv: string[], apiUrl: string, token: string): Promise<void> {\n const key = argv[0];\n if (!key || key.startsWith(\"--\")) {\n console.error(\"Error: Missing knob key.\");\n console.error(\"Usage: canary knobs toggle <key>\");\n process.exit(1);\n }\n\n const result = await apiRequest(\n apiUrl,\n token,\n \"POST\",\n `/superadmin/knobs/${encodeURIComponent(key)}/toggle`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n const knob = result.knob;\n console.log(`Toggled knob: ${key} -> ${formatValue(knob?.valueType ?? \"boolean\", knob?.value)}`);\n}\n\nasync function handleLifecycle(argv: string[], apiUrl: string, token: string): Promise<void> {\n const key = argv[0];\n if (!key || key.startsWith(\"--\")) {\n console.error(\"Error: Missing knob key.\");\n console.error(\n \"Usage: canary knobs lifecycle <key> --stage <active|deprecated|ready_for_cleanup> [--final-value <value>]\"\n );\n process.exit(1);\n }\n\n const stage = parseLifecycleStage(argv);\n const rawFinalValue = getArgValue(argv, \"--final-value\");\n const clearFinalValue = hasFlag(argv, \"--clear-final-value\");\n\n if (rawFinalValue !== undefined && clearFinalValue) {\n console.error(\"Error: use either --final-value or --clear-final-value, not both.\");\n process.exit(1);\n }\n\n const knobs = await fetchKnobs(apiUrl, token);\n const knob = knobs.find((k) => k.key === key);\n\n if (!knob) {\n console.error(`Knob not found: ${key}`);\n process.exit(1);\n }\n\n let finalValue: unknown = undefined;\n\n if (stage === \"active\") {\n if (rawFinalValue !== undefined) {\n console.error(\"Error: active stage does not accept --final-value. Use --stage deprecated|ready_for_cleanup.\");\n process.exit(1);\n }\n finalValue = clearFinalValue ? undefined : undefined;\n } else {\n if (clearFinalValue) {\n console.error(\"Error: --clear-final-value can only be used with --stage active.\");\n process.exit(1);\n }\n if (rawFinalValue === undefined) {\n console.error(\"Error: --final-value is required when stage is deprecated or ready_for_cleanup.\");\n process.exit(1);\n }\n finalValue = parseTypedValue(rawFinalValue, knob.valueType);\n }\n\n const result = await apiRequest(\n apiUrl,\n token,\n \"POST\",\n `/superadmin/knobs/${encodeURIComponent(key)}/lifecycle`,\n {\n stage,\n finalValue,\n }\n );\n\n if (!result.ok || !result.knob) {\n console.error(`Error: ${result.error ?? \"Failed to update lifecycle\"}`);\n process.exit(1);\n }\n\n console.log(\n `Updated lifecycle for ${key}: stage=${toLifecycleLabel(result.knob.lifecycleStage)}, final=${formatFinalValue(result.knob)}`\n );\n}\n\nfunction printKnobsHelp(): void {\n console.log(\n [\n \"Usage: canary knobs <sub-command> [options]\",\n \"\",\n \"Sub-commands:\",\n \" list List all knobs\",\n \" get <key> Get a knob value\",\n \" set <key> <value> --type <type> [--description <text>]\",\n \" Set a knob value\",\n \" delete <key> Delete a knob\",\n \" toggle <key> Toggle a boolean knob\",\n \" lifecycle <key> --stage <stage> [--final-value <value>]\",\n \" Mark knob lifecycle + final value\",\n \"\",\n \"Types: boolean, string, number, json\",\n \"Stages: active, deprecated, ready_for_cleanup\",\n \"\",\n \"Options:\",\n \" --final-value <value> Final value for deprecated/ready_for_cleanup\",\n \" --clear-final-value Clear final value (only valid with --stage active)\",\n \" --env <env> Target environment (prod, dev, local)\",\n \" --json Output as JSON (list/get 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 runKnobs(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === \"help\" || hasFlag(argv, \"--help\", \"-h\")) {\n printKnobsHelp();\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 \"get\":\n await handleGet(rest, apiUrl, token);\n break;\n case \"set\":\n await handleSet(rest, apiUrl, token);\n break;\n case \"delete\":\n await handleDelete(rest, apiUrl, token);\n break;\n case \"toggle\":\n await handleToggle(rest, apiUrl, token);\n break;\n case \"lifecycle\":\n await handleLifecycle(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printKnobsHelp();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LocalBrowserHost
3
- } from "./chunk-L26U3BST.js";
3
+ } from "./chunk-UEOXNF5X.js";
4
4
  import {
5
5
  readStoredToken
6
6
  } from "./chunk-2T64Z2NI.js";
@@ -137,4 +137,4 @@ async function runLocalBrowser(args) {
137
137
  export {
138
138
  runLocalBrowser
139
139
  };
140
- //# sourceMappingURL=local-browser-SYPTG6IQ.js.map
140
+ //# sourceMappingURL=local-browser-MKKPBTYI.js.map
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-V7U52ISX.js";
6
6
  import {
7
7
  LocalBrowserHost
8
- } from "./chunk-L26U3BST.js";
8
+ } from "./chunk-UEOXNF5X.js";
9
9
  import {
10
10
  readStoredToken
11
11
  } from "./chunk-2T64Z2NI.js";
@@ -381,4 +381,4 @@ function formatReport(input) {
381
381
  export {
382
382
  runMcp
383
383
  };
384
- //# sourceMappingURL=mcp-TMD2R5Z6.js.map
384
+ //# sourceMappingURL=mcp-4F4HI7L2.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canaryai/cli",
3
- "version": "0.1.13",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,6 +45,7 @@
45
45
  "ai": "^5.0.60",
46
46
  "dotenv": "^17.2.3",
47
47
  "eventsource-parser": "^3.0.0",
48
+ "@chatsdet/browser-core": "workspace:*",
48
49
  "zod": "^4.1.12"
49
50
  },
50
51
  "devDependencies": {