@hasna/skills 0.1.36 → 0.1.38

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.36",
1913
+ version: "0.1.38",
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) {
@@ -21315,6 +21318,18 @@ function handleBrowseError(error48) {
21315
21318
  console.error(chalk3.red(error48.message));
21316
21319
  process.exitCode = 1;
21317
21320
  }
21321
+ async function writeJson(value, space) {
21322
+ const text = `${JSON.stringify(value, null, space)}
21323
+ `;
21324
+ await new Promise((resolve, reject) => {
21325
+ process.stdout.write(text, (error48) => {
21326
+ if (error48)
21327
+ reject(error48);
21328
+ else
21329
+ resolve();
21330
+ });
21331
+ });
21332
+ }
21318
21333
  async function getBrowseRegistry(options) {
21319
21334
  if (options.remote)
21320
21335
  return loadRemoteRegistry();
@@ -21339,11 +21354,11 @@ async function handleList(options) {
21339
21354
  if (options.json) {
21340
21355
  const meta4 = getInstallMeta();
21341
21356
  const registry4 = loadRegistry();
21342
- console.log(JSON.stringify(installed.map((name) => {
21357
+ await writeJson(installed.map((name) => {
21343
21358
  const m = meta4.skills[name];
21344
21359
  const s = registry4.find((r) => r.name === name);
21345
21360
  return { name, version: m?.version ?? null, installedAt: m?.installedAt ?? null, source: s?.source ?? "official" };
21346
- })));
21361
+ }));
21347
21362
  return;
21348
21363
  }
21349
21364
  if (installed.length === 0) {
@@ -21382,7 +21397,7 @@ Available: ${categories.join(", ")}`);
21382
21397
  skills = skills.filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
21383
21398
  const output = enrichDiscovery(skills);
21384
21399
  if (options.json) {
21385
- console.log(JSON.stringify(output, null, 2));
21400
+ await writeJson(output, 2);
21386
21401
  return;
21387
21402
  }
21388
21403
  if (brief) {
@@ -21401,7 +21416,7 @@ ${category} (${skills.length}):
21401
21416
  const skills = registry2.filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
21402
21417
  const output = enrichDiscovery(skills);
21403
21418
  if (options.json) {
21404
- console.log(JSON.stringify(output, null, 2));
21419
+ await writeJson(output, 2);
21405
21420
  return;
21406
21421
  }
21407
21422
  if (brief) {
@@ -21418,7 +21433,7 @@ Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
21418
21433
  }
21419
21434
  const allSkills = enrichDiscovery(registry2);
21420
21435
  if (options.json) {
21421
- console.log(JSON.stringify(allSkills, null, 2));
21436
+ await writeJson(allSkills, 2);
21422
21437
  return;
21423
21438
  }
21424
21439
  if (fmt === "compact") {
@@ -21480,7 +21495,7 @@ Available: ${categories.join(", ")}`);
21480
21495
  const fmt = !options.json ? options.format : undefined;
21481
21496
  const output = enrichDiscovery(results);
21482
21497
  if (options.json) {
21483
- console.log(JSON.stringify(output, null, 2));
21498
+ await writeJson(output, 2);
21484
21499
  return;
21485
21500
  }
21486
21501
  if (results.length === 0) {
@@ -21524,7 +21539,7 @@ async function handleCategories(options) {
21524
21539
  count: registry2.filter((skill) => skill.category === category).length
21525
21540
  }));
21526
21541
  if (options.json) {
21527
- console.log(JSON.stringify(cats, null, 2));
21542
+ await writeJson(cats, 2);
21528
21543
  return;
21529
21544
  }
21530
21545
  console.log(chalk3.bold(`
@@ -21541,7 +21556,7 @@ async function handleTags(options) {
21541
21556
  }
21542
21557
  const sorted = Array.from(tagCounts.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({ name, count }));
21543
21558
  if (options.json) {
21544
- console.log(JSON.stringify(sorted, null, 2));
21559
+ await writeJson(sorted, 2);
21545
21560
  return;
21546
21561
  }
21547
21562
  console.log(chalk3.bold(`
@@ -21607,7 +21622,8 @@ function getSkillRequirements(name) {
21607
21622
  envVars.delete(envVar);
21608
21623
  }
21609
21624
  }
21610
- envVars.add("SKILL_API_KEY");
21625
+ envVars.delete("SKILL_API_KEY");
21626
+ envVars.add("SKILLS_API_KEY");
21611
21627
  }
21612
21628
  const systemDeps = new Set;
21613
21629
  const depPatterns = [
@@ -38590,7 +38606,7 @@ var MCP_CONTRACT_SCHEMA_VERSION = 1, stringSchema = (description) => ({
38590
38606
  type: "array",
38591
38607
  items,
38592
38608
  ...description ? { description } : {}
38593
- }), 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;
38594
38610
  var init_mcp_contracts = __esm(() => {
38595
38611
  skillNameInput = stringSchema("skill name or alias.");
38596
38612
  optionalAgentInput = stringSchema("Optional target agent slug. Use MCP registration instead of direct skill-folder installs.");
@@ -38610,6 +38626,10 @@ var init_mcp_contracts = __esm(() => {
38610
38626
  default: [],
38611
38627
  description: "CLI-style string arguments passed to the skill."
38612
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
+ };
38613
38633
  errorSchema = objectSchema({
38614
38634
  code: stringSchema("Stable error code."),
38615
38635
  message: stringSchema("Human-readable error message."),
@@ -38837,11 +38857,16 @@ var init_mcp_contracts = __esm(() => {
38837
38857
  name: "run_skill",
38838
38858
  title: "Run Skill",
38839
38859
  description: "Run a skill locally or through a configured remote runner.",
38840
- params: ["name", "input?", "args?"],
38860
+ params: ["name", "input?", "args?", "approved?"],
38841
38861
  category: "execution",
38842
38862
  sideEffects: "local-process-or-remote-run",
38843
38863
  stable: true,
38844
- 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"]),
38845
38870
  outputSchema: runOutputSchema
38846
38871
  },
38847
38872
  {
@@ -39529,9 +39554,10 @@ function registerOperationTools(server) {
39529
39554
  inputSchema: {
39530
39555
  name: exports_external.string(),
39531
39556
  input: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
39532
- args: exports_external.array(exports_external.string()).optional()
39557
+ args: exports_external.array(exports_external.string()).optional(),
39558
+ approved: exports_external.boolean().optional()
39533
39559
  }
39534
- }, async ({ name, input, args }) => {
39560
+ }, async ({ name, input, args, approved }) => {
39535
39561
  const skill = getSkill(name);
39536
39562
  if (!skill) {
39537
39563
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
@@ -39569,6 +39595,17 @@ function registerOperationTools(server) {
39569
39595
  const run = completeSkillRun(runContext, { status: "failed", error: error48, costCents });
39570
39596
  return mcpError("AUTH_REQUIRED", `${error48}. Local run metadata: ${run.paths.runDir}/run.json`, ["skills auth login"]);
39571
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
+ }
39572
39609
  if (isPremiumSkill2(skillName) && apiKey) {
39573
39610
  try {
39574
39611
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
@@ -40724,7 +40761,7 @@ import { dirname as dirname5, join as join16 } from "path";
40724
40761
  import { createInterface } from "readline";
40725
40762
  function registerRuntime(parent) {
40726
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));
40727
- 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));
40728
40765
  const runs = parent.command("runs").description("Inspect local skill run records");
40729
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));
40730
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));
@@ -40919,6 +40956,34 @@ async function handleRun(name, args2, options) {
40919
40956
  process.exitCode = 1;
40920
40957
  return;
40921
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
+ }
40922
40987
  try {
40923
40988
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
40924
40989
  const client = new RemoteSkillsClient2(apiKey);
@@ -41029,6 +41094,18 @@ async function handleRun(name, args2, options) {
41029
41094
  }
41030
41095
  process.exitCode = result.exitCode;
41031
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
+ }
41032
41109
  function writeBlogArticleValidationError(errors4, json2) {
41033
41110
  const payload = {
41034
41111
  error: "invalid blog article options",
@@ -41829,26 +41906,46 @@ Scheduled skills (${schedules.length}):
41829
41906
  if (!ok)
41830
41907
  process.exitCode = 1;
41831
41908
  });
41832
- 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) => {
41833
41910
  const due = getDueSchedules();
41834
41911
  if (!due.length) {
41835
41912
  console.log(options.json ? JSON.stringify({ ran: 0, schedules: [] }) : chalk10.dim("No schedules are due."));
41836
41913
  return;
41837
41914
  }
41915
+ const dueDetails = await Promise.all(due.map((schedule) => describeDueSchedule(schedule)));
41916
+ const paidTotalCents = dueDetails.reduce((total, schedule) => total + (schedule.costCents ?? 0), 0);
41838
41917
  if (options.dryRun) {
41839
- 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:
41840
41919
  `));
41841
41920
  if (!options.json)
41842
- for (const s of due)
41843
- 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;
41844
41941
  return;
41845
41942
  }
41846
41943
  const results = [];
41847
41944
  for (const s of due) {
41848
41945
  try {
41849
- await executeScheduledSkill(s.skill, s.args ?? []);
41946
+ const execution = await executeScheduledSkill(s.skill, s.args ?? [], { allowPaid: options.allowPaid });
41850
41947
  recordScheduleRun(s.id, "success");
41851
- results.push({ name: s.name, skill: s.skill, status: "success" });
41948
+ results.push({ name: s.name, skill: s.skill, status: "success", ...execution });
41852
41949
  } catch (err) {
41853
41950
  recordScheduleRun(s.id, "error");
41854
41951
  results.push({ name: s.name, skill: s.skill, status: "error", error: err.message });
@@ -41894,13 +41991,17 @@ Next 5 run times:`));
41894
41991
  console.log(` ${new Date(nextRun).toLocaleString()}`);
41895
41992
  });
41896
41993
  }
41897
- async function executeScheduledSkill(skillName, args2) {
41994
+ async function executeScheduledSkill(skillName, args2, options) {
41898
41995
  const { getSkill: getSkill2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
41899
41996
  const skill = getSkill2(skillName);
41900
41997
  if (!skill)
41901
41998
  throw new Error(`Skill '${skillName}' not found`);
41902
41999
  const pricing = await Promise.resolve().then(() => (init_pricing(), exports_pricing));
41903
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
+ }
41904
42005
  const { getApiKey: getApiKey2 } = await Promise.resolve().then(() => (init_auth_store(), exports_auth_store));
41905
42006
  const apiKey = getApiKey2();
41906
42007
  if (!apiKey) {
@@ -41911,13 +42012,40 @@ async function executeScheduledSkill(skillName, args2) {
41911
42012
  const run = await client.submitRun(skill.name, {}, args2);
41912
42013
  if (run.error)
41913
42014
  throw new Error(String(run.error));
41914
- return;
42015
+ return { paid: true, costCents: publicPricing.costCents, cost: publicPricing.formattedCost };
41915
42016
  }
41916
42017
  const { runSkill: runSkill2 } = await Promise.resolve().then(() => (init_skillinfo(), exports_skillinfo));
41917
42018
  const result = await runSkill2(skill.name, args2);
41918
42019
  if (result.exitCode !== 0) {
41919
42020
  throw new Error(result.error || result.stderr || `Skill '${skill.name}' exited with ${result.exitCode}`);
41920
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)}`;
41921
42049
  }
41922
42050
  var init_schedule = __esm(() => {
41923
42051
  init_scheduler();
@@ -42062,11 +42190,58 @@ function prompt(question) {
42062
42190
  }
42063
42191
  async function apiRequest(path, options) {
42064
42192
  const url2 = getApiUrl();
42065
- const res = await fetch(`${url2}${path}`, {
42066
- ...options,
42067
- headers: { "Content-Type": "application/json", ...options?.headers }
42068
- });
42069
- 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;
42070
42245
  }
42071
42246
  function sleep(ms) {
42072
42247
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -42128,55 +42303,77 @@ function printLoginSuccess(loginResult, json2) {
42128
42303
  console.log(chalk12.dim(` API key saved to ~/.hasna/skills/auth.json`));
42129
42304
  }
42130
42305
  }
42131
- async function doLogin(email3, code) {
42306
+ async function doLogin(email3, code, json2) {
42132
42307
  if (!email3 || !email3.includes("@")) {
42133
- console.error(chalk12.red("Invalid email"));
42308
+ writeCommandError(new Error("Invalid email"), "Invalid email", json2);
42134
42309
  process.exitCode = 1;
42135
42310
  return;
42136
42311
  }
42137
42312
  if (!code) {
42138
- console.log(chalk12.dim("Sending code..."));
42139
- const sendRes = await apiRequest("/api/auth/login", {
42140
- method: "POST",
42141
- body: JSON.stringify({ email: email3 })
42142
- });
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
+ }
42143
42325
  if (sendRes.error) {
42144
- console.error(chalk12.red(sendRes.error));
42145
- process.exitCode = 1;
42326
+ writeCommandError(new Error(sendRes.error), "Failed to request login code", json2);
42146
42327
  return;
42147
42328
  }
42148
- console.log(chalk12.green("\u2713 Code sent to " + email3));
42149
- if (!isTTY) {
42329
+ if (!json2)
42330
+ console.log(chalk12.green("\u2713 Code sent to " + email3));
42331
+ if (json2 || !isTTY) {
42150
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>" }));
42151
42333
  return;
42152
42334
  }
42153
42335
  code = await prompt(chalk12.bold("Code: "));
42154
42336
  }
42155
- const verifyRes = await apiRequest("/api/auth/verify", {
42156
- method: "POST",
42157
- body: JSON.stringify({ email: email3, code })
42158
- });
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
+ }
42159
42347
  if (verifyRes.error) {
42160
- console.error(chalk12.red(verifyRes.error));
42161
- 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);
42162
42356
  return;
42163
42357
  }
42164
- const storedKey = await persistLoginResult(verifyRes);
42165
42358
  if (!storedKey) {
42166
- console.error(chalk12.red("Login succeeded but API key creation failed"));
42167
- process.exitCode = 1;
42359
+ writeCommandError(new Error("Login succeeded but API key creation failed"), "Login succeeded but API key creation failed", json2);
42168
42360
  return;
42169
42361
  }
42170
- printLoginSuccess(verifyRes, false);
42362
+ printLoginSuccess(verifyRes, Boolean(json2));
42171
42363
  }
42172
42364
  async function doDeviceLogin(options) {
42173
- const start = await apiRequest("/api/auth/device/start", {
42174
- method: "POST",
42175
- body: JSON.stringify({ client: "skills-cli" })
42176
- });
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
+ }
42177
42375
  if (start.error) {
42178
- console.error(chalk12.red(start.error));
42179
- process.exitCode = 1;
42376
+ writeCommandError(new Error(start.error), "Failed to start device login", options.json);
42180
42377
  return;
42181
42378
  }
42182
42379
  const verificationUrl = start.verificationUriComplete || start.verificationUri;
@@ -42218,23 +42415,33 @@ Waiting for authentication...`));
42218
42415
  const timeoutMs = Number(options.pollTimeoutMs || DEFAULT_DEVICE_POLL_TIMEOUT_MS);
42219
42416
  const deadline = Date.now() + timeoutMs;
42220
42417
  while (Date.now() < deadline) {
42221
- const tokenRes = await apiRequest("/api/auth/device/token", {
42222
- method: "POST",
42223
- body: JSON.stringify({ deviceCode: start.deviceCode })
42224
- });
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
+ }
42225
42428
  if (tokenRes.error === "authorization_pending" || tokenRes.status === "pending") {
42226
42429
  await sleep(intervalMs);
42227
42430
  continue;
42228
42431
  }
42229
42432
  if (tokenRes.error) {
42230
- console.error(chalk12.red(tokenRes.detail || tokenRes.error));
42231
- 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);
42232
42441
  return;
42233
42442
  }
42234
- const storedKey = await persistLoginResult(tokenRes);
42235
42443
  if (!storedKey) {
42236
- console.error(chalk12.red("Login succeeded but API key creation failed"));
42237
- process.exitCode = 1;
42444
+ writeCommandError(new Error("Login succeeded but API key creation failed"), "Login succeeded but API key creation failed", options.json);
42238
42445
  return;
42239
42446
  }
42240
42447
  printLoginSuccess(tokenRes, Boolean(options.json));
@@ -42270,7 +42477,7 @@ function registerAuth(parent) {
42270
42477
  process.exitCode = 1;
42271
42478
  return;
42272
42479
  }
42273
- await doLogin(email3, options.code);
42480
+ await doLogin(email3, options.code, options.json);
42274
42481
  });
