@hasna/skills 0.1.37 → 0.1.39

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/bin/index.js CHANGED
@@ -1910,7 +1910,7 @@ var package_default;
1910
1910
  var init_package = __esm(() => {
1911
1911
  package_default = {
1912
1912
  name: "@hasna/skills",
1913
- version: "0.1.37",
1913
+ version: "0.1.39",
1914
1914
  description: "Skills library for AI coding agents",
1915
1915
  type: "module",
1916
1916
  bin: {
@@ -6661,6 +6661,7 @@ var init_pricing = __esm(() => {
6661
6661
  init_skill_aliases();
6662
6662
  MUSIC_ALBUM_SONG_COUNTS = [7, 14, 21];
6663
6663
  PREMIUM_SKILLS = [
6664
+ { slug: "icon-pack", displayName: "Icon Pack", tier: "premium", costCents: 200, providers: ["hosted"], description: "Hosted coordinated icon pack with SVGs, transparent PNGs, size variants, and manifest" },
6664
6665
  { slug: "logo-design", displayName: "Logo Design", tier: "premium", costCents: 50, providers: ["hosted"], description: "Hosted multi-variant logo package with transparent PNGs, vector-style SVGs, usage notes, and manifest" },
6665
6666
  { slug: "deepresearch", displayName: "Deep Research", tier: "premium", costCents: 20, providers: ["exa"], description: "Agentic web research with semantic search and synthesis" },
6666
6667
  { slug: "playlist-maker", displayName: "Playlist Maker", tier: "premium", costCents: 30, providers: ["exa", "gemini-3-pro"], description: "Curated playlist with research, track selection, and album art" },
@@ -6773,8 +6774,8 @@ function sanitizePublicDiscoveryText(text) {
6773
6774
  function publicDiscoveryEnvVars(skillName, envVars) {
6774
6775
  if (!isPremiumSkill(skillName))
6775
6776
  return envVars;
6776
- const filtered = envVars.filter((envVar) => !VENDOR_ENV_PREFIXES.some((prefix) => envVar.startsWith(prefix)));
6777
- return filtered.includes("SKILL_API_KEY") ? filtered : ["SKILL_API_KEY", ...filtered];
6777
+ const filtered = envVars.filter((envVar) => envVar !== "SKILL_API_KEY" && !VENDOR_ENV_PREFIXES.some((prefix) => envVar.startsWith(prefix)));
6778
+ return filtered.includes("SKILLS_API_KEY") ? filtered : ["SKILLS_API_KEY", ...filtered];
6778
6779
  }
6779
6780
  function publicDiscoveryDependencies(skillName, dependencies) {
6780
6781
  if (!isPremiumSkill(skillName))
@@ -6790,7 +6791,7 @@ function publicDiscoveryDocumentation(skill, documentation) {
6790
6791
  `# ${skill.displayName || skill.name}`,
6791
6792
  sanitizePublicDiscoveryText(skill.description),
6792
6793
  `Pricing: ${getPublicSkillPricing(skill.name).formattedCost}.`,
6793
- "Set `SKILL_API_KEY` for hosted runtime execution. Runtime routing and model selection are managed by the hosted Skills runtime."
6794
+ "Set `SKILLS_API_KEY` or run `skills auth login` for hosted runtime execution. Runtime routing and model selection are managed by the hosted Skills runtime."
6794
6795
  ].join(`
6795
6796
 
6796
6797
  `);
@@ -20907,6 +20908,8 @@ function clearAuthConfig() {
20907
20908
  function getApiKey() {
20908
20909
  if (process.env.SKILLS_API_KEY)
20909
20910
  return process.env.SKILLS_API_KEY;
20911
+ if (process.env.SKILL_API_KEY)
20912
+ return process.env.SKILL_API_KEY;
20910
20913
  return getAuthConfig()?.apiKey || null;
20911
20914
  }
20912
20915
  function normalizeSkillsApiOrigin(apiUrl) {
@@ -21619,7 +21622,8 @@ function getSkillRequirements(name) {
21619
21622
  envVars.delete(envVar);
21620
21623
  }
21621
21624
  }
21622
- envVars.add("SKILL_API_KEY");
21625
+ envVars.delete("SKILL_API_KEY");
21626
+ envVars.add("SKILLS_API_KEY");
21623
21627
  }
21624
21628
  const systemDeps = new Set;
21625
21629
  const depPatterns = [
@@ -38602,7 +38606,7 @@ var MCP_CONTRACT_SCHEMA_VERSION = 1, stringSchema = (description) => ({
38602
38606
  type: "array",
38603
38607
  items,
38604
38608
  ...description ? { description } : {}
38605
- }), skillNameInput, optionalAgentInput, scopeInput, runInputSchema, runArgsSchema, errorSchema, pricingSchema, skillSummarySchema, validationMessageSchema, validationOutputSchema, installOutputSchema, runOutputSchema, toolContracts, contracts, resourceContracts;
38609
+ }), skillNameInput, optionalAgentInput, scopeInput, runInputSchema, runArgsSchema, paidRunApprovalSchema, errorSchema, pricingSchema, skillSummarySchema, validationMessageSchema, validationOutputSchema, installOutputSchema, runOutputSchema, toolContracts, contracts, resourceContracts;
38606
38610
  var init_mcp_contracts = __esm(() => {
38607
38611
  skillNameInput = stringSchema("skill name or alias.");
38608
38612
  optionalAgentInput = stringSchema("Optional target agent slug. Use MCP registration instead of direct skill-folder installs.");
@@ -38622,6 +38626,10 @@ var init_mcp_contracts = __esm(() => {
38622
38626
  default: [],
38623
38627
  description: "CLI-style string arguments passed to the skill."
38624
38628
  };
38629
+ paidRunApprovalSchema = {
38630
+ type: "boolean",
38631
+ description: "Set true only after the user has approved the quoted cost for a paid hosted run."
38632
+ };
38625
38633
  errorSchema = objectSchema({
38626
38634
  code: stringSchema("Stable error code."),
38627
38635
  message: stringSchema("Human-readable error message."),
@@ -38849,11 +38857,16 @@ var init_mcp_contracts = __esm(() => {
38849
38857
  name: "run_skill",
38850
38858
  title: "Run Skill",
38851
38859
  description: "Run a skill locally or through a configured remote runner.",
38852
- params: ["name", "input?", "args?"],
38860
+ params: ["name", "input?", "args?", "approved?"],
38853
38861
  category: "execution",
38854
38862
  sideEffects: "local-process-or-remote-run",
38855
38863
  stable: true,
38856
- inputSchema: objectSchema({ name: skillNameInput, input: runInputSchema, args: runArgsSchema }, ["name"]),
38864
+ inputSchema: objectSchema({
38865
+ name: skillNameInput,
38866
+ input: runInputSchema,
38867
+ args: runArgsSchema,
38868
+ approved: paidRunApprovalSchema
38869
+ }, ["name"]),
38857
38870
  outputSchema: runOutputSchema
38858
38871
  },
38859
38872
  {
@@ -39541,9 +39554,10 @@ function registerOperationTools(server) {
39541
39554
  inputSchema: {
39542
39555
  name: exports_external.string(),
39543
39556
  input: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
39544
- args: exports_external.array(exports_external.string()).optional()
39557
+ args: exports_external.array(exports_external.string()).optional(),
39558
+ approved: exports_external.boolean().optional()
39545
39559
  }
39546
- }, async ({ name, input, args }) => {
39560
+ }, async ({ name, input, args, approved }) => {
39547
39561
  const skill = getSkill(name);
39548
39562
  if (!skill) {
39549
39563
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
@@ -39581,6 +39595,17 @@ function registerOperationTools(server) {
39581
39595
  const run = completeSkillRun(runContext, { status: "failed", error: error48, costCents });
39582
39596
  return mcpError("AUTH_REQUIRED", `${error48}. Local run metadata: ${run.paths.runDir}/run.json`, ["skills auth login"]);
39583
39597
  }
39598
+ if (isPremiumSkill2(skillName) && apiKey && approved !== true) {
39599
+ const cost = formatCost2(costCents ?? 0);
39600
+ const error48 = `${skillName} is a paid hosted skill (${cost}). Call quote_skill first, then call run_skill with approved: true after user approval.`;
39601
+ writeRunLogs(runContext, "", error48 + `
39602
+ `);
39603
+ const run = completeSkillRun(runContext, { status: "failed", error: error48, costCents });
39604
+ return mcpError("APPROVAL_REQUIRED", `${error48}. Local run metadata: ${run.paths.runDir}/run.json`, [
39605
+ "quote_skill",
39606
+ "run_skill approved=true"
39607
+ ]);
39608
+ }
39584
39609
  if (isPremiumSkill2(skillName) && apiKey) {
39585
39610
  try {
39586
39611
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
@@ -40736,7 +40761,7 @@ import { dirname as dirname5, join as join16 } from "path";
40736
40761
  import { createInterface } from "readline";
40737
40762
  function registerRuntime(parent) {
40738
40763
  parent.command("quote").argument("<skill>", "Skill name").argument("[args...]", "Arguments that affect pricing, such as --count 8").allowUnknownOption(true).passThroughOptions(true).option("--json", "Output quote as JSON", false).description("Quote a skill run before spending account balance").action((name, args2, options) => handleQuote(name, args2, options));
40739
- parent.command("run").argument("<skill>", "Skill name").argument("[args...]", "Arguments to pass to the skill").allowUnknownOption(true).passThroughOptions(true).option("--json", "Output result as JSON", false).option("--wait", "Poll remote runs until a terminal status", false).option("--poll-interval-ms <ms>", "Remote polling interval in milliseconds", "1000").option("--poll-timeout-ms <ms>", "Maximum time to wait for a remote run", "300000").description("Run a skill directly").action(async (name, args2, options) => handleRun(name, args2, options));
40764
+ parent.command("run").argument("<skill>", "Skill name").argument("[args...]", "Arguments to pass to the skill").allowUnknownOption(true).passThroughOptions(true).option("--json", "Output result as JSON", false).option("-y, --yes", "Approve paid hosted execution without an interactive prompt", false).option("--wait", "Poll remote runs until a terminal status", false).option("--poll-interval-ms <ms>", "Remote polling interval in milliseconds", "1000").option("--poll-timeout-ms <ms>", "Maximum time to wait for a remote run", "300000").description("Run a skill directly").action(async (name, args2, options) => handleRun(name, args2, options));
40740
40765
  const runs = parent.command("runs").description("Inspect local skill run records");
40741
40766
  runs.command("list").option("--json", "Output as JSON", false).option("--limit <n>", "Maximum number of runs", "20").description("List recent skill runs").action((options) => handleRunsList(options));
40742
40767
  runs.command("show").argument("<run-id>", "Run id").option("--json", "Output as JSON", false).description("Show a skill run record").action((runId, options) => handleRunsShow(runId, options));
@@ -40931,6 +40956,34 @@ async function handleRun(name, args2, options) {
40931
40956
  process.exitCode = 1;
40932
40957
  return;
40933
40958
  }
40959
+ const approval = await approvePaidHostedRun({
40960
+ skill: skill.name,
40961
+ formattedCost: publicPricing.formattedCost,
40962
+ json: options.json,
40963
+ yes: Boolean(options.yes)
40964
+ });
40965
+ if (!approval.approved) {
40966
+ writeRunLogs(runContext, "", approval.error + `
40967
+ `);
40968
+ const run = completeSkillRun(runContext, { status: "failed", error: approval.error, costCents });
40969
+ if (options.json) {
40970
+ console.log(JSON.stringify({
40971
+ contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
40972
+ skill: skill.name,
40973
+ args: args2,
40974
+ exitCode: 1,
40975
+ remote: true,
40976
+ approvalRequired: true,
40977
+ error: approval.error,
40978
+ pricing: publicPricing,
40979
+ run
40980
+ }, null, 2));
40981
+ } else {
40982
+ console.error(chalk8.red(approval.error));
40983
+ }
40984
+ process.exitCode = 1;
40985
+ return;
40986
+ }
40934
40987
  try {
40935
40988
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
40936
40989
  const client = new RemoteSkillsClient2(apiKey);
@@ -41041,6 +41094,18 @@ async function handleRun(name, args2, options) {
41041
41094
  }
41042
41095
  process.exitCode = result.exitCode;
41043
41096
  }
41097
+ async function approvePaidHostedRun(params) {
41098
+ if (params.yes)
41099
+ return { approved: true };
41100
+ const error48 = `${params.skill} is a paid hosted skill (${params.formattedCost}). Run skills quote ${params.skill} first, then rerun with --yes to approve the charge.`;
41101
+ if (params.json || !process.stdin.isTTY || !process.stdout.isTTY) {
41102
+ return { approved: false, error: error48 };
41103
+ }
41104
+ const answer = await promptLine(`Run paid hosted skill ${params.skill} for ${params.formattedCost}? [y/N] `);
41105
+ if (/^(y|yes)$/i.test(answer.trim()))
41106
+ return { approved: true };
41107
+ return { approved: false, error: `Paid hosted run for ${params.skill} was not approved.` };
41108
+ }
41044
41109
  function writeBlogArticleValidationError(errors4, json2) {
41045
41110
  const payload = {
41046
41111
  error: "invalid blog article options",
@@ -41841,26 +41906,46 @@ Scheduled skills (${schedules.length}):
41841
41906
  if (!ok)
41842
41907
  process.exitCode = 1;
41843
41908
  });
41844
- scheduleCmd.command("run").option("--dry-run", "Show which schedules are due without running them", false).option("--json", "Output as JSON", false).description("Execute all due schedules now").action(async (options) => {
41909
+ scheduleCmd.command("run").option("--dry-run", "Show which schedules are due without running them", false).option("--allow-paid", "Allow due paid hosted schedules to spend account balance", false).option("--max-paid-cents <cents>", "Maximum paid hosted spend approved for this run").option("--json", "Output as JSON", false).description("Execute all due schedules now").action(async (options) => {
41845
41910
  const due = getDueSchedules();
41846
41911
  if (!due.length) {
41847
41912
  console.log(options.json ? JSON.stringify({ ran: 0, schedules: [] }) : chalk10.dim("No schedules are due."));
41848
41913
  return;
41849
41914
  }
41915
+ const dueDetails = await Promise.all(due.map((schedule) => describeDueSchedule(schedule)));
41916
+ const paidTotalCents = dueDetails.reduce((total, schedule) => total + (schedule.costCents ?? 0), 0);
41850
41917
  if (options.dryRun) {
41851
- console.log(options.json ? JSON.stringify({ due: due.map((s) => s.name) }) : chalk10.bold(`${due.length} schedule(s) due:
41918
+ console.log(options.json ? JSON.stringify({ due: dueDetails, paidTotalCents, paidTotal: formatCost2(paidTotalCents) }) : chalk10.bold(`${due.length} schedule(s) due:
41852
41919
  `));
41853
41920
  if (!options.json)
41854
- for (const s of due)
41855
- console.log(` ${chalk10.cyan(s.name)} \u2014 ${s.skill} (${s.cron})`);
41921
+ for (const s of dueDetails)
41922
+ console.log(` ${chalk10.cyan(s.name)} \u2014 ${s.skill} (${s.cron})${s.cost ? ` \u2014 ${s.cost}` : ""}`);
41923
+ return;
41924
+ }
41925
+ const approvedMaxCents = parseMaxPaidCents(options.maxPaidCents);
41926
+ if (paidTotalCents > 0 && (!options.allowPaid || approvedMaxCents === null || approvedMaxCents < paidTotalCents)) {
41927
+ const error48 = `Due paid hosted schedules cost ${formatCost2(paidTotalCents)} total. Review with skills schedule run --dry-run, then rerun with --allow-paid --max-paid-cents ${paidTotalCents}.`;
41928
+ if (options.json) {
41929
+ console.log(JSON.stringify({
41930
+ ran: 0,
41931
+ approvalRequired: true,
41932
+ error: error48,
41933
+ paidTotalCents,
41934
+ paidTotal: formatCost2(paidTotalCents),
41935
+ schedules: dueDetails.filter((schedule) => schedule.paid)
41936
+ }));
41937
+ } else {
41938
+ console.error(chalk10.red(`\u2717 ${error48}`));
41939
+ }
41940
+ process.exitCode = 1;
41856
41941
  return;
41857
41942
  }
41858
41943
  const results = [];
41859
41944
  for (const s of due) {
41860
41945
  try {
41861
- await executeScheduledSkill(s.skill, s.args ?? []);
41946
+ const execution = await executeScheduledSkill(s.skill, s.args ?? [], { allowPaid: options.allowPaid });
41862
41947
  recordScheduleRun(s.id, "success");
41863
- results.push({ name: s.name, skill: s.skill, status: "success" });
41948
+ results.push({ name: s.name, skill: s.skill, status: "success", ...execution });
41864
41949
  } catch (err) {
41865
41950
  recordScheduleRun(s.id, "error");
41866
41951
  results.push({ name: s.name, skill: s.skill, status: "error", error: err.message });
@@ -41906,13 +41991,17 @@ Next 5 run times:`));
41906
41991
  console.log(` ${new Date(nextRun).toLocaleString()}`);
41907
41992
  });
41908
41993
  }
41909
- async function executeScheduledSkill(skillName, args2) {
41994
+ async function executeScheduledSkill(skillName, args2, options) {
41910
41995
  const { getSkill: getSkill2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
41911
41996
  const skill = getSkill2(skillName);
41912
41997
  if (!skill)
41913
41998
  throw new Error(`Skill '${skillName}' not found`);
41914
41999
  const pricing = await Promise.resolve().then(() => (init_pricing(), exports_pricing));
41915
42000
  if (pricing.isPremiumSkill(skill.name)) {
42001
+ const publicPricing = pricing.getPublicSkillPricing(skill.name, {}, args2);
42002
+ if (!options.allowPaid) {
42003
+ throw new Error(`${skill.name} is a paid hosted skill (${publicPricing.formattedCost}). Review with skills schedule run --dry-run, then rerun with --allow-paid --max-paid-cents ${publicPricing.costCents}.`);
42004
+ }
41916
42005
  const { getApiKey: getApiKey2 } = await Promise.resolve().then(() => (init_auth_store(), exports_auth_store));
41917
42006
  const apiKey = getApiKey2();
41918
42007
  if (!apiKey) {
@@ -41923,13 +42012,40 @@ async function executeScheduledSkill(skillName, args2) {
41923
42012
  const run = await client.submitRun(skill.name, {}, args2);
41924
42013
  if (run.error)
41925
42014
  throw new Error(String(run.error));
41926
- return;
42015
+ return { paid: true, costCents: publicPricing.costCents, cost: publicPricing.formattedCost };
41927
42016
  }
41928
42017
  const { runSkill: runSkill2 } = await Promise.resolve().then(() => (init_skillinfo(), exports_skillinfo));
41929
42018
  const result = await runSkill2(skill.name, args2);
41930
42019
  if (result.exitCode !== 0) {
41931
42020
  throw new Error(result.error || result.stderr || `Skill '${skill.name}' exited with ${result.exitCode}`);
41932
42021
  }
42022
+ return { paid: false };
42023
+ }
42024
+ async function describeDueSchedule(schedule) {
42025
+ const { getSkill: getSkill2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
42026
+ const pricing = await Promise.resolve().then(() => (init_pricing(), exports_pricing));
42027
+ const skill = getSkill2(schedule.skill);
42028
+ const paid = Boolean(skill && pricing.isPremiumSkill(skill.name));
42029
+ const publicPricing = paid && skill ? pricing.getPublicSkillPricing(skill.name, {}, schedule.args ?? []) : null;
42030
+ return {
42031
+ name: schedule.name,
42032
+ skill: schedule.skill,
42033
+ cron: schedule.cron,
42034
+ paid,
42035
+ costCents: publicPricing?.costCents,
42036
+ cost: publicPricing?.formattedCost
42037
+ };
42038
+ }
42039
+ function parseMaxPaidCents(value) {
42040
+ if (!value)
42041
+ return null;
42042
+ const parsed = Number(value);
42043
+ if (!Number.isInteger(parsed) || parsed < 0)
42044
+ return null;
42045
+ return parsed;
42046
+ }
42047
+ function formatCost2(cents) {
42048
+ return `$${(cents / 100).toFixed(2)}`;
41933
42049
  }
41934
42050
  var init_schedule = __esm(() => {
41935
42051
  init_scheduler();
@@ -42074,11 +42190,58 @@ function prompt(question) {
42074
42190
  }
42075
42191
  async function apiRequest(path, options) {
42076
42192
  const url2 = getApiUrl();
42077
- const res = await fetch(`${url2}${path}`, {
42078
- ...options,
42079
- headers: { "Content-Type": "application/json", ...options?.headers }
42080
- });
42081
- return res.json();
42193
+ let res;
42194
+ try {
42195
+ res = await fetch(`${url2}${path}`, {
42196
+ ...options,
42197
+ headers: { "Content-Type": "application/json", ...options?.headers }
42198
+ });
42199
+ } catch (err) {
42200
+ throw new HostedApiError(`Unable to reach hosted Skills API: ${err.message}`);
42201
+ }
42202
+ const text = await res.text();
42203
+ const body = text ? parseJsonBody(text) : {};
42204
+ if (!res.ok) {
42205
+ const record3 = isRecord2(body) ? body : {};
42206
+ const detail = typeof record3.detail === "string" ? record3.detail : undefined;
42207
+ const error48 = typeof record3.error === "string" ? record3.error : undefined;
42208
+ const code = typeof record3.code === "string" ? record3.code : undefined;
42209
+ throw new HostedApiError(detail || error48 || `${res.status} ${res.statusText}`, {
42210
+ status: res.status,
42211
+ code,
42212
+ detail
42213
+ });
42214
+ }
42215
+ return body;
42216
+ }
42217
+ function parseJsonBody(text) {
42218
+ try {
42219
+ return JSON.parse(text);
42220
+ } catch {
42221
+ return { detail: text };
42222
+ }
42223
+ }
42224
+ function isRecord2(value) {
42225
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
42226
+ }
42227
+ function commandErrorPayload(err, fallback) {
42228
+ if (err instanceof HostedApiError) {
42229
+ return {
42230
+ error: err.message || fallback,
42231
+ ...err.status !== undefined ? { status: err.status } : {},
42232
+ ...err.code ? { code: err.code } : {},
42233
+ ...err.detail && err.detail !== err.message ? { detail: err.detail } : {}
42234
+ };
42235
+ }
42236
+ return { error: err?.message || fallback };
42237
+ }
42238
+ function writeCommandError(err, fallback, json2) {
42239
+ const payload = commandErrorPayload(err, fallback);
42240
+ if (json2)
42241
+ console.log(JSON.stringify(payload, null, 2));
42242
+ else
42243
+ console.error(chalk12.red(String(payload.detail || payload.error || fallback)));
42244
+ process.exitCode = 1;
42082
42245
  }
42083
42246
  function sleep(ms) {
42084
42247
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -42140,55 +42303,77 @@ function printLoginSuccess(loginResult, json2) {
42140
42303
  console.log(chalk12.dim(` API key saved to ~/.hasna/skills/auth.json`));
42141
42304
  }
42142
42305
  }
42143
- async function doLogin(email3, code) {
42306
+ async function doLogin(email3, code, json2) {
42144
42307
  if (!email3 || !email3.includes("@")) {
42145
- console.error(chalk12.red("Invalid email"));
42308
+ writeCommandError(new Error("Invalid email"), "Invalid email", json2);
42146
42309
  process.exitCode = 1;
42147
42310
  return;
42148
42311
  }
42149
42312
  if (!code) {
42150
- console.log(chalk12.dim("Sending code..."));
42151
- const sendRes = await apiRequest("/api/auth/login", {
42152
- method: "POST",
42153
- body: JSON.stringify({ email: email3 })
42154
- });
42313
+ if (!json2)
42314
+ console.log(chalk12.dim("Sending code..."));
42315
+ let sendRes;
42316
+ try {
42317
+ sendRes = await apiRequest("/api/auth/login", {
42318
+ method: "POST",
42319
+ body: JSON.stringify({ email: email3 })
42320
+ });
42321
+ } catch (err) {
42322
+ writeCommandError(err, "Failed to request login code", json2);
42323
+ return;
42324
+ }
42155
42325
  if (sendRes.error) {
42156
- console.error(chalk12.red(sendRes.error));
42157
- process.exitCode = 1;
42326
+ writeCommandError(new Error(sendRes.error), "Failed to request login code", json2);
42158
42327
  return;
42159
42328
  }
42160
- console.log(chalk12.green("\u2713 Code sent to " + email3));
42161
- if (!isTTY) {
42329
+ if (!json2)
42330
+ console.log(chalk12.green("\u2713 Code sent to " + email3));
42331
+ if (json2 || !isTTY) {
42162
42332
  console.log(JSON.stringify({ status: "code_sent", email: email3, message: "Check email for 6-digit code, then run: skills auth login --email " + email3 + " --code <CODE>" }));
42163
42333
  return;
42164
42334
  }
42165
42335
  code = await prompt(chalk12.bold("Code: "));
42166
42336
  }
42167
- const verifyRes = await apiRequest("/api/auth/verify", {
42168
- method: "POST",
42169
- body: JSON.stringify({ email: email3, code })
42170
- });
42337
+ let verifyRes;
42338
+ try {
42339
+ verifyRes = await apiRequest("/api/auth/verify", {
42340
+ method: "POST",
42341
+ body: JSON.stringify({ email: email3, code })
42342
+ });
42343
+ } catch (err) {
42344
+ writeCommandError(err, "Failed to verify login code", json2);
42345
+ return;
42346
+ }
42171
42347
  if (verifyRes.error) {
42172
- console.error(chalk12.red(verifyRes.error));
42173
- process.exitCode = 1;
42348
+ writeCommandError(new Error(verifyRes.error), "Failed to verify login code", json2);
42349
+ return;
42350
+ }
42351
+ let storedKey;
42352
+ try {
42353
+ storedKey = await persistLoginResult(verifyRes);
42354
+ } catch (err) {
42355
+ writeCommandError(err, "Login succeeded but API key creation failed", json2);
42174
42356
  return;
42175
42357
  }
42176
- const storedKey = await persistLoginResult(verifyRes);
42177
42358
  if (!storedKey) {
42178
- console.error(chalk12.red("Login succeeded but API key creation failed"));
42179
- process.exitCode = 1;
42359
+ writeCommandError(new Error("Login succeeded but API key creation failed"), "Login succeeded but API key creation failed", json2);
42180
42360
  return;
42181
42361
  }
42182
- printLoginSuccess(verifyRes, false);
42362
+ printLoginSuccess(verifyRes, Boolean(json2));
42183
42363
  }
42184
42364
  async function doDeviceLogin(options) {
42185
- const start = await apiRequest("/api/auth/device/start", {
42186
- method: "POST",
42187
- body: JSON.stringify({ client: "skills-cli" })
42188
- });
42365
+ let start;
42366
+ try {
42367
+ start = await apiRequest("/api/auth/device/start", {
42368
+ method: "POST",
42369
+ body: JSON.stringify({ client: "skills-cli" })
42370
+ });
42371
+ } catch (err) {
42372
+ writeCommandError(err, "Failed to start device login", options.json);
42373
+ return;
42374
+ }
42189
42375
  if (start.error) {
42190
- console.error(chalk12.red(start.error));
42191
- process.exitCode = 1;
42376
+ writeCommandError(new Error(start.error), "Failed to start device login", options.json);
42192
42377
  return;
42193
42378
  }
42194
42379
  const verificationUrl = start.verificationUriComplete || start.verificationUri;
@@ -42230,23 +42415,33 @@ Waiting for authentication...`));
42230
42415
  const timeoutMs = Number(options.pollTimeoutMs || DEFAULT_DEVICE_POLL_TIMEOUT_MS);
42231
42416
  const deadline = Date.now() + timeoutMs;
42232
42417
  while (Date.now() < deadline) {
42233
- const tokenRes = await apiRequest("/api/auth/device/token", {
42234
- method: "POST",
42235
- body: JSON.stringify({ deviceCode: start.deviceCode })
42236
- });
42418
+ let tokenRes;
42419
+ try {
42420
+ tokenRes = await apiRequest("/api/auth/device/token", {
42421
+ method: "POST",
42422
+ body: JSON.stringify({ deviceCode: start.deviceCode })
42423
+ });
42424
+ } catch (err) {
42425
+ writeCommandError(err, "Failed to poll device login", options.json);
42426
+ return;
42427
+ }
42237
42428
  if (tokenRes.error === "authorization_pending" || tokenRes.status === "pending") {
42238
42429
  await sleep(intervalMs);
42239
42430
  continue;
42240
42431
  }
42241
42432
  if (tokenRes.error) {
42242
- console.error(chalk12.red(tokenRes.detail || tokenRes.error));
42243
- process.exitCode = 1;
42433
+ writeCommandError(new Error(tokenRes.detail || tokenRes.error), "Failed to poll device login", options.json);
42434
+ return;
42435
+ }
42436
+ let storedKey;
42437
+ try {
42438
+ storedKey = await persistLoginResult(tokenRes);
42439
+ } catch (err) {
42440
+ writeCommandError(err, "Login succeeded but API key creation failed", options.json);
42244
42441
  return;
42245
42442
  }
42246
- const storedKey = await persistLoginResult(tokenRes);
42247
42443
  if (!storedKey) {
42248
- console.error(chalk12.red("Login succeeded but API key creation failed"));
42249
- process.exitCode = 1;
42444
+ writeCommandError(new Error("Login succeeded but API key creation failed"), "Login succeeded but API key creation failed", options.json);
42250
42445
  return;
42251
42446
  }
42252
42447
  printLoginSuccess(tokenRes, Boolean(options.json));
@@ -42282,7 +42477,7 @@ function registerAuth(parent) {
42282
42477
  process.exitCode = 1;
42283
42478
  return;
42284
42479
  }
42285
- await doLogin(email3, options.code);
42480
+ await doLogin(email3, options.code, options.json);
42286
42481
  });
42287
42482
  auth.command("signup").description("Create or sign in with your email (passwordless)").option("--email <email>", "Email address (non-interactive)").option("--code <code>", "Verification code (non-interactive)").action(async (options) => {
42288
42483
  let email3 = options.email;
@@ -42370,22 +42565,26 @@ async function handleBillingStatus(options = {}) {
42370
42565
  }
42371
42566
  console.log(chalk12.bold("Plan: ") + res.plan);
42372
42567
  console.log(chalk12.bold("Balance: ") + res.balance);
42373
- } catch {
42374
- console.error(chalk12.red("Failed to fetch billing status"));
42375
- process.exitCode = 1;
42568
+ } catch (err) {
42569
+ writeCommandError(err, "Failed to fetch billing status", options.json);
42376
42570
  }
42377
42571
  }
42378
42572
  async function handleCheckout(options = {}) {
42379
42573
  const config2 = requireHostedAuth(options.json);
42380
42574
  if (!config2)
42381
42575
  return;
42382
- const res = await apiRequest("/api/v1/billing/checkout", {
42383
- method: "POST",
42384
- headers: { Authorization: `Bearer ${config2.apiKey}` }
42385
- });
42576
+ let res;
42577
+ try {
42578
+ res = await apiRequest("/api/v1/billing/checkout", {
42579
+ method: "POST",
42580
+ headers: { Authorization: `Bearer ${config2.apiKey}` }
42581
+ });
42582
+ } catch (err) {
42583
+ writeCommandError(err, "Failed to create checkout session", options.json);
42584
+ return;
42585
+ }
42386
42586
  if (res.error || !res.url) {
42387
- console.error(chalk12.red(res.detail || res.error || "Failed to create checkout session"));
42388
- process.exitCode = 1;
42587
+ writeCommandError(new Error(res.detail || res.error || "Failed to create checkout session"), "Failed to create checkout session", options.json);
42389
42588
  return;
42390
42589
  }
42391
42590
  if (options.json)
@@ -42397,13 +42596,18 @@ async function handlePortal(options = {}) {
42397
42596
  const config2 = requireHostedAuth(options.json);
42398
42597
  if (!config2)
42399
42598
  return;
42400
- const res = await apiRequest("/api/v1/billing/portal", {
42401
- method: "POST",
42402
- headers: { Authorization: `Bearer ${config2.apiKey}` }
42403
- });
42599
+ let res;
42600
+ try {
42601
+ res = await apiRequest("/api/v1/billing/portal", {
42602
+ method: "POST",
42603
+ headers: { Authorization: `Bearer ${config2.apiKey}` }
42604
+ });
42605
+ } catch (err) {
42606
+ writeCommandError(err, "Failed to create customer portal session", options.json);
42607
+ return;
42608
+ }
42404
42609
  if (res.error || !res.url) {
42405
- console.error(chalk12.red(res.detail || res.error || "Failed to create customer portal session"));
42406
- process.exitCode = 1;
42610
+ writeCommandError(new Error(res.detail || res.error || "Failed to create customer portal session"), "Failed to create customer portal session", options.json);
42407
42611
  return;
42408
42612
  }
42409
42613
  if (options.json)
@@ -42415,14 +42619,19 @@ async function handleBuyCredits(amount, options = {}) {
42415
42619
  const config2 = requireHostedAuth(options.json);
42416
42620
  if (!config2)
42417
42621
  return;
42418
- const res = await apiRequest("/api/v1/billing/credits", {
42419
- method: "POST",
42420
- headers: { Authorization: `Bearer ${config2.apiKey}` },
42421
- body: JSON.stringify({ amount })
42422
- });
42622
+ let res;
42623
+ try {
42624
+ res = await apiRequest("/api/v1/billing/credits", {
42625
+ method: "POST",
42626
+ headers: { Authorization: `Bearer ${config2.apiKey}` },
42627
+ body: JSON.stringify({ amount })
42628
+ });
42629
+ } catch (err) {
42630
+ writeCommandError(err, "Failed to create credit checkout session", options.json);
42631
+ return;
42632
+ }
42423
42633
  if (res.error || !res.url) {
42424
- console.error(chalk12.red(res.detail || res.error || "Failed to create credit checkout session"));
42425
- process.exitCode = 1;
42634
+ writeCommandError(new Error(res.detail || res.error || "Failed to create credit checkout session"), "Failed to create credit checkout session", options.json);
42426
42635
  return;
42427
42636
  }
42428
42637
  if (options.json)
@@ -42434,12 +42643,17 @@ async function handleListCreditPacks(options = {}) {
42434
42643
  const config2 = requireHostedAuth(options.json);
42435
42644
  if (!config2)
42436
42645
  return;
42437
- const res = await apiRequest("/api/v1/billing/credits", {
42438
- headers: { Authorization: `Bearer ${config2.apiKey}` }
42439
- });
42646
+ let res;
42647
+ try {
42648
+ res = await apiRequest("/api/v1/billing/credits", {
42649
+ headers: { Authorization: `Bearer ${config2.apiKey}` }
42650
+ });
42651
+ } catch (err) {
42652
+ writeCommandError(err, "Failed to list credit packs", options.json);
42653
+ return;
42654
+ }
42440
42655
  if (res.error) {
42441
- console.error(chalk12.red(res.detail || res.error || "Failed to list credit packs"));
42442
- process.exitCode = 1;
42656
+ writeCommandError(new Error(res.detail || res.error || "Failed to list credit packs"), "Failed to list credit packs", options.json);
42443
42657
  return;
42444
42658
  }
42445
42659
  if (options.json) {
@@ -42463,11 +42677,23 @@ function registerCredits(parent) {
42463
42677
  credits.command("buy").description("Create a credit pack checkout session").argument("<amount>", "Credit pack amount: 1, 5, 20, 50, or 100").option("--json", "Output as JSON", false).action(handleBuyCredits);
42464
42678
  credits.command("packs").description("List available credit packs").option("--json", "Output as JSON", false).action(handleListCreditPacks);
42465
42679
  }
42466
- var isTTY, DEFAULT_DEVICE_POLL_TIMEOUT_MS;
42680
+ var isTTY, DEFAULT_DEVICE_POLL_TIMEOUT_MS, HostedApiError;
42467
42681
  var init_auth = __esm(() => {
42468
42682
  init_auth_store();
42469
42683
  isTTY = process.stdin.isTTY && process.stdout.isTTY;
42470
42684
  DEFAULT_DEVICE_POLL_TIMEOUT_MS = 10 * 60 * 1000;
42685
+ HostedApiError = class HostedApiError extends Error {
42686
+ status;
42687
+ code;
42688
+ detail;
42689
+ constructor(message, options = {}) {
42690
+ super(message);
42691
+ this.name = "HostedApiError";
42692
+ this.status = options.status;
42693
+ this.code = options.code;
42694
+ this.detail = options.detail;
42695
+ }
42696
+ };
42471
42697
  });
42472
42698
 
42473
42699
  // src/cli/commands/feedback.ts