@hasna/skills 0.1.37 → 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 +315 -89
- package/bin/mcp.js +34 -9
- package/dist/index.js +20 -7
- package/package.json +1 -1
- package/skills/_common/auth.ts +8 -12
- package/skills/_common/http-client.ts +4 -4
- package/skills/audio/SKILL.md +1 -1
- package/skills/codefix/package.json +4 -4
- package/skills/codefix/src/index-local.ts +2 -1
- package/skills/codefix/src/index.ts +2 -108
- package/skills/convert/SKILL.md +2 -2
- package/skills/convert/package.json +4 -4
- package/skills/convert/src/index-local.ts +2 -1
- package/skills/convert/src/index.ts +2 -108
- package/skills/deploy/package.json +3 -3
- package/skills/deploy/src/http-client.ts +4 -4
- package/skills/deploy/src/index-local.ts +2 -1
- package/skills/deploy/src/index.ts +2 -109
- package/skills/extract/package.json +4 -4
- package/skills/extract/src/index-local.ts +2 -1
- package/skills/extract/src/index.ts +2 -108
- package/skills/image/SKILL.md +2 -2
- package/skills/music/SKILL.md +1 -1
- package/skills/transcript/SKILL.md +2 -2
- package/skills/video/SKILL.md +1 -1
- package/skills/write/SKILL.md +2 -2
- package/skills/write/package.json +4 -4
- package/skills/write/src/index-local.ts +2 -1
- package/skills/write/src/index.ts +2 -108
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.
|
|
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("
|
|
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 `
|
|
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.
|
|
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({
|
|
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:
|
|
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
|
|
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
|
-
|
|
42078
|
-
|
|
42079
|
-
|
|
42080
|
-
|
|
42081
|
-
|
|
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
|
-
|
|
42308
|
+
writeCommandError(new Error("Invalid email"), "Invalid email", json2);
|
|
42146
42309
|
process.exitCode = 1;
|
|
42147
42310
|
return;
|
|
42148
42311
|
}
|
|
42149
42312
|
if (!code) {
|
|
42150
|
-
|
|
42151
|
-
|
|
42152
|
-
|
|
42153
|
-
|
|
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
|
-
|
|
42157
|
-
process.exitCode = 1;
|
|
42326
|
+
writeCommandError(new Error(sendRes.error), "Failed to request login code", json2);
|
|
42158
42327
|
return;
|
|
42159
42328
|
}
|
|
42160
|
-
|
|
42161
|
-
|
|
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
|
-
|
|
42168
|
-
|
|
42169
|
-
|
|
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
|
-
|
|
42173
|
-
|
|
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
|
-
|
|
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,
|
|
42362
|
+
printLoginSuccess(verifyRes, Boolean(json2));
|
|
42183
42363
|
}
|
|
42184
42364
|
async function doDeviceLogin(options) {
|
|
42185
|
-
|
|
42186
|
-
|
|
42187
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42234
|
-
|
|
42235
|
-
|
|
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
|
-
|
|
42243
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42383
|
-
|
|
42384
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42401
|
-
|
|
42402
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42419
|
-
|
|
42420
|
-
|
|
42421
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42438
|
-
|
|
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
|
-
|
|
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
|