42275
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) => {
42276
42483
  let email3 = options.email;
@@ -42358,22 +42565,26 @@ async function handleBillingStatus(options = {}) {
42358
42565
  }
42359
42566
  console.log(chalk12.bold("Plan: ") + res.plan);
42360
42567
  console.log(chalk12.bold("Balance: ") + res.balance);
42361
- } catch {
42362
- console.error(chalk12.red("Failed to fetch billing status"));
42363
- process.exitCode = 1;
42568
+ } catch (err) {
42569
+ writeCommandError(err, "Failed to fetch billing status", options.json);
42364
42570
  }
42365
42571
  }
42366
42572
  async function handleCheckout(options = {}) {
42367
42573
  const config2 = requireHostedAuth(options.json);
42368
42574
  if (!config2)
42369
42575
  return;
42370
- const res = await apiRequest("/api/v1/billing/checkout", {
42371
- method: "POST",
42372
- headers: { Authorization: `Bearer ${config2.apiKey}` }
42373
- });
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
+ }
42374
42586
  if (res.error || !res.url) {
42375
- console.error(chalk12.red(res.detail || res.error || "Failed to create checkout session"));
42376
- process.exitCode = 1;
42587
+ writeCommandError(new Error(res.detail || res.error || "Failed to create checkout session"), "Failed to create checkout session", options.json);
42377
42588
  return;
42378
42589
  }
