diff --git a/cli/src/clients/skillhub-client.ts b/cli/src/clients/skillhub-client.ts index c1ff5fe3..14e14ec3 100644 --- a/cli/src/clients/skillhub-client.ts +++ b/cli/src/clients/skillhub-client.ts @@ -151,9 +151,12 @@ export class SkillHubClient { } private async handleJsonResponse(response: Response): Promise { - if (response.status === 401 || response.status === 403) { + if (response.status === 401) { throw new CliError('authentication failed', EXIT.auth, { registry: this.registry, next: 'run `skillhub login`' }) } + if (response.status === 403) { + throw new CliError('access denied — token may lack required scope', EXIT.auth, { registry: this.registry, next: 'regenerate token with required scopes or run `skillhub login`' }) + } if (response.status === 404) { throw new CliError('resource not found', EXIT.generic, { registry: this.registry }) } diff --git a/cli/test/integration/publish-dry-run.test.ts b/cli/test/integration/publish-dry-run.test.ts index bc2deb13..7a44b779 100644 --- a/cli/test/integration/publish-dry-run.test.ts +++ b/cli/test/integration/publish-dry-run.test.ts @@ -50,12 +50,12 @@ describe('publish --dry-run', () => { expect(registry.received.publish).toBeNull() }) - test('--dry-run with --json returns structured response', async () => { + test('--dry-run with --json returns structured response on warnings (valid=false)', async () => { const env = await createTempHome() registry = await startFakeRegistry({ token: 'sk_ok', dryRunResponse: { - valid: true, + valid: false, errors: [], warnings: ['Disallowed file extension: data.bin'], resolvedSlug: 'my-skill', @@ -70,9 +70,9 @@ describe('publish --dry-run', () => { USERPROFILE: env.home }) - expect(result.exitCode).toBe(0) + expect(result.exitCode).toBe(6) const json = JSON.parse(result.stdout) - expect(json.valid).toBe(true) + expect(json.valid).toBe(false) expect(json.resolvedSlug).toBe('my-skill') expect(json.resolvedVersion).toBe('2.0.0') expect(json.warnings).toContain('Disallowed file extension: data.bin')