From a8492fab7257855bbd86b2e965a5a7fb7c0ee0fb Mon Sep 17 00:00:00 2001 From: Jorel97 <83238249+Jorel97@users.noreply.github.com> Date: Fri, 29 May 2026 12:46:42 -0600 Subject: [PATCH 1/4] fix(skills): validate listing price as integer sats --- packages/cli/src/commands/skills.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/skills.ts b/packages/cli/src/commands/skills.ts index d7ecaf82..6dd4671f 100644 --- a/packages/cli/src/commands/skills.ts +++ b/packages/cli/src/commands/skills.ts @@ -78,6 +78,12 @@ function slugify(s: string): string { } function q(s: string): string { return JSON.stringify(s); } async function exists(path: string): Promise { try { await access(path); return true; } catch { return false; } } +function parseListingPrice(value: string): number { + if (!/^\d+$/.test(value.trim())) { + throw new Error('--price must be a non-negative integer sat amount'); + } + return Number.parseInt(value, 10); +} function frontmatterValue(text: string, key: string): string | undefined { const m = text.match(new RegExp(`^${key}:\\s*["']?([^"'\\n]+)["']?\\s*$`, 'm')); return m?.[1]?.trim(); @@ -349,17 +355,19 @@ skillsCmd const name = slugify(opts.name ?? inferred.name ?? basename(dirname(skillFile))); const title = opts.title ?? inferred.title ?? name; const description = opts.description ?? inferred.description ?? `Agent skill: ${title}`; + const price = parseListingPrice(opts.price); + const tags = opts.tags.split(',').map(t => t.trim()).filter(Boolean).slice(0, 10); const manifest: SkillManifest = { name, title, description, tagline: opts.tagline, category: opts.category, - tags: opts.tags.split(',').map(t => t.trim()).filter(Boolean).slice(0, 10), - price: Number.parseInt(opts.price, 10) || 0, + tags, + price, skillFile, sourceUrl: opts.sourceUrl, - marketplaces: Object.fromEntries(MARKETPLACES.map(mp => [mp.id, { enabled: true, status: 'pending', command: 'command' in mp && mp.command ? mp.command({ name, title, description, tagline: opts.tagline, category: opts.category, tags: opts.tags.split(',').map(t => t.trim()).filter(Boolean).slice(0, 10), price: Number.parseInt(opts.price, 10) || 0, skillFile, sourceUrl: opts.sourceUrl, marketplaces: {} }) : undefined, note: 'note' in mp ? mp.note : undefined }])) as SkillManifest['marketplaces'], + marketplaces: Object.fromEntries(MARKETPLACES.map(mp => [mp.id, { enabled: true, status: 'pending', command: 'command' in mp && mp.command ? mp.command({ name, title, description, tagline: opts.tagline, category: opts.category, tags, price, skillFile, sourceUrl: opts.sourceUrl, marketplaces: {} }) : undefined, note: 'note' in mp ? mp.note : undefined }])) as SkillManifest['marketplaces'], }; await mkdir(dirname(resolve(opts.out)), { recursive: true }); await saveManifest(opts.out, manifest); From f7c3b1ea08073e6d48c60339113d70decda48e03 Mon Sep 17 00:00:00 2001 From: Jorel97 <83238249+Jorel97@users.noreply.github.com> Date: Fri, 29 May 2026 12:46:44 -0600 Subject: [PATCH 2/4] test(skills): reject invalid listing prices --- packages/cli/src/commands/skills.test.ts | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/cli/src/commands/skills.test.ts b/packages/cli/src/commands/skills.test.ts index 0f23e4ca..2168bd34 100644 --- a/packages/cli/src/commands/skills.test.ts +++ b/packages/cli/src/commands/skills.test.ts @@ -151,3 +151,40 @@ describe('skills marketplaces --json', () => { } }); }); + +describe('skills new command', () => { + let tempDir: string; + + afterEach(() => { + vi.restoreAllMocks(); + if (tempDir) { + rmSync(tempDir, { recursive: true, force: true }); + tempDir = ''; + } + }); + + it('stores a non-negative integer listing price in the manifest and marketplace command', async () => { + tempDir = mkdtempSync(join(tmpdir(), 'sh1pt-skills-new-')); + const out = join(tempDir, 'sh1pt.skill.json'); + const newCmd = skillsCmd.commands.find((c) => c.name() === 'new'); + expect(newCmd).toBeDefined(); + + await newCmd?.parseAsync(['--out', out, '--name', 'qa-helper', '--price', '25'], { from: 'user' }); + + const manifest = JSON.parse(readFileSync(out, 'utf8')); + expect(manifest.price).toBe(25); + expect(manifest.marketplaces.ugig.command).toContain('--price 25'); + }); + + it.each(['-5', '1.9', 'abc'])('rejects invalid listing price %s before writing the manifest', async (price) => { + tempDir = mkdtempSync(join(tmpdir(), 'sh1pt-skills-new-')); + const out = join(tempDir, 'sh1pt.skill.json'); + const newCmd = skillsCmd.commands.find((c) => c.name() === 'new'); + expect(newCmd).toBeDefined(); + + await expect( + newCmd?.parseAsync(['--out', out, '--name', 'qa-helper', '--price', price], { from: 'user' }) + ).rejects.toThrow('--price must be a non-negative integer sat amount'); + expect(existsSync(out)).toBe(false); + }); +}); From 34c7dab99f0390520efa27a5b819545037de0730 Mon Sep 17 00:00:00 2001 From: Jorel97 <83238249+Jorel97@users.noreply.github.com> Date: Fri, 29 May 2026 12:49:33 -0600 Subject: [PATCH 3/4] fix(skills): reject unsafe integer prices --- packages/cli/src/commands/skills.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/skills.ts b/packages/cli/src/commands/skills.ts index 6dd4671f..605eeb6a 100644 --- a/packages/cli/src/commands/skills.ts +++ b/packages/cli/src/commands/skills.ts @@ -79,10 +79,15 @@ function slugify(s: string): string { function q(s: string): string { return JSON.stringify(s); } async function exists(path: string): Promise { try { await access(path); return true; } catch { return false; } } function parseListingPrice(value: string): number { - if (!/^\d+$/.test(value.trim())) { + const normalized = value.trim(); + if (!/^\d+$/.test(normalized)) { throw new Error('--price must be a non-negative integer sat amount'); } - return Number.parseInt(value, 10); + const price = Number.parseInt(normalized, 10); + if (!Number.isSafeInteger(price)) { + throw new Error('--price must be less than or equal to Number.MAX_SAFE_INTEGER'); + } + return price; } function frontmatterValue(text: string, key: string): string | undefined { const m = text.match(new RegExp(`^${key}:\\s*["']?([^"'\\n]+)["']?\\s*$`, 'm')); From 9d6498631977bf79a1c54b024cb08f5bf2072e53 Mon Sep 17 00:00:00 2001 From: Jorel97 <83238249+Jorel97@users.noreply.github.com> Date: Fri, 29 May 2026 12:49:35 -0600 Subject: [PATCH 4/4] test(skills): cover unsafe price values --- packages/cli/src/commands/skills.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/skills.test.ts b/packages/cli/src/commands/skills.test.ts index 2168bd34..11837d76 100644 --- a/packages/cli/src/commands/skills.test.ts +++ b/packages/cli/src/commands/skills.test.ts @@ -176,7 +176,7 @@ describe('skills new command', () => { expect(manifest.marketplaces.ugig.command).toContain('--price 25'); }); - it.each(['-5', '1.9', 'abc'])('rejects invalid listing price %s before writing the manifest', async (price) => { + it.each(['-5', '1.9', 'abc', '9007199254740992'])('rejects invalid listing price %s before writing the manifest', async (price) => { tempDir = mkdtempSync(join(tmpdir(), 'sh1pt-skills-new-')); const out = join(tempDir, 'sh1pt.skill.json'); const newCmd = skillsCmd.commands.find((c) => c.name() === 'new'); @@ -184,7 +184,7 @@ describe('skills new command', () => { await expect( newCmd?.parseAsync(['--out', out, '--name', 'qa-helper', '--price', price], { from: 'user' }) - ).rejects.toThrow('--price must be a non-negative integer sat amount'); + ).rejects.toThrow(/--price must be/); expect(existsSync(out)).toBe(false); }); });