42379
42590
  if (options.json)
@@ -42385,13 +42596,18 @@ async function handlePortal(options = {}) {
42385
42596
  const config2 = requireHostedAuth(options.json);
42386
42597
  if (!config2)
42387
42598
  return;
42388
- const res = await apiRequest("/api/v1/billing/portal", {
42389
- method: "POST",
42390
- headers: { Authorization: `Bearer ${config2.apiKey}` }
42391
- });
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
+ }
42392
42609
  if (res.error || !res.url) {
42393
- console.error(chalk12.red(res.detail || res.error || "Failed to create customer portal session"));
42394
- 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);
42395
42611
  return;
42396
42612
  }
42397
42613
  if (options.json)
@@ -42403,14 +42619,19 @@ async function handleBuyCredits(amount, options = {}) {
42403
42619
  const config2 = requireHostedAuth(options.json);
42404
42620
  if (!config2)
42405
42621
  return;
42406
- const res = await apiRequest("/api/v1/billing/credits", {
42407
- method: "POST",
42408
- headers: { Authorization: `Bearer ${config2.apiKey}` },
42409
- body: JSON.stringify({ amount })
42410
- });
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
+ }
42411
42633
  if (res.error || !res.url) {
42412
- console.error(chalk12.red(res.detail || res.error || "Failed to create credit checkout session"));
42413
- 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);
42414
42635
  return;
42415
42636
  }
42416
42637
  if (options.json)
@@ -42422,12 +42643,17 @@ async function handleListCreditPacks(options = {}) {
42422
42643
  const config2 = requireHostedAuth(options.json);
42423
42644
  if (!config2)
42424
42645
  return;
42425
- const res = await apiRequest("/api/v1/billing/credits", {
42426
- headers: { Authorization: `Bearer ${config2.apiKey}` }
42427
- });
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
+ }
42428
42655
  if (res.error) {
42429
- console.error(chalk12.red(res.detail || res.error || "Failed to list credit packs"));
42430
- process.exitCode = 1;
42656
+ writeCommandError(new Error(res.detail || res.error || "Failed to list credit packs"), "Failed to list credit packs", options.json);
42431
42657
  return;
42432
42658
  }
42433
42659
  if (options.json) {
@@ -42451,11 +42677,23 @@ function registerCredits(parent) {
42451
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);
42452
42678
  credits.command("packs").description("List available credit packs").option("--json", "Output as JSON", false).action(handleListCreditPacks);
42453
42679
  }
42454
- var isTTY, DEFAULT_DEVICE_POLL_TIMEOUT_MS;
42680
+ var isTTY, DEFAULT_DEVICE_POLL_TIMEOUT_MS, HostedApiError;
42455
42681
  var init_auth = __esm(() => {
42456
42682
  init_auth_store();
42457
42683
  isTTY = process.stdin.isTTY && process.stdout.isTTY;
42458
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
+ };
42459
42697
  });
42460
42698
 
42461
42699
  // src/cli/commands/feedback.